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 }