/* * Process some bytes, then compute the output. * * This function is supposed to implement the processing in * constant time (and thus constant memory access pattern) for * all values of 'len' between 'minLen' and 'maxLen' * (inclusive). This function works only for the supported * underlying hash functions (MD5, SHA-1 and the SHA-2 * functions). * * The source array (buf[]) must contain at least maxLen bytes * (starting at offset 'off'); they will all be read. */ public void ComputeCT(byte[] buf, int off, int len, int minLen, int maxLen, byte[] outBuf, int outOff) { /* * Padding is 0x80, followed by 0 to 63 bytes of value * 0x00 (up to 127 bytes for SHA-384 and SHA-512), then * the input bit length expressed over 64 bits * (little-endian for MD5, big-endian for * the SHA-* functions)(for SHA-384 and SHA-512, this is * 128 bits). * * Note that we only support bit lengths that fit on * 64 bits, so we can handle SHA-384/SHA-512 padding * almost as if it was the same as SHA-256; we just have * to take care of the larger blocks (128 bytes instead * of 64) and the larger minimal overhead (17 bytes * instead of 9 bytes). * * be true for big-endian length encoding * bs block size, in bytes (must be a power of 2) * po padding overhead (0x80 byte and length encoding) */ bool be; int bs, po; if (h is MD5) { be = false; bs = 64; po = 9; } else if ((h is SHA1) || (h is SHA2Small)) { be = true; bs = 64; po = 9; } else if (h is SHA2Big) { be = true; bs = 128; po = 17; } else { throw new NotSupportedException(); } /* * Method implemented here is inspired from the one * described there: * https://www.imperialviolet.org/2013/02/04/luckythirteen.html */ /* * We compute the data bit length; let's not forget * the initial first block (the one with the HMAC key). */ ulong bitLen = ((ulong)bs + dataLen + (ulong)len) << 3; /* * All complete blocks before minLen can be processed * efficiently. */ ulong nDataLen = (dataLen + (ulong)minLen) & ~(ulong)(bs - 1); if (nDataLen > dataLen) { int zlen = (int)(nDataLen - dataLen); h.Update(buf, off, (int)(nDataLen - dataLen)); dataLen = nDataLen; off += zlen; len -= zlen; maxLen -= zlen; } /* * At that point: * -- dataLen contains the number of bytes already processed * (in total, not counting the initial key block). * -- We must input 'len' bytes, which may be up to 'maxLen' * (inclusive). * * We compute kr, kl, kz and km: * kr number of input bytes already in the current block * km index of the first byte after the end of the last * padding block, if 'len' is equal to 'maxLen' * kz index of the last byte of the actual last padding * block * kl index of the start of the encoded length */ int kr = (int)dataLen & (bs - 1); int kz = ((kr + len + po + bs - 1) & ~(bs - 1)) - 1 - kr; int kl = kz - 7; int km = ((kr + maxLen + po + bs - 1) & ~(bs - 1)) - kr; /* * We must process km bytes. For index i from 0 to km-1: * d is from data[] if i < maxLen, 0x00 otherwise * e is an encoded length byte or 0x00, depending on i * These tests do not depend on the actual length, so * they need not be constant-time. * * Actual input byte is: * d if i < len * 0x80 if i == len * 0x00 if i > len and i < kl * e if i >= kl * * We extract hash state whenever we reach a full block; * we keep it only if i == kz. */ int hlen = h.DigestSize; for (int k = 0; k < hlen; k++) { tmp[k] = 0; } for (int i = 0; i < km; i++) { int d = (i < maxLen) ? buf[off + i] : 0x00; int e; int j = (kr + i) & (bs - 1); if (j >= (bs - 8)) { int k = (j - (bs - 8)) << 3; if (be) { e = (int)(bitLen >> (56 - k)); } else { e = (int)(bitLen >> k); } e &= 0xFF; } else { e = 0x00; } /* * x0 is 0x80 if i == len; otherwise it is d. */ int z = i - len; int x0 = 0x80 ^ (((z | -z) >> 31) & (0x80 ^ d)); /* * x1 is e if i >= kl; otherwise it is 0x00. */ int x1 = e & ~((i - kl) >> 31); /* * We input x0 if i <= len, x1 otherwise. */ h.Update((byte)(x0 ^ (((len - i) >> 31) & (x0 ^ x1)))); /* * Get current state if we are at the end of a block, * and keep it if i == kz. */ if (j == (bs - 1)) { h.CurrentState(tmpCT, 0); z = i - kz; z = ~((z | -z) >> 31); for (int k = 0; k < hlen; k++) { tmp[k] |= (byte)(z & tmpCT[k]); } } } /* * We got the hash output in tmp[]; we must complete * the HMAC computation. */ h.Reset(); ProcessKey(0x5C); h.Update(tmp); h.DoFinal(outBuf, outOff); Reset(); }