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 }