diff --git a/.travis.yml b/.travis.yml index 9209a957..94e37020 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ matrix: env: OPENPGP_NODE_JS='10' OPENPGPJSTEST='unit' - node_js: "9" env: BROWSER='Firefox' VERSION='26' PLATFORM='macOS 10.13' OPENPGPJSTEST='saucelabs' COMPAT=1 - - node_js: "9" + - node_js: "10" env: BROWSER='Firefox' VERSION='61' PLATFORM='macOS 10.13' OPENPGPJSTEST='saucelabs' - node_js: "9" env: BROWSER='Chrome' VERSION='49' PLATFORM='macOS 10.13' OPENPGPJSTEST='saucelabs' COMPAT=1 diff --git a/src/crypto/cfb.js b/src/crypto/cfb.js index 41e871cb..1a83972e 100644 --- a/src/crypto/cfb.js +++ b/src/crypto/cfb.js @@ -18,273 +18,68 @@ */ /** + * @requires web-stream-tools * @requires crypto/cipher + * @requires util * @module crypto/cfb */ +import { AES_CFB } from 'asmcrypto.js/dist_es5/aes/cfb'; + +import stream from 'web-stream-tools'; import cipher from './cipher'; +import config from '../config'; +import util from '../util'; + +const webCrypto = util.getWebCrypto(); +const nodeCrypto = util.getNodeCrypto(); +const Buffer = util.getNodeBuffer(); export default { - - /** - * This function encrypts a given plaintext with the specified prefixrandom - * using the specified blockcipher - * @param {Uint8Array} prefixrandom random bytes of block_size length - * to be used in prefixing the data - * @param {String} cipherfn the algorithm cipher class to encrypt - * data in one block_size encryption, {@link module:crypto/cipher}. - * @param {Uint8Array} plaintext data to be encrypted - * @param {Uint8Array} key key to be used to encrypt the plaintext. - * This will be passed to the cipherfn - * @param {Boolean} resync a boolean value specifying if a resync of the - * IV should be used or not. The encrypteddatapacket uses the - * "old" style with a resync. Encryption within an - * encryptedintegrityprotecteddata packet is not resyncing the IV. - * @returns {Uint8Array} encrypted data - */ - encrypt: function(prefixrandom, cipherfn, plaintext, key, resync) { - cipherfn = new cipher[cipherfn](key); - const block_size = cipherfn.blockSize; - - const FR = new Uint8Array(block_size); - let FRE = new Uint8Array(block_size); - - const new_prefix = new Uint8Array(prefixrandom.length + 2); - new_prefix.set(prefixrandom); - new_prefix[prefixrandom.length] = prefixrandom[block_size-2]; - new_prefix[prefixrandom.length+1] = prefixrandom[block_size-1]; - prefixrandom = new_prefix; - - let ciphertext = new Uint8Array(plaintext.length + 2 + block_size * 2); - let i; - let n; - let begin; - const 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; + encrypt: function(algo, key, plaintext, iv) { + if (algo.substr(0, 3) === 'aes') { + return aesEncrypt(algo, key, plaintext, iv); } - // 2. FR is encrypted to produce FRE (FR Encrypted). This is the - // encryption of an all-zero value. - FRE = cipherfn.encrypt(FR); - // 3. FRE is xored with the first BS octets of random data prefixed to - // the plaintext to produce C[1] through C[BS], the first BS octets - // of ciphertext. - for (i = 0; i < block_size; i++) { - ciphertext[i] = FRE[i] ^ prefixrandom[i]; - } - - // 4. FR is loaded with C[1] through C[BS]. - FR.set(ciphertext.subarray(0, block_size)); - - // 5. FR is encrypted to produce FRE, the encryption of the first BS - // octets of ciphertext. - FRE = cipherfn.encrypt(FR); - - // 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[block_size] = FRE[0] ^ prefixrandom[block_size]; - ciphertext[block_size + 1] = FRE[1] ^ prefixrandom[block_size + 1]; - - if (resync) { - // 7. (The resync step) FR is loaded with C[3] through C[BS+2]. - FR.set(ciphertext.subarray(2, block_size + 2)); - } else { - FR.set(ciphertext.subarray(0, block_size)); - } - // 8. FR is encrypted to produce FRE. - FRE = cipherfn.encrypt(FR); - - // 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[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[block_size + begin + i] = FRE[i] ^ plaintext[n + i - offset]; - } - } - - ciphertext = ciphertext.subarray(0, plaintext.length + 2 + block_size); - return ciphertext; - }, - - /** - * Decrypts the prefixed data for the Modification Detection Code (MDC) computation - * @param {String} cipherfn.encrypt Cipher function to use, - * @see module:crypto/cipher. - * @param {Uint8Array} key Uint8Array representation of key to be used to check the mdc - * This will be passed to the cipherfn - * @param {Uint8Array} ciphertext The encrypted data - * @returns {Uint8Array} plaintext Data of D(ciphertext) with blocksize length +2 - */ - mdc: function(cipherfn, key, ciphertext) { - cipherfn = new cipher[cipherfn](key); - const block_size = cipherfn.blockSize; - - let iblock = new Uint8Array(block_size); - let ablock = new Uint8Array(block_size); - let i; - - - // initialisation vector - for (i = 0; i < block_size; i++) { - iblock[i] = 0; - } - - iblock = cipherfn.encrypt(iblock); - for (i = 0; i < block_size; i++) { - ablock[i] = ciphertext[i]; - iblock[i] ^= ablock[i]; - } - - ablock = cipherfn.encrypt(ablock); - - const result = new Uint8Array(iblock.length + 2); - result.set(iblock); - result[iblock.length] = ablock[0] ^ ciphertext[block_size]; - result[iblock.length + 1] = ablock[1] ^ ciphertext[block_size + 1]; - return result; - }, - - /** - * This function decrypts a given ciphertext using the specified blockcipher - * @param {String} cipherfn the algorithm cipher class to decrypt - * data in one block_size encryption, {@link module:crypto/cipher}. - * @param {Uint8Array} key Uint8Array representation of key to be used to decrypt the ciphertext. - * This will be passed to the cipherfn - * @param {Uint8Array} ciphertext to be decrypted - * @param {Boolean} resync a boolean value specifying if a resync of the - * IV should be used or not. The encrypteddatapacket uses the - * "old" style with a resync. Decryption within an - * encryptedintegrityprotecteddata packet is not resyncing the IV. - * @returns {Uint8Array} the plaintext data - */ - decrypt: function(cipherfn, key, ciphertext, resync) { - cipherfn = new cipher[cipherfn](key); - const block_size = cipherfn.blockSize; - - const iblock = new Uint8Array(block_size); - let ablock = new Uint8Array(block_size); - - let i; - let j; - let n; - let text = new Uint8Array(ciphertext.length - block_size); - - /* 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. - */ - - j = 0; - if (resync) { - for (i = 0; i < block_size; i++) { - iblock[i] = ciphertext[i + 2]; - } - for (n = block_size + 2; n < ciphertext.length; n += block_size) { - ablock = cipherfn.encrypt(iblock); - - for (i = 0; i < block_size && i + n < ciphertext.length; i++) { - iblock[i] = ciphertext[n + i]; - if (j < text.length) { - text[j] = ablock[i] ^ iblock[i]; - j++; - } - } - } - } else { - for (i = 0; i < block_size; i++) { - iblock[i] = ciphertext[i]; - } - for (n = block_size; n < ciphertext.length; n += block_size) { - ablock = cipherfn.encrypt(iblock); - for (i = 0; i < block_size && i + n < ciphertext.length; i++) { - iblock[i] = ciphertext[n + i]; - if (j < text.length) { - text[j] = ablock[i] ^ iblock[i]; - j++; - } - } - } - } - - n = resync ? 0 : 2; - - text = text.subarray(n, ciphertext.length - block_size - 2 + n); - - return text; - }, - - normalEncrypt: function(cipherfn, key, plaintext, iv) { - cipherfn = new cipher[cipherfn](key); + const cipherfn = new cipher[algo](key); const block_size = cipherfn.blockSize; let blocki = new Uint8Array(block_size); - const blockc = new Uint8Array(block_size); + const blockc = iv; let pos = 0; - const cyphertext = new Uint8Array(plaintext.length); + const ciphertext = new Uint8Array(plaintext.length); let i; let j = 0; - if (iv === null) { - for (i = 0; i < block_size; i++) { - blockc[i] = 0; - } - } else { - for (i = 0; i < block_size; i++) { - blockc[i] = iv[i]; - } - } while (plaintext.length > block_size * pos) { const encblock = cipherfn.encrypt(blockc); blocki = plaintext.subarray((pos * block_size), (pos * block_size) + block_size); for (i = 0; i < blocki.length; i++) { blockc[i] = blocki[i] ^ encblock[i]; - cyphertext[j++] = blockc[i]; + ciphertext[j++] = blockc[i]; } pos++; } - return cyphertext; + return ciphertext; }, - normalDecrypt: function(cipherfn, key, ciphertext, iv) { - cipherfn = new cipher[cipherfn](key); + decrypt: async function(algo, key, ciphertext, iv) { + if (algo.substr(0, 3) === 'aes') { + return aesDecrypt(algo, key, ciphertext, iv); + } + + ciphertext = await stream.readToEnd(ciphertext); + + const cipherfn = new cipher[algo](key); const block_size = cipherfn.blockSize; - let blockp; + let blockp = iv; let pos = 0; const plaintext = new Uint8Array(ciphertext.length); const offset = 0; let i; let j = 0; - if (iv === null) { - blockp = new Uint8Array(block_size); - for (i = 0; i < block_size; i++) { - blockp[i] = 0; - } - } else { - blockp = iv.subarray(0, block_size); - } while (ciphertext.length > (block_size * pos)) { const decblock = cipherfn.encrypt(blockp); blockp = ciphertext.subarray((pos * (block_size)) + offset, (pos * (block_size)) + (block_size) + offset); @@ -297,3 +92,60 @@ export default { return plaintext; } }; + +function aesEncrypt(algo, key, pt, iv) { + if ( + util.getWebCrypto() && + key.length !== 24 && // Chrome doesn't support 192 bit keys, see https://www.chromium.org/blink/webcrypto#TOC-AES-support + !util.isStream(pt) && + pt.length >= 3000 * config.min_bytes_for_web_crypto // Default to a 3MB minimum. Chrome is pretty slow for small messages, see: https://bugs.chromium.org/p/chromium/issues/detail?id=701188#c2 + ) { // Web Crypto + return webEncrypt(algo, key, pt, iv); + } + if (nodeCrypto) { // Node crypto library. + return nodeEncrypt(algo, key, pt, iv); + } // asm.js fallback + const cfb = new AES_CFB(key, iv); + return stream.transform(pt, value => cfb.AES_Encrypt_process(value), () => cfb.AES_Encrypt_finish()); +} + +function aesDecrypt(algo, key, ct, iv) { + if (nodeCrypto) { // Node crypto library. + return nodeDecrypt(algo, key, ct, iv); + } + if (util.isStream(ct)) { + const cfb = new AES_CFB(key, iv); + return stream.transform(ct, value => cfb.AES_Decrypt_process(value), () => cfb.AES_Decrypt_finish()); + } + return AES_CFB.decrypt(ct, key, iv); +} + +function xorMut(a, b) { + for (let i = 0; i < a.length; i++) { + a[i] = a[i] ^ b[i]; + } +} + +async function webEncrypt(algo, key, pt, iv) { + const ALGO = 'AES-CBC'; + const _key = await webCrypto.importKey('raw', key, { name: ALGO }, false, ['encrypt']); + const { blockSize } = cipher[algo]; + const cbc_pt = util.concatUint8Array([new Uint8Array(blockSize), pt]); + const ct = new Uint8Array(await webCrypto.encrypt({ name: ALGO, iv }, _key, cbc_pt)).subarray(0, pt.length); + xorMut(ct, pt); + return ct; +} + +function nodeEncrypt(algo, key, pt, iv) { + key = new Buffer(key); + iv = new Buffer(iv); + const cipherObj = new nodeCrypto.createCipheriv('aes-' + algo.substr(3, 3) + '-cfb', key, iv); + return stream.transform(pt, value => new Uint8Array(cipherObj.update(new Buffer(value)))); +} + +function nodeDecrypt(algo, key, ct, iv) { + key = new Buffer(key); + iv = new Buffer(iv); + const decipherObj = new nodeCrypto.createDecipheriv('aes-' + algo.substr(3, 3) + '-cfb', key, iv); + return stream.transform(ct, value => new Uint8Array(decipherObj.update(new Buffer(value)))); +} diff --git a/src/crypto/crypto.js b/src/crypto/crypto.js index c3692837..38d8e8f7 100644 --- a/src/crypto/crypto.js +++ b/src/crypto/crypto.js @@ -28,6 +28,7 @@ * @requires type/mpi * @requires type/oid * @requires enums + * @requires util * @module crypto/crypto */ @@ -39,6 +40,7 @@ import type_kdf_params from '../type/kdf_params'; import type_mpi from '../type/mpi'; import type_oid from '../type/oid'; import enums from '../enums'; +import util from '../util'; function constructParams(types, data) { return types.map(function(type, i) { @@ -291,11 +293,13 @@ export default { * Generates a random byte prefix for the specified algorithm * See {@link https://tools.ietf.org/html/rfc4880#section-9.2|RFC 4880 9.2} for algorithms. * @param {module:enums.symmetric} algo Symmetric encryption algorithm - * @returns {Uint8Array} Random bytes with length equal to the block size of the cipher + * @returns {Uint8Array} Random bytes with length equal to the block size of the cipher, plus the last two bytes repeated. * @async */ - getPrefixRandom: function(algo) { - return random.getRandomBytes(cipher[algo].blockSize); + getPrefixRandom: async function(algo) { + const prefixrandom = await random.getRandomBytes(cipher[algo].blockSize); + const repeat = new Uint8Array([prefixrandom[prefixrandom.length - 2], prefixrandom[prefixrandom.length - 1]]); + return util.concat([prefixrandom, repeat]); }, /** diff --git a/src/packet/secret_key.js b/src/packet/secret_key.js index c335114d..13f660f1 100644 --- a/src/packet/secret_key.js +++ b/src/packet/secret_key.js @@ -195,7 +195,7 @@ SecretKey.prototype.encrypt = async function (passphrase) { arr = [new Uint8Array([254, enums.write(enums.symmetric, symmetric)])]; arr.push(s2k.write()); arr.push(iv); - arr.push(crypto.cfb.normalEncrypt(symmetric, key, util.concatUint8Array([ + arr.push(crypto.cfb.encrypt(symmetric, key, util.concatUint8Array([ cleartext, await crypto.hash.sha1(cleartext) ]), iv)); @@ -293,7 +293,7 @@ SecretKey.prototype.decrypt = async function (passphrase) { } } } else { - const cleartextWithHash = crypto.cfb.normalDecrypt(symmetric, key, ciphertext, iv); + const cleartextWithHash = await crypto.cfb.decrypt(symmetric, key, ciphertext, iv); let hash; let hashlen; diff --git a/src/packet/sym_encrypted_integrity_protected.js b/src/packet/sym_encrypted_integrity_protected.js index 4362ee5d..a2c4e66b 100644 --- a/src/packet/sym_encrypted_integrity_protected.js +++ b/src/packet/sym_encrypted_integrity_protected.js @@ -24,17 +24,12 @@ * @requires util */ -import { AES_CFB } from 'asmcrypto.js/dist_es5/aes/cfb'; - import stream from 'web-stream-tools'; import config from '../config'; import crypto from '../crypto'; import enums from '../enums'; import util from '../util'; -const nodeCrypto = util.getNodeCrypto(); -const Buffer = util.getNodeBuffer(); - const VERSION = 1; // A one-octet version number of the data packet. /** @@ -94,22 +89,14 @@ SymEncryptedIntegrityProtected.prototype.write = function () { SymEncryptedIntegrityProtected.prototype.encrypt = async function (sessionKeyAlgorithm, key, streaming) { let bytes = this.packets.write(); if (!streaming) bytes = await stream.readToEnd(bytes); - const prefixrandom = await crypto.getPrefixRandom(sessionKeyAlgorithm); - const repeat = new Uint8Array([prefixrandom[prefixrandom.length - 2], prefixrandom[prefixrandom.length - 1]]); - const prefix = util.concat([prefixrandom, repeat]); + const prefix = await crypto.getPrefixRandom(sessionKeyAlgorithm); const mdc = new Uint8Array([0xD3, 0x14]); // modification detection code packet - let tohash = util.concat([bytes, mdc]); - const hash = await crypto.hash.sha1(util.concat([prefix, stream.passiveClone(tohash)])); - tohash = util.concat([tohash, hash]); + const tohash = util.concat([prefix, bytes, mdc]); + const hash = await crypto.hash.sha1(stream.passiveClone(tohash)); + const plaintext = util.concat([tohash, hash]); - if (sessionKeyAlgorithm.substr(0, 3) === 'aes') { // AES optimizations. Native code for node, asmCrypto for browser. - this.encrypted = aesEncrypt(sessionKeyAlgorithm, util.concat([prefix, tohash]), key); - } else { - tohash = await stream.readToEnd(tohash); - this.encrypted = crypto.cfb.encrypt(prefixrandom, sessionKeyAlgorithm, tohash, key, false); - this.encrypted = stream.slice(this.encrypted, 0, prefix.length + tohash.length); - } + this.encrypted = await crypto.cfb.encrypt(sessionKeyAlgorithm, key, plaintext, new Uint8Array(crypto.cipher[sessionKeyAlgorithm].blockSize)); return true; }; @@ -124,23 +111,14 @@ SymEncryptedIntegrityProtected.prototype.encrypt = async function (sessionKeyAlg SymEncryptedIntegrityProtected.prototype.decrypt = async function (sessionKeyAlgorithm, key, streaming) { if (!streaming) this.encrypted = await stream.readToEnd(this.encrypted); const encrypted = stream.clone(this.encrypted); - const encryptedClone = stream.passiveClone(encrypted); - let decrypted; - if (sessionKeyAlgorithm.substr(0, 3) === 'aes') { // AES optimizations. Native code for node, asmCrypto for browser. - decrypted = aesDecrypt(sessionKeyAlgorithm, encrypted, key, streaming); - } else { - decrypted = crypto.cfb.decrypt(sessionKeyAlgorithm, key, await stream.readToEnd(encrypted), false); - } + const decrypted = await crypto.cfb.decrypt(sessionKeyAlgorithm, key, encrypted, new Uint8Array(crypto.cipher[sessionKeyAlgorithm].blockSize)); // there must be a modification detection code packet as the // last packet and everything gets hashed except the hash itself - const encryptedPrefix = await stream.readToEnd(stream.slice(encryptedClone, 0, crypto.cipher[sessionKeyAlgorithm].blockSize + 2)); - const prefix = crypto.cfb.mdc(sessionKeyAlgorithm, key, encryptedPrefix); const realHash = stream.slice(stream.passiveClone(decrypted), -20); - const bytes = stream.slice(decrypted, 0, -20); - const tohash = util.concat([prefix, stream.passiveClone(bytes)]); + const tohash = stream.slice(decrypted, 0, -20); const verifyHash = Promise.all([ - stream.readToEnd(await crypto.hash.sha1(tohash)), + stream.readToEnd(await crypto.hash.sha1(stream.passiveClone(tohash))), stream.readToEnd(realHash) ]).then(([hash, mdc]) => { if (!util.equalsUint8Array(hash, mdc)) { @@ -148,7 +126,8 @@ SymEncryptedIntegrityProtected.prototype.decrypt = async function (sessionKeyAlg } return new Uint8Array(); }); - let packetbytes = stream.slice(bytes, 0, -2); + const bytes = stream.slice(tohash, crypto.cipher[sessionKeyAlgorithm].blockSize + 2); // Remove random prefix + let packetbytes = stream.slice(bytes, 0, -2); // Remove MDC packet packetbytes = stream.concat([packetbytes, stream.fromAsync(() => verifyHash)]); if (!util.isStream(encrypted) || !config.allow_unauthenticated_stream) { packetbytes = await stream.readToEnd(packetbytes); @@ -158,48 +137,3 @@ SymEncryptedIntegrityProtected.prototype.decrypt = async function (sessionKeyAlg }; export default SymEncryptedIntegrityProtected; - - -////////////////////////// -// // -// Helper functions // -// // -////////////////////////// - - -function aesEncrypt(algo, pt, key) { - if (nodeCrypto) { // Node crypto library. - return nodeEncrypt(algo, pt, key); - } // asm.js fallback - const cfb = new AES_CFB(key); - return stream.transform(pt, value => cfb.AES_Encrypt_process(value), () => cfb.AES_Encrypt_finish()); -} - -function aesDecrypt(algo, ct, key) { - let pt; - if (nodeCrypto) { // Node crypto library. - pt = nodeDecrypt(algo, ct, key); - } else { // asm.js fallback - if (util.isStream(ct)) { - const cfb = new AES_CFB(key); - pt = stream.transform(ct, value => cfb.AES_Decrypt_process(value), () => cfb.AES_Decrypt_finish()); - } else { - pt = AES_CFB.decrypt(ct, key); - } - } - return stream.slice(pt, crypto.cipher[algo].blockSize + 2); // Remove random prefix -} - -function nodeEncrypt(algo, pt, key) { - key = new Buffer(key); - const iv = new Buffer(new Uint8Array(crypto.cipher[algo].blockSize)); - const cipherObj = new nodeCrypto.createCipheriv('aes-' + algo.substr(3, 3) + '-cfb', key, iv); - return stream.transform(pt, value => new Uint8Array(cipherObj.update(new Buffer(value)))); -} - -function nodeDecrypt(algo, ct, key) { - key = new Buffer(key); - const iv = new Buffer(new Uint8Array(crypto.cipher[algo].blockSize)); - const decipherObj = new nodeCrypto.createDecipheriv('aes-' + algo.substr(3, 3) + '-cfb', key, iv); - return stream.transform(ct, value => new Uint8Array(decipherObj.update(new Buffer(value)))); -} diff --git a/src/packet/sym_encrypted_session_key.js b/src/packet/sym_encrypted_session_key.js index b371f0e9..a526c3f5 100644 --- a/src/packet/sym_encrypted_session_key.js +++ b/src/packet/sym_encrypted_session_key.js @@ -145,7 +145,7 @@ SymEncryptedSessionKey.prototype.decrypt = async function(passphrase) { const modeInstance = await mode(algo, key); this.sessionKey = await modeInstance.decrypt(this.encrypted, this.iv, adata); } else if (this.encrypted !== null) { - const decrypted = crypto.cfb.normalDecrypt(algo, key, this.encrypted, null); + const decrypted = await crypto.cfb.decrypt(algo, key, this.encrypted, new Uint8Array(crypto.cipher[algo].blockSize)); this.sessionKeyAlgorithm = enums.read(enums.symmetric, decrypted[0]); this.sessionKey = decrypted.subarray(1, decrypted.length); @@ -188,7 +188,7 @@ SymEncryptedSessionKey.prototype.encrypt = async function(passphrase) { } else { const algo_enum = new Uint8Array([enums.write(enums.symmetric, this.sessionKeyAlgorithm)]); const private_key = util.concatUint8Array([algo_enum, this.sessionKey]); - this.encrypted = crypto.cfb.normalEncrypt(algo, key, private_key, null); + this.encrypted = await crypto.cfb.encrypt(algo, key, private_key, new Uint8Array(crypto.cipher[algo].blockSize)); } return true; diff --git a/src/packet/symmetrically_encrypted.js b/src/packet/symmetrically_encrypted.js index da730b19..b178ecb3 100644 --- a/src/packet/symmetrically_encrypted.js +++ b/src/packet/symmetrically_encrypted.js @@ -20,12 +20,14 @@ * @requires config * @requires crypto * @requires enums + * @requires util */ import stream from 'web-stream-tools'; import config from '../config'; import crypto from '../crypto'; import enums from '../enums'; +import util from '../util'; /** * Implementation of the Symmetrically Encrypted Data Packet (Tag 9) @@ -79,7 +81,11 @@ SymmetricallyEncrypted.prototype.write = function () { */ SymmetricallyEncrypted.prototype.decrypt = async function (sessionKeyAlgorithm, key) { this.encrypted = await stream.readToEnd(this.encrypted); - const decrypted = crypto.cfb.decrypt(sessionKeyAlgorithm, key, this.encrypted, true); + const decrypted = await crypto.cfb.decrypt(sessionKeyAlgorithm, key, + this.encrypted.subarray(crypto.cipher[sessionKeyAlgorithm].blockSize + 2), + this.encrypted.subarray(2, crypto.cipher[sessionKeyAlgorithm].blockSize + 2) + ); + // If MDC errors are not being ignored, all missing MDC packets in symmetrically encrypted data should throw an error if (!this.ignore_mdc_error) { throw new Error('Decryption failed due to missing MDC.'); @@ -100,7 +106,10 @@ SymmetricallyEncrypted.prototype.decrypt = async function (sessionKeyAlgorithm, SymmetricallyEncrypted.prototype.encrypt = async function (algo, key) { const data = this.packets.write(); - this.encrypted = crypto.cfb.encrypt(await crypto.getPrefixRandom(algo), algo, await stream.readToEnd(data), key, true); + const prefix = await crypto.getPrefixRandom(algo); + const FRE = await crypto.cfb.encrypt(algo, key, prefix, new Uint8Array(crypto.cipher[algo].blockSize)); + const ciphertext = await crypto.cfb.encrypt(algo, key, data, FRE.subarray(2)); + this.encrypted = util.concatUint8Array([FRE, ciphertext]); return true; }; diff --git a/test/crypto/crypto.js b/test/crypto/crypto.js index 00d03e70..6459d346 100644 --- a/test/crypto/crypto.js +++ b/test/crypto/crypto.js @@ -277,31 +277,14 @@ describe('API functional testing', function() { return algo !== 'idea' && algo !== 'plaintext'; }); - function testCFB(plaintext, resync) { - symmAlgos.forEach(async function(algo) { + async function testCFB(plaintext) { + await Promise.all(symmAlgos.map(async function(algo) { const symmKey = await crypto.generateSessionKey(algo); - const symmencData = crypto.cfb.encrypt(await crypto.getPrefixRandom(algo), algo, util.str_to_Uint8Array(plaintext), symmKey, resync); - const text = util.Uint8Array_to_str(crypto.cfb.decrypt(algo, symmKey, symmencData, resync)); + const IV = new Uint8Array(crypto.cipher[algo].blockSize); + const symmencData = await crypto.cfb.encrypt(algo, symmKey, util.str_to_Uint8Array(plaintext), IV); + const text = util.Uint8Array_to_str(await crypto.cfb.decrypt(algo, symmKey, symmencData, new Uint8Array(crypto.cipher[algo].blockSize))); expect(text).to.equal(plaintext); - }); - } - - function testAESCFB(plaintext) { - symmAlgos.forEach(async function(algo) { - if(algo.substr(0,3) === 'aes') { - const symmKey = await crypto.generateSessionKey(algo); - const rndm = await crypto.getPrefixRandom(algo); - - const repeat = new Uint8Array([rndm[rndm.length - 2], rndm[rndm.length - 1]]); - const prefix = util.concatUint8Array([rndm, repeat]); - - const symmencData = crypto.cfb.encrypt(rndm, algo, util.str_to_Uint8Array(plaintext), symmKey, false); - const decrypted = crypto.cfb.decrypt(algo, symmKey, symmencData, false); - - const text = util.Uint8Array_to_str(decrypted); - expect(text).to.equal(plaintext); - } - }); + })); } function testAESGCM(plaintext, nativeDecrypt) { @@ -325,25 +308,11 @@ describe('API functional testing', function() { }); } - 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("asmCrypto AES without OpenPGP CFB resync", function () { - testAESCFB("hello"); - testAESCFB("1234567"); - testAESCFB("foobarfoobar1234567890"); - testAESCFB("12345678901234567890123456789012345678901234567890"); + it("Symmetric with OpenPGP CFB", async function () { + await testCFB("hello"); + await testCFB("1234567"); + await testCFB("foobarfoobar1234567890"); + await testCFB("12345678901234567890123456789012345678901234567890"); }); describe('Symmetric AES-GCM (native)', function() {