diff --git a/src/crypto/eax.js b/src/crypto/eax.js index 28d9cce5..c8c81f8e 100644 --- a/src/crypto/eax.js +++ b/src/crypto/eax.js @@ -47,79 +47,113 @@ class OMAC extends CMAC { } } - -/** - * Encrypt plaintext input. - * @param {String} cipher The symmetric cipher algorithm to use e.g. 'aes128' - * @param {Uint8Array} plaintext The cleartext input to be encrypted - * @param {Uint8Array} key The encryption key - * @param {Uint8Array} nonce The nonce (16 bytes) - * @param {Uint8Array} adata Associated data to sign - * @returns {Promise} The ciphertext output - */ -async function encrypt(cipher, plaintext, key, nonce, adata) { - if (cipher.substr(0, 3) !== 'aes') { - throw new Error('EAX mode supports only AES cipher'); +class CTR { + constructor(key) { + if (util.getWebCryptoAll() && key.length !== 24) { // WebCrypto (no 192 bit support) see: https://www.chromium.org/blink/webcrypto#TOC-AES-support + this.key = webCrypto.importKey('raw', key, { name: 'AES-CTR', length: key.length * 8 }, false, ['encrypt']); + this.ctr = this.webCtr; + } else if (util.getNodeCrypto()) { // Node crypto library + this.key = new Buffer(key); + this.ctr = this.nodeCtr; + } else { + // asm.js fallback + this.key = key; + } } - const omac = new OMAC(key); - const _nonce = omac.mac(zero, nonce); - const _adata = omac.mac(one, adata); - const ciphered = await CTR(plaintext, key, _nonce); - const _ciphered = omac.mac(two, ciphered); - const tag = xor3(_nonce, _ciphered, _adata); // Assumes that omac.mac(*).length === tagLength. - return concat(ciphered, tag); -} - -/** - * Decrypt ciphertext input. - * @param {String} cipher The symmetric cipher algorithm to use e.g. 'aes128' - * @param {Uint8Array} ciphertext The ciphertext input to be decrypted - * @param {Uint8Array} key The encryption key - * @param {Uint8Array} nonce The nonce (16 bytes) - * @param {Uint8Array} adata Associated data to verify - * @returns {Promise} The plaintext output - */ -async function decrypt(cipher, ciphertext, key, nonce, adata) { - if (cipher.substr(0, 3) !== 'aes') { - throw new Error('EAX mode supports only AES cipher'); + webCtr(pt, iv) { + return this.key + .then(keyObj => webCrypto.encrypt({ name: 'AES-CTR', counter: iv, length: blockLength * 8 }, keyObj, pt)) + .then(ct => new Uint8Array(ct)); } - if (ciphertext.length < tagLength) throw new Error('Invalid EAX ciphertext'); - const ciphered = ciphertext.subarray(0, ciphertext.length - tagLength); - const tag = ciphertext.subarray(ciphertext.length - tagLength); - const omac = new OMAC(key); - const _nonce = omac.mac(zero, nonce); - const _adata = omac.mac(one, adata); - const _ciphered = omac.mac(two, ciphered); - const _tag = xor3(_nonce, _ciphered, _adata); // Assumes that omac.mac(*).length === tagLength. - if (!util.equalsUint8Array(tag, _tag)) throw new Error('Authentication tag mismatch in EAX ciphertext'); - const plaintext = await CTR(ciphered, key, _nonce); - return plaintext; + nodeCtr(pt, iv) { + pt = new Buffer(pt); + iv = new Buffer(iv); + const en = new nodeCrypto.createCipheriv('aes-' + (this.key.length * 8) + '-ctr', this.key, iv); + const ct = Buffer.concat([en.update(pt), en.final()]); + return Promise.resolve(new Uint8Array(ct)); + } + + ctr(pt, iv) { + return Promise.resolve(AES_CTR.encrypt(pt, this.key, iv)); + } } + +class EAX { + /** + * Class to en/decrypt using EAX mode. + * @param {String} cipher The symmetric cipher algorithm to use e.g. 'aes128' + * @param {Uint8Array} key The encryption key + */ + constructor(cipher, key) { + if (cipher.substr(0, 3) !== 'aes') { + throw new Error('EAX mode supports only AES cipher'); + } + + const omac = new OMAC(key); + this.omac = omac.mac.bind(omac); + const ctr = new CTR(key); + this.ctr = ctr.ctr.bind(ctr); + } + + /** + * Encrypt plaintext input. + * @param {Uint8Array} plaintext The cleartext input to be encrypted + * @param {Uint8Array} nonce The nonce (16 bytes) + * @param {Uint8Array} adata Associated data to sign + * @returns {Promise} The ciphertext output + */ + async encrypt(plaintext, nonce, adata) { + const _nonce = this.omac(zero, nonce); + const _adata = this.omac(one, adata); + const ciphered = await this.ctr(plaintext, _nonce); + const _ciphered = this.omac(two, ciphered); + const tag = xor3(_nonce, _ciphered, _adata); // Assumes that omac(*).length === tagLength. + return concat(ciphered, tag); + } + + /** + * Decrypt ciphertext input. + * @param {Uint8Array} ciphertext The ciphertext input to be decrypted + * @param {Uint8Array} nonce The nonce (16 bytes) + * @param {Uint8Array} adata Associated data to verify + * @returns {Promise} The plaintext output + */ + async decrypt(ciphertext, nonce, adata) { + if (ciphertext.length < tagLength) throw new Error('Invalid EAX ciphertext'); + const ciphered = ciphertext.subarray(0, ciphertext.length - tagLength); + const tag = ciphertext.subarray(ciphertext.length - tagLength); + const _nonce = this.omac(zero, nonce); + const _adata = this.omac(one, adata); + const _ciphered = this.omac(two, ciphered); + const _tag = xor3(_nonce, _ciphered, _adata); // Assumes that omac(*).length === tagLength. + if (!util.equalsUint8Array(tag, _tag)) throw new Error('Authentication tag mismatch in EAX ciphertext'); + const plaintext = await this.ctr(ciphered, _nonce); + return plaintext; + } +} + + /** * Get EAX nonce as defined by {@link https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-04#section-5.16.1|RFC4880bis-04, section 5.16.1}. * @param {Uint8Array} iv The initialization vector (16 bytes) * @param {Uint8Array} chunkIndex The chunk index (8 bytes) */ -function getNonce(iv, chunkIndex) { +EAX.getNonce = function(iv, chunkIndex) { const nonce = iv.slice(); for (let i = 0; i < chunkIndex.length; i++) { nonce[8 + i] ^= chunkIndex[i]; } return nonce; -} - - -export default { - blockLength, - ivLength, - encrypt, - decrypt, - getNonce }; +EAX.blockLength = blockLength; +EAX.ivLength = ivLength; + +export default EAX; + ////////////////////////// // // @@ -135,27 +169,3 @@ function xor3(a, b, c) { function concat(...arrays) { return util.concatUint8Array(arrays); } - -function CTR(plaintext, key, iv) { - if (util.getWebCryptoAll() && key.length !== 24) { // WebCrypto (no 192 bit support) see: https://www.chromium.org/blink/webcrypto#TOC-AES-support - return webCtr(plaintext, key, iv); - } else if (util.getNodeCrypto()) { // Node crypto library - return nodeCtr(plaintext, key, iv); - } // asm.js fallback - return Promise.resolve(AES_CTR.encrypt(plaintext, key, iv)); -} - -function webCtr(pt, key, iv) { - return webCrypto.importKey('raw', key, { name: 'AES-CTR', length: key.length * 8 }, false, ['encrypt']) - .then(keyObj => webCrypto.encrypt({ name: 'AES-CTR', counter: iv, length: blockLength * 8 }, keyObj, pt)) - .then(ct => new Uint8Array(ct)); -} - -function nodeCtr(pt, key, iv) { - pt = new Buffer(pt); - key = new Buffer(key); - iv = new Buffer(iv); - const en = new nodeCrypto.createCipheriv('aes-' + (key.length * 8) + '-ctr', key, iv); - const ct = Buffer.concat([en.update(pt), en.final()]); - return Promise.resolve(new Uint8Array(ct)); -} diff --git a/src/crypto/ocb.js b/src/crypto/ocb.js index 590192c3..5bfe4aab 100644 --- a/src/crypto/ocb.js +++ b/src/crypto/ocb.js @@ -79,237 +79,247 @@ function double(S) { const zeros_16 = zeros(16); const one = new Uint8Array([1]); -function constructKeyVariables(cipher, key, text, adata) { - const aes = new ciphers[cipher](key); - const encipher = aes.encrypt.bind(aes); - const decipher = aes.decrypt.bind(aes); - - const L_x = encipher(zeros_16); - const L_$ = double(L_x); - const L = []; - L[0] = double(L_$); - - const max_ntz = util.nbits(Math.max(text.length, adata.length) >> 4) - 1; - for (let i = 1; i <= max_ntz; i++) { - L[i] = double(L[i - 1]); +class OCB { + /** + * Class to en/decrypt using OCB mode. + * @param {String} cipher The symmetric cipher algorithm to use e.g. 'aes128' + * @param {Uint8Array} key The encryption key + */ + constructor(cipher, key) { + this.max_ntz = 0; + this.constructKeyVariables(cipher, key); } - L.x = L_x; - L.$ = L_$; + constructKeyVariables(cipher, key) { + const aes = new ciphers[cipher](key); + const encipher = aes.encrypt.bind(aes); + const decipher = aes.decrypt.bind(aes); - return { encipher, decipher, L }; + const L_x = encipher(zeros_16); + const L_$ = double(L_x); + const L = []; + L[0] = double(L_$); + + + L.x = L_x; + L.$ = L_$; + + this.kv = { encipher, decipher, L }; + } + + extendKeyVariables(text, adata) { + const { L } = this.kv; + const max_ntz = util.nbits(Math.max(text.length, adata.length) >> 4) - 1; + for (let i = this.max_ntz + 1; i <= max_ntz; i++) { + L[i] = double(L[i - 1]); + } + this.max_ntz = max_ntz; + } + + hash(adata) { + if (!adata.length) { + // Fast path + return zeros_16; + } + + const { encipher, L } = this.kv; + + // + // Consider A as a sequence of 128-bit blocks + // + const m = adata.length >> 4; + + const offset = zeros(16); + const sum = zeros(16); + for (let i = 0; i < m; i++) { + set_xor(offset, L[ntz(i + 1)]); + set_xor(sum, encipher(xor(offset, adata))); + adata = adata.subarray(16); + } + + // + // Process any final partial block; compute final hash value + // + if (adata.length) { + set_xor(offset, L.x); + + const cipherInput = zeros(16); + cipherInput.set(adata, 0); + cipherInput[adata.length] = 0b10000000; + set_xor(cipherInput, offset); + + set_xor(sum, encipher(cipherInput)); + } + + return sum; + } + + + /** + * Encrypt plaintext input. + * @param {Uint8Array} plaintext The cleartext input to be encrypted + * @param {Uint8Array} nonce The nonce (15 bytes) + * @param {Uint8Array} adata Associated data to sign + * @returns {Promise} The ciphertext output + */ + async encrypt(plaintext, nonce, adata) { + // + // Consider P as a sequence of 128-bit blocks + // + const m = plaintext.length >> 4; + + // + // Key-dependent variables + // + this.extendKeyVariables(plaintext, adata); + const { encipher, L } = this.kv; + + // + // Nonce-dependent and per-encryption variables + // + // We assume here that TAGLEN mod 128 == 0 (tagLength === 16). + const Nonce = concat(zeros_16.subarray(0, 15 - nonce.length), one, nonce); + const bottom = Nonce[15] & 0b111111; + Nonce[15] &= 0b11000000; + const Ktop = encipher(Nonce); + const Stretch = concat(Ktop, xor(Ktop.subarray(0, 8), Ktop.subarray(1, 9))); + // Offset_0 = Stretch[1+bottom..128+bottom] + const offset = shiftRight(Stretch.subarray(0 + (bottom >> 3), 17 + (bottom >> 3)), 8 - (bottom & 7)).subarray(1); + const checksum = zeros(16); + + const C = new Uint8Array(plaintext.length + tagLength); + + // + // Process any whole blocks + // + let i; + let pos = 0; + for (i = 0; i < m; i++) { + set_xor(offset, L[ntz(i + 1)]); + C.set(set_xor(encipher(xor(offset, plaintext)), offset), pos); + set_xor(checksum, plaintext); + + plaintext = plaintext.subarray(16); + pos += 16; + } + + // + // Process any final partial block and compute raw tag + // + if (plaintext.length) { + set_xor(offset, L.x); + const Pad = encipher(offset); + C.set(xor(plaintext, Pad), pos); + + // Checksum_* = Checksum_m xor (P_* || 1 || zeros(127-bitlen(P_*))) + const xorInput = zeros(16); + xorInput.set(plaintext, 0); + xorInput[plaintext.length] = 0b10000000; + set_xor(checksum, xorInput); + pos += plaintext.length; + } + const Tag = set_xor(encipher(set_xor(set_xor(checksum, offset), L.$)), this.hash(adata)); + + // + // Assemble ciphertext + // + C.set(Tag, pos); + return C; + } + + + /** + * Decrypt ciphertext input. + * @param {Uint8Array} ciphertext The ciphertext input to be decrypted + * @param {Uint8Array} nonce The nonce (15 bytes) + * @param {Uint8Array} adata Associated data to verify + * @returns {Promise} The plaintext output + */ + async decrypt(ciphertext, nonce, adata) { + // + // Consider C as a sequence of 128-bit blocks + // + const T = ciphertext.subarray(ciphertext.length - tagLength); + ciphertext = ciphertext.subarray(0, ciphertext.length - tagLength); + const m = ciphertext.length >> 4; + + // + // Key-dependent variables + // + this.extendKeyVariables(ciphertext, adata); + const { encipher, decipher, L } = this.kv; + + // + // Nonce-dependent and per-encryption variables + // + // We assume here that TAGLEN mod 128 == 0 (tagLength === 16). + const Nonce = concat(zeros_16.subarray(0, 15 - nonce.length), one, nonce); + const bottom = Nonce[15] & 0b111111; + Nonce[15] &= 0b11000000; + const Ktop = encipher(Nonce); + const Stretch = concat(Ktop, xor(Ktop.subarray(0, 8), Ktop.subarray(1, 9))); + // Offset_0 = Stretch[1+bottom..128+bottom] + const offset = shiftRight(Stretch.subarray(0 + (bottom >> 3), 17 + (bottom >> 3)), 8 - (bottom & 7)).subarray(1); + const checksum = zeros(16); + + const P = new Uint8Array(ciphertext.length); + + // + // Process any whole blocks + // + let i; + let pos = 0; + for (i = 0; i < m; i++) { + set_xor(offset, L[ntz(i + 1)]); + P.set(set_xor(decipher(xor(offset, ciphertext)), offset), pos); + set_xor(checksum, P.subarray(pos)); + + ciphertext = ciphertext.subarray(16); + pos += 16; + } + + // + // Process any final partial block and compute raw tag + // + if (ciphertext.length) { + set_xor(offset, L.x); + const Pad = encipher(offset); + P.set(xor(ciphertext, Pad), pos); + + // Checksum_* = Checksum_m xor (P_* || 1 || zeros(127-bitlen(P_*))) + const xorInput = zeros(16); + xorInput.set(P.subarray(pos), 0); + xorInput[ciphertext.length] = 0b10000000; + set_xor(checksum, xorInput); + pos += ciphertext.length; + } + const Tag = set_xor(encipher(set_xor(set_xor(checksum, offset), L.$)), this.hash(adata)); + + // + // Check for validity and assemble plaintext + // + if (!util.equalsUint8Array(Tag, T)) { + throw new Error('Authentication tag mismatch in OCB ciphertext'); + } + return P; + } } -function hash(kv, key, adata) { - if (!adata.length) { - // Fast path - return zeros_16; - } - - const { encipher, L } = kv; - - // - // Consider A as a sequence of 128-bit blocks - // - const m = adata.length >> 4; - - const offset = zeros(16); - const sum = zeros(16); - for (let i = 0; i < m; i++) { - set_xor(offset, L[ntz(i + 1)]); - set_xor(sum, encipher(xor(offset, adata))); - adata = adata.subarray(16); - } - - // - // Process any final partial block; compute final hash value - // - if (adata.length) { - set_xor(offset, L.x); - - const cipherInput = zeros(16); - cipherInput.set(adata, 0); - cipherInput[adata.length] = 0b10000000; - set_xor(cipherInput, offset); - - set_xor(sum, encipher(cipherInput)); - } - - return sum; -} - - -/** - * Encrypt plaintext input. - * @param {String} cipher The symmetric cipher algorithm to use e.g. 'aes128' - * @param {Uint8Array} plaintext The cleartext input to be encrypted - * @param {Uint8Array} key The encryption key - * @param {Uint8Array} nonce The nonce (15 bytes) - * @param {Uint8Array} adata Associated data to sign - * @returns {Promise} The ciphertext output - */ -async function encrypt(cipher, plaintext, key, nonce, adata) { - // - // Consider P as a sequence of 128-bit blocks - // - const m = plaintext.length >> 4; - - // - // Key-dependent variables - // - const kv = constructKeyVariables(cipher, key, plaintext, adata); - const { encipher, L } = kv; - - // - // Nonce-dependent and per-encryption variables - // - // We assume here that TAGLEN mod 128 == 0 (tagLength === 16). - const Nonce = concat(zeros_16.subarray(0, 15 - nonce.length), one, nonce); - const bottom = Nonce[15] & 0b111111; - Nonce[15] &= 0b11000000; - const Ktop = encipher(Nonce); - const Stretch = concat(Ktop, xor(Ktop.subarray(0, 8), Ktop.subarray(1, 9))); - // Offset_0 = Stretch[1+bottom..128+bottom] - const offset = shiftRight(Stretch.subarray(0 + (bottom >> 3), 17 + (bottom >> 3)), 8 - (bottom & 7)).subarray(1); - const checksum = zeros(16); - - const C = new Uint8Array(plaintext.length + tagLength); - - // - // Process any whole blocks - // - let i; - let pos = 0; - for (i = 0; i < m; i++) { - set_xor(offset, L[ntz(i + 1)]); - C.set(set_xor(encipher(xor(offset, plaintext)), offset), pos); - set_xor(checksum, plaintext); - - plaintext = plaintext.subarray(16); - pos += 16; - } - - // - // Process any final partial block and compute raw tag - // - if (plaintext.length) { - set_xor(offset, L.x); - const Pad = encipher(offset); - C.set(xor(plaintext, Pad), pos); - - // Checksum_* = Checksum_m xor (P_* || 1 || zeros(127-bitlen(P_*))) - const xorInput = zeros(16); - xorInput.set(plaintext, 0); - xorInput[plaintext.length] = 0b10000000; - set_xor(checksum, xorInput); - pos += plaintext.length; - } - const Tag = set_xor(encipher(set_xor(set_xor(checksum, offset), L.$)), hash(kv, key, adata)); - - // - // Assemble ciphertext - // - C.set(Tag, pos); - return C; -} - - -/** - * Decrypt ciphertext input. - * @param {String} cipher The symmetric cipher algorithm to use e.g. 'aes128' - * @param {Uint8Array} ciphertext The ciphertext input to be decrypted - * @param {Uint8Array} key The encryption key - * @param {Uint8Array} nonce The nonce (15 bytes) - * @param {Uint8Array} adata Associated data to verify - * @returns {Promise} The plaintext output - */ -async function decrypt(cipher, ciphertext, key, nonce, adata) { - // - // Consider C as a sequence of 128-bit blocks - // - const T = ciphertext.subarray(ciphertext.length - tagLength); - ciphertext = ciphertext.subarray(0, ciphertext.length - tagLength); - const m = ciphertext.length >> 4; - - // - // Key-dependent variables - // - const kv = constructKeyVariables(cipher, key, ciphertext, adata); - const { encipher, decipher, L } = kv; - - // - // Nonce-dependent and per-encryption variables - // - // We assume here that TAGLEN mod 128 == 0 (tagLength === 16). - const Nonce = concat(zeros_16.subarray(0, 15 - nonce.length), one, nonce); - const bottom = Nonce[15] & 0b111111; - Nonce[15] &= 0b11000000; - const Ktop = encipher(Nonce); - const Stretch = concat(Ktop, xor(Ktop.subarray(0, 8), Ktop.subarray(1, 9))); - // Offset_0 = Stretch[1+bottom..128+bottom] - const offset = shiftRight(Stretch.subarray(0 + (bottom >> 3), 17 + (bottom >> 3)), 8 - (bottom & 7)).subarray(1); - const checksum = zeros(16); - - const P = new Uint8Array(ciphertext.length); - - // - // Process any whole blocks - // - let i; - let pos = 0; - for (i = 0; i < m; i++) { - set_xor(offset, L[ntz(i + 1)]); - P.set(set_xor(decipher(xor(offset, ciphertext)), offset), pos); - set_xor(checksum, P.subarray(pos)); - - ciphertext = ciphertext.subarray(16); - pos += 16; - } - - // - // Process any final partial block and compute raw tag - // - if (ciphertext.length) { - set_xor(offset, L.x); - const Pad = encipher(offset); - P.set(xor(ciphertext, Pad), pos); - - // Checksum_* = Checksum_m xor (P_* || 1 || zeros(127-bitlen(P_*))) - const xorInput = zeros(16); - xorInput.set(P.subarray(pos), 0); - xorInput[ciphertext.length] = 0b10000000; - set_xor(checksum, xorInput); - pos += ciphertext.length; - } - const Tag = set_xor(encipher(set_xor(set_xor(checksum, offset), L.$)), hash(kv, key, adata)); - - // - // Check for validity and assemble plaintext - // - if (!util.equalsUint8Array(Tag, T)) { - throw new Error('Authentication tag mismatch in OCB ciphertext'); - } - return P; -} /** * Get OCB nonce as defined by {@link https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-04#section-5.16.2|RFC4880bis-04, section 5.16.2}. * @param {Uint8Array} iv The initialization vector (15 bytes) * @param {Uint8Array} chunkIndex The chunk index (8 bytes) */ -function getNonce(iv, chunkIndex) { +OCB.getNonce = function(iv, chunkIndex) { const nonce = iv.slice(); for (let i = 0; i < chunkIndex.length; i++) { nonce[7 + i] ^= chunkIndex[i]; } return nonce; -} - - -export default { - blockLength, - ivLength, - encrypt, - decrypt, - getNonce }; + +OCB.blockLength = blockLength; +OCB.ivLength = ivLength; + +export default OCB; diff --git a/src/packet/secret_key.js b/src/packet/secret_key.js index f764ff80..59105443 100644 --- a/src/packet/secret_key.js +++ b/src/packet/secret_key.js @@ -211,7 +211,7 @@ SecretKey.prototype.encrypt = async function (passphrase) { arr = [new Uint8Array([253, optionalFields.length])]; arr.push(optionalFields); const mode = crypto[aead]; - const encrypted = await mode.encrypt(symmetric, cleartext, key, iv.subarray(0, mode.ivLength), new Uint8Array()); + const encrypted = await new mode(symmetric, key).encrypt(cleartext, iv.subarray(0, mode.ivLength), new Uint8Array()); arr.push(util.writeNumber(encrypted.length, 4)); arr.push(encrypted); } else { @@ -305,7 +305,7 @@ SecretKey.prototype.decrypt = async function (passphrase) { if (aead) { const mode = crypto[aead]; try { - cleartext = await mode.decrypt(symmetric, ciphertext, key, iv.subarray(0, mode.ivLength), new Uint8Array()); + cleartext = await new mode(symmetric, key).decrypt(ciphertext, iv.subarray(0, mode.ivLength), new Uint8Array()); } catch(err) { if (err.message.startsWith('Authentication tag mismatch')) { throw new Error('Incorrect key passphrase: ' + err.message); diff --git a/src/packet/sym_encrypted_aead_protected.js b/src/packet/sym_encrypted_aead_protected.js index e5268cc5..fcd4c80d 100644 --- a/src/packet/sym_encrypted_aead_protected.js +++ b/src/packet/sym_encrypted_aead_protected.js @@ -107,15 +107,16 @@ SymEncryptedAEADProtected.prototype.decrypt = async function (sessionKeyAlgorith adataArray.set([0xC0 | this.tag, this.version, this.cipherAlgo, this.aeadAlgo, this.chunkSizeByte], 0); adataView.setInt32(13 + 4, data.length - mode.blockLength); // Should be setInt64(13, ...) const decryptedPromises = []; + const modeInstance = new mode(cipher, key); for (let chunkIndex = 0; chunkIndex === 0 || data.length;) { decryptedPromises.push( - mode.decrypt(cipher, data.subarray(0, chunkSize), key, mode.getNonce(this.iv, chunkIndexArray), adataArray) + modeInstance.decrypt(data.subarray(0, chunkSize), mode.getNonce(this.iv, chunkIndexArray), adataArray) ); data = data.subarray(chunkSize); adataView.setInt32(5 + 4, ++chunkIndex); // Should be setInt64(5, ...) } decryptedPromises.push( - mode.decrypt(cipher, authTag, key, mode.getNonce(this.iv, chunkIndexArray), adataTagArray) + modeInstance.decrypt(authTag, mode.getNonce(this.iv, chunkIndexArray), adataTagArray) ); this.packets.read(util.concatUint8Array(await Promise.all(decryptedPromises))); } else { @@ -148,9 +149,10 @@ SymEncryptedAEADProtected.prototype.encrypt = async function (sessionKeyAlgorith adataArray.set([0xC0 | this.tag, this.version, this.cipherAlgo, this.aeadAlgo, this.chunkSizeByte], 0); adataView.setInt32(13 + 4, data.length); // Should be setInt64(13, ...) const encryptedPromises = []; + const modeInstance = new mode(sessionKeyAlgorithm, key); for (let chunkIndex = 0; chunkIndex === 0 || data.length;) { encryptedPromises.push( - mode.encrypt(sessionKeyAlgorithm, data.subarray(0, chunkSize), key, mode.getNonce(this.iv, chunkIndexArray), adataArray) + modeInstance.encrypt(data.subarray(0, chunkSize), mode.getNonce(this.iv, chunkIndexArray), adataArray) ); // We take a chunk of data, encrypt it, and shift `data` to the // next chunk. After the final chunk, we encrypt a final, empty @@ -159,7 +161,7 @@ SymEncryptedAEADProtected.prototype.encrypt = async function (sessionKeyAlgorith adataView.setInt32(5 + 4, ++chunkIndex); // Should be setInt64(5, ...) } encryptedPromises.push( - mode.encrypt(sessionKeyAlgorithm, data, key, mode.getNonce(this.iv, chunkIndexArray), adataTagArray) + modeInstance.encrypt(data, mode.getNonce(this.iv, chunkIndexArray), adataTagArray) ); this.encrypted = util.concatUint8Array(await Promise.all(encryptedPromises)); } else { diff --git a/src/packet/sym_encrypted_session_key.js b/src/packet/sym_encrypted_session_key.js index 275f6431..39687f40 100644 --- a/src/packet/sym_encrypted_session_key.js +++ b/src/packet/sym_encrypted_session_key.js @@ -142,7 +142,7 @@ SymEncryptedSessionKey.prototype.decrypt = async function(passphrase) { if (this.version === 5) { const mode = crypto[this.aeadAlgorithm]; const adata = new Uint8Array([0xC0 | this.tag, this.version, enums.write(enums.symmetric, this.sessionKeyEncryptionAlgorithm), enums.write(enums.aead, this.aeadAlgorithm)]); - this.sessionKey = await mode.decrypt(algo, this.encrypted, key, this.iv, adata); + this.sessionKey = await new mode(algo, key).decrypt(this.encrypted, this.iv, adata); } else if (this.encrypted !== null) { const decrypted = crypto.cfb.normalDecrypt(algo, key, this.encrypted, null); @@ -182,7 +182,7 @@ SymEncryptedSessionKey.prototype.encrypt = async function(passphrase) { const mode = crypto[this.aeadAlgorithm]; this.iv = await crypto.random.getRandomBytes(mode.ivLength); // generate new random IV const adata = new Uint8Array([0xC0 | this.tag, this.version, enums.write(enums.symmetric, this.sessionKeyEncryptionAlgorithm), enums.write(enums.aead, this.aeadAlgorithm)]); - this.encrypted = await mode.encrypt(algo, this.sessionKey, key, this.iv, adata); + this.encrypted = await new mode(algo, key).encrypt(this.sessionKey, this.iv, adata); } else { const algo_enum = new Uint8Array([enums.write(enums.symmetric, this.sessionKeyAlgorithm)]); const private_key = util.concatUint8Array([algo_enum, this.sessionKey]); diff --git a/test/crypto/eax.js b/test/crypto/eax.js index 369dcfd4..c5aec8b9 100644 --- a/test/crypto/eax.js +++ b/test/crypto/eax.js @@ -9,8 +9,6 @@ chai.use(require('chai-as-promised')); const expect = chai.expect; -const eax = openpgp.crypto.eax; - function testAESEAX() { it('Passes all test vectors', async function() { var vectors = [ @@ -96,28 +94,30 @@ function testAESEAX() { headerBytes = openpgp.util.hex_to_Uint8Array(vec.header), ctBytes = openpgp.util.hex_to_Uint8Array(vec.ct); + const eax = new openpgp.crypto.eax(cipher, keyBytes); + // encryption test - let ct = await eax.encrypt(cipher, msgBytes, keyBytes, nonceBytes, headerBytes); + let ct = await eax.encrypt(msgBytes, nonceBytes, headerBytes); expect(openpgp.util.Uint8Array_to_hex(ct)).to.equal(vec.ct.toLowerCase()); // decryption test with verification - let pt = await eax.decrypt(cipher, ctBytes, keyBytes, nonceBytes, headerBytes); + let pt = await eax.decrypt(ctBytes, nonceBytes, headerBytes); expect(openpgp.util.Uint8Array_to_hex(pt)).to.equal(vec.msg.toLowerCase()); // tampering detection test - ct = await eax.encrypt(cipher, msgBytes, keyBytes, nonceBytes, headerBytes); + ct = await eax.encrypt(msgBytes, nonceBytes, headerBytes); ct[2] ^= 8; - pt = eax.decrypt(cipher, ct, keyBytes, nonceBytes, headerBytes); + pt = eax.decrypt(ct, nonceBytes, headerBytes); await expect(pt).to.eventually.be.rejectedWith('Authentication tag mismatch in EAX ciphertext') // testing without additional data - ct = await eax.encrypt(cipher, msgBytes, keyBytes, nonceBytes, new Uint8Array()); - pt = await eax.decrypt(cipher, ct, keyBytes, nonceBytes, new Uint8Array()); + ct = await eax.encrypt(msgBytes, nonceBytes, new Uint8Array()); + pt = await eax.decrypt(ct, nonceBytes, new Uint8Array()); expect(openpgp.util.Uint8Array_to_hex(pt)).to.equal(vec.msg.toLowerCase()); // testing with multiple additional data - ct = await eax.encrypt(cipher, msgBytes, keyBytes, nonceBytes, openpgp.util.concatUint8Array([headerBytes, headerBytes, headerBytes])); - pt = await eax.decrypt(cipher, ct, keyBytes, nonceBytes, openpgp.util.concatUint8Array([headerBytes, headerBytes, headerBytes])); + ct = await eax.encrypt(msgBytes, nonceBytes, openpgp.util.concatUint8Array([headerBytes, headerBytes, headerBytes])); + pt = await eax.decrypt(ct, nonceBytes, openpgp.util.concatUint8Array([headerBytes, headerBytes, headerBytes])); expect(openpgp.util.Uint8Array_to_hex(pt)).to.equal(vec.msg.toLowerCase()); } }); diff --git a/test/crypto/ocb.js b/test/crypto/ocb.js index 04de3065..a1e269cb 100644 --- a/test/crypto/ocb.js +++ b/test/crypto/ocb.js @@ -9,8 +9,6 @@ chai.use(require('chai-as-promised')); const expect = chai.expect; -const ocb = openpgp.crypto.ocb; - describe('Symmetric AES-OCB', function() { it('Passes all test vectors', async function() { const K = '000102030405060708090A0B0C0D0E0F'; @@ -124,28 +122,30 @@ describe('Symmetric AES-OCB', function() { headerBytes = openpgp.util.hex_to_Uint8Array(vec.A), ctBytes = openpgp.util.hex_to_Uint8Array(vec.C); + const ocb = new openpgp.crypto.ocb(cipher, keyBytes); + // encryption test - let ct = await ocb.encrypt(cipher, msgBytes, keyBytes, nonceBytes, headerBytes); + let ct = await ocb.encrypt(msgBytes, nonceBytes, headerBytes); expect(openpgp.util.Uint8Array_to_hex(ct)).to.equal(vec.C.toLowerCase()); // decryption test with verification - let pt = await ocb.decrypt(cipher, ctBytes, keyBytes, nonceBytes, headerBytes); + let pt = await ocb.decrypt(ctBytes, nonceBytes, headerBytes); expect(openpgp.util.Uint8Array_to_hex(pt)).to.equal(vec.P.toLowerCase()); // tampering detection test - ct = await ocb.encrypt(cipher, msgBytes, keyBytes, nonceBytes, headerBytes); + ct = await ocb.encrypt(msgBytes, nonceBytes, headerBytes); ct[2] ^= 8; - pt = ocb.decrypt(cipher, ct, keyBytes, nonceBytes, headerBytes); + pt = ocb.decrypt(ct, nonceBytes, headerBytes); await expect(pt).to.eventually.be.rejectedWith('Authentication tag mismatch in OCB ciphertext') // testing without additional data - ct = await ocb.encrypt(cipher, msgBytes, keyBytes, nonceBytes, new Uint8Array()); - pt = await ocb.decrypt(cipher, ct, keyBytes, nonceBytes, new Uint8Array()); + ct = await ocb.encrypt(msgBytes, nonceBytes, new Uint8Array()); + pt = await ocb.decrypt(ct, nonceBytes, new Uint8Array()); expect(openpgp.util.Uint8Array_to_hex(pt)).to.equal(vec.P.toLowerCase()); // testing with multiple additional data - ct = await ocb.encrypt(cipher, msgBytes, keyBytes, nonceBytes, openpgp.util.concatUint8Array([headerBytes, headerBytes, headerBytes])); - pt = await ocb.decrypt(cipher, ct, keyBytes, nonceBytes, openpgp.util.concatUint8Array([headerBytes, headerBytes, headerBytes])); + ct = await ocb.encrypt(msgBytes, nonceBytes, openpgp.util.concatUint8Array([headerBytes, headerBytes, headerBytes])); + pt = await ocb.decrypt(ct, nonceBytes, openpgp.util.concatUint8Array([headerBytes, headerBytes, headerBytes])); expect(openpgp.util.Uint8Array_to_hex(pt)).to.equal(vec.P.toLowerCase()); } }); @@ -162,19 +162,21 @@ describe('Symmetric AES-OCB', function() { const K = new Uint8Array(KEYLEN / 8); K[K.length - 1] = TAGLEN; + const ocb = new openpgp.crypto.ocb('aes' + KEYLEN, K); + const C = []; let N; for (let i = 0; i < 128; i++) { const S = new Uint8Array(i); N = openpgp.util.concatUint8Array([new Uint8Array(8), openpgp.util.writeNumber(3 * i + 1, 4)]); - C.push(await ocb.encrypt('aes' + KEYLEN, S, K, N, S)); + C.push(await ocb.encrypt(S, N, S)); N = openpgp.util.concatUint8Array([new Uint8Array(8), openpgp.util.writeNumber(3 * i + 2, 4)]); - C.push(await ocb.encrypt('aes' + KEYLEN, S, K, N, new Uint8Array())); + C.push(await ocb.encrypt(S, N, new Uint8Array())); N = openpgp.util.concatUint8Array([new Uint8Array(8), openpgp.util.writeNumber(3 * i + 3, 4)]); - C.push(await ocb.encrypt('aes' + KEYLEN, new Uint8Array(), K, N, S)); + C.push(await ocb.encrypt(new Uint8Array(), N, S)); } N = openpgp.util.concatUint8Array([new Uint8Array(8), openpgp.util.writeNumber(385, 4)]); - const output = await ocb.encrypt('aes' + KEYLEN, new Uint8Array(), K, N, openpgp.util.concatUint8Array(C)); + const output = await ocb.encrypt(new Uint8Array(), N, openpgp.util.concatUint8Array(C)); expect(openpgp.util.Uint8Array_to_hex(output)).to.equal(outputs[KEYLEN].toLowerCase()); } });