diff --git a/src/crypto/cmac.js b/src/crypto/cmac.js new file mode 100644 index 00000000..34061258 --- /dev/null +++ b/src/crypto/cmac.js @@ -0,0 +1,21 @@ +/** + * @requires asmcrypto.js + */ + +import { AES_CMAC } from 'asmcrypto.js/src/aes/cmac/cmac'; + +export default class CMAC extends AES_CMAC { + constructor(key) { + super(key); + this._k = this.k.slice(); + } + + mac(data) { + if (this.result) { + this.bufferLength = 0; + this.k.set(this._k, 0); + this.cbc.AES_reset(undefined, new Uint8Array(16), false); + } + return this.process(data).finish().result; + } +} diff --git a/src/crypto/eax.js b/src/crypto/eax.js index ac139f6a..6a8dcad8 100644 --- a/src/crypto/eax.js +++ b/src/crypto/eax.js @@ -19,12 +19,13 @@ * @fileoverview This module implements AES-EAX en/decryption on top of * native AES-CTR using either the WebCrypto API or Node.js' crypto API. * @requires asmcrypto.js + * @requires crypto/cmac * @requires util * @module crypto/eax */ -import { AES_CMAC } from 'asmcrypto.js/src/aes/cmac/exports'; import { AES_CTR } from 'asmcrypto.js/src/aes/ctr/exports'; +import CMAC from './cmac'; import util from '../util'; const webCrypto = util.getWebCryptoAll(); @@ -40,6 +41,12 @@ const zero = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); const one = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); const two = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]); +class OMAC extends CMAC { + mac(t, message) { + return super.mac(concat(t, message)); + } +} + /** * Encrypt plaintext input. @@ -55,11 +62,12 @@ async function encrypt(cipher, plaintext, key, nonce, adata) { throw new Error('EAX mode supports only AES cipher'); } - const _nonce = OMAC(zero, nonce, key); - const _adata = OMAC(one, adata, 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(two, ciphered, key); - const tag = xor3(_nonce, _ciphered, _adata); // Assumes that OMAC(*).length === tagLength. + const _ciphered = omac.mac(two, ciphered); + const tag = xor3(_nonce, _ciphered, _adata); // Assumes that omac.mac(*).length === tagLength. return concat(ciphered, tag); } @@ -80,10 +88,11 @@ async function decrypt(cipher, ciphertext, key, 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 = OMAC(zero, nonce, key); - const _adata = OMAC(one, adata, key); - const _ciphered = OMAC(two, ciphered, key); - const _tag = xor3(_nonce, _ciphered, _adata); // Assumes that OMAC(*).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; @@ -127,10 +136,6 @@ function concat(...arrays) { return util.concatUint8Array(arrays); } -function OMAC(t, message, key) { - return AES_CMAC.bytes(concat(t, message), key); -} - function CTR(plaintext, key, iv) { if (webCrypto && key.length !== 24) { // WebCrypto (no 192 bit support) see: https://www.chromium.org/blink/webcrypto#TOC-AES-support return webCtr(plaintext, key, iv);