From 07e4278a3af95f23fab30144ae0c0357270fee9a Mon Sep 17 00:00:00 2001 From: Peter Breitling Date: Wed, 10 Jun 2015 21:12:55 +0200 Subject: [PATCH] Optimized memory footprint for encrypting and decrypting --- src/crypto/cfb.js | 25 ++++--- src/encoding/armor.js | 70 +++++++++---------- src/encoding/base64.js | 49 +++++++------ .../sym_encrypted_integrity_protected.js | 21 ++++-- src/packet/sym_encrypted_session_key.js | 1 + src/packet/symmetrically_encrypted.js | 2 +- src/util.js | 6 +- test/crypto/crypto.js | 2 +- 8 files changed, 94 insertions(+), 82 deletions(-) diff --git a/src/crypto/cfb.js b/src/crypto/cfb.js index 5083b12f..14a5bb5b 100644 --- a/src/crypto/cfb.js +++ b/src/crypto/cfb.js @@ -178,7 +178,7 @@ module.exports = { var iblock = new Uint8Array(block_size); var ablock = new Uint8Array(block_size); var i, n = ''; - var text = ''; + var text = []; // initialisation vector for (i = 0; i < block_size; i++) { @@ -200,11 +200,11 @@ module.exports = { } /* RFC4880: Tag 18 and Resync: - * [...] Unlike the Symmetrically Encrypted Data Packet, no - * special CFB resynchronization is done after encrypting this prefix - * data. See "OpenPGP CFB Mode" below for more details. + * [...] Unlike the Symmetrically Encrypted Data Packet, no + * special CFB resynchronization is done after encrypting this prefix + * data. See "OpenPGP CFB Mode" below for more details. - */ + */ if (resync) { for (i = 0; i < block_size; i++) { @@ -215,7 +215,7 @@ module.exports = { for (i = 0; i < block_size && i + n < ciphertext.length; i++) { iblock[i] = ciphertext.charCodeAt(n + i); - text += String.fromCharCode(ablock[i] ^ iblock[i]); + text.push(String.fromCharCode(ablock[i] ^ iblock[i])); } } } else { @@ -226,19 +226,18 @@ module.exports = { ablock = cipherfn.encrypt(iblock); for (i = 0; i < block_size && i + n < ciphertext.length; i++) { iblock[i] = ciphertext.charCodeAt(n + i); - text += String.fromCharCode(ablock[i] ^ iblock[i]); + text.push(String.fromCharCode(ablock[i] ^ iblock[i])); } } } - - n = resync ? 0 : 2; - - text = text.substring(n, ciphertext.length - block_size - 2 + n); - + if (!resync) + { + text.splice(0, 2); + } + text.splice(ciphertext.length - block_size - 2); return text; }, - normalEncrypt: function(cipherfn, key, plaintext, iv) { cipherfn = new cipher[cipherfn](key); var block_size = cipherfn.blockSize; diff --git a/src/encoding/armor.js b/src/encoding/armor.js index dcbb6209..567a8a1c 100644 --- a/src/encoding/armor.js +++ b/src/encoding/armor.js @@ -350,56 +350,56 @@ function dearmor(text) { * @static */ function armor(messagetype, body, partindex, parttotal) { - var result = ""; + var result = []; switch (messagetype) { case enums.armor.multipart_section: - result += "-----BEGIN PGP MESSAGE, PART " + partindex + "/" + parttotal + "-----\r\n"; - result += addheader(); - result += base64.encode(body); - result += "\r\n=" + getCheckSum(body) + "\r\n"; - result += "-----END PGP MESSAGE, PART " + partindex + "/" + parttotal + "-----\r\n"; + result.push("-----BEGIN PGP MESSAGE, PART " + partindex + "/" + parttotal + "-----\r\n"); + result.push(addheader()); + result.push(base64.encode(body)); + result.push("\r\n=" + getCheckSum(body) + "\r\n"); + result.push("-----END PGP MESSAGE, PART " + partindex + "/" + parttotal + "-----\r\n"); break; case enums.armor.multipart_last: - result += "-----BEGIN PGP MESSAGE, PART " + partindex + "-----\r\n"; - result += addheader(); - result += base64.encode(body); - result += "\r\n=" + getCheckSum(body) + "\r\n"; - result += "-----END PGP MESSAGE, PART " + partindex + "-----\r\n"; + result.push("-----BEGIN PGP MESSAGE, PART " + partindex + "-----\r\n"); + result.push(addheader()); + result.push(base64.encode(body)); + result.push("\r\n=" + getCheckSum(body) + "\r\n"); + result.push("-----END PGP MESSAGE, PART " + partindex + "-----\r\n"); break; case enums.armor.signed: - result += "\r\n-----BEGIN PGP SIGNED MESSAGE-----\r\n"; - result += "Hash: " + body.hash + "\r\n\r\n"; - result += body.text.replace(/\n-/g, "\n- -"); - result += "\r\n-----BEGIN PGP SIGNATURE-----\r\n"; - result += addheader(); - result += base64.encode(body.data); - result += "\r\n=" + getCheckSum(body.data) + "\r\n"; - result += "-----END PGP SIGNATURE-----\r\n"; + result.push("\r\n-----BEGIN PGP SIGNED MESSAGE-----\r\n"); + result.push("Hash: " + body.hash + "\r\n\r\n"); + result.push(body.text.replace(/\n-/g, "\n- -")); + result.push("\r\n-----BEGIN PGP SIGNATURE-----\r\n"); + result.push(addheader()); + result.push(base64.encode(body.data)); + result.push("\r\n=" + getCheckSum(body.data) + "\r\n"); + result.push("-----END PGP SIGNATURE-----\r\n"); break; case enums.armor.message: - result += "-----BEGIN PGP MESSAGE-----\r\n"; - result += addheader(); - result += base64.encode(body); - result += "\r\n=" + getCheckSum(body) + "\r\n"; - result += "-----END PGP MESSAGE-----\r\n"; + result.push("-----BEGIN PGP MESSAGE-----\r\n"); + result.push(addheader()); + result.push(base64.encode(body)); + result.push("\r\n=" + getCheckSum(body) + "\r\n"); + result.push("-----END PGP MESSAGE-----\r\n"); break; case enums.armor.public_key: - result += "-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n"; - result += addheader(); - result += base64.encode(body); - result += "\r\n=" + getCheckSum(body) + "\r\n"; - result += "-----END PGP PUBLIC KEY BLOCK-----\r\n\r\n"; + result.push("-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n"); + result.push(addheader()); + result.push(base64.encode(body)); + result.push("\r\n=" + getCheckSum(body) + "\r\n"); + result.push("-----END PGP PUBLIC KEY BLOCK-----\r\n\r\n"); break; case enums.armor.private_key: - result += "-----BEGIN PGP PRIVATE KEY BLOCK-----\r\n"; - result += addheader(); - result += base64.encode(body); - result += "\r\n=" + getCheckSum(body) + "\r\n"; - result += "-----END PGP PRIVATE KEY BLOCK-----\r\n"; + result.push("-----BEGIN PGP PRIVATE KEY BLOCK-----\r\n"); + result.push(addheader()); + result.push(base64.encode(body)); + result.push("\r\n=" + getCheckSum(body) + "\r\n"); + result.push("-----END PGP PRIVATE KEY BLOCK-----\r\n"); break; } - return result; + return result.join(''); } module.exports = { diff --git a/src/encoding/base64.js b/src/encoding/base64.js index 59bd512d..088efc40 100644 --- a/src/encoding/base64.js +++ b/src/encoding/base64.js @@ -23,51 +23,55 @@ var b64s = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; * @returns {string} radix-64 version of input string * @static */ -function s2r(t) { +function s2r(t, o) { + // TODO check btoa alternative var a, c, n; - var r = '', - l = 0, - s = 0; + var r = o ? o : [], + l = 0, + s = 0; var tl = t.length; for (n = 0; n < tl; n++) { c = t.charCodeAt(n); if (s === 0) { - r += b64s.charAt((c >> 2) & 63); + r.push(b64s.charAt((c >> 2) & 63)); a = (c & 3) << 4; } else if (s == 1) { - r += b64s.charAt((a | (c >> 4) & 15)); + r.push(b64s.charAt((a | (c >> 4) & 15))); a = (c & 15) << 2; } else if (s == 2) { - r += b64s.charAt(a | ((c >> 6) & 3)); + r.push(b64s.charAt(a | ((c >> 6) & 3))); l += 1; if ((l % 60) === 0) - r += "\n"; - r += b64s.charAt(c & 63); + r.push("\n"); + r.push(b64s.charAt(c & 63)); } l += 1; if ((l % 60) === 0) - r += "\n"; + r.push("\n"); s += 1; if (s == 3) s = 0; } if (s > 0) { - r += b64s.charAt(a); + r.push(b64s.charAt(a)); l += 1; if ((l % 60) === 0) - r += "\n"; - r += '='; + r.push("\n"); + r.push('='); l += 1; } if (s == 1) { if ((l % 60) === 0) - r += "\n"; - r += '='; + r.push("\n"); + r.push('='); } - - return r; + if (o) + { + return; + } + return r.join(''); } /** @@ -77,22 +81,23 @@ function s2r(t) { * @static */ function r2s(t) { + // TODO check atob alternative var c, n; - var r = '', - s = 0, - a = 0; + var r = [], + s = 0, + a = 0; var tl = t.length; for (n = 0; n < tl; n++) { c = b64s.indexOf(t.charAt(n)); if (c >= 0) { if (s) - r += String.fromCharCode(a | (c >> (6 - s)) & 255); + r.push(String.fromCharCode(a | (c >> (6 - s)) & 255)); s = (s + 2) & 7; a = (c << s) & 255; } } - return r; + return r.join(''); } module.exports = { diff --git a/src/packet/sym_encrypted_integrity_protected.js b/src/packet/sym_encrypted_integrity_protected.js index 6737c322..18e40b25 100644 --- a/src/packet/sym_encrypted_integrity_protected.js +++ b/src/packet/sym_encrypted_integrity_protected.js @@ -93,8 +93,12 @@ SymEncryptedIntegrityProtected.prototype.encrypt = function (sessionKeyAlgorithm this.encrypted = crypto.cfb.encrypt(prefixrandom, - sessionKeyAlgorithm, tohash, key, false).substring(0, - prefix.length + tohash.length); + sessionKeyAlgorithm, tohash, key, false); + + 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( 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 // last packet and everything gets hashed except the hash itself 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) { throw new Error('Modification detected.'); - } else - this.packets.read(decrypted.substr(0, decrypted.length - 22)); + } else { + decrypted.splice(decrypted.length - 2); + this.packets.read(decrypted.join('')); + } }; diff --git a/src/packet/sym_encrypted_session_key.js b/src/packet/sym_encrypted_session_key.js index 1557d631..b5e5ae14 100644 --- a/src/packet/sym_encrypted_session_key.js +++ b/src/packet/sym_encrypted_session_key.js @@ -119,6 +119,7 @@ SymEncryptedSessionKey.prototype.decrypt = function(passphrase) { } else { var decrypted = crypto.cfb.decrypt( this.sessionKeyEncryptionAlgorithm, key, this.encrypted, true); + decrypted = decrypted.join(''); this.sessionKeyAlgorithm = enums.read(enums.symmetric, decrypted[0].keyCodeAt()); diff --git a/src/packet/symmetrically_encrypted.js b/src/packet/symmetrically_encrypted.js index 95b991da..01a527cd 100644 --- a/src/packet/symmetrically_encrypted.js +++ b/src/packet/symmetrically_encrypted.js @@ -65,7 +65,7 @@ SymmetricallyEncrypted.prototype.decrypt = function (sessionKeyAlgorithm, key) { var decrypted = crypto.cfb.decrypt( sessionKeyAlgorithm, key, this.encrypted, true); - this.packets.read(decrypted); + this.packets.read(decrypted.join('')) }; SymmetricallyEncrypted.prototype.encrypt = function (algo, key) { diff --git a/src/util.js b/src/util.js index a88a2952..75d8f4bd 100644 --- a/src/util.js +++ b/src/util.js @@ -202,11 +202,11 @@ module.exports = { * @return {String} String representation of the array */ Uint8Array2str: function (bin) { - var result = ''; + var result = []; for (var i = 0; i < bin.length; i++) { - result += String.fromCharCode(bin[i]); + result[i] = String.fromCharCode(bin[i]); } - return result; + return result.join(''); }, /** diff --git a/test/crypto/crypto.js b/test/crypto/crypto.js index 7b1ac556..f7dec980 100644 --- a/test/crypto/crypto.js +++ b/test/crypto/crypto.js @@ -261,7 +261,7 @@ describe('API functional testing', function() { symmAlgos.forEach(function(algo) { var symmKey = openpgp.crypto.generateSessionKey(algo); 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); }); }