Skip to content

Commit

Permalink
Initial version
Browse files Browse the repository at this point in the history
  • Loading branch information
chesstrian committed Oct 23, 2014
1 parent 18b65a8 commit d57ed6f
Show file tree
Hide file tree
Showing 9 changed files with 648 additions and 1 deletion.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.idea/
node_modules/
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
JSCryptor
=========

Javascript implementation of RNCryptor
*Javascript implementation of RNCryptor*

This implementation try to be compatible with Rob Napier's Objective-C implementation of RNCryptor, but currently it only supports decrypt of cbc mode through version 3.
This code is based in the [PHP implementation of RNCryptor](https://github.com/RNCryptor/RNCryptor-php). Any pull request is welcomed.
8 changes: 8 additions & 0 deletions examples.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
var RNCryptor = require('./index');

// Example taken from https://github.com/RNCryptor/RNCryptor-php/blob/master/examples/decrypt.php

var password = 'myPassword';
var b64string = "AwHsr+ZD87myaoHm51kZX96u4hhaTuLkEsHwpCRpDywMO1Moz35wdS6OuDgq+SIAK6BOSVKQFSbX/GiFSKhWNy1q94JidKc8hs581JwVJBrEEoxDaMwYE+a+sZeirThbfpup9WZQgp3XuZsGuZPGvy6CvHWt08vsxFAn9tiHW9EFVtdSK7kAGzpnx53OUSt451Jpy6lXl1TKek8m64RT4XPr";

console.log(RNCryptor.Decrypt(b64string, password));
5 changes: 5 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
(function() {
var RNCryptor = require('./lib/RNCryptor');

module.exports = RNCryptor;
})();
139 changes: 139 additions & 0 deletions lib/RNCryptor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
(function() {
var crypto = require('crypto');
var pbkdf2 = require('./pbkdf2_sha1');
var base64_decode = require('./base64_decode');

var MCrypt = require('mcrypt').MCrypt;

var RNCryptor = {};

var _settings = {};

var _configureSettings = function(version) {
var settings = {
algorithm: 'rijndael-128',
salt_length: 8,
iv_length: 16,
hmac: {
length: 32
}
};

switch(version) {
case 3:
settings.mode = 'cbc';
settings.options = 1;
settings.hmac.includes_header = true;
settings.hmac.algorithm = 'sha256';
settings.hmac.includes_padding = false;
settings.truncatesMultibytePasswords = false;
break;
default:
throw "Unsupported schema version " + version
}

_settings = settings;
};

var _unpackEncryptedBase64Data = function(b64str) {
var binary_data = base64_decode(b64str);

var components = {
headers: _parseHeaders(binary_data),
hmac: binary_data.substr(-_settings.hmac.length)
};

var header_length = components.headers.length;
var cipher_text_length = binary_data.length - header_length - components.hmac.length;

components.cipher_text = binary_data.substr(header_length, cipher_text_length);

return components;
};

var _parseHeaders = function(bin_data) {
var offset = 0;

var version_char = bin_data[0];
offset += version_char.length;

_configureSettings(version_char.charCodeAt());

var options_char = bin_data[1];
offset += options_char.length;

var encryption_salt = bin_data.substr(offset, _settings.salt_length);
offset += encryption_salt.length;

var hmac_salt = bin_data.substr(offset, _settings.salt_length);
offset += hmac_salt.length;

var iv = bin_data.substr(offset, _settings.iv_length);
offset += iv.length;

return {
version: version_char,
options: options_char,
encryption_salt: encryption_salt,
hmac_salt: hmac_salt,
iv: iv,
length: offset
};
};

var _hmac_is_valid = function(components, password) {
var hmac_key = _generate_key(components.headers.hmac_salt, password);
return components.hmac == _generate_hmac(components, hmac_key);
};

var _generate_key = function (salt, password) {
return pbkdf2(password, salt, 10000, 32);
};

var _generate_hmac = function(components, hmac_key) {
var hmac_message = '';

if (_settings.hmac.includes_header) {
hmac_message += components.headers.version;
hmac_message += components.headers.options;
hmac_message += components.headers.encryption_salt != null ? components.headers.encryption_salt : '';
hmac_message += components.headers.hmac_salt != null ? components.headers.hmac_salt : '';
hmac_message += components.headers.iv;
}

hmac_message += components.cipher_text;

return _hmac_sha256(hmac_key, hmac_message);
};

var _hmac_sha256 = function(password, salt) {
var hmac = crypto.createHmac(_settings.hmac.algorithm, password);
hmac.setEncoding('binary');
hmac.write(salt);
hmac.end();

return hmac.read();
};

var _strip_pkcs7_padding = function(plain_text) {
var pad_length = plain_text.charCodeAt(plain_text.length - 1);
return plain_text.substr(0, plain_text.length - pad_length);
};

RNCryptor.Decrypt = function(b64str, password) {
var components = _unpackEncryptedBase64Data(b64str);

if (!_hmac_is_valid(components, password)) {
return;
}

var key = _generate_key(components.headers.encryption_salt, password);
var decrypter = new MCrypt(_settings.algorithm, _settings.mode);
decrypter.open(key, components.headers.iv);

var padded_plain_text = decrypter.decrypt(new Buffer(components.cipher_text, 'binary')).toString();
return _strip_pkcs7_padding(padded_plain_text);
};

module.exports = RNCryptor;
})();
59 changes: 59 additions & 0 deletions lib/base64_decode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
function base64_decode(data) {
// discuss at: http://phpjs.org/functions/base64_decode/
// original by: Tyler Akins (http://rumkin.com)
// improved by: Thunder.m
// improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// input by: Aman Gupta
// input by: Brett Zamir (http://brett-zamir.me)
// bugfixed by: Onno Marsman
// bugfixed by: Pellentesque Malesuada
// bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// example 1: base64_decode('S2V2aW4gdmFuIFpvbm5ldmVsZA==');
// returns 1: 'Kevin van Zonneveld'
// example 2: base64_decode('YQ===');
// returns 2: 'a'
// example 3: base64_decode('4pyTIMOgIGxhIG1vZGU=');
// returns 3: '✓ à la mode'

var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
var o1, o2, o3, h1, h2, h3, h4, bits, i = 0,
ac = 0,
dec = '',
tmp_arr = [];

if (!data) {
return data;
}

data += '';

do {
// unpack four hexets into three octets using index points in b64
h1 = b64.indexOf(data.charAt(i++));
h2 = b64.indexOf(data.charAt(i++));
h3 = b64.indexOf(data.charAt(i++));
h4 = b64.indexOf(data.charAt(i++));

bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;

o1 = bits >> 16 & 0xff;
o2 = bits >> 8 & 0xff;
o3 = bits & 0xff;

if (h3 == 64) {
tmp_arr[ac++] = String.fromCharCode(o1);
} else if (h4 == 64) {
tmp_arr[ac++] = String.fromCharCode(o1, o2);
} else {
tmp_arr[ac++] = String.fromCharCode(o1, o2, o3);
}
} while (i < data.length);

dec = tmp_arr.join('');

return dec.replace(/\0+$/, '');
}

module.exports = base64_decode;

Loading

0 comments on commit d57ed6f

Please sign in to comment.