Mercurial > projects > dcrypt
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 } |