comparison dcrypt/crypto/ciphers/Salsa20.d @ 24:6428dfd7fede

Implemented Salsa20. Added some error checking to RC4's returnByte. Fixed copyright year in Bitwise.d and ByteConverter.d. Added Robert Smith to contributors file. Thanks buddy :)
author Thomas Dixon <reikon@reikon.us>
date Thu, 19 Feb 2009 14:45:13 -0500
parents
children 528676d20398
comparison
equal deleted inserted replaced
23:4589f8c5eb3c 24:6428dfd7fede
1 /**
2 * This file is part of the dcrypt project.
3 *
4 * Copyright: Copyright (C) dcrypt contributors 2009. All rights reserved.
5 * License: MIT
6 * Authors: Thomas Dixon
7 */
8
9 module dcrypt.crypto.ciphers.Salsa20;
10
11 import dcrypt.crypto.StreamCipher;
12 import dcrypt.crypto.params.ParametersWithIV;
13 import dcrypt.misc.ByteConverter;
14 import dcrypt.misc.Bitwise;
15
16 /** Implementation of Salsa20 designed by Daniel J. Bernstein. */
17 class Salsa20 : StreamCipher {
18 private {
19 // Constants
20 final ubyte[] sigma = cast(ubyte[])"expand 32-byte k",
21 tau = cast(ubyte[])"expand 16-byte k";
22
23 // Internal state
24 uint[] state;
25
26 // Keystream and index marker
27 ubyte[] keyStream;
28 uint index;
29
30 // Internal copies of the key and IV for resetting the cipher
31 ubyte[] workingKey,
32 workingIV;
33 }
34
35 this() {
36 state = new uint[16];
37
38 // State expanded into bytes
39 keyStream = new ubyte[64];
40 }
41
42 void init(bool encrypt, CipherParameters params) {
43 ParametersWithIV ivParams = cast(ParametersWithIV)params;
44
45 if (!ivParams)
46 throw new InvalidParameterError(
47 name()~": init parameters must include an IV. (use ParametersWithIV)");
48
49 SymmetricKey keyParams = cast(SymmetricKey)ivParams.parameters;
50
51 ubyte[] iv = ivParams.iv,
52 key = keyParams.key;
53
54 if (key) {
55 if (key.length != 16 && key.length != 32)
56 throw new InvalidKeyError(
57 name()~": Invalid key length. (requires 16 or 32 bytes)");
58
59 workingKey = key;
60 keySetup();
61
62 index = 0;
63 }
64
65 if (!workingKey)
66 throw new InvalidKeyError(name()~": Key not set.");
67
68 if (!iv || iv.length != 8)
69 throw new InvalidParameterError(name()~": 8 byte IV required.");
70
71 workingIV = iv;
72 ivSetup();
73
74 _encrypt = _initialized = true;
75 }
76
77 char[] name() {
78 return "Salsa20";
79 }
80
81 ubyte returnByte(ubyte input) {
82 if (!_initialized)
83 throw new NotInitializedError(name()~": Cipher not initialized");
84
85 if (index == 0) {
86 salsa20WordToByte(state, keyStream);
87 state[8]++;
88 if (!state[8])
89 state[9]++;
90 // As in djb's, changing the IV after 2^70 bytes is the user's responsibility
91 // lol glwt
92 }
93
94 ubyte result = (keyStream[index]^input);
95 index = (index + 1) & 0x3f;
96
97 return result;
98 }
99
100 uint update(void[] input_, void[] output_) {
101 if (!_initialized)
102 throw new NotInitializedError(name()~": Cipher not initialized");
103
104 ubyte[] input = cast(ubyte[]) input_,
105 output = cast(ubyte[]) output_;
106
107 if (input.length > output.length)
108 throw new ShortBufferError(name()~": Output buffer too short");
109
110 for (int i = 0; i < input.length; i++) {
111 if (index == 0) {
112 salsa20WordToByte(state, keyStream);
113 state[8]++;
114 if (!state[8])
115 state[9]++;
116 // As in djb's, changing the IV after 2^70 bytes is the user's responsibility
117 // lol glwt
118 }
119 output[i] = (keyStream[index]^input[i]);
120 index = (index + 1) & 0x3f;
121 }
122
123 return input.length;
124 }
125
126 void reset() {
127 keySetup();
128 ivSetup();
129 index = 0;
130 }
131
132 private void keySetup() {
133 uint offset;
134 ubyte[] constants;
135
136 state[1] = ByteConverter.LittleEndian.to!(uint)(workingKey[0..4]);
137 state[2] = ByteConverter.LittleEndian.to!(uint)(workingKey[4..8]);
138 state[3] = ByteConverter.LittleEndian.to!(uint)(workingKey[8..12]);
139 state[4] = ByteConverter.LittleEndian.to!(uint)(workingKey[12..16]);
140
141 if (workingKey.length == 32) {
142 constants = sigma;
143 offset = 16;
144 } else
145 constants = tau;
146
147 state[11] = ByteConverter.LittleEndian.to!(uint)(workingKey[offset..offset+4]);
148 state[12] = ByteConverter.LittleEndian.to!(uint)(workingKey[offset+4..offset+8]);
149 state[13] = ByteConverter.LittleEndian.to!(uint)(workingKey[offset+8..offset+12]);
150 state[14] = ByteConverter.LittleEndian.to!(uint)(workingKey[offset+12..offset+16]);
151 state[ 0] = ByteConverter.LittleEndian.to!(uint)(constants[0..4]);
152 state[ 5] = ByteConverter.LittleEndian.to!(uint)(constants[4..8]);
153 state[10] = ByteConverter.LittleEndian.to!(uint)(constants[8..12]);
154 state[15] = ByteConverter.LittleEndian.to!(uint)(constants[12..16]);
155 }
156
157 private void ivSetup() {
158 state[6] = ByteConverter.LittleEndian.to!(uint)(workingIV[0..4]);
159 state[7] = ByteConverter.LittleEndian.to!(uint)(workingIV[4..8]);
160 state[8] = state[9] = 0;
161 }
162
163 private void salsa20WordToByte(uint[] input, ref ubyte[] output) {
164 uint[] x = new uint[16];
165 x[] = input;
166
167 int i;
168 for (i = 0; i < 10; i++) {
169 x[ 4] ^= Bitwise.rotateLeft(x[ 0]+x[12], 7);
170 x[ 8] ^= Bitwise.rotateLeft(x[ 4]+x[ 0], 9);
171 x[12] ^= Bitwise.rotateLeft(x[ 8]+x[ 4], 13);
172 x[ 0] ^= Bitwise.rotateLeft(x[12]+x[ 8], 18);
173 x[ 9] ^= Bitwise.rotateLeft(x[ 5]+x[ 1], 7);
174 x[13] ^= Bitwise.rotateLeft(x[ 9]+x[ 5], 9);
175 x[ 1] ^= Bitwise.rotateLeft(x[13]+x[ 9], 13);
176 x[ 5] ^= Bitwise.rotateLeft(x[ 1]+x[13], 18);
177 x[14] ^= Bitwise.rotateLeft(x[10]+x[ 6], 7);
178 x[ 2] ^= Bitwise.rotateLeft(x[14]+x[10], 9);
179 x[ 6] ^= Bitwise.rotateLeft(x[ 2]+x[14], 13);
180 x[10] ^= Bitwise.rotateLeft(x[ 6]+x[ 2], 18);
181 x[ 3] ^= Bitwise.rotateLeft(x[15]+x[11], 7);
182 x[ 7] ^= Bitwise.rotateLeft(x[ 3]+x[15], 9);
183 x[11] ^= Bitwise.rotateLeft(x[ 7]+x[ 3], 13);
184 x[15] ^= Bitwise.rotateLeft(x[11]+x[ 7], 18);
185 x[ 1] ^= Bitwise.rotateLeft(x[ 0]+x[ 3], 7);
186 x[ 2] ^= Bitwise.rotateLeft(x[ 1]+x[ 0], 9);
187 x[ 3] ^= Bitwise.rotateLeft(x[ 2]+x[ 1], 13);
188 x[ 0] ^= Bitwise.rotateLeft(x[ 3]+x[ 2], 18);
189 x[ 6] ^= Bitwise.rotateLeft(x[ 5]+x[ 4], 7);
190 x[ 7] ^= Bitwise.rotateLeft(x[ 6]+x[ 5], 9);
191 x[ 4] ^= Bitwise.rotateLeft(x[ 7]+x[ 6], 13);
192 x[ 5] ^= Bitwise.rotateLeft(x[ 4]+x[ 7], 18);
193 x[11] ^= Bitwise.rotateLeft(x[10]+x[ 9], 7);
194 x[ 8] ^= Bitwise.rotateLeft(x[11]+x[10], 9);
195 x[ 9] ^= Bitwise.rotateLeft(x[ 8]+x[11], 13);
196 x[10] ^= Bitwise.rotateLeft(x[ 9]+x[ 8], 18);
197 x[12] ^= Bitwise.rotateLeft(x[15]+x[14], 7);
198 x[13] ^= Bitwise.rotateLeft(x[12]+x[15], 9);
199 x[14] ^= Bitwise.rotateLeft(x[13]+x[12], 13);
200 x[15] ^= Bitwise.rotateLeft(x[14]+x[13], 18);
201 }
202
203 for (i = 0; i < 16; i++)
204 x[i] += input[i];
205
206 int j;
207 for (i = j = 0; i < x.length; i++,j+=int.sizeof)
208 output[j..j+int.sizeof] = ByteConverter.LittleEndian.from!(uint)(x[i]);
209 }
210
211 /** Salsa20 test vectors */
212 version (UnitTest) {
213 unittest {
214 static const char[][] test_keys = [
215 "80000000000000000000000000000000",
216 "0053a6f94c9ff24598eb3e91e4378add",
217 "00002000000000000000000000000000"~
218 "00000000000000000000000000000000",
219 "0f62b5085bae0154a7fa4da0f34699ec"~
220 "3f92e5388bde3184d72a7dd02376c91c"
221
222 ];
223
224 static const char[][] test_ivs = [
225 "0000000000000000",
226 "0d74db42a91077de",
227 "0000000000000000",
228 "288ff65dc42b92f9"
229 ];
230
231 static const char[][] test_plaintexts = [
232 "00000000000000000000000000000000"~
233 "00000000000000000000000000000000"~
234 "00000000000000000000000000000000"~
235 "00000000000000000000000000000000",
236
237 "00000000000000000000000000000000"~
238 "00000000000000000000000000000000"~
239 "00000000000000000000000000000000"~
240 "00000000000000000000000000000000",
241
242 "00000000000000000000000000000000"~
243 "00000000000000000000000000000000"~
244 "00000000000000000000000000000000"~
245 "00000000000000000000000000000000",
246
247 "00000000000000000000000000000000"~
248 "00000000000000000000000000000000"~
249 "00000000000000000000000000000000"~
250 "00000000000000000000000000000000"
251
252
253 ];
254
255 static const char[][] test_ciphertexts = [
256 "4dfa5e481da23ea09a31022050859936"~ // Expected output
257 "da52fcee218005164f267cb65f5cfd7f"~
258 "2b4f97e0ff16924a52df269515110a07"~
259 "f9e460bc65ef95da58f740b7d1dbb0aa",
260
261 "05e1e7beb697d999656bf37c1b978806"~
262 "735d0b903a6007bd329927efbe1b0e2a"~
263 "8137c1ae291493aa83a821755bee0b06"~
264 "cd14855a67e46703ebf8f3114b584cba",
265
266 "c29ba0da9ebebfacdebbdd1d16e5f598"~
267 "7e1cb12e9083d437eaaaa4ba0cdc909e"~
268 "53d052ac387d86acda8d956ba9e6f654"~
269 "3065f6912a7df710b4b57f27809bafe3",
270
271 "5e5e71f90199340304abb22a37b6625b"~
272 "f883fb89ce3b21f54a10b81066ef87da"~
273 "30b77699aa7379da595c77dd59542da2"~
274 "08e5954f89e40eb7aa80a84a6176663f"
275 ];
276
277 Salsa20 s20 = new Salsa20();
278 ubyte[] buffer = new ubyte[64];
279 char[] result;
280 for (int i = 0; i < test_keys.length; i++) {
281 SymmetricKey key = new SymmetricKey(ByteConverter.hexDecode(test_keys[i]));
282 ParametersWithIV params = new ParametersWithIV(key, ByteConverter.hexDecode(test_ivs[i]));
283
284 // Encryption
285 s20.init(true, params);
286 s20.update(ByteConverter.hexDecode(test_plaintexts[i]), buffer);
287 result = ByteConverter.hexEncode(buffer);
288 assert(result == test_ciphertexts[i],
289 s20.name()~": ("~result~") != ("~test_ciphertexts[i]~")");
290
291 // Decryption
292 s20.init(false, params);
293 s20.update(ByteConverter.hexDecode(test_ciphertexts[i]), buffer);
294 result = ByteConverter.hexEncode(buffer);
295 assert(result == test_plaintexts[i],
296 s20.name()~": ("~result~") != ("~test_plaintexts[i]~")");
297 }
298 }
299 }
300 }