ctr.ha (3161B)
1 // SPDX-License-Identifier: MPL-2.0 2 // (c) Hare authors <https://harelang.org> 3 4 use bytes; 5 use crypto::math::{xor}; 6 use io; 7 8 // A counter mode (CTR) stream. 9 export type ctr_stream = struct { 10 xorstream, 11 b: *block, 12 counter: []u8, 13 xorbuf: []u8, 14 xorused: size, 15 }; 16 17 // Creates a counter mode (CTR) cipher stream which can be used for encryption 18 // (by encrypting writes to the underlying handle) or decryption (or by 19 // decrypting reads from the underlying handle), but not both. 20 // 21 // The user must supply an initialization vector (IV) equal in length to the 22 // block size of the underlying [[block]] cipher, and a temporary state buffer 23 // whose size is equal to the block size times two. The module providing the 24 // underlying block cipher usually provides constants which define the lengths 25 // of these buffers for static allocation. 26 // 27 // The user must call [[io::close]] when they are done using the stream to 28 // securely erase secret information stored in the stream state. This will also 29 // finish the underlying [[block]] cipher. 30 export fn ctr(h: io::handle, b: *block, iv: []u8, buf: []u8) ctr_stream = { 31 assert(len(iv) == blocksz(b), "iv is of invalid block size"); 32 assert(len(buf) >= blocksz(b) * 2, "buf must be at least 2 * blocksize"); 33 34 const bsz = blocksz(b); 35 36 // one buf block is used for the counter 37 let counter = buf[0..bsz]; 38 39 // the remaining space is used to store the key stream. It needs 40 // to be at least the size of one block and ideally the size of 41 // nparallel(b) times the block size. A bigger buffer than the latter 42 // option is of no use. 43 let xorbuf = buf[bsz..]; 44 45 counter[..] = iv[..]; 46 47 // cap the buffer to a multiple of bsz. 48 let maxxorbufsz = blocksz(b) * nparallel(b); 49 const xorbufsz = if (len(xorbuf) < maxxorbufsz) { 50 yield len(xorbuf) - len(xorbuf) % blocksz(b); 51 } else { 52 yield maxxorbufsz; 53 }; 54 55 let s = ctr_stream { 56 stream = &xorstream_vtable, 57 h = h, 58 keybuf = &ctr_keybuf, 59 advance = &ctr_advance, 60 finish = &ctr_finish, 61 62 b = b, 63 counter = counter, 64 xorbuf = xorbuf[..xorbufsz], 65 // mark all as used to force fill xorbuf 66 xorused = xorbufsz, 67 ... 68 }; 69 return s; 70 }; 71 72 fn fill_xorbuf(ctr: *ctr_stream) void = { 73 const bsz = blocksz(ctr.b); 74 75 // Write and increment the counter to each available block 76 for (let i = 0z; i < len(ctr.xorbuf) / bsz; i += 1) { 77 ctr.xorbuf[i * bsz..(i * bsz + bsz)] = ctr.counter[0..bsz]; 78 79 for (let j = len(ctr.counter); j > 0; j -= 1) { 80 ctr.counter[j - 1] += 1; 81 if (ctr.counter[j - 1] != 0) { 82 break; 83 }; 84 }; 85 }; 86 87 encrypt(ctr.b, ctr.xorbuf, ctr.xorbuf); 88 ctr.xorused = 0; 89 }; 90 91 fn ctr_keybuf(s: *xorstream) []u8 = { 92 let ctr = s: *ctr_stream; 93 if (ctr.xorused >= len(ctr.xorbuf)) { 94 fill_xorbuf(ctr); 95 }; 96 return ctr.xorbuf[ctr.xorused..]; 97 }; 98 99 fn ctr_advance(s: *xorstream, n: size) void = { 100 let ctr = s: *ctr_stream; 101 102 // fill_xorbuf could be smarter, to skip multiple blocks at once. 103 // It's of no use, since xorstream doesn't support skipping an arbritary 104 // number of blocks. 105 assert(n <= len(ctr.xorbuf)); 106 107 ctr.xorused += n; 108 }; 109 110 fn ctr_finish(s: *xorstream) void = { 111 let ctr = s: *ctr_stream; 112 bytes::zero(ctr.xorbuf); 113 finish(ctr.b); 114 };