1package org.bouncycastle.crypto.engines; 2 3import java.security.SecureRandom; 4 5import org.bouncycastle.crypto.CipherParameters; 6import org.bouncycastle.crypto.Digest; 7import org.bouncycastle.crypto.InvalidCipherTextException; 8import org.bouncycastle.crypto.Wrapper; 9// BEGIN android-changed 10import org.bouncycastle.crypto.digests.AndroidDigestFactory; 11// END android-changed 12import org.bouncycastle.crypto.modes.CBCBlockCipher; 13import org.bouncycastle.crypto.params.KeyParameter; 14import org.bouncycastle.crypto.params.ParametersWithIV; 15import org.bouncycastle.crypto.params.ParametersWithRandom; 16import org.bouncycastle.util.Arrays; 17 18/** 19 * Wrap keys according to 20 * <A HREF="http://www.ietf.org/internet-drafts/draft-ietf-smime-key-wrap-01.txt"> 21 * draft-ietf-smime-key-wrap-01.txt</A>. 22 * <p> 23 * Note: 24 * <ul> 25 * <li>this is based on a draft, and as such is subject to change - don't use this class for anything requiring long term storage. 26 * <li>if you are using this to wrap triple-des keys you need to set the 27 * parity bits on the key and, if it's a two-key triple-des key, pad it 28 * yourself. 29 * </ul> 30 */ 31public class DESedeWrapEngine 32 implements Wrapper 33{ 34 /** Field engine */ 35 private CBCBlockCipher engine; 36 37 /** Field param */ 38 private KeyParameter param; 39 40 /** Field paramPlusIV */ 41 private ParametersWithIV paramPlusIV; 42 43 /** Field iv */ 44 private byte[] iv; 45 46 /** Field forWrapping */ 47 private boolean forWrapping; 48 49 /** Field IV2 */ 50 private static final byte[] IV2 = { (byte) 0x4a, (byte) 0xdd, (byte) 0xa2, 51 (byte) 0x2c, (byte) 0x79, (byte) 0xe8, 52 (byte) 0x21, (byte) 0x05 }; 53 54 // 55 // checksum digest 56 // 57 // BEGIN android-changed 58 Digest sha1 = AndroidDigestFactory.getSHA1(); 59 // END android-changed 60 byte[] digest = new byte[20]; 61 62 /** 63 * Method init 64 * 65 * @param forWrapping 66 * @param param 67 */ 68 public void init(boolean forWrapping, CipherParameters param) 69 { 70 71 this.forWrapping = forWrapping; 72 this.engine = new CBCBlockCipher(new DESedeEngine()); 73 74 SecureRandom sr; 75 if (param instanceof ParametersWithRandom) 76 { 77 ParametersWithRandom pr = (ParametersWithRandom) param; 78 param = pr.getParameters(); 79 sr = pr.getRandom(); 80 } 81 else 82 { 83 sr = new SecureRandom(); 84 } 85 86 if (param instanceof KeyParameter) 87 { 88 this.param = (KeyParameter)param; 89 90 if (this.forWrapping) 91 { 92 93 // Hm, we have no IV but we want to wrap ?!? 94 // well, then we have to create our own IV. 95 this.iv = new byte[8]; 96 sr.nextBytes(iv); 97 98 this.paramPlusIV = new ParametersWithIV(this.param, this.iv); 99 } 100 } 101 else if (param instanceof ParametersWithIV) 102 { 103 this.paramPlusIV = (ParametersWithIV)param; 104 this.iv = this.paramPlusIV.getIV(); 105 this.param = (KeyParameter)this.paramPlusIV.getParameters(); 106 107 if (this.forWrapping) 108 { 109 if ((this.iv == null) || (this.iv.length != 8)) 110 { 111 throw new IllegalArgumentException("IV is not 8 octets"); 112 } 113 } 114 else 115 { 116 throw new IllegalArgumentException( 117 "You should not supply an IV for unwrapping"); 118 } 119 } 120 } 121 122 /** 123 * Method getAlgorithmName 124 * 125 * @return the algorithm name "DESede". 126 */ 127 public String getAlgorithmName() 128 { 129 return "DESede"; 130 } 131 132 /** 133 * Method wrap 134 * 135 * @param in 136 * @param inOff 137 * @param inLen 138 * @return the wrapped bytes. 139 */ 140 public byte[] wrap(byte[] in, int inOff, int inLen) 141 { 142 if (!forWrapping) 143 { 144 throw new IllegalStateException("Not initialized for wrapping"); 145 } 146 147 byte keyToBeWrapped[] = new byte[inLen]; 148 149 System.arraycopy(in, inOff, keyToBeWrapped, 0, inLen); 150 151 // Compute the CMS Key Checksum, (section 5.6.1), call this CKS. 152 byte[] CKS = calculateCMSKeyChecksum(keyToBeWrapped); 153 154 // Let WKCKS = WK || CKS where || is concatenation. 155 byte[] WKCKS = new byte[keyToBeWrapped.length + CKS.length]; 156 157 System.arraycopy(keyToBeWrapped, 0, WKCKS, 0, keyToBeWrapped.length); 158 System.arraycopy(CKS, 0, WKCKS, keyToBeWrapped.length, CKS.length); 159 160 // Encrypt WKCKS in CBC mode using KEK as the key and IV as the 161 // initialization vector. Call the results TEMP1. 162 163 int blockSize = engine.getBlockSize(); 164 165 if (WKCKS.length % blockSize != 0) 166 { 167 throw new IllegalStateException("Not multiple of block length"); 168 } 169 170 engine.init(true, paramPlusIV); 171 172 byte TEMP1[] = new byte[WKCKS.length]; 173 174 for (int currentBytePos = 0; currentBytePos != WKCKS.length; currentBytePos += blockSize) 175 { 176 engine.processBlock(WKCKS, currentBytePos, TEMP1, currentBytePos); 177 } 178 179 // Let TEMP2 = IV || TEMP1. 180 byte[] TEMP2 = new byte[this.iv.length + TEMP1.length]; 181 182 System.arraycopy(this.iv, 0, TEMP2, 0, this.iv.length); 183 System.arraycopy(TEMP1, 0, TEMP2, this.iv.length, TEMP1.length); 184 185 // Reverse the order of the octets in TEMP2 and call the result TEMP3. 186 byte[] TEMP3 = reverse(TEMP2); 187 188 // Encrypt TEMP3 in CBC mode using the KEK and an initialization vector 189 // of 0x 4a dd a2 2c 79 e8 21 05. The resulting cipher text is the desired 190 // result. It is 40 octets long if a 168 bit key is being wrapped. 191 ParametersWithIV param2 = new ParametersWithIV(this.param, IV2); 192 193 this.engine.init(true, param2); 194 195 for (int currentBytePos = 0; currentBytePos != TEMP3.length; currentBytePos += blockSize) 196 { 197 engine.processBlock(TEMP3, currentBytePos, TEMP3, currentBytePos); 198 } 199 200 return TEMP3; 201 } 202 203 /** 204 * Method unwrap 205 * 206 * @param in 207 * @param inOff 208 * @param inLen 209 * @return the unwrapped bytes. 210 * @throws InvalidCipherTextException 211 */ 212 public byte[] unwrap(byte[] in, int inOff, int inLen) 213 throws InvalidCipherTextException 214 { 215 if (forWrapping) 216 { 217 throw new IllegalStateException("Not set for unwrapping"); 218 } 219 220 if (in == null) 221 { 222 throw new InvalidCipherTextException("Null pointer as ciphertext"); 223 } 224 225 final int blockSize = engine.getBlockSize(); 226 if (inLen % blockSize != 0) 227 { 228 throw new InvalidCipherTextException("Ciphertext not multiple of " + blockSize); 229 } 230 231 /* 232 // Check if the length of the cipher text is reasonable given the key 233 // type. It must be 40 bytes for a 168 bit key and either 32, 40, or 234 // 48 bytes for a 128, 192, or 256 bit key. If the length is not supported 235 // or inconsistent with the algorithm for which the key is intended, 236 // return error. 237 // 238 // we do not accept 168 bit keys. it has to be 192 bit. 239 int lengthA = (estimatedKeyLengthInBit / 8) + 16; 240 int lengthB = estimatedKeyLengthInBit % 8; 241 242 if ((lengthA != keyToBeUnwrapped.length) || (lengthB != 0)) { 243 throw new XMLSecurityException("empty"); 244 } 245 */ 246 247 // Decrypt the cipher text with TRIPLedeS in CBC mode using the KEK 248 // and an initialization vector (IV) of 0x4adda22c79e82105. Call the output TEMP3. 249 ParametersWithIV param2 = new ParametersWithIV(this.param, IV2); 250 251 this.engine.init(false, param2); 252 253 byte TEMP3[] = new byte[inLen]; 254 255 for (int currentBytePos = 0; currentBytePos != inLen; currentBytePos += blockSize) 256 { 257 engine.processBlock(in, inOff + currentBytePos, TEMP3, currentBytePos); 258 } 259 260 // Reverse the order of the octets in TEMP3 and call the result TEMP2. 261 byte[] TEMP2 = reverse(TEMP3); 262 263 // Decompose TEMP2 into IV, the first 8 octets, and TEMP1, the remaining octets. 264 this.iv = new byte[8]; 265 266 byte[] TEMP1 = new byte[TEMP2.length - 8]; 267 268 System.arraycopy(TEMP2, 0, this.iv, 0, 8); 269 System.arraycopy(TEMP2, 8, TEMP1, 0, TEMP2.length - 8); 270 271 // Decrypt TEMP1 using TRIPLedeS in CBC mode using the KEK and the IV 272 // found in the previous step. Call the result WKCKS. 273 this.paramPlusIV = new ParametersWithIV(this.param, this.iv); 274 275 this.engine.init(false, this.paramPlusIV); 276 277 byte[] WKCKS = new byte[TEMP1.length]; 278 279 for (int currentBytePos = 0; currentBytePos != WKCKS.length; currentBytePos += blockSize) 280 { 281 engine.processBlock(TEMP1, currentBytePos, WKCKS, currentBytePos); 282 } 283 284 // Decompose WKCKS. CKS is the last 8 octets and WK, the wrapped key, are 285 // those octets before the CKS. 286 byte[] result = new byte[WKCKS.length - 8]; 287 byte[] CKStoBeVerified = new byte[8]; 288 289 System.arraycopy(WKCKS, 0, result, 0, WKCKS.length - 8); 290 System.arraycopy(WKCKS, WKCKS.length - 8, CKStoBeVerified, 0, 8); 291 292 // Calculate a CMS Key Checksum, (section 5.6.1), over the WK and compare 293 // with the CKS extracted in the above step. If they are not equal, return error. 294 if (!checkCMSKeyChecksum(result, CKStoBeVerified)) 295 { 296 throw new InvalidCipherTextException( 297 "Checksum inside ciphertext is corrupted"); 298 } 299 300 // WK is the wrapped key, now extracted for use in data decryption. 301 return result; 302 } 303 304 /** 305 * Some key wrap algorithms make use of the Key Checksum defined 306 * in CMS [CMS-Algorithms]. This is used to provide an integrity 307 * check value for the key being wrapped. The algorithm is 308 * 309 * - Compute the 20 octet SHA-1 hash on the key being wrapped. 310 * - Use the first 8 octets of this hash as the checksum value. 311 * 312 * @param key 313 * @return the CMS checksum. 314 * @throws RuntimeException 315 * @see http://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum 316 */ 317 private byte[] calculateCMSKeyChecksum( 318 byte[] key) 319 { 320 byte[] result = new byte[8]; 321 322 sha1.update(key, 0, key.length); 323 sha1.doFinal(digest, 0); 324 325 System.arraycopy(digest, 0, result, 0, 8); 326 327 return result; 328 } 329 330 /** 331 * @param key 332 * @param checksum 333 * @return true if okay, false otherwise. 334 * @see http://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum 335 */ 336 private boolean checkCMSKeyChecksum( 337 byte[] key, 338 byte[] checksum) 339 { 340 return Arrays.constantTimeAreEqual(calculateCMSKeyChecksum(key), checksum); 341 } 342 343 private static byte[] reverse(byte[] bs) 344 { 345 byte[] result = new byte[bs.length]; 346 for (int i = 0; i < bs.length; i++) 347 { 348 result[i] = bs[bs.length - (i + 1)]; 349 } 350 return result; 351 } 352} 353