1 module dcrypt.crypto.engines.chacha; 2 3 import std.algorithm: min; 4 5 import dcrypt.util; 6 import dcrypt.bitmanip; 7 8 /// Implementation of the ChaCha stream cipher as first described by D. J. Bernstein (http://cr.yp.to/chacha.html), 9 /// following RFC 7539. 10 /// 11 /// Standard: RFC 7539 12 /// 13 /// Note: This might not be compatible with BouncyCastle's implementation because that one uses a 64-bit counter. 14 /// 15 public struct ChaCha20 { 16 17 @safe nothrow @nogc: 18 19 public { 20 enum name = "ChaCha"~rounds; 21 } 22 23 private { 24 enum rounds = 20; 25 static immutable uint[4] constants = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574]; 26 27 uint[16] state; 28 ubyte[16*4] keyStream; 29 size_t keyStreamIndex = 0; 30 31 bool initialized = false; 32 } 33 34 ~this () { 35 wipe(state); 36 wipe(keyStream); 37 } 38 39 /// Initialize the ChaCha20 stream cipher. 40 /// 41 /// Params: 42 /// forEncryption = Not used, because encryption and decryptioin are the same. 43 /// key = A secret key of 32 bytes length (256 bit). 44 /// iv = A nonce of 12 bytes length (96 bit). 45 public void start(bool forEncryption, in ubyte[] key, in ubyte[] iv) 46 in { 47 assert(key.length == 32, name~" requires a 32 byte key."); 48 assert(iv.length == 12, name~" requires a 12 byte nonce."); 49 } body { 50 51 ubyte[32] _key = key; 52 ubyte[12] _iv = iv; 53 54 initState(state, _key, 1, _iv); 55 keyStreamIndex = 0; 56 initialized = true; 57 } 58 59 /// Process a single byte. 60 public ubyte returnByte(in ubyte input) 61 in { 62 assert(initialized, name~" not initialized."); 63 } body { 64 65 if (keyStreamIndex == 0) { 66 genKeyStream(); 67 } 68 69 ubyte output = keyStream[keyStreamIndex]^input; 70 keyStreamIndex = (keyStreamIndex + 1) % keyStream.length; 71 72 return output; 73 } 74 75 /// Returns: Slice pointing to processed data which might be smaller than `output`. 76 public ubyte[] processBytes(in ubyte[] input, ubyte[] output) 77 in { 78 assert(initialized, name~" not initialized."); 79 assert(output.length >= input.length, "Output buffer too small."); 80 } body { 81 82 const (ubyte)[] inp = input; 83 ubyte[] initialOutput = output; 84 85 while(inp.length > 0) { 86 87 if (keyStreamIndex == 0) { 88 genKeyStream(); 89 } 90 91 size_t len = min(keyStream.length-keyStreamIndex, inp.length); 92 output[0..len] = inp[0..len] ^ keyStream[keyStreamIndex..keyStreamIndex+len]; 93 keyStreamIndex = (keyStreamIndex + len) % keyStream.length; 94 95 inp = inp[len..$]; 96 output = output[len..$]; 97 } 98 99 return initialOutput[0..input.length]; // Return slice to processed data. 100 } 101 102 /// Reset the cipher to its initial state. Same as calling start() with same parameters again. 103 /// Warning: Don't encrypt different data with the same initial state. 104 deprecated("The reset() function might lead to insecure use of a stream cipher.") 105 public void reset() { 106 state[12] = 1; // reset the counter 107 keyStreamIndex = 0; 108 } 109 110 /// Performs a ChaCha quarter round on a, b, c, d 111 /// Params: 112 /// a, b, c, d = Values to perform the round on. They get modified. 113 private static void quarterRound(ref uint a, ref uint b, ref uint c, ref uint d) pure { 114 a += b; d = rol(d^a, 16); 115 c += d; b = rol(b^c, 12); 116 a += b; d = rol(d^a, 8); 117 c += d; b = rol(b^c, 7); 118 } 119 120 // Test quarter round. 121 // Test vectors from RFC7539, section 2.1.1 122 unittest { 123 uint a = 0x11111111, b = 0x01020304, c = 0x9b8d6f43, d = 0x01234567; 124 quarterRound(a, b, c, d); 125 126 assert(a == 0xea2a92f4 && b == 0xcb1cf8ce && c == 0x4581472e && d == 0x5881c4bb, 127 "ChaCha quarter round is doing weird things..."); 128 } 129 130 private static void innerRound(ref uint[16] state) pure { 131 quarterRound(state[0], state[4], state[8], state[12]); 132 quarterRound(state[1], state[5], state[9], state[13]); 133 quarterRound(state[2], state[6], state[10], state[14]); 134 quarterRound(state[3], state[7], state[11], state[15]); 135 136 quarterRound(state[0], state[5], state[10], state[15]); 137 quarterRound(state[1], state[6], state[11], state[12]); 138 quarterRound(state[2], state[7], state[8], state[13]); 139 quarterRound(state[3], state[4], state[9], state[14]); 140 } 141 142 /// Set the state as follows: 143 /// state = constants ~ key ~ counter ~ nonce 144 /// 145 /// Params: 146 /// state = The state. 147 /// key = 32 bytes. 148 /// nonce = 12 bytes. 149 package static void initState(ref uint[16] state, in ubyte[] key, in uint counter, in ubyte[] nonce) pure 150 in { 151 assert(key.length == 32, "ChaCha requires 256 bit key."); 152 assert(nonce.length == 12, "ChaCha requires 96 bit nonce."); 153 } body { 154 state[0..4] = constants; 155 fromLittleEndian(key[0..32], state[4..12]); 156 state[12] = counter; 157 fromLittleEndian(nonce[0..12], state[13..16]); 158 } 159 160 /// Performs the ChaCha block function on `inState`, result in `outState` 161 /// Params: 162 /// inState = the state created with `initState()` 163 /// outState = buffer for the new state 164 package static void block(uint rounds = 20)(in ref uint[16] inState, ref uint[16] outState) pure 165 if(rounds % 2 == 0, "'rounds' must be even.") 166 { 167 168 uint[16] workingState = inState; 169 170 foreach(i; 0..rounds / 2) { 171 innerRound(workingState); 172 } 173 174 workingState[] += inState[]; 175 outState[] = workingState[]; 176 } 177 178 /// Performs the ChaCha block function on `inState`, result in `outState` 179 /// Params: 180 /// inState = the state created with `initState()` 181 /// outState = buffer for the new state 182 package static void block(uint rounds = 20)(in ref uint[16] inState, ref ubyte[16*4] outState) pure 183 { 184 uint[16] key; 185 block!rounds(inState, key); 186 toLittleEndian!uint(key, outState); 187 } 188 189 private void incrementCounter() { 190 state[12]++; 191 } 192 193 /// Generate a block of key stream and write it to `keyStream`. 194 private void genKeyStream() 195 in { 196 assert(initialized, name~" not initialized."); 197 } body { 198 // generate the key stream 199 block(state, keyStream); 200 incrementCounter(); 201 } 202 } 203 204 205 206 // test the ChaCha20 block function. 207 unittest { 208 209 ubyte[32] key = cast(const ubyte[]) x"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"; 210 uint counter = 1; 211 ubyte[12] nonce = cast(const ubyte[]) x"000000090000004a00000000"; 212 213 uint[16] state; 214 215 ChaCha20.initState(state, key, counter, nonce); 216 217 enum uint[16] expectedInitialState = [ 218 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574, 219 0x03020100, 0x07060504, 0x0b0a0908, 0x0f0e0d0c, 220 0x13121110, 0x17161514, 0x1b1a1918, 0x1f1e1d1c, 221 0x00000001, 0x09000000, 0x4a000000, 0x00000000 222 ]; 223 224 assert(state == expectedInitialState, "initState() failed!"); 225 226 ChaCha20.block(state, state); 227 228 enum uint[16] expectedState= [ 229 0xe4e7f110, 0x15593bd1, 0x1fdd0f50, 0xc47120a3, 230 0xc7f4d1c7, 0x0368c033, 0x9aaa2204, 0x4e6cd4c3, 231 0x466482d2, 0x09aa9f07, 0x05d7c214, 0xa2028bd9, 232 0xd19c12b5, 0xb94e16de, 0xe883d0cb, 0x4e3c50a2 233 ]; 234 235 assert(state == expectedState, "chaCha20Block() failed!"); 236 237 ubyte[16*4] keyStream; 238 239 toLittleEndian!uint(state, keyStream); 240 241 ubyte[16*4] expectedKeyStream = cast(const ubyte[]) x" 242 10 f1 e7 e4 d1 3b 59 15 50 0f dd 1f a3 20 71 c4 243 c7 d1 f4 c7 33 c0 68 03 04 22 aa 9a c3 d4 6c 4e 244 d2 82 64 46 07 9f aa 09 14 c2 d7 05 d9 8b 02 a2 245 b5 12 9c d1 de 16 4e b9 cb d0 83 e8 a2 50 3c 4e"; 246 247 assert(keyStream == expectedKeyStream, "Got unexpected key stream."); 248 249 }