1 /**
2 * ChaCha20
3 * 
4 * Copyright:
5 * (C) 2014 Jack Lloyd
6 * (C) 2014-2015 Etienne Cimon
7 *
8 * License:
9 * Botan is released under the Simplified BSD License (see LICENSE.md)
10 */
11 module botan.stream.chacha;
12 
13 import botan.constants;
14 static if (BOTAN_HAS_CHACHA):
15 
16 import botan.stream.stream_cipher;
17 import botan.utils.loadstor;
18 import botan.utils.rotate;
19 import botan.utils.xor_buf;
20 import botan.utils.types;
21 import botan.utils.mem_ops;
22 import std.format : format;
23 
24 /**
25 * DJB's ChaCha (http://cr.yp.to/chacha.html)
26 */
27 final class ChaCha : StreamCipher, SymmetricAlgorithm
28 {
29 public:
30 	this(size_t rounds) {
31 		m_rounds = rounds;
32 		if (m_rounds != 8 && m_rounds != 12 && m_rounds != 20)
33 			throw new InvalidArgument("ChaCha only supports 8, 12 or 20 rounds");
34 	}
35 
36     /*
37     * Combine cipher stream with message
38     */
39     override void cipher(const(ubyte)* input, ubyte* output, size_t length)
40     {
41         while (length >= m_buffer.length - m_position)
42         {
43             xorBuf(output, input, m_buffer.ptr + m_position, m_buffer.length - m_position);
44             length -= (m_buffer.length - m_position);
45             input += (m_buffer.length - m_position);
46             output += (m_buffer.length - m_position);
47             chacha(*cast(ubyte[64]*) m_buffer.ptr, *cast(uint[16]*) m_state.ptr, m_rounds);
48             
49             ++m_state[12];
50             m_state[13] += (m_state[12] == 0);
51             
52             m_position = 0;
53         }
54         
55         xorBuf(output, input, &m_buffer[m_position], length);
56         
57         m_position += length;
58     }
59 
60     /*
61     * Return the name of this type
62     */
63     override void setIv(const(ubyte)* iv, size_t length)
64     {
65         if (!validIvLength(length))
66             throw new InvalidIVLength(name, length);
67         
68         m_state[12] = 0;
69 
70         m_state[13] = 0;
71         
72 		if (length == 8) {
73 	        m_state[14] = loadLittleEndian!uint(iv, 0);
74 	        m_state[15] = loadLittleEndian!uint(iv, 1);
75 		} else if (length == 12) {
76 			m_state[13] = loadLittleEndian!uint(iv, 0);
77 			m_state[14] = loadLittleEndian!uint(iv, 1);
78 			m_state[15] = loadLittleEndian!uint(iv, 2);
79 		}
80         
81         chacha(*cast(ubyte[64]*) m_buffer.ptr, *cast(uint[16]*) m_state.ptr, m_rounds);
82         ++m_state[12];
83         m_state[13] += (m_state[12] == 0);
84         
85         m_position = 0;
86     }
87 
88     override bool validIvLength(size_t iv_len) const
89     { return (iv_len == 8 || iv_len == 12); }
90 
91     KeyLengthSpecification keySpec() const
92     {
93         return KeyLengthSpecification(16, 32, 16);
94     }
95 
96     /*
97     * Clear memory of sensitive data
98     */
99     void clear()
100     {
101         zap(m_state);
102         zap(m_buffer);
103         m_position = 0;
104     }
105 
106     /*
107     * Return the name of this type
108     */
109     @property string name() const
110     {
111         return "ChaCha(" ~ m_rounds.to!string ~ ")";
112     }
113 
114     override StreamCipher clone() const { return new ChaCha(m_rounds); }
115    
116 
117 protected:
118     /*
119     * ChaCha Key Schedule
120     */
121     override void keySchedule(const(ubyte)* key, size_t length)
122     {
123         __gshared immutable uint[] TAU =    [ 0x61707865, 0x3120646e, 0x79622d36, 0x6b206574 ];
124         
125         __gshared immutable uint[] SIGMA = [ 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574 ];
126         
127         const uint[] CONSTANTS = (length == 16) ? TAU : SIGMA;
128         
129         m_state.resize(16);
130         m_buffer.resize(64);
131         
132         m_state[0] = CONSTANTS[0];
133         m_state[1] = CONSTANTS[1];
134         m_state[2] = CONSTANTS[2];
135         m_state[3] = CONSTANTS[3];
136         
137         m_state[4] = loadLittleEndian!uint(key, 0);
138         m_state[5] = loadLittleEndian!uint(key, 1);
139         m_state[6] = loadLittleEndian!uint(key, 2);
140         m_state[7] = loadLittleEndian!uint(key, 3);
141         
142         if (length == 32)
143             key += 16;
144         
145         m_state[8] = loadLittleEndian!uint(key, 0);
146         m_state[9] = loadLittleEndian!uint(key, 1);
147         m_state[10] = loadLittleEndian!uint(key, 2);
148         m_state[11] = loadLittleEndian!uint(key, 3);
149         
150         m_position = 0;
151         
152         const ubyte[8] ZERO;
153         setIv(ZERO.ptr, ZERO.length);
154     }
155 
156 	SecureVector!uint m_state;
157     SecureVector!ubyte m_buffer;
158     size_t m_position = 0;
159 	size_t m_rounds;
160 }
161 
162 enum string CHACHA_QUARTER_ROUND(alias _a, alias _b, alias _c, alias _d) = q{
163     %1$s += %2$s; %4$s ^= %1$s; %4$s = rotateLeft(%4$s, 16);
164     %3$s += %4$s; %2$s ^= %3$s; %2$s = rotateLeft(%2$s, 12);
165     %1$s += %2$s; %4$s ^= %1$s; %4$s = rotateLeft(%4$s, 8);
166     %3$s += %4$s; %2$s ^= %3$s; %2$s = rotateLeft(%2$s, 7);
167 }.format(__traits(identifier, _a), __traits(identifier, _b), __traits(identifier, _c), __traits(identifier, _d));
168 
169 private void chacha(ref ubyte[64] output, in uint[16] input, size_t rounds)
170 {
171 	assert(rounds % 2 == 0, "Valid rounds");
172 	uint x00 = input[ 0], x01 = input[ 1], x02 = input[ 2], x03 = input[ 3],
173 		x04 = input[ 4], x05 = input[ 5], x06 = input[ 6], x07 = input[ 7],
174 		x08 = input[ 8], x09 = input[ 9], x10 = input[10], x11 = input[11],
175 		x12 = input[12], x13 = input[13], x14 = input[14], x15 = input[15];
176 	
177 	
178 	foreach (size_t i; 0 .. rounds/2)
179 	{
180 		mixin(CHACHA_QUARTER_ROUND!(x00, x04, x08, x12) ~
181 			CHACHA_QUARTER_ROUND!(x01, x05, x09, x13) ~
182 			CHACHA_QUARTER_ROUND!(x02, x06, x10, x14) ~
183 			CHACHA_QUARTER_ROUND!(x03, x07, x11, x15) ~
184 			
185 			CHACHA_QUARTER_ROUND!(x00, x05, x10, x15) ~
186 			CHACHA_QUARTER_ROUND!(x01, x06, x11, x12) ~
187 			CHACHA_QUARTER_ROUND!(x02, x07, x08, x13) ~
188 			CHACHA_QUARTER_ROUND!(x03, x04, x09, x14)
189 			);
190 	}
191 	
192 	storeLittleEndian(x00 + input[ 0], output.ptr + 4 *  0);
193 	storeLittleEndian(x01 + input[ 1], output.ptr + 4 *  1);
194 	storeLittleEndian(x02 + input[ 2], output.ptr + 4 *  2);
195 	storeLittleEndian(x03 + input[ 3], output.ptr + 4 *  3);
196 	storeLittleEndian(x04 + input[ 4], output.ptr + 4 *  4);
197 	storeLittleEndian(x05 + input[ 5], output.ptr + 4 *  5);
198 	storeLittleEndian(x06 + input[ 6], output.ptr + 4 *  6);
199 	storeLittleEndian(x07 + input[ 7], output.ptr + 4 *  7);
200 	storeLittleEndian(x08 + input[ 8], output.ptr + 4 *  8);
201 	storeLittleEndian(x09 + input[ 9], output.ptr + 4 *  9);
202 	storeLittleEndian(x10 + input[10], output.ptr + 4 * 10);
203 	storeLittleEndian(x11 + input[11], output.ptr + 4 * 11);
204 	storeLittleEndian(x12 + input[12], output.ptr + 4 * 12);
205 	storeLittleEndian(x13 + input[13], output.ptr + 4 * 13);
206 	storeLittleEndian(x14 + input[14], output.ptr + 4 * 14);
207 	storeLittleEndian(x15 + input[15], output.ptr + 4 * 15);
208 }