diff options
Diffstat (limited to 'source/crypto')
| -rw-r--r-- | source/crypto/Kdf.mc | 94 |
1 files changed, 76 insertions, 18 deletions
diff --git a/source/crypto/Kdf.mc b/source/crypto/Kdf.mc index 07c97ba..5bace1c 100644 --- a/source/crypto/Kdf.mc +++ b/source/crypto/Kdf.mc @@ -25,8 +25,15 @@ module Kdf { // keeps the simulator (a PC) from choosing a count a watch can't afford. const MIN_ITERATIONS = 2000; const MAX_ITERATIONS = 200000; - // Iterations per job step: small enough for the 120,000-bytecode watchdog. - const ITERATIONS_PER_STEP = 200; + // Iterations per job step. The watchdog allows 120,000 bytecodes per + // event on the Forerunner 255/255S (240,000 elsewhere); 200 iterations of + // the byte-wise version tripped it there in the simulator (M4). With the + // word-wise XOR this keeps a wide margin; the UI flow derives PINs on the + // Forerunner 255S profile in every `make screenshots`. + const ITERATIONS_PER_STEP = 100; + + // decodeNumber/encodeNumber options for one 32-bit word. + typedef WordOptions as {:offset as Number, :endianness as Lang.Endian}; // PBKDF2-HMAC-SHA256 as a resumable job (RFC 8018 section 5.2). class Pbkdf2 extends Jobs.Job { @@ -39,8 +46,20 @@ module Kdf { private var _block as Number = 1; // current output block, 1-based private var _done as Number = 0; // iterations done for the current block private var _u as ByteArray = []b; - private var _t as ByteArray = []b; private var _out as ByteArray = []b; + // T of the current block as eight 32-bit words: XOR-ing words takes + // far fewer bytecodes than XOR-ing 32 bytes, which matters for the + // per-event watchdog budget (see ITERATIONS_PER_STEP). + private var _t0 as Number = 0; + private var _t1 as Number = 0; + private var _t2 as Number = 0; + private var _t3 as Number = 0; + private var _t4 as Number = 0; + private var _t5 as Number = 0; + private var _t6 as Number = 0; + private var _t7 as Number = 0; + // decodeNumber options for word 0..7, built once. + private var _w as Array<WordOptions> = [] as Array<WordOptions>; // iterations >= 1; 1 <= length <= 255 * 32 bytes. function initialize(password as ByteArray, salt as ByteArray, iterations as Number, length as Number) { @@ -56,44 +75,83 @@ module Kdf { _iterations = iterations; _length = length; _blockCount = (length + SHA256_BYTES - 1) / SHA256_BYTES; + for (var k = 0; k < 8; k++) { + _w.add({:offset => k * 4, :endianness => Lang.ENDIAN_BIG}); + } } // Run up to `budget` iterations. Returns true when the key is complete. function step(budget as Number) as Boolean { var left = budget; + var f = Lang.NUMBER_FORMAT_SINT32; + var w = _w; while (left > 0 && _block <= _blockCount) { if (_done == 0) { - // U1 = HMAC(P, S || INT(i)) + // U1 = HMAC(P, S || INT(i)); T = U1 _mac.update(Bytes.concat(_salt, Bytes.uint32BigEndian(_block))); _u = _mac.digest(); - _t = Bytes.copy(_u); + var u = _u; + _t0 = u.decodeNumber(f, w[0]) as Number; + _t1 = u.decodeNumber(f, w[1]) as Number; + _t2 = u.decodeNumber(f, w[2]) as Number; + _t3 = u.decodeNumber(f, w[3]) as Number; + _t4 = u.decodeNumber(f, w[4]) as Number; + _t5 = u.decodeNumber(f, w[5]) as Number; + _t6 = u.decodeNumber(f, w[6]) as Number; + _t7 = u.decodeNumber(f, w[7]) as Number; } else { // Uj = HMAC(P, Uj-1); T ^= Uj. The previous Uj isn't wiped // every iteration: it is useless without the password, and - // a wipe loop per iteration would halve the iterations the - // watch can afford in its time budget. The final values - // are wiped below. + // a wipe per iteration would cut the iterations the watch + // can afford. The final values are wiped below. _mac.update(_u); _u = _mac.digest(); - var t = _t; var u = _u; - for (var i = 0; i < SHA256_BYTES; i++) { - t[i] = t[i] ^ u[i]; - } + _t0 ^= u.decodeNumber(f, w[0]) as Number; + _t1 ^= u.decodeNumber(f, w[1]) as Number; + _t2 ^= u.decodeNumber(f, w[2]) as Number; + _t3 ^= u.decodeNumber(f, w[3]) as Number; + _t4 ^= u.decodeNumber(f, w[4]) as Number; + _t5 ^= u.decodeNumber(f, w[5]) as Number; + _t6 ^= u.decodeNumber(f, w[6]) as Number; + _t7 ^= u.decodeNumber(f, w[7]) as Number; } _done++; left--; if (_done == _iterations) { - _out.addAll(_t); - Bytes.wipe(_t); - Bytes.wipe(_u); - _block++; - _done = 0; + finishBlock(); } } return _block > _blockCount; } + // Append T to the output and clear it. + private function finishBlock() as Void { + var t = new [SHA256_BYTES]b; + var words = [_t0, _t1, _t2, _t3, _t4, _t5, _t6, _t7]; + for (var k = 0; k < 8; k++) { + t.encodeNumber(words[k] as Number, Lang.NUMBER_FORMAT_SINT32, _w[k]); + words[k] = 0; + } + _out.addAll(t); + Bytes.wipe(t); + Bytes.wipe(_u); + clearWords(); + _block++; + _done = 0; + } + + private function clearWords() as Void { + _t0 = 0; + _t1 = 0; + _t2 = 0; + _t3 = 0; + _t4 = 0; + _t5 = 0; + _t6 = 0; + _t7 = 0; + } + // The derived key; only valid after step() returned true. function result() as ByteArray { return _out.slice(0, _length); @@ -102,9 +160,9 @@ module Kdf { // Wipe every intermediate value. function wipe() as Void { Bytes.wipe(_u); - Bytes.wipe(_t); Bytes.wipe(_out); Bytes.wipe(_salt); + clearWords(); } } |
