1 package org.bouncycastle.crypto.modes; 2 3 import org.bouncycastle.crypto.BlockCipher; 4 import org.bouncycastle.crypto.CipherParameters; 5 import org.bouncycastle.crypto.DataLengthException; 6 import org.bouncycastle.crypto.params.ParametersWithIV; 7 import org.bouncycastle.util.Arrays; 8 9 /** 10 * implements Cipher-Block-Chaining (CBC) mode on top of a simple cipher. 11 */ 12 public class CBCBlockCipher 13 implements BlockCipher 14 { 15 private byte[] IV; 16 private byte[] cbcV; 17 private byte[] cbcNextV; 18 19 private int blockSize; 20 private BlockCipher cipher = null; 21 private boolean encrypting; 22 23 /** 24 * Basic constructor. 25 * 26 * @param cipher the block cipher to be used as the basis of chaining. 27 */ CBCBlockCipher( BlockCipher cipher)28 public CBCBlockCipher( 29 BlockCipher cipher) 30 { 31 this.cipher = cipher; 32 this.blockSize = cipher.getBlockSize(); 33 34 this.IV = new byte[blockSize]; 35 this.cbcV = new byte[blockSize]; 36 this.cbcNextV = new byte[blockSize]; 37 } 38 39 /** 40 * return the underlying block cipher that we are wrapping. 41 * 42 * @return the underlying block cipher that we are wrapping. 43 */ getUnderlyingCipher()44 public BlockCipher getUnderlyingCipher() 45 { 46 return cipher; 47 } 48 49 /** 50 * Initialise the cipher and, possibly, the initialisation vector (IV). 51 * If an IV isn't passed as part of the parameter, the IV will be all zeros. 52 * 53 * @param encrypting if true the cipher is initialised for 54 * encryption, if false for decryption. 55 * @param params the key and other data required by the cipher. 56 * @exception IllegalArgumentException if the params argument is 57 * inappropriate. 58 */ init( boolean encrypting, CipherParameters params)59 public void init( 60 boolean encrypting, 61 CipherParameters params) 62 throws IllegalArgumentException 63 { 64 boolean oldEncrypting = this.encrypting; 65 66 this.encrypting = encrypting; 67 68 if (params instanceof ParametersWithIV) 69 { 70 ParametersWithIV ivParam = (ParametersWithIV)params; 71 byte[] iv = ivParam.getIV(); 72 73 if (iv.length != blockSize) 74 { 75 throw new IllegalArgumentException("initialisation vector must be the same length as block size"); 76 } 77 78 System.arraycopy(iv, 0, IV, 0, iv.length); 79 80 reset(); 81 82 // if null it's an IV changed only. 83 if (ivParam.getParameters() != null) 84 { 85 cipher.init(encrypting, ivParam.getParameters()); 86 } 87 else if (oldEncrypting != encrypting) 88 { 89 throw new IllegalArgumentException("cannot change encrypting state without providing key."); 90 } 91 } 92 else 93 { 94 reset(); 95 96 // if it's null, key is to be reused. 97 if (params != null) 98 { 99 cipher.init(encrypting, params); 100 } 101 else if (oldEncrypting != encrypting) 102 { 103 throw new IllegalArgumentException("cannot change encrypting state without providing key."); 104 } 105 } 106 } 107 108 /** 109 * return the algorithm name and mode. 110 * 111 * @return the name of the underlying algorithm followed by "/CBC". 112 */ getAlgorithmName()113 public String getAlgorithmName() 114 { 115 return cipher.getAlgorithmName() + "/CBC"; 116 } 117 118 /** 119 * return the block size of the underlying cipher. 120 * 121 * @return the block size of the underlying cipher. 122 */ getBlockSize()123 public int getBlockSize() 124 { 125 return cipher.getBlockSize(); 126 } 127 128 /** 129 * Process one block of input from the array in and write it to 130 * the out array. 131 * 132 * @param in the array containing the input data. 133 * @param inOff offset into the in array the data starts at. 134 * @param out the array the output data will be copied into. 135 * @param outOff the offset into the out array the output will start at. 136 * @exception DataLengthException if there isn't enough data in in, or 137 * space in out. 138 * @exception IllegalStateException if the cipher isn't initialised. 139 * @return the number of bytes processed and produced. 140 */ processBlock( byte[] in, int inOff, byte[] out, int outOff)141 public int processBlock( 142 byte[] in, 143 int inOff, 144 byte[] out, 145 int outOff) 146 throws DataLengthException, IllegalStateException 147 { 148 return (encrypting) ? encryptBlock(in, inOff, out, outOff) : decryptBlock(in, inOff, out, outOff); 149 } 150 151 /** 152 * reset the chaining vector back to the IV and reset the underlying 153 * cipher. 154 */ reset()155 public void reset() 156 { 157 System.arraycopy(IV, 0, cbcV, 0, IV.length); 158 Arrays.fill(cbcNextV, (byte)0); 159 160 cipher.reset(); 161 } 162 163 /** 164 * Do the appropriate chaining step for CBC mode encryption. 165 * 166 * @param in the array containing the data to be encrypted. 167 * @param inOff offset into the in array the data starts at. 168 * @param out the array the encrypted data will be copied into. 169 * @param outOff the offset into the out array the output will start at. 170 * @exception DataLengthException if there isn't enough data in in, or 171 * space in out. 172 * @exception IllegalStateException if the cipher isn't initialised. 173 * @return the number of bytes processed and produced. 174 */ encryptBlock( byte[] in, int inOff, byte[] out, int outOff)175 private int encryptBlock( 176 byte[] in, 177 int inOff, 178 byte[] out, 179 int outOff) 180 throws DataLengthException, IllegalStateException 181 { 182 if ((inOff + blockSize) > in.length) 183 { 184 throw new DataLengthException("input buffer too short"); 185 } 186 187 /* 188 * XOR the cbcV and the input, 189 * then encrypt the cbcV 190 */ 191 for (int i = 0; i < blockSize; i++) 192 { 193 cbcV[i] ^= in[inOff + i]; 194 } 195 196 int length = cipher.processBlock(cbcV, 0, out, outOff); 197 198 /* 199 * copy ciphertext to cbcV 200 */ 201 System.arraycopy(out, outOff, cbcV, 0, cbcV.length); 202 203 return length; 204 } 205 206 /** 207 * Do the appropriate chaining step for CBC mode decryption. 208 * 209 * @param in the array containing the data to be decrypted. 210 * @param inOff offset into the in array the data starts at. 211 * @param out the array the decrypted data will be copied into. 212 * @param outOff the offset into the out array the output will start at. 213 * @exception DataLengthException if there isn't enough data in in, or 214 * space in out. 215 * @exception IllegalStateException if the cipher isn't initialised. 216 * @return the number of bytes processed and produced. 217 */ decryptBlock( byte[] in, int inOff, byte[] out, int outOff)218 private int decryptBlock( 219 byte[] in, 220 int inOff, 221 byte[] out, 222 int outOff) 223 throws DataLengthException, IllegalStateException 224 { 225 if ((inOff + blockSize) > in.length) 226 { 227 throw new DataLengthException("input buffer too short"); 228 } 229 230 System.arraycopy(in, inOff, cbcNextV, 0, blockSize); 231 232 int length = cipher.processBlock(in, inOff, out, outOff); 233 234 /* 235 * XOR the cbcV and the output 236 */ 237 for (int i = 0; i < blockSize; i++) 238 { 239 out[outOff + i] ^= cbcV[i]; 240 } 241 242 /* 243 * swap the back up buffer into next position 244 */ 245 byte[] tmp; 246 247 tmp = cbcV; 248 cbcV = cbcNextV; 249 cbcNextV = tmp; 250 251 return length; 252 } 253 } 254