1 module dcrypt.blockcipher.modes.cbc;
2 
3 import std.algorithm: fill;
4 import dcrypt.blockcipher.blockcipher;
5 import dcrypt.exceptions;
6 
7 
8 /// test AES/CBC encryption
9 /// test vectors: http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
10 @safe
11 unittest {
12 	import dcrypt.blockcipher.aes;
13 	import std.range;
14 	import std.conv: text;
15 	
16 	CBC!AES cbc;
17 	
18 	const ubyte[] key = cast(const ubyte[])x"2b7e151628aed2a6abf7158809cf4f3c";
19 	const ubyte[] iv = cast(const ubyte[])x"000102030405060708090a0b0c0d0e0f";
20 	
21 	const ubyte[] plain = cast(const ubyte[])x"
22 		6bc1bee22e409f96e93d7e117393172a
23 		ae2d8a571e03ac9c9eb76fac45af8e51
24 		30c81c46a35ce411e5fbc1191a0a52ef
25 		f69f2445df4f9b17ad2b417be66c3710
26 	";
27 	
28 	const ubyte[] expected_ciphertext = cast(const ubyte[])x"
29 		7649abac8119b246cee98e9b12e9197d
30 		5086cb9b507219ee95db113a917678b2
31 		73bed6b8e3c1743b7116e69e22229516
32 		3ff1caa1681fac09120eca307586e1a7
33 	";
34 	
35 	
36 	// encryption mode
37 	cbc.start(true, key, iv);
38 	
39 	ubyte[plain.length] buf;
40 	buf = plain;
41 	
42 	foreach(block; chunks(buf[],16)) {
43 		cbc.processBlock(block,block);
44 	}
45 	
46 	assert(buf == expected_ciphertext, text(cbc.name,": encryption failed"));
47 	
48 	// decryption mode
49 	cbc.start(false, key, iv);
50 	
51 	foreach(block; chunks(buf[],16)) {
52 		cbc.processBlock(block,block);
53 	}
54 	
55 	assert(buf == plain, text(cbc.name,": decryption failed"));
56 	
57 }
58 
59 // OOP API wrapper
60 alias CBCBlockCipher(T) = BlockCipherWrapper!(CBC!T);
61 
62 /**
63  * implements Cipher-Block-Chaining (CBC) mode on top of a simple cipher.
64  */
65 @safe
66 public struct CBC(Cipher) if(isBlockCipher!Cipher)
67 {
68 	public enum blockSize = Cipher.blockSize;
69 	public enum name = Cipher.name ~ "/CBC";
70 
71 	private{
72 		ubyte[blockSize]	cbcV;
73 		ubyte[blockSize]	cbcNextV;
74 		ubyte[blockSize]	IV;			// IV as provided by user.
75 
76 		Cipher		cipher;
77 		bool		forEncryption;
78 		bool		initialized = false;
79 	}
80 
81 	
82 	/**
83 	 * return the underlying block cipher that we are wrapping.
84 	 *
85 	 * Returns the underlying block cipher that we are wrapping.
86 	 */
87 	ref Cipher getUnderlyingCipher() pure nothrow
88 	{
89 		return cipher;
90 	}
91 
92 	/// Initialize the cipher and, possibly, the initialization vector (IV).
93 	/// If the cipher is already initialized a new IV can be set without the overhead
94 	/// of a new key setup: init(forEncryption, null, newIV)
95 	/// 
96 	/// Params: 
97 	/// forEncryption = if true the cipher is initialized for encryption, if false for decryption.
98 	/// params = the key and other data required by the cipher.
99 	public void start(bool forEncryption, in ubyte[] userKey, in ubyte[] iv = null) nothrow @nogc 
100 	in {
101 		//assert(iv !is null, "CBC without IV not supported!");
102 		assert(iv is null || iv.length == blockSize, "Length ov IV does not match block size!");
103 	}
104 	body {
105 
106 		bool oldMode = this.forEncryption;
107 		this.forEncryption = forEncryption;
108 
109 		if(userKey is null) {
110 			// possible to change iv overhead of new key setup
111 			assert(initialized, "cipher not initialized");
112 			assert(forEncryption == oldMode, "Cant switch between encryption and decryption without providing a new key.");
113 
114 			IV[] = iv[];
115 		} else {
116 			
117 			cipher.start(forEncryption, userKey);
118 			
119 			if(iv !is null) {
120 				IV[] = iv[];
121 			} else {
122 				IV[] = 0;
123 			}
124 		}
125 		initialized = true;
126 		reset();
127 	}
128 
129 	/**
130 	 * Process one block of input from the array in and write it to
131 	 * the out array.
132 	 *
133 	 * Params
134 	 * input = the array containing the input data.
135 	 * output = the array the output data will be copied into.
136 	 * Returns: the number of bytes processed and produced.
137 	 */
138 	public uint processBlock(in ubyte[] input, ubyte[] output)
139 	in {
140 		assert(input.length == blockSize, "input.length != blockSize");
141 		assert(output.length >= blockSize, "output buffer too small");
142 		assert(initialized, "cipher not initialized");
143 	}
144 	body {
145 		return (forEncryption) ? encryptBlock(input, output) : decryptBlock(input, output);
146 	}
147 
148 	/**
149 	 * reset the chaining vector back to the IV and reset the underlying
150 	 * cipher.
151 	 */
152 	public void reset() nothrow
153 	in {
154 		assert(initialized, "cipher not initialized");
155 	}
156 	body {
157 		cbcV[] = IV[];
158 		cbcNextV[] = 0; // fill with zeros
159 		cipher.reset();
160 	}
161 
162 	private nothrow @nogc @safe:
163 
164 	/**
165 	 * Do the appropriate chaining step for CBC mode encryption.
166 	 *
167 	 * Params
168 	 * input = the array containing the input data.
169 	 * output = the array the output data will be copied into.
170 	 * Returns: the number of bytes processed and produced.
171 	 */
172 	uint encryptBlock(in ubyte[] input, ubyte[] output)
173 	in {
174 		assert(input.length >= blockSize, "input buffer too short");
175 		assert(output.length >= blockSize, "output buffer too short");
176 		assert(initialized, "cipher not initialized");
177 	}
178 	body {
179 		/*
180 		 * XOR the cbcV and the input,
181 		 * then encrypt the cbcV
182 		 */
183 		cbcV[0..blockSize] ^= input[0..blockSize];
184 
185 		uint length = cipher.processBlock(cbcV,output);
186 
187 		/*
188 		 * copy ciphertext to cbcV
189 		 */
190 
191 		cbcV[] = output[0..cbcV.length];
192 
193 		return length;
194 	}
195 
196 	/**
197 	 * Do the appropriate chaining step for CBC mode decryption.
198 	 *
199 	 * Params
200 	 * input = the array containing the input data.
201 	 * output = the array the output data will be copied into.
202 	 * Returns: the number of bytes processed and produced.
203 	 */
204 	uint decryptBlock(in ubyte[] input, ubyte[] output)
205 	in {
206 		assert(input.length >= blockSize, "input buffer too short");
207 		assert(output.length >= blockSize, "output buffer too short");
208 		assert(initialized, "cipher not initialized");
209 	}
210 	body  {
211 
212 		cbcNextV[0..blockSize] =  input[0..blockSize];
213 
214 		uint length = cipher.processBlock(input, output);
215 
216 		/*
217 		 * XOR the cbcV and the output
218 		 */
219 		output[] ^= cbcV[];
220 
221 		/*
222 		 * swap the back up buffer into next position
223 		 */
224 		ubyte[]  tmp;
225 
226 		tmp = cbcV;
227 		cbcV = cbcNextV;
228 		cbcNextV = tmp;
229 
230 		return length;
231 	}
232 }