From bbf71d149b92ec787ae968dcc1f3860721b4707f Mon Sep 17 00:00:00 2001 From: Daniel Huigens Date: Tue, 24 Apr 2018 13:22:53 +0200 Subject: [PATCH] Deduplicate OCB encrypt / decrypt --- src/crypto/ocb.js | 256 +++++++++------------ src/packet/sym_encrypted_aead_protected.js | 2 +- 2 files changed, 112 insertions(+), 146 deletions(-) diff --git a/src/crypto/ocb.js b/src/crypto/ocb.js index ad5acdb1..ef4c7830 100644 --- a/src/crypto/ocb.js +++ b/src/crypto/ocb.js @@ -66,30 +66,29 @@ const one = new Uint8Array([1]); async function OCB(cipher, key) { let maxNtz = 0; - let kv; + let encipher; + let decipher; + let mask; constructKeyVariables(cipher, key); function constructKeyVariables(cipher, key) { const aes = new ciphers[cipher](key); - const encipher = aes.encrypt.bind(aes); - const decipher = aes.decrypt.bind(aes); + encipher = aes.encrypt.bind(aes); + decipher = aes.decrypt.bind(aes); const mask_x = encipher(zeroBlock); const mask_$ = util.double(mask_x); - const mask = []; + mask = []; mask[0] = util.double(mask_$); mask.x = mask_x; mask.$ = mask_$; - - kv = { encipher, decipher, mask }; } function extendKeyVariables(text, adata) { - const { mask } = kv; - const newMaxNtz = util.nbits(Math.max(text.length, adata.length) >> 4) - 1; + const newMaxNtz = util.nbits(Math.max(text.length, adata.length) / blockLength | 0) - 1; for (let i = maxNtz + 1; i <= newMaxNtz; i++) { mask[i] = util.double(mask[i - 1]); } @@ -102,19 +101,17 @@ async function OCB(cipher, key) { return zeroBlock; } - const { encipher, mask } = kv; - // // Consider A as a sequence of 128-bit blocks // - const m = adata.length >> 4; + const m = adata.length / blockLength | 0; - const offset = new Uint8Array(16); - const sum = new Uint8Array(16); + const offset = new Uint8Array(blockLength); + const sum = new Uint8Array(blockLength); for (let i = 0; i < m; i++) { xorMut(offset, mask[ntz(i + 1)]); xorMut(sum, encipher(xor(offset, adata))); - adata = adata.subarray(16); + adata = adata.subarray(blockLength); } // @@ -123,7 +120,7 @@ async function OCB(cipher, key) { if (adata.length) { xorMut(offset, mask.x); - const cipherInput = new Uint8Array(16); + const cipherInput = new Uint8Array(blockLength); cipherInput.set(adata, 0); cipherInput[adata.length] = 0b10000000; xorMut(cipherInput, offset); @@ -134,6 +131,92 @@ async function OCB(cipher, key) { return sum; } + /** + * Encrypt/decrypt data. + * @param {encipher|decipher} fn Encryption/decryption block cipher function + * @param {Uint8Array} text The cleartext or ciphertext (without tag) input + * @param {Uint8Array} nonce The nonce (15 bytes) + * @param {Uint8Array} adata Associated data to sign + * @returns {Promise} The ciphertext or plaintext output, with tag appended in both cases + */ + function crypt(fn, text, nonce, adata) { + // + // Consider P as a sequence of 128-bit blocks + // + const m = text.length / blockLength | 0; + + // + // Key-dependent variables + // + extendKeyVariables(text, adata); + + // + // Nonce-dependent and per-encryption variables + // + // Nonce = num2str(TAGLEN mod 128,7) || zeros(120-bitlen(N)) || 1 || N + // Note: We assume here that tagLength mod 16 == 0. + const paddedNonce = util.concatUint8Array([zeroBlock.subarray(0, ivLength - nonce.length), one, nonce]); + // bottom = str2num(Nonce[123..128]) + const bottom = paddedNonce[blockLength - 1] & 0b111111; + // Ktop = ENCIPHER(K, Nonce[1..122] || zeros(6)) + paddedNonce[blockLength - 1] &= 0b11000000; + const kTop = encipher(paddedNonce); + // Stretch = Ktop || (Ktop[1..64] xor Ktop[9..72]) + const stretched = util.concatUint8Array([kTop, xor(kTop.subarray(0, 8), kTop.subarray(1, 9))]); + // Offset_0 = Stretch[1+bottom..128+bottom] + const offset = util.shiftRight(stretched.subarray(0 + (bottom >> 3), 17 + (bottom >> 3)), 8 - (bottom & 7)).subarray(1); + // Checksum_0 = zeros(128) + const checksum = new Uint8Array(blockLength); + + const ct = new Uint8Array(text.length + tagLength); + + // + // Process any whole blocks + // + let i; + let pos = 0; + for (i = 0; i < m; i++) { + // Offset_i = Offset_{i-1} xor L_{ntz(i)} + xorMut(offset, mask[ntz(i + 1)]); + // C_i = Offset_i xor ENCIPHER(K, P_i xor Offset_i) + // P_i = Offset_i xor DECIPHER(K, C_i xor Offset_i) + ct.set(xorMut(fn(xor(offset, text)), offset), pos); + // Checksum_i = Checksum_{i-1} xor P_i + xorMut(checksum, fn === encipher ? text : ct.subarray(pos)); + + text = text.subarray(blockLength); + pos += blockLength; + } + + // + // Process any final partial block and compute raw tag + // + if (text.length) { + // Offset_* = Offset_m xor L_* + xorMut(offset, mask.x); + // Pad = ENCIPHER(K, Offset_*) + const padding = encipher(offset); + // C_* = P_* xor Pad[1..bitlen(P_*)] + ct.set(xor(text, padding), pos); + + // Checksum_* = Checksum_m xor (P_* || 1 || new Uint8Array(127-bitlen(P_*))) + const xorInput = new Uint8Array(blockLength); + xorInput.set(fn === encipher ? text : ct.subarray(pos, -tagLength), 0); + xorInput[text.length] = 0b10000000; + xorMut(checksum, xorInput); + pos += text.length; + } + // Tag = ENCIPHER(K, Checksum_* xor Offset_* xor L_$) xor HASH(K,A) + const tag = xorMut(encipher(xorMut(xorMut(checksum, offset), mask.$)), hash(adata)); + + // + // Assemble ciphertext + // + // C = C_1 || C_2 || ... || C_m || C_* || Tag[1..TAGLEN] + ct.set(tag, pos); + return ct; + } + return { /** @@ -144,145 +227,28 @@ async function OCB(cipher, key) { * @returns {Promise} The ciphertext output */ encrypt: async function(plaintext, nonce, adata) { - // - // Consider P as a sequence of 128-bit blocks - // - const m = plaintext.length >> 4; - - // - // Key-dependent variables - // - extendKeyVariables(plaintext, adata); - const { encipher, mask } = kv; - - // - // Nonce-dependent and per-encryption variables - // - // We assume here that tagLength mod 16 == 0. - const paddedNonce = util.concatUint8Array([zeroBlock.subarray(0, 15 - nonce.length), one, nonce]); - const bottom = paddedNonce[15] & 0b111111; - paddedNonce[15] &= 0b11000000; - const kTop = encipher(paddedNonce); - const stretched = util.concatUint8Array([kTop, xor(kTop.subarray(0, 8), kTop.subarray(1, 9))]); - // Offset_0 = Stretch[1+bottom..128+bottom] - const offset = util.shiftRight(stretched.subarray(0 + (bottom >> 3), 17 + (bottom >> 3)), 8 - (bottom & 7)).subarray(1); - const checksum = new Uint8Array(16); - - const ct = new Uint8Array(plaintext.length + tagLength); - - // - // Process any whole blocks - // - let i; - let pos = 0; - for (i = 0; i < m; i++) { - xorMut(offset, mask[ntz(i + 1)]); - ct.set(xorMut(encipher(xor(offset, plaintext)), offset), pos); - xorMut(checksum, plaintext); - - plaintext = plaintext.subarray(16); - pos += 16; - } - - // - // Process any final partial block and compute raw tag - // - if (plaintext.length) { - xorMut(offset, mask.x); - const padding = encipher(offset); - ct.set(xor(plaintext, padding), pos); - - // Checksum_* = Checksum_m xor (P_* || 1 || new Uint8Array(127-bitlen(P_*))) - const xorInput = new Uint8Array(16); - xorInput.set(plaintext, 0); - xorInput[plaintext.length] = 0b10000000; - xorMut(checksum, xorInput); - pos += plaintext.length; - } - const tag = xorMut(encipher(xorMut(xorMut(checksum, offset), mask.$)), hash(adata)); - - // - // Assemble ciphertext - // - ct.set(tag, pos); - return ct; + return crypt(encipher, plaintext, nonce, adata); }, - /** * 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 + * @param {Uint8Array} ciphertext The ciphertext input to be decrypted + * @param {Uint8Array} nonce The nonce (15 bytes) + * @param {Uint8Array} adata Associated data to sign + * @returns {Promise} The ciphertext output */ decrypt: async function(ciphertext, nonce, adata) { - // - // Consider C as a sequence of 128-bit blocks - // - const ctTag = ciphertext.subarray(ciphertext.length - tagLength); - ciphertext = ciphertext.subarray(0, ciphertext.length - tagLength); - const m = ciphertext.length >> 4; + if (ciphertext.length < tagLength) throw new Error('Invalid OCB ciphertext'); - // - // Key-dependent variables - // - extendKeyVariables(ciphertext, adata); - const { encipher, decipher, mask } = kv; + const tag = ciphertext.subarray(-tagLength); + ciphertext = ciphertext.subarray(0, -tagLength); - // - // Nonce-dependent and per-encryption variables - // - // We assume here that tagLength mod 16 == 0. - const paddedNonce = util.concatUint8Array([zeroBlock.subarray(0, 15 - nonce.length), one, nonce]); - const bottom = paddedNonce[15] & 0b111111; - paddedNonce[15] &= 0b11000000; - const kTop = encipher(paddedNonce); - const stretched = util.concatUint8Array([kTop, xor(kTop.subarray(0, 8), kTop.subarray(1, 9))]); - // Offset_0 = Stretch[1+bottom..128+bottom] - const offset = util.shiftRight(stretched.subarray(0 + (bottom >> 3), 17 + (bottom >> 3)), 8 - (bottom & 7)).subarray(1); - const checksum = new Uint8Array(16); - - const pt = new Uint8Array(ciphertext.length); - - // - // Process any whole blocks - // - let i; - let pos = 0; - for (i = 0; i < m; i++) { - xorMut(offset, mask[ntz(i + 1)]); - pt.set(xorMut(decipher(xor(offset, ciphertext)), offset), pos); - xorMut(checksum, pt.subarray(pos)); - - ciphertext = ciphertext.subarray(16); - pos += 16; + const crypted = crypt(decipher, ciphertext, nonce, adata); + // if (Tag[1..TAGLEN] == T) + if (util.equalsUint8Array(tag, crypted.subarray(-tagLength))) { + return crypted.subarray(0, -tagLength); } - - // - // Process any final partial block and compute raw tag - // - if (ciphertext.length) { - xorMut(offset, mask.x); - const padding = encipher(offset); - pt.set(xor(ciphertext, padding), pos); - - // Checksum_* = Checksum_m xor (P_* || 1 || new Uint8Array(127-bitlen(P_*))) - const xorInput = new Uint8Array(16); - xorInput.set(pt.subarray(pos), 0); - xorInput[ciphertext.length] = 0b10000000; - xorMut(checksum, xorInput); - pos += ciphertext.length; - } - const tag = xorMut(encipher(xorMut(xorMut(checksum, offset), mask.$)), hash(adata)); - - // - // Check for validity and assemble plaintext - // - if (!util.equalsUint8Array(ctTag, tag)) { - throw new Error('Authentication tag mismatch in OCB ciphertext'); - } - return pt; + throw new Error('Authentication tag mismatch in OCB ciphertext'); } }; } diff --git a/src/packet/sym_encrypted_aead_protected.js b/src/packet/sym_encrypted_aead_protected.js index 43836977..1fe5e1ed 100644 --- a/src/packet/sym_encrypted_aead_protected.js +++ b/src/packet/sym_encrypted_aead_protected.js @@ -165,4 +165,4 @@ SymEncryptedAEADProtected.prototype.crypt = async function (fn, key, data, final } else { return modeInstance[fn](data, this.iv); } -} +};