Merge pull request #329 from 1and1/master

Optimized memory footprint for encrypting and decrypting
This commit is contained in:
Tankred Hase 2015-06-11 10:18:55 +02:00
commit 58cac452db
8 changed files with 94 additions and 82 deletions

View File

@ -178,7 +178,7 @@ module.exports = {
var iblock = new Uint8Array(block_size); var iblock = new Uint8Array(block_size);
var ablock = new Uint8Array(block_size); var ablock = new Uint8Array(block_size);
var i, n = ''; var i, n = '';
var text = ''; var text = [];
// initialisation vector // initialisation vector
for (i = 0; i < block_size; i++) { for (i = 0; i < block_size; i++) {
@ -200,11 +200,11 @@ module.exports = {
} }
/* RFC4880: Tag 18 and Resync: /* RFC4880: Tag 18 and Resync:
* [...] Unlike the Symmetrically Encrypted Data Packet, no * [...] Unlike the Symmetrically Encrypted Data Packet, no
* special CFB resynchronization is done after encrypting this prefix * special CFB resynchronization is done after encrypting this prefix
* data. See "OpenPGP CFB Mode" below for more details. * data. See "OpenPGP CFB Mode" below for more details.
*/ */
if (resync) { if (resync) {
for (i = 0; i < block_size; i++) { for (i = 0; i < block_size; i++) {
@ -215,7 +215,7 @@ module.exports = {
for (i = 0; i < block_size && i + n < ciphertext.length; i++) { for (i = 0; i < block_size && i + n < ciphertext.length; i++) {
iblock[i] = ciphertext.charCodeAt(n + i); iblock[i] = ciphertext.charCodeAt(n + i);
text += String.fromCharCode(ablock[i] ^ iblock[i]); text.push(String.fromCharCode(ablock[i] ^ iblock[i]));
} }
} }
} else { } else {
@ -226,19 +226,18 @@ module.exports = {
ablock = cipherfn.encrypt(iblock); ablock = cipherfn.encrypt(iblock);
for (i = 0; i < block_size && i + n < ciphertext.length; i++) { for (i = 0; i < block_size && i + n < ciphertext.length; i++) {
iblock[i] = ciphertext.charCodeAt(n + i); iblock[i] = ciphertext.charCodeAt(n + i);
text += String.fromCharCode(ablock[i] ^ iblock[i]); text.push(String.fromCharCode(ablock[i] ^ iblock[i]));
} }
} }
} }
if (!resync)
n = resync ? 0 : 2; {
text.splice(0, 2);
text = text.substring(n, ciphertext.length - block_size - 2 + n); }
text.splice(ciphertext.length - block_size - 2);
return text; return text;
}, },
normalEncrypt: function(cipherfn, key, plaintext, iv) { normalEncrypt: function(cipherfn, key, plaintext, iv) {
cipherfn = new cipher[cipherfn](key); cipherfn = new cipher[cipherfn](key);
var block_size = cipherfn.blockSize; var block_size = cipherfn.blockSize;

View File

@ -350,56 +350,56 @@ function dearmor(text) {
* @static * @static
*/ */
function armor(messagetype, body, partindex, parttotal) { function armor(messagetype, body, partindex, parttotal) {
var result = ""; var result = [];
switch (messagetype) { switch (messagetype) {
case enums.armor.multipart_section: case enums.armor.multipart_section:
result += "-----BEGIN PGP MESSAGE, PART " + partindex + "/" + parttotal + "-----\r\n"; result.push("-----BEGIN PGP MESSAGE, PART " + partindex + "/" + parttotal + "-----\r\n");
result += addheader(); result.push(addheader());
result += base64.encode(body); result.push(base64.encode(body));
result += "\r\n=" + getCheckSum(body) + "\r\n"; result.push("\r\n=" + getCheckSum(body) + "\r\n");
result += "-----END PGP MESSAGE, PART " + partindex + "/" + parttotal + "-----\r\n"; result.push("-----END PGP MESSAGE, PART " + partindex + "/" + parttotal + "-----\r\n");
break; break;
case enums.armor.multipart_last: case enums.armor.multipart_last:
result += "-----BEGIN PGP MESSAGE, PART " + partindex + "-----\r\n"; result.push("-----BEGIN PGP MESSAGE, PART " + partindex + "-----\r\n");
result += addheader(); result.push(addheader());
result += base64.encode(body); result.push(base64.encode(body));
result += "\r\n=" + getCheckSum(body) + "\r\n"; result.push("\r\n=" + getCheckSum(body) + "\r\n");
result += "-----END PGP MESSAGE, PART " + partindex + "-----\r\n"; result.push("-----END PGP MESSAGE, PART " + partindex + "-----\r\n");
break; break;
case enums.armor.signed: case enums.armor.signed:
result += "\r\n-----BEGIN PGP SIGNED MESSAGE-----\r\n"; result.push("\r\n-----BEGIN PGP SIGNED MESSAGE-----\r\n");
result += "Hash: " + body.hash + "\r\n\r\n"; result.push("Hash: " + body.hash + "\r\n\r\n");
result += body.text.replace(/\n-/g, "\n- -"); result.push(body.text.replace(/\n-/g, "\n- -"));
result += "\r\n-----BEGIN PGP SIGNATURE-----\r\n"; result.push("\r\n-----BEGIN PGP SIGNATURE-----\r\n");
result += addheader(); result.push(addheader());
result += base64.encode(body.data); result.push(base64.encode(body.data));
result += "\r\n=" + getCheckSum(body.data) + "\r\n"; result.push("\r\n=" + getCheckSum(body.data) + "\r\n");
result += "-----END PGP SIGNATURE-----\r\n"; result.push("-----END PGP SIGNATURE-----\r\n");
break; break;
case enums.armor.message: case enums.armor.message:
result += "-----BEGIN PGP MESSAGE-----\r\n"; result.push("-----BEGIN PGP MESSAGE-----\r\n");
result += addheader(); result.push(addheader());
result += base64.encode(body); result.push(base64.encode(body));
result += "\r\n=" + getCheckSum(body) + "\r\n"; result.push("\r\n=" + getCheckSum(body) + "\r\n");
result += "-----END PGP MESSAGE-----\r\n"; result.push("-----END PGP MESSAGE-----\r\n");
break; break;
case enums.armor.public_key: case enums.armor.public_key:
result += "-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n"; result.push("-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n");
result += addheader(); result.push(addheader());
result += base64.encode(body); result.push(base64.encode(body));
result += "\r\n=" + getCheckSum(body) + "\r\n"; result.push("\r\n=" + getCheckSum(body) + "\r\n");
result += "-----END PGP PUBLIC KEY BLOCK-----\r\n\r\n"; result.push("-----END PGP PUBLIC KEY BLOCK-----\r\n\r\n");
break; break;
case enums.armor.private_key: case enums.armor.private_key:
result += "-----BEGIN PGP PRIVATE KEY BLOCK-----\r\n"; result.push("-----BEGIN PGP PRIVATE KEY BLOCK-----\r\n");
result += addheader(); result.push(addheader());
result += base64.encode(body); result.push(base64.encode(body));
result += "\r\n=" + getCheckSum(body) + "\r\n"; result.push("\r\n=" + getCheckSum(body) + "\r\n");
result += "-----END PGP PRIVATE KEY BLOCK-----\r\n"; result.push("-----END PGP PRIVATE KEY BLOCK-----\r\n");
break; break;
} }
return result; return result.join('');
} }
module.exports = { module.exports = {

View File

@ -23,51 +23,55 @@ var b64s = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
* @returns {string} radix-64 version of input string * @returns {string} radix-64 version of input string
* @static * @static
*/ */
function s2r(t) { function s2r(t, o) {
// TODO check btoa alternative
var a, c, n; var a, c, n;
var r = '', var r = o ? o : [],
l = 0, l = 0,
s = 0; s = 0;
var tl = t.length; var tl = t.length;
for (n = 0; n < tl; n++) { for (n = 0; n < tl; n++) {
c = t.charCodeAt(n); c = t.charCodeAt(n);
if (s === 0) { if (s === 0) {
r += b64s.charAt((c >> 2) & 63); r.push(b64s.charAt((c >> 2) & 63));
a = (c & 3) << 4; a = (c & 3) << 4;
} else if (s == 1) { } else if (s == 1) {
r += b64s.charAt((a | (c >> 4) & 15)); r.push(b64s.charAt((a | (c >> 4) & 15)));
a = (c & 15) << 2; a = (c & 15) << 2;
} else if (s == 2) { } else if (s == 2) {
r += b64s.charAt(a | ((c >> 6) & 3)); r.push(b64s.charAt(a | ((c >> 6) & 3)));
l += 1; l += 1;
if ((l % 60) === 0) if ((l % 60) === 0)
r += "\n"; r.push("\n");
r += b64s.charAt(c & 63); r.push(b64s.charAt(c & 63));
} }
l += 1; l += 1;
if ((l % 60) === 0) if ((l % 60) === 0)
r += "\n"; r.push("\n");
s += 1; s += 1;
if (s == 3) if (s == 3)
s = 0; s = 0;
} }
if (s > 0) { if (s > 0) {
r += b64s.charAt(a); r.push(b64s.charAt(a));
l += 1; l += 1;
if ((l % 60) === 0) if ((l % 60) === 0)
r += "\n"; r.push("\n");
r += '='; r.push('=');
l += 1; l += 1;
} }
if (s == 1) { if (s == 1) {
if ((l % 60) === 0) if ((l % 60) === 0)
r += "\n"; r.push("\n");
r += '='; r.push('=');
} }
if (o)
return r; {
return;
}
return r.join('');
} }
/** /**
@ -77,22 +81,23 @@ function s2r(t) {
* @static * @static
*/ */
function r2s(t) { function r2s(t) {
// TODO check atob alternative
var c, n; var c, n;
var r = '', var r = [],
s = 0, s = 0,
a = 0; a = 0;
var tl = t.length; var tl = t.length;
for (n = 0; n < tl; n++) { for (n = 0; n < tl; n++) {
c = b64s.indexOf(t.charAt(n)); c = b64s.indexOf(t.charAt(n));
if (c >= 0) { if (c >= 0) {
if (s) if (s)
r += String.fromCharCode(a | (c >> (6 - s)) & 255); r.push(String.fromCharCode(a | (c >> (6 - s)) & 255));
s = (s + 2) & 7; s = (s + 2) & 7;
a = (c << s) & 255; a = (c << s) & 255;
} }
} }
return r; return r.join('');
} }
module.exports = { module.exports = {

View File

@ -93,8 +93,12 @@ SymEncryptedIntegrityProtected.prototype.encrypt = function (sessionKeyAlgorithm
this.encrypted = crypto.cfb.encrypt(prefixrandom, this.encrypted = crypto.cfb.encrypt(prefixrandom,
sessionKeyAlgorithm, tohash, key, false).substring(0, sessionKeyAlgorithm, tohash, key, false);
prefix.length + tohash.length);
if (prefix.length + tohash.length != this.encrypted.length)
{
this.encrypted = this.encrypted.substring(0, prefix.length + tohash.length);
}
}; };
/** /**
@ -110,17 +114,20 @@ SymEncryptedIntegrityProtected.prototype.decrypt = function (sessionKeyAlgorithm
var decrypted = crypto.cfb.decrypt( var decrypted = crypto.cfb.decrypt(
sessionKeyAlgorithm, key, this.encrypted, false); sessionKeyAlgorithm, key, this.encrypted, false);
var mdc = decrypted.slice(decrypted.length - 20, decrypted.length).join('');
decrypted.splice(decrypted.length - 20);
// there must be a modification detection code packet as the // there must be a modification detection code packet as the
// last packet and everything gets hashed except the hash itself // last packet and everything gets hashed except the hash itself
this.hash = crypto.hash.sha1( this.hash = crypto.hash.sha1(
crypto.cfb.mdc(sessionKeyAlgorithm, key, this.encrypted) + decrypted.substring(0, decrypted.length - 20)); crypto.cfb.mdc(sessionKeyAlgorithm, key, this.encrypted) + decrypted.join(''));
var mdc = decrypted.substr(decrypted.length - 20, 20);
if (this.hash != mdc) { if (this.hash != mdc) {
throw new Error('Modification detected.'); throw new Error('Modification detected.');
} else } else {
this.packets.read(decrypted.substr(0, decrypted.length - 22)); decrypted.splice(decrypted.length - 2);
this.packets.read(decrypted.join(''));
}
}; };

View File

@ -119,6 +119,7 @@ SymEncryptedSessionKey.prototype.decrypt = function(passphrase) {
} else { } else {
var decrypted = crypto.cfb.decrypt( var decrypted = crypto.cfb.decrypt(
this.sessionKeyEncryptionAlgorithm, key, this.encrypted, true); this.sessionKeyEncryptionAlgorithm, key, this.encrypted, true);
decrypted = decrypted.join('');
this.sessionKeyAlgorithm = enums.read(enums.symmetric, this.sessionKeyAlgorithm = enums.read(enums.symmetric,
decrypted[0].keyCodeAt()); decrypted[0].keyCodeAt());

View File

@ -65,7 +65,7 @@ SymmetricallyEncrypted.prototype.decrypt = function (sessionKeyAlgorithm, key) {
var decrypted = crypto.cfb.decrypt( var decrypted = crypto.cfb.decrypt(
sessionKeyAlgorithm, key, this.encrypted, true); sessionKeyAlgorithm, key, this.encrypted, true);
this.packets.read(decrypted); this.packets.read(decrypted.join(''))
}; };
SymmetricallyEncrypted.prototype.encrypt = function (algo, key) { SymmetricallyEncrypted.prototype.encrypt = function (algo, key) {

View File

@ -202,11 +202,11 @@ module.exports = {
* @return {String} String representation of the array * @return {String} String representation of the array
*/ */
Uint8Array2str: function (bin) { Uint8Array2str: function (bin) {
var result = ''; var result = [];
for (var i = 0; i < bin.length; i++) { for (var i = 0; i < bin.length; i++) {
result += String.fromCharCode(bin[i]); result[i] = String.fromCharCode(bin[i]);
} }
return result; return result.join('');
}, },
/** /**

View File

@ -261,7 +261,7 @@ describe('API functional testing', function() {
symmAlgos.forEach(function(algo) { symmAlgos.forEach(function(algo) {
var symmKey = openpgp.crypto.generateSessionKey(algo); var symmKey = openpgp.crypto.generateSessionKey(algo);
var symmencData = openpgp.crypto.cfb.encrypt(openpgp.crypto.getPrefixRandom(algo), algo, plaintext, symmKey, resync); var symmencData = openpgp.crypto.cfb.encrypt(openpgp.crypto.getPrefixRandom(algo), algo, plaintext, symmKey, resync);
var text = openpgp.crypto.cfb.decrypt(algo, symmKey, symmencData, resync); var text = openpgp.crypto.cfb.decrypt(algo, symmKey, symmencData, resync).join('');
expect(text).to.equal(plaintext); expect(text).to.equal(plaintext);
}); });
} }