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