diff --git a/src/crypto/cfb.js b/src/crypto/cfb.js index e10dc0e6..5083b12f 100644 --- a/src/crypto/cfb.js +++ b/src/crypto/cfb.js @@ -52,9 +52,10 @@ module.exports = { var FRE = new Uint8Array(block_size); prefixrandom = prefixrandom + prefixrandom.charAt(block_size - 2) + prefixrandom.charAt(block_size - 1); - util.print_debug("prefixrandom:" + util.hexstrdump(prefixrandom)); - var ciphertext = ''; - var i, n; + var ciphertext = new Uint8Array(plaintext.length + 2 + block_size * 2); + var i, n, begin; + var offset = resync ? 0 : 2; + // 1. The feedback register (FR) is set to the IV, which is all zeros. for (i = 0; i < block_size; i++) { FR[i] = 0; @@ -67,13 +68,11 @@ module.exports = { // the plaintext to produce C[1] through C[BS], the first BS octets // of ciphertext. for (i = 0; i < block_size; i++) { - ciphertext += String.fromCharCode(FRE[i] ^ prefixrandom.charCodeAt(i)); + ciphertext[i] = FRE[i] ^ prefixrandom.charCodeAt(i); } // 4. FR is loaded with C[1] through C[BS]. - for (i = 0; i < block_size; i++) { - FR[i] = ciphertext.charCodeAt(i); - } + FR.set(ciphertext.subarray(0, block_size)); // 5. FR is encrypted to produce FRE, the encryption of the first BS // octets of ciphertext. @@ -82,82 +81,44 @@ module.exports = { // 6. The left two octets of FRE get xored with the next two octets of // data that were prefixed to the plaintext. This produces C[BS+1] // and C[BS+2], the next two octets of ciphertext. - ciphertext += String.fromCharCode(FRE[0] ^ prefixrandom.charCodeAt(block_size)); - ciphertext += String.fromCharCode(FRE[1] ^ prefixrandom.charCodeAt(block_size + 1)); + ciphertext[block_size] = FRE[0] ^ prefixrandom.charCodeAt(block_size); + ciphertext[block_size + 1] = FRE[1] ^ prefixrandom.charCodeAt(block_size + 1); if (resync) { - // 7. (The resync step) FR is loaded with C3-C10. - for (i = 0; i < block_size; i++) { - FR[i] = ciphertext.charCodeAt(i + 2); - } + // 7. (The resync step) FR is loaded with C[3] through C[BS+2]. + FR.set(ciphertext.subarray(2, block_size + 2)); } else { - for (i = 0; i < block_size; i++) { - FR[i] = ciphertext.charCodeAt(i); - } + FR.set(ciphertext.subarray(0, block_size)); } // 8. FR is encrypted to produce FRE. FRE = cipherfn.encrypt(FR); - if (resync) { - // 9. FRE is xored with the first 8 octets of the given plaintext, now - // that we have finished encrypting the 10 octets of prefixed data. - // This produces C11-C18, the next 8 octets of ciphertext. + // 9. FRE is xored with the first BS octets of the given plaintext, now + // that we have finished encrypting the BS+2 octets of prefixed + // data. This produces C[BS+3] through C[BS+(BS+2)], the next BS + // octets of ciphertext. + for (i = 0; i < block_size; i++) { + ciphertext[block_size + 2 + i] = FRE[i + offset] ^ plaintext.charCodeAt(i); + } + for (n = block_size; n < plaintext.length + offset; n += block_size) { + // 10. FR is loaded with C[BS+3] to C[BS + (BS+2)] (which is C11-C18 for + // an 8-octet block). + begin = n + 2 - offset; + FR.set(ciphertext.subarray(begin, begin + block_size)); + + // 11. FR is encrypted to produce FRE. + FRE = cipherfn.encrypt(FR); + + // 12. FRE is xored with the next BS octets of plaintext, to produce + // the next BS octets of ciphertext. These are loaded into FR, and + // the process is repeated until the plaintext is used up. for (i = 0; i < block_size; i++) { - ciphertext += String.fromCharCode(FRE[i] ^ plaintext.charCodeAt(i)); + ciphertext[block_size + begin + i] = FRE[i] ^ plaintext.charCodeAt(n + i - offset); } - for (n = block_size + 2; n < plaintext.length; n += block_size) { - // 10. FR is loaded with C11-C18 - for (i = 0; i < block_size; i++) { - FR[i] = ciphertext.charCodeAt(n + i); - } - - // 11. FR is encrypted to produce FRE. - FRE = cipherfn.encrypt(FR); - - // 12. FRE is xored with the next 8 octets of plaintext, to produce the - // next 8 octets of ciphertext. These are loaded into FR and the - // process is repeated until the plaintext is used up. - for (i = 0; i < block_size; i++) { - ciphertext += String.fromCharCode(FRE[i] ^ plaintext.charCodeAt((n - 2) + i)); - } - } - } else { - plaintext = " " + plaintext; - // 9. FRE is xored with the first 8 octets of the given plaintext, now - // that we have finished encrypting the 10 octets of prefixed data. - // This produces C11-C18, the next 8 octets of ciphertext. - for (i = 2; i < block_size; i++) { - ciphertext += String.fromCharCode(FRE[i] ^ plaintext.charCodeAt(i)); - } - - var tempCiphertext = ciphertext.substring(0, 2 * block_size); - var tempCiphertextString = ciphertext.substring(block_size); - var tempCiphertextBlock; - for (n = block_size; n < plaintext.length; n += block_size) { - // 10. FR is loaded with C11-C18 - for (i = 0; i < block_size; i++) { - FR[i] = tempCiphertextString.charCodeAt(i); - } - tempCiphertextString = ''; - - // 11. FR is encrypted to produce FRE. - FRE = cipherfn.encrypt(FR); - - // 12. FRE is xored with the next 8 octets of plaintext, to produce the - // next 8 octets of ciphertext. These are loaded into FR and the - // process is repeated until the plaintext is used up. - for (i = 0; i < block_size; i++) { - tempCiphertextBlock = String.fromCharCode(FRE[i] ^ plaintext.charCodeAt(n + i)); - tempCiphertext += tempCiphertextBlock; - tempCiphertextString += tempCiphertextBlock; - } - } - ciphertext = tempCiphertext; } - ciphertext = ciphertext.substring(0, plaintext.length + 2 + block_size); - - return ciphertext; + ciphertext = ciphertext.subarray(0, plaintext.length + 2 + block_size); + return util.Uint8Array2str(ciphertext); }, /** @@ -233,9 +194,9 @@ module.exports = { ablock = cipherfn.encrypt(ablock); // test check octets - if (iblock[block_size - 2] != (ablock[0] ^ ciphertext.charCodeAt(block_size)) || iblock[block_size - 1] != (ablock[ - 1] ^ ciphertext.charCodeAt(block_size + 1))) { - throw new Error('Invalid data.'); + if (iblock[block_size - 2] != (ablock[0] ^ ciphertext.charCodeAt(block_size)) || + iblock[block_size - 1] != (ablock[1] ^ ciphertext.charCodeAt(block_size + 1))) { + throw new Error('CFB decrypt: invalid key'); } /* RFC4880: Tag 18 and Resync: diff --git a/src/util.js b/src/util.js index 6edd68de..488a791a 100644 --- a/src/util.js +++ b/src/util.js @@ -159,29 +159,23 @@ module.exports = { */ bin2str: function (bin) { var result = []; - for (var i = 0; i < bin.length; i++) { - result.push(String.fromCharCode(bin[i])); + result[i] = String.fromCharCode(bin[i]); } - return result.join(''); }, - _str2bin: function (str, result) { - for (var i = 0; i < str.length; i++) { - result[i] = str.charCodeAt(i); - } - - return result; - }, - /** * Convert a string to an array of integers(0.255) * @param {String} str String to convert * @return {Array} An array of (binary) integers */ str2bin: function (str) { - return this._str2bin(str, new Array(str.length)); + var result = []; + for (var i = 0; i < str.length; i++) { + result[i] = str.charCodeAt(i); + } + return result; }, @@ -191,7 +185,11 @@ module.exports = { * @return {Uint8Array} The array of (binary) integers */ str2Uint8Array: function (str) { - return this._str2bin(str, new Uint8Array(new ArrayBuffer(str.length))); + var result = new Uint8Array(str.length); + for (var i = 0; i < str.length; i++) { + result[i] = str.charCodeAt(i); + } + return result; }, /** @@ -202,7 +200,11 @@ module.exports = { * @return {String} String representation of the array */ Uint8Array2str: function (bin) { - return this.bin2str(bin); + var result = ''; + for (var i = 0; i < bin.length; i++) { + result += String.fromCharCode(bin[i]); + } + return result; }, /** diff --git a/test/crypto/crypto.js b/test/crypto/crypto.js index acd43981..7b1ac556 100644 --- a/test/crypto/crypto.js +++ b/test/crypto/crypto.js @@ -252,26 +252,36 @@ describe('API functional testing', function() { }); describe('Encrypt and decrypt', function () { - var symmAlgo = "aes256"; // AES256 - var symmKey = openpgp.crypto.generateSessionKey(symmAlgo); - var symmencDataOCFB = openpgp.crypto.cfb.encrypt(openpgp.crypto.getPrefixRandom(symmAlgo), symmAlgo, "foobarfoobar1234567890", symmKey, true); - var symmencDataCFB = openpgp.crypto.cfb.encrypt(openpgp.crypto.getPrefixRandom(symmAlgo), symmAlgo, "foobarfoobar1234567890", symmKey, false); - - it("Symmetric with OpenPGP CFB resync", function (done) { - var text = openpgp.crypto.cfb.decrypt(symmAlgo,symmKey,symmencDataOCFB,true); - - expect(text).to.equal("foobarfoobar1234567890"); - done(); + var symmAlgos = Object.keys(openpgp.enums.symmetric); + symmAlgos = symmAlgos.filter(function(algo) { + return algo !== 'idea' && algo !== 'plaintext'; }); - it.skip("Symmetric without OpenPGP CFB resync", function (done) { - var text = openpgp.crypto.cfb.decrypt(symmAlgo,symmKey,symmencDataCFB,false); + function testCFB(plaintext, resync) { + 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); + expect(text).to.equal(plaintext); + }); + } - expect(text).to.equal("foobarfoobar1234567890"); - done(); + it("Symmetric with OpenPGP CFB resync", function () { + testCFB("hello", true); + testCFB("1234567", true); + testCFB("foobarfoobar1234567890", true); + testCFB("12345678901234567890123456789012345678901234567890", true); + }); + + it("Symmetric without OpenPGP CFB resync", function () { + testCFB("hello", false); + testCFB("1234567", false); + testCFB("foobarfoobar1234567890", false); + testCFB("12345678901234567890123456789012345678901234567890", false); }); it('Asymmetric using RSA with eme_pkcs1 padding', function (done) { + var symmKey = openpgp.crypto.generateSessionKey('aes256'); var RSAUnencryptedData = new openpgp.MPI(); RSAUnencryptedData.fromBytes(openpgp.crypto.pkcs1.eme.encode(symmKey, RSApubMPIs[0].byteLength())); var RSAEncryptedData = openpgp.crypto.publicKeyEncrypt("rsa_encrypt_sign", RSApubMPIs, RSAUnencryptedData); @@ -282,6 +292,7 @@ describe('API functional testing', function() { }); it('Asymmetric using Elgamal with eme_pkcs1 padding', function (done) { + var symmKey = openpgp.crypto.generateSessionKey('aes256'); var ElgamalUnencryptedData = new openpgp.MPI(); ElgamalUnencryptedData.fromBytes(openpgp.crypto.pkcs1.eme.encode(symmKey, ElgamalpubMPIs[0].byteLength())); var ElgamalEncryptedData = openpgp.crypto.publicKeyEncrypt("elgamal", ElgamalpubMPIs, ElgamalUnencryptedData);