1 module dcrypt.blockcipher.padding.padding; 2 3 public import dcrypt.exceptions: InvalidCipherTextException; 4 public import dcrypt.blockcipher.blockcipher; 5 6 /// 7 /// test if T is a block cipher padding 8 /// 9 @safe 10 template isBlockCipherPadding(T) 11 { 12 enum bool isBlockCipherPadding = 13 is(T == struct) && 14 is(typeof( 15 { 16 ubyte[] block; 17 T padding = void; //Can define 18 string name = T.name; 19 padding.addPadding(block, cast(uint) 0); 20 uint padcount = padding.padCount(block); 21 })); 22 } 23 24 // TODO test vectors, doFinal() require minimal output buffer length 25 /// Test PaddedBufferedBlockCipher with AES and PKCS7 padding. 26 @safe 27 unittest { 28 import dcrypt.blockcipher.padding.pkcs7; 29 import dcrypt.blockcipher.aes; 30 31 PaddedBufferedBlockCipher!(AES, PKCS7Pad) c; 32 33 immutable ubyte[] key = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; 34 ubyte[] plain = new ubyte[65]; 35 ubyte[] output = new ubyte[96]; 36 37 foreach(plainTextLength; [0,1,15,16,17,24,31,32,33,63,64,65]) { // some odd number, smaller than one block, larger than two blocks 38 39 plain.length = plainTextLength; 40 41 foreach(i,ref b; plain) { 42 b = cast(ubyte)i; 43 } 44 45 output.length = ((plainTextLength/c.blockSize)+2)*c.blockSize; // next even block size 46 47 c.start(true, key); 48 49 size_t len = c.processBytes(plain, output); 50 len += c.doFinal(output[len..$]); 51 output = output[0..len]; // crop slice to its size 52 53 ubyte[] cipher = output.dup; 54 output[] = 0; 55 c.start(false, key); 56 len = c.processBytes(cipher, output); 57 len += c.doFinal(output[len..$]); 58 59 assert(len == plain.length, "length does not match"); 60 assert(output[0..len] == plain, "decrypted ciphertext is not equal to original plaintext"); 61 } 62 } 63 64 /// 65 /// PaddedBufferedBlockCipher extends a block cipher or mode (CTR, CBC, ...) by 66 /// the ability to process data that is not a multiple of the block size. 67 /// The last block will be padded according to the chosen padding scheme. If the 68 /// last block is full, then a additional padding block will be appended. 69 /// 70 @safe 71 public struct PaddedBufferedBlockCipher(C, Padding, bool permitPartialBlock = false) 72 if(isBlockCipher!C && isBlockCipherPadding!Padding) 73 { 74 75 public enum blockSize = C.blockSize; 76 public enum name = C.name ~ "/" ~ Padding.name; 77 78 public { 79 80 void start(bool forEncryption, in ubyte[] key, in ubyte[] iv = null) nothrow @nogc { 81 this.forEncryption = forEncryption; 82 cipher.start(forEncryption, key, iv); 83 reset(); 84 } 85 86 /** 87 * takes one byte and stores it in a buffer. If the buffer is already full it gets 88 * encrypted and written to output 89 * 90 * Params: 91 * b = the byte to encrypt 92 * output = the output buffer 93 * 94 * Returns: the number of bytes written to output. Will be 0 or BLOCKSIZE of underlying cipher. 95 */ 96 @nogc 97 uint processByte(in ubyte b, ubyte[] output) nothrow 98 in { 99 assert(bufOff < blockSize, "bufOff can't be larger than buf.length"); 100 assert(output.length >= blockSize, "output buffer too small"); 101 } 102 body { 103 uint outLen = 0; 104 105 if(bufOff == blockSize) { 106 bufOff = 0; 107 outLen = cipher.processBlock(buf, output); 108 } 109 buf[bufOff] = b; 110 ++bufOff; 111 112 return outLen; 113 } 114 115 /** 116 * input length not limited to multiples of block size. 117 * ensure that length of output buffer is sufficiently large (see below). 118 * 119 * Params: 120 * i = the input data 121 * output = the output buffer. this buffer must be able to hold the 122 * same amount of data as given by the input + padding bytes. 123 * output.length >= i.length rounded up to the next multiple of block size 124 * 125 */ 126 @nogc 127 uint processBytes(in ubyte[] i, ubyte[] output) nothrow 128 in { 129 assert(bufOff < blockSize, "bufOff can't be larger than buf.length"); 130 assert(output.length >= bufOff + i.length, "output buffer too small"); 131 } 132 body { 133 uint outLen = 0; 134 135 const(ubyte)[] input = i; 136 137 uint remainingBuf = blockSize-bufOff; 138 139 if(input.length > remainingBuf) { 140 // fill the buffer and process it if full 141 142 buf[bufOff..bufOff + remainingBuf] = input[0..remainingBuf]; 143 bufOff += remainingBuf; 144 // drop used input bytes 145 input = input[remainingBuf..$]; 146 147 // process the now full buffer 148 assert(bufOff == blockSize); 149 uint len = cipher.processBlock(buf, output); 150 output = output[len..$]; 151 outLen += len; 152 bufOff = 0; 153 154 // got some more complete blocks? 155 while (input.length > blockSize) { 156 len = cipher.processBlock(input[0..blockSize], output); 157 158 assert(len == blockSize); // this can be assumed. 159 160 input = input[blockSize..$]; 161 output = output[blockSize..$]; 162 outLen += blockSize; 163 } 164 } 165 assert(input.length <= blockSize-bufOff); // it should not be possible to have now more bytes than the buffer can take 166 167 // still some remaining bytes? 168 if(input.length > 0){ 169 buf[bufOff..bufOff+input.length] = input[]; 170 bufOff += cast(uint)input.length; // cast is safe, because length has to be smaller than blocksize 171 } 172 173 return outLen; 174 } 175 176 /** 177 * encrypt the remaining bytes in the buffer, add the padding 178 * 179 * Params: output = output buffer. length should be 2*blockSize 180 */ 181 uint doFinal(ubyte[] output) 182 in { 183 184 assert(output.length >= buf.length, "output buffer too small"); 185 186 if(forEncryption){ 187 assert(output.length >= 2*blockSize, "output buffer too small. 2*blockSize required, because possibly appending one full padding block"); 188 }else{ 189 assert(bufOff == blockSize, "last block incomplete for decryption"); 190 } 191 } 192 body { 193 scope(success) {reset();} 194 195 static if(!is(Padding == void)) { 196 return doFinalWithPad(output); 197 } else { 198 return doFinalNoPad(output); 199 } 200 } 201 202 203 private uint doFinalWithPad(ubyte[] output) { 204 size_t len; /// the number of bytes written to output[] 205 if(forEncryption) { 206 207 // for padded schemes only 208 if(bufOff == blockSize) { 209 // this block is full and therefore can't be padded 210 // so an aditional (empty) block has to be appended 211 212 assert(output.length >= 2*blockSize, "output buffer too small"); 213 214 len = cipher.processBlock(buf, output); 215 output = output[blockSize..$]; 216 bufOff = 0; // the appended block does not contain data, only padding 217 buf[] = 0; // clear buffer 218 } 219 220 // add padding to the last block 221 padding.addPadding(buf, bufOff); 222 len += cipher.processBlock(buf, output); 223 } else{ 224 // remove padding 225 cipher.processBlock(buf, buf); 226 227 uint padBytes = padding.padCount(buf); 228 len = buf.length - padBytes; 229 output[0..len] = buf[0..len]; 230 231 } 232 233 return cast(uint)len; 234 } 235 236 private uint doFinalNoPad(ubyte[] output) nothrow @nogc { 237 // no padding scheme 238 uint outLen = 0; 239 if(bufOff != 0) { 240 buf[bufOff..$] = 0; // don't encrypt old bytes 241 cipher.processBlock(buf, buf); 242 243 static if(permitPartialBlock) { 244 output[0..bufOff] = buf[0..bufOff]; // copy partial block 245 } else { 246 output[0..buf.length] = buf[]; // copy full block 247 } 248 249 outLen = bufOff; 250 } 251 return outLen; 252 } 253 254 public void reset() nothrow @nogc { 255 cipher.reset(); 256 bufOff = 0; 257 buf[] = 0; 258 } 259 } 260 261 public ref Padding getUnderlyingPadding() nothrow { 262 return padding; 263 } 264 265 266 private { 267 C cipher; 268 uint bufOff = 0; 269 ubyte[blockSize] buf; 270 bool forEncryption; 271 272 Padding padding; 273 } 274 }