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