1 module dcrypt.crypto.engines.rc6;
2 
3 import dcrypt.crypto.blockcipher;
4 import dcrypt.util.bitmanip;
5 import dcrypt.util.pack;
6 
7 /// test the RC6 block cipher engine
8 unittest {
9 
10 	// 160, 224 bit keys commented out
11 	string[] test_keys = [
12 		x"01010101010101010101010101010101",
13 		//x"0101010101010101010101010101010101010101",
14 		x"010101010101010101010101010101010101010101010101",
15 		//x"01010101010101010101010101010101010101010101010101010101",
16 		x"0101010101010101010101010101010101010101010101010101010101010101",
17 	];
18 	string[] test_plaintexts = [
19 		x"01010101010101010101010101010101",
20 		//x"01010101010101010101010101010101",
21 		x"01010101010101010101010101010101",
22 		//x"01010101010101010101010101010101",
23 		x"01010101010101010101010101010101",
24 	];
25 	string[] test_ciphertexts = [
26 		x"efb2c7dd69614683dab0bc607036c425",
27 		//x"ff59d24608b42833e9292f4be0a239a5",
28 		x"142aa9f25dd64a8a6444304735aa6641",
29 		//x"26e64b9c2bb63e93494d0d803994bdfd",
30 		x"41ecee06dda0946c816f528a767c0ef6",
31 	];
32 	
33 	RC6Engine t = new RC6Engine();
34 	blockCipherTest(t, test_keys, test_plaintexts, test_ciphertexts);
35 }
36 
37 alias BlockCipherWrapper!RC6 RC6Engine;
38 
39 /**
40  * An RC6 engine.
41  */
42 @safe
43 public struct RC6
44 {
45 	public {
46 
47 		enum name = "RC6";
48 		enum blockSize = 4*bytesPerWord;
49 
50 		
51 		/// Params:
52 		/// forEncryption = `false`: decrypt, `true`: encrypt
53 		/// userKey = Secret key.
54 		/// iv = Not used.
55 		/// Throws: Error if key has unsupported size.	
56 		void start(bool forEncryption, in ubyte[] userKey, in ubyte[] iv = null) nothrow @nogc
57 		{
58 			this.forEncryption = forEncryption;
59 			setKey(userKey);
60 		}
61 
62 		uint processBlock(in ubyte[]  input, ubyte[]  output) nothrow @nogc
63 		in {
64 			assert(initialized, "RC6 engine not initialized");
65 			assert(blockSize <= input.length, "input buffer too short");
66 			assert(blockSize <= output.length, "output buffer too short");
67 		}
68 		body {
69 			return forEncryption ? encryptBlock(input, output) : decryptBlock(input, output);
70 		}
71 
72 		void reset() nothrow @nogc
73 		{
74 		}
75 
76 		
77 		private nothrow @nogc:
78 
79 		private {
80 			enum wordSize = 32;
81 			enum bytesPerWord = wordSize / 8;
82 
83 			/*
84 			 * the number of rounds to perform
85 			 */
86 			enum _noRounds = 20;
87 
88 			/*
89 			 * our "magic constants" for wordSize 32
90 			 *
91 			 * Pw = Odd((e-2) * 2^wordsize)
92 			 * Qw = Odd((o-2) * 2^wordsize)
93 			 *
94 			 * where e is the base of natural logarithms (2.718281828...)
95 			 * and o is the golden ratio (1.61803398...)
96 			 */
97 			enum uint    P32 = 0xb7e15163;
98 			enum uint    Q32 = 0x9e3779b9;
99 
100 			enum    LGW = 5;        // log2(32)
101 
102 			/*
103 			 * the expanded key array of size 2*(rounds + 1)
104 			 */
105 			uint[2+2*_noRounds+2] _S;
106 			bool forEncryption;
107 			bool initialized = false; // set to true in setKey()
108 
109 		}
110 		/**
111 		 * Re-key the cipher.
112 		 * Params:  key  the key to be used
113 		 */
114 		private void setKey(in ubyte[] key) nothrow @nogc
115 		in {
116 			size_t len = key.length;
117 			assert(len == 16 || len == 24 || len == 32, "RC6: Unsupported key length. Should be 128, 192, or 256 bits."); 
118 		}
119 		body {
120 			
121 			enum maxKeyLength = 32;
122 			
123 			//
124 			// KEY EXPANSION:
125 			//
126 			// There are 3 phases to the key expansion.
127 			//
128 			// Phase 1:
129 			//   Copy the secret key K[0...b-1] into an array L[0..c-1] of
130 			//   c = ceil(b/u), where u = wordSize/8 in little-endian order.
131 			//   In other words, we fill up L using u consecutive key bytes
132 			//   of K. Any unfilled byte positions in L are zeroed. In the
133 			//   case that b = c = 0, set c = 1 and L[0] = 0.
134 			//
135 			// compute number of dwords
136 			size_t c = (key.length + (bytesPerWord - 1)) / bytesPerWord;
137 			if (c == 0)
138 			{
139 				c = 1;
140 			}
141 			
142 			uint[(maxKeyLength + bytesPerWord - 1) / bytesPerWord] L;	///	Static length is hight enough to support 256 bit keys.
143 			immutable size_t Llength = (key.length + bytesPerWord - 1) / bytesPerWord;	/// Holds the actual length of L.
144 			
145 			// load all key bytes into array of key dwords
146 			
147 			foreach(size_t i; 0..key.length) {
148 				L[i / bytesPerWord] += key[i] << (8*i);
149 			}
150 			
151 			//
152 			// Phase 2:
153 			//   Key schedule is placed in a array of 2+2*ROUNDS+2 = 44 dwords.
154 			//   Initialize S to a particular fixed pseudo-random bit pattern
155 			//   using an arithmetic progression modulo 2^wordsize determined
156 			//   by the magic numbers, Pw & Qw.
157 			//
158 			//        _S            = new uint[2+2*_noRounds+2];
159 			
160 			_S[0] = P32;
161 			foreach (size_t i; 1.._S.length)
162 			{
163 				_S[i] = (_S[i-1] + Q32);
164 			}
165 			
166 			//
167 			// Phase 3:
168 			//   Mix in the user's secret key in 3 passes over the arrays S & L.
169 			//   The max of the arrays sizes is used as the loop control
170 			//
171 			size_t iter;
172 			
173 			if (Llength > _S.length)
174 			{
175 				iter = 3 * Llength;
176 			}
177 			else
178 			{
179 				iter = 3 * _S.length;
180 			}
181 			
182 			uint A = 0;
183 			uint B = 0;
184 			uint i = 0, j = 0;
185 			
186 			foreach (k; 0..iter)
187 			{
188 				A = _S[i] = rotateLeft(_S[i] + A + B, 3);
189 				B =  L[j] = rotateLeft(L[j] + A + B, A+B);
190 				i = (i+1) % _S.length;
191 				j = (j+1) %  Llength;
192 			}
193 			
194 			initialized = true;
195 		}
196 	}
197 
198 	@nogc
199 	private uint encryptBlock(in ubyte[]  input, ubyte[]  output) nothrow
200 	{
201 		// load A,B,C and D registers from in.
202 		uint A = fromLittleEndian!uint(input);
203 		uint B = fromLittleEndian!uint(input[bytesPerWord..$]);
204 		uint C = fromLittleEndian!uint(input[bytesPerWord*2..$]);
205 		uint D = fromLittleEndian!uint(input[bytesPerWord*3..$]);
206 
207 		// Do pseudo-round #0: pre-whitening of B and D
208 		B += _S[0];
209 		D += _S[1];
210 
211 		// perform round #1,#2 ... #ROUNDS of encryption
212 		foreach (uint i; 1.._noRounds+1)
213 		{
214 			uint t = 0,u = 0;
215 
216 			t = B*(2*B+1);
217 			t = rotateLeft(t,5);
218 
219 			u = D*(2*D+1);
220 			u = rotateLeft(u,5);
221 
222 			A ^= t;
223 			A = rotateLeft(A,u);
224 			A += _S[2*i];
225 
226 			C ^= u;
227 			C = rotateLeft(C,t);
228 			C += _S[2*i+1];
229 
230 			uint temp = A;
231 			A = B;
232 			B = C;
233 			C = D;
234 			D = temp;
235 		}
236 		// do pseudo-round #(ROUNDS+1) : post-whitening of A and C
237 		A += _S[2*_noRounds+2];
238 		C += _S[2*_noRounds+3];
239 
240 		// store A, B, C and D registers to out
241 		toLittleEndian(A, output);
242 		toLittleEndian(B, output[bytesPerWord..$]);
243 		toLittleEndian(C, output[bytesPerWord*2..$]);
244 		toLittleEndian(D, output[bytesPerWord*3..$]);
245 
246 		return 4 * bytesPerWord;
247 	}
248 
249 	@nogc
250 	private uint decryptBlock(in ubyte[]  input, ubyte[]  output) nothrow
251 	{
252 		// load A,B,C and D registers from out.
253 		uint A = fromLittleEndian!uint(input);
254 		uint B = fromLittleEndian!uint(input[bytesPerWord..$]);
255 		uint C = fromLittleEndian!uint(input[bytesPerWord*2..$]);
256 		uint D = fromLittleEndian!uint(input[bytesPerWord*3..$]);
257 
258 		// Undo pseudo-round #(ROUNDS+1) : post whitening of A and C
259 		C -= _S[2*_noRounds+3];
260 		A -= _S[2*_noRounds+2];
261 
262 		// Undo round #ROUNDS, .., #2,#1 of encryption
263 		for (uint i = _noRounds; i >= 1; i--)
264 		{
265 			uint t=0,u = 0;
266 
267 			uint temp = D;
268 			D = C;
269 			C = B;
270 			B = A;
271 			A = temp;
272 
273 			t = B*(2*B+1);
274 			t = rotateLeft(t, LGW);
275 
276 			u = D*(2*D+1);
277 			u = rotateLeft(u, LGW);
278 
279 			C -= _S[2*i+1];
280 			C = rotateRight(C,t);
281 			C ^= u;
282 
283 			A -= _S[2*i];
284 			A = rotateRight(A,u);
285 			A ^= t;
286 
287 		}
288 		// Undo pseudo-round #0: pre-whitening of B and D
289 		D -= _S[1];
290 		B -= _S[0];
291 
292 		toLittleEndian(A, output);
293 		toLittleEndian(B, output[bytesPerWord..$]);
294 		toLittleEndian(C, output[bytesPerWord*2..$]);
295 		toLittleEndian(D, output[bytesPerWord*3..$]);
296 
297 		return 4 * bytesPerWord;
298 	}
299 }