1 module dcrypt.blockcipher.bufferedblockcipher; 2 3 import dcrypt.blockcipher.blockcipher; 4 import dcrypt.blockcipher.modes.ctr; 5 import std.algorithm: min; 6 7 unittest { 8 import dcrypt.blockcipher.aes; 9 10 alias const(ubyte)[] octets; 11 12 IBufferedBlockCipher bbc = new BufferedBlockCipherWrapper!AES; 13 bbc.start(true, cast(octets) x"2b7e151628aed2a6abf7158809cf4f3c"); 14 15 octets plain = cast(octets) x"6bc1bee22e409f96e93d7e117393172a"; 16 plain ~= plain; 17 octets cipher = cast(octets) x"3ad77bb40d7a3660a89ecaf32466ef97"; 18 cipher ~= cipher; 19 20 ubyte[] output = new ubyte[32]; 21 22 uint len = 0; 23 len += bbc.processBytes(plain[0..0], output[len..$]); 24 len += bbc.processBytes(plain[0..1], output[len..$]); 25 len += bbc.processBytes(plain[1..7], output[len..$]); 26 len += bbc.processBytes(plain[7..14], output[len..$]); 27 assert(len == 0); 28 len += bbc.processBytes(plain[14..20], output[len..$]); 29 assert(len == 16); 30 31 assert(output[0..16] == cipher[0..16], "BufferedBlockCipher failed"); 32 33 // feed it with single bytes 34 foreach(b; plain[20..$]) { 35 len += bbc.processByte(b, output[len..$]); 36 } 37 assert(output == cipher, "BufferedBlockCipher.processByte(...) failed"); 38 } 39 40 /// test buffered AES/CTR encryption 41 /// test vectors: http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf 42 @safe 43 unittest { 44 import dcrypt.blockcipher.aes; 45 import dcrypt.blockcipher.modes.ctr; 46 import dcrypt.blockcipher.modes.cbc; 47 import std.range; 48 import std.conv: text; 49 50 BufferedBlockCipher!(CTR!AES) cipher; 51 52 const ubyte[] key = cast(const ubyte[])x"2b7e151628aed2a6abf7158809cf4f3c"; 53 const ubyte[] iv = cast(const ubyte[])x"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"; 54 55 const ubyte[] plain = cast(const ubyte[])x" 56 6bc1bee22e409f96e93d7e117393172a 57 ae2d8a571e03ac9c9eb76fac45af8e51 58 30c81c46a35ce411e5fbc1191a0a52ef 59 f69f2445df4f9b17ad2b417be66c3710 60 "; 61 62 const ubyte[] expected_ciphertext = cast(const ubyte[])x" 63 874d6191b620e3261bef6864990db6ce 64 9806f66b7970fdff8617187bb9fffdff 65 5ae4df3edbd5d35e5b4f09020db03eab 66 1e031dda2fbe03d1792170a0f3009cee 67 "; 68 69 70 // encryption mode 71 cipher.start(true, key, iv); 72 73 ubyte[plain.length] buf; 74 75 size_t len; 76 len = cipher.processBytes(plain, buf); 77 len += cipher.doFinal(buf[len..$]); 78 assert(len == plain.length); 79 80 assert(buf == expected_ciphertext, text(cipher.name,": encryption failed")); 81 82 // decryption mode 83 cipher.start(false, key, iv); 84 85 len = cipher.processBytes(buf, buf); 86 len += cipher.doFinal(buf[len..$]); 87 assert(len == plain.length); 88 89 assert(buf == plain, text(cipher.name,": decryption failed")); 90 91 } 92 93 /// test buffered AES/CTR encryption with incomplete last block 94 /// test vectors: http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf 95 @safe 96 unittest { 97 import dcrypt.blockcipher.aes; 98 import dcrypt.blockcipher.modes.ctr; 99 import dcrypt.blockcipher.modes.cbc; 100 import std.range; 101 import std.conv: text; 102 103 BufferedBlockCipher!(CTR!AES, true) cipher; // true: allow partial block 104 105 const ubyte[] key = cast(const ubyte[])x"2b7e151628aed2a6abf7158809cf4f3c"; 106 const ubyte[] iv = cast(const ubyte[])x"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"; 107 108 const ubyte[] plain = cast(const ubyte[])x" 109 6bc1bee22e409f96e93d7e117393172a 110 ae2d8a571e03ac9c9eb76fac45af8e51 111 30c81c46a35ce411e5fbc1191a0a52ef 112 f69f2445df4f9b17 113 "; 114 115 const ubyte[] expected_ciphertext = cast(const ubyte[])x" 116 874d6191b620e3261bef6864990db6ce 117 9806f66b7970fdff8617187bb9fffdff 118 5ae4df3edbd5d35e5b4f09020db03eab 119 1e031dda2fbe03d1 120 "; 121 122 123 // encryption mode 124 cipher.start(true, key, iv); 125 126 ubyte[plain.length] buf; 127 128 size_t len; 129 len = cipher.processBytes(plain, buf); 130 len += cipher.doFinal(buf[len..$]); 131 assert(len == plain.length); 132 133 assert(buf == expected_ciphertext, text(cipher.name,": encryption failed")); 134 135 // decryption mode 136 cipher.start(false, key, iv); 137 138 len = cipher.processBytes(buf, buf); 139 len += cipher.doFinal(buf[len..$]); 140 assert(len == plain.length); 141 142 assert(buf == plain, text(cipher.name,": decryption failed")); 143 144 } 145 146 /// 147 /// test if T is a block cipher 148 /// 149 @safe 150 template isBufferedBlockCipher(T) 151 { 152 enum bool isBufferedBlockCipher = 153 is(T == struct) && 154 is(typeof( 155 { 156 ubyte[0] block; 157 T bc = void; 158 string name = bc.getAlgorithmName(); 159 uint blockSize = T.blockSize; 160 bc.start(true, cast(const(ubyte)[]) block, cast(const(ubyte)[]) block); // init with key and iv 161 uint len = bc.processByte(cast(ubyte)0,block); 162 uint len = bc.processBytes(block, block); 163 uint len = bc.doFinal(block); 164 bc.reset(); 165 })); 166 } 167 168 /// 169 /// Params: 170 /// T = a block cipher or a block cipher combined with a mode (CTR, CBC, ...) 171 /// permitPartialBlock = tells wether the underlying cipher supports a partial last block (CTR does). default: false 172 /// 173 /// Examples: 174 /// BufferedBlockCipher!AES) ecbEncryption; 175 /// BufferedBlockCipher!(CTR!AES, true) ctrEncryption; 176 /// 177 @safe 178 public struct BufferedBlockCipher(Cipher, bool permitPartialBlock = false) if(isBlockCipher!Cipher) 179 { 180 public { 181 182 enum blockSize = Cipher.blockSize; 183 enum name = Cipher.name; 184 185 void start(bool forEncryption, in ubyte[] key, in ubyte[] iv = null) nothrow @nogc { 186 cipher.start(forEncryption, key, iv); 187 } 188 189 void reset() nothrow { 190 cipher.reset(); 191 buf[] = 0; 192 bufOff = 0; 193 } 194 195 /** 196 * takes one byte and stores it in a buffer. Only if the buffer is full it gets encrypted 197 * and the cipher text gets written to output. 198 * 199 * Params: 200 * b = the byte to encrypt 201 * output = the output buffer 202 * 203 * Returns: the number of bytes written to output. Will be 0 or BLOCKSIZE of underlying cipher. 204 */ 205 @nogc 206 uint processByte(in ubyte b, ubyte[] output) nothrow 207 in { 208 assert(bufOff < buf.length, "bufOff can't be larger than buf.length"); 209 assert(output.length >= buf.length, "output buffer too small"); 210 } 211 body { 212 buf[bufOff] = b; 213 ++bufOff; 214 215 if(bufOff == buf.length) { 216 bufOff = 0; 217 return cipher.processBlock(buf, output); 218 } 219 return 0; 220 } 221 222 /** 223 * encrypt or decrypt byte array 224 * 225 * Params: 226 * i = the bytes to encrypt 227 * output = the output buffer 228 * 229 * Returns: the number of bytes written to output. Will be 0 or BLOCKSIZE of underlying cipher. 230 */ 231 @nogc 232 uint processBytes(in ubyte[] i, ubyte[] output) nothrow 233 in { 234 assert(output.length >= bufOff + i.length, "output buffer too small"); 235 } 236 body { 237 uint outLen = 0; 238 239 const(ubyte)[] input = i; 240 241 if(bufOff > 0) { 242 // fill the buffer and process it if full 243 uint remainingBuf = cast(uint)(buf.length)-bufOff; 244 uint len = min(remainingBuf, input.length); 245 246 buf[bufOff..bufOff + len] = input[0..len]; 247 bufOff += len; 248 249 // drop used input bytes 250 input = input[len..$]; 251 252 if(bufOff == buf.length) { 253 // block is full, process it 254 bufOff = 0; 255 256 len = cipher.processBlock(buf, output); 257 output = output[len..$]; 258 outLen += len; 259 } 260 } 261 262 while (input.length >= buf.length) { 263 assert(bufOff == 0, "blocks not aligned"); 264 265 uint len = cipher.processBlock(input[0..buf.length], output); 266 267 assert(len == buf.length); // this can be assumed. TODO: replace len with blockSize 268 269 input = input[len..$]; 270 output = output[len..$]; 271 outLen += len; 272 } 273 274 // still some remaining bytes? 275 if(input.length > 0){ 276 assert(input.length < buf.length); 277 278 buf[0..input.length] = input[]; // copy remaining data into buffer 279 bufOff += cast(uint)input.length; // cast is safe, because length has to be smaller than blocksize 280 } 281 282 return outLen; 283 } 284 285 /// 286 /// encrypt the remaining bytes in the buffer 287 /// 288 /// Params: output = output buffer 289 /// Returns: number of written bytes 290 uint doFinal(ubyte[] output) 291 in { 292 if(permitPartialBlock) { 293 assert(output.length >= bufOff, "output buffer too small"); 294 }else if (bufOff > 0) { 295 assert(output.length >= buf.length, "output buffer too small"); 296 } 297 } 298 299 body { 300 scope(success) {reset();} // ensure reset() is called after successful encryption/decryption 301 302 uint outLen = 0; 303 if(bufOff != 0) { 304 buf[bufOff..$] = 0; // don't encrypt old bytes 305 cipher.processBlock(buf, buf); 306 307 static if(permitPartialBlock) { 308 output[0..bufOff] = buf[0..bufOff]; // copy partial block 309 } else { 310 output[0..buf.length] = buf[]; // copy full block 311 } 312 313 outLen = bufOff; 314 } 315 return outLen; 316 } 317 318 319 /// Returns: the BlockCipher passed once to the constructor. 320 ref Cipher getUnderlyingCipher() nothrow @nogc { 321 return cipher; 322 } 323 } 324 325 protected { 326 Cipher cipher; /// the underlying block cipher 327 ubyte[blockSize] buf; /// buffer for incomplete blocks 328 uint bufOff = 0; /// where the next byte get added to the buffer (i.e. buf[bufOff++] = newByte) 329 } 330 331 private { 332 invariant { 333 // there's no reason for the offset to be larger than the buffer length 334 assert(bufOff <= buf.length, "bufOff can't be larger than buf.length"); 335 } 336 } 337 } 338 339 @safe 340 public interface IBufferedBlockCipher 341 { 342 343 public { 344 345 void start(bool forEncryption, in ubyte[] userKey, in ubyte[] iv = null) nothrow @nogc; 346 347 @property 348 string name() pure nothrow; 349 350 @property 351 uint blockSize() pure nothrow; 352 353 void reset() nothrow; 354 355 /** 356 * takes one byte and stores it in a buffer. Only if the buffer is full it gets encrypted 357 * and the cipher text gets written to output. 358 * 359 * Params: 360 * b = the byte to encrypt 361 * output = the output buffer 362 * 363 * Returns: the number of bytes written to output. Will be 0 or BLOCKSIZE of underlying cipher. 364 */ 365 uint processByte(in ubyte b, ubyte[] output) nothrow @nogc; 366 367 /** 368 * Params: 369 * i = the bytes to encrypt 370 * output = the output buffer 371 * 372 * Returns: the number of bytes written to output. Will be 0 or BLOCKSIZE of underlying cipher. 373 */ 374 uint processBytes(in ubyte[] i, ubyte[] output) nothrow @nogc; 375 376 /** 377 * encrypt the remaining bytes in the buffer 378 */ 379 uint doFinal(ubyte[] output) nothrow @nogc; 380 381 } 382 383 } 384 385 /// OOP API wrapper class for BufferedBlockCipher 386 @safe 387 public class BufferedBlockCipherWrapper(Cipher) if(isBlockCipher!Cipher): IBufferedBlockCipher 388 { 389 390 private BufferedBlockCipher!Cipher cipher; 391 392 public { 393 394 void start(bool forEncryption, in ubyte[] userKey, in ubyte[] iv = null) nothrow @nogc { 395 cipher.start(forEncryption, userKey, iv); 396 } 397 398 @property 399 string name() pure nothrow { 400 return Cipher.name; 401 } 402 403 @property 404 uint blockSize() pure nothrow { 405 return Cipher.blockSize; 406 } 407 408 void reset() nothrow { 409 cipher.reset(); 410 } 411 412 /** 413 * takes one byte and stores it in a buffer. Only if the buffer is full it gets encrypted 414 * and the cipher text gets written to output. 415 * 416 * Params: 417 * b = the byte to encrypt 418 * output = the output buffer 419 * 420 * Returns: the number of bytes written to output. Will be 0 or BLOCKSIZE of underlying cipher. 421 */ 422 @nogc 423 uint processByte(in ubyte b, ubyte[] output) nothrow 424 { 425 return cipher.processByte(b, output); 426 } 427 428 /** 429 * Params: 430 * i = the bytes to encrypt 431 * output = the output buffer 432 * 433 * Returns: the number of bytes written to output. Will be 0 or BLOCKSIZE of underlying cipher. 434 */ 435 @nogc 436 uint processBytes(in ubyte[] i, ubyte[] output) nothrow 437 { 438 return cipher.processBytes(i, output); 439 } 440 441 /** 442 * encrypt the remaining bytes in the buffer 443 */ 444 uint doFinal(ubyte[] output) 445 { 446 return cipher.doFinal(output); 447 } 448 } 449 450 }