From d5a7cb303731d5da56e2d5bdf79e40c034c3fa8a Mon Sep 17 00:00:00 2001 From: Daniel Huigens Date: Wed, 18 Apr 2018 15:42:16 +0200 Subject: [PATCH] Constant-time double() in OCB --- src/crypto/cmac.js | 22 ++-------------------- src/crypto/ocb.js | 16 +++------------- src/util.js | 28 ++++++++++++++-------------- 3 files changed, 19 insertions(+), 47 deletions(-) diff --git a/src/crypto/cmac.js b/src/crypto/cmac.js index dde7af74..060b2efb 100644 --- a/src/crypto/cmac.js +++ b/src/crypto/cmac.js @@ -46,24 +46,6 @@ function rightXorMut(data, padding) { return data; } -/** - * 2L = L<<1 if the first bit of L is 0 and 2L = (L<<1) xor (0^120 || - * 10000111) otherwise, where L<<1 means the left shift of L by one - * position (the first bit vanishing and a zero entering into the last - * bit). The value of 4L is simply 2(2L). We warn that to avoid side- - * channel attacks one must implement the doubling operation in a - * constant-time manner. - * @param {Uint8Array} data - */ -function mul2(data) { - const t = data[0] & 0x80; - for (let i = 0; i < 15; i++) { - data[i] = (data[i] << 1) ^ ((data[i + 1] & 0x80) ? 1 : 0); - } - data[15] = (data[15] << 1) ^ (t ? 0x87 : 0); - return data; -} - function pad(data, padding, padding2) { // if |M| in {n, 2n, 3n, ...} if (data.length % blockLength === 0) { @@ -83,8 +65,8 @@ export default async function CMAC(key) { const cbc = await CBC(key); // L ← E_K(0^n); B ← 2L; P ← 4L - const padding = mul2(await cbc(zeroBlock)); - const padding2 = mul2(padding.slice()); + const padding = util.double(await cbc(zeroBlock)); + const padding2 = util.double(padding); return async function(data) { // return CBC_K(pad(M; B, P)) diff --git a/src/crypto/ocb.js b/src/crypto/ocb.js index f22f3aa4..e0bd88f4 100644 --- a/src/crypto/ocb.js +++ b/src/crypto/ocb.js @@ -55,16 +55,6 @@ function xor(S, T) { return xorMut(S.slice(), T); } -function double(S) { - const double = S.slice(); - util.shiftLeft(double, 1); - if (S[0] & 0b10000000) { - double[15] ^= 0b10000111; - } - return double; -} - - const zeroBlock = new Uint8Array(blockLength); const one = new Uint8Array([1]); @@ -86,9 +76,9 @@ async function OCB(cipher, key) { const decipher = aes.decrypt.bind(aes); const mask_x = encipher(zeroBlock); - const mask_$ = double(mask_x); + const mask_$ = util.double(mask_x); const mask = []; - mask[0] = double(mask_$); + mask[0] = util.double(mask_$); mask.x = mask_x; @@ -101,7 +91,7 @@ async function OCB(cipher, key) { const { mask } = kv; const newMaxNtz = util.nbits(Math.max(text.length, adata.length) >> 4) - 1; for (let i = maxNtz + 1; i <= newMaxNtz; i++) { - mask[i] = double(mask[i - 1]); + mask[i] = util.double(mask[i - 1]); } maxNtz = newMaxNtz; } diff --git a/src/util.js b/src/util.js index af8a93cd..719d2bef 100644 --- a/src/util.js +++ b/src/util.js @@ -444,22 +444,22 @@ export default { }, /** - * Shift a Uint8Array to the left by n bits - * @param {Uint8Array} array The array to shift - * @param {Integer} bits Amount of bits to shift (MUST be smaller - * than 8) - * @returns {String} Resulting array. + * If S[1] == 0, then double(S) == (S[2..128] || 0); + * otherwise, double(S) == (S[2..128] || 0) xor + * (zeros(120) || 10000111). + * + * Both OCB and EAX (through CMAC) require this function to be constant-time. + * + * @param {Uint8Array} data */ - shiftLeft: function (array, bits) { - if (bits) { - for (let i = 0; i < array.length; i++) { - array[i] <<= bits; - if (i + 1 < array.length) { - array[i] |= array[i + 1] >> (8 - bits); - } - } + double: function(data) { + const double = new Uint8Array(data.length); + const last = data.length - 1; + for (let i = 0; i < last; i++) { + double[i] = (data[i] << 1) ^ (data[i + 1] >> 7); } - return array; + double[last] = (data[last] << 1) ^ ((data[0] >> 7) * 0x87); + return double; }, /**