001: package org.bouncycastle.crypto.engines;
002:
003: import java.security.SecureRandom;
004:
005: import org.bouncycastle.crypto.CipherParameters;
006: import org.bouncycastle.crypto.Digest;
007: import org.bouncycastle.crypto.InvalidCipherTextException;
008: import org.bouncycastle.crypto.Wrapper;
009: import org.bouncycastle.crypto.digests.SHA1Digest;
010: import org.bouncycastle.crypto.modes.CBCBlockCipher;
011: import org.bouncycastle.crypto.params.KeyParameter;
012: import org.bouncycastle.crypto.params.ParametersWithIV;
013:
014: /**
015: * Wrap keys according to
016: * <A HREF="http://www.ietf.org/internet-drafts/draft-ietf-smime-key-wrap-01.txt">
017: * draft-ietf-smime-key-wrap-01.txt</A>.
018: * <p>
019: * Note:
020: * <ul>
021: * <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.
022: * <li>if you are using this to wrap triple-des keys you need to set the
023: * parity bits on the key and, if it's a two-key triple-des key, pad it
024: * yourself.
025: * </ul>
026: */
027: public class DESedeWrapEngine implements Wrapper {
028: /** Field engine */
029: private CBCBlockCipher engine;
030:
031: /** Field param */
032: private KeyParameter param;
033:
034: /** Field paramPlusIV */
035: private ParametersWithIV paramPlusIV;
036:
037: /** Field iv */
038: private byte[] iv;
039:
040: /** Field forWrapping */
041: private boolean forWrapping;
042:
043: /** Field IV2 */
044: private static final byte[] IV2 = { (byte) 0x4a, (byte) 0xdd,
045: (byte) 0xa2, (byte) 0x2c, (byte) 0x79, (byte) 0xe8,
046: (byte) 0x21, (byte) 0x05 };
047:
048: //
049: // checksum digest
050: //
051: Digest sha1 = new SHA1Digest();
052: byte[] digest = new byte[20];
053:
054: /**
055: * Method init
056: *
057: * @param forWrapping
058: * @param param
059: */
060: public void init(boolean forWrapping, CipherParameters param) {
061:
062: this .forWrapping = forWrapping;
063: this .engine = new CBCBlockCipher(new DESedeEngine());
064:
065: if (param instanceof KeyParameter) {
066: this .param = (KeyParameter) param;
067:
068: if (this .forWrapping) {
069:
070: // Hm, we have no IV but we want to wrap ?!?
071: // well, then we have to create our own IV.
072: this .iv = new byte[8];
073:
074: SecureRandom sr = new SecureRandom();
075:
076: sr.nextBytes(iv);
077:
078: this .paramPlusIV = new ParametersWithIV(this .param,
079: this .iv);
080: }
081: } else if (param instanceof ParametersWithIV) {
082: this .paramPlusIV = (ParametersWithIV) param;
083: this .iv = this .paramPlusIV.getIV();
084: this .param = (KeyParameter) this .paramPlusIV
085: .getParameters();
086:
087: if (this .forWrapping) {
088: if ((this .iv == null) || (this .iv.length != 8)) {
089: throw new IllegalArgumentException(
090: "IV is not 8 octets");
091: }
092: } else {
093: throw new IllegalArgumentException(
094: "You should not supply an IV for unwrapping");
095: }
096: }
097: }
098:
099: /**
100: * Method getAlgorithmName
101: *
102: * @return the algorithm name "DESede".
103: */
104: public String getAlgorithmName() {
105: return "DESede";
106: }
107:
108: /**
109: * Method wrap
110: *
111: * @param in
112: * @param inOff
113: * @param inLen
114: * @return the wrapped bytes.
115: */
116: public byte[] wrap(byte[] in, int inOff, int inLen) {
117: if (!forWrapping) {
118: throw new IllegalStateException(
119: "Not initialized for wrapping");
120: }
121:
122: byte keyToBeWrapped[] = new byte[inLen];
123:
124: System.arraycopy(in, inOff, keyToBeWrapped, 0, inLen);
125:
126: // Compute the CMS Key Checksum, (section 5.6.1), call this CKS.
127: byte[] CKS = calculateCMSKeyChecksum(keyToBeWrapped);
128:
129: // Let WKCKS = WK || CKS where || is concatenation.
130: byte[] WKCKS = new byte[keyToBeWrapped.length + CKS.length];
131:
132: System.arraycopy(keyToBeWrapped, 0, WKCKS, 0,
133: keyToBeWrapped.length);
134: System.arraycopy(CKS, 0, WKCKS, keyToBeWrapped.length,
135: CKS.length);
136:
137: // Encrypt WKCKS in CBC mode using KEK as the key and IV as the
138: // initialization vector. Call the results TEMP1.
139: byte TEMP1[] = new byte[WKCKS.length];
140:
141: System.arraycopy(WKCKS, 0, TEMP1, 0, WKCKS.length);
142:
143: int noOfBlocks = WKCKS.length / engine.getBlockSize();
144: int extraBytes = WKCKS.length % engine.getBlockSize();
145:
146: if (extraBytes != 0) {
147: throw new IllegalStateException(
148: "Not multiple of block length");
149: }
150:
151: engine.init(true, paramPlusIV);
152:
153: for (int i = 0; i < noOfBlocks; i++) {
154: int currentBytePos = i * engine.getBlockSize();
155:
156: engine.processBlock(TEMP1, currentBytePos, TEMP1,
157: currentBytePos);
158: }
159:
160: // Left TEMP2 = IV || TEMP1.
161: byte[] TEMP2 = new byte[this .iv.length + TEMP1.length];
162:
163: System.arraycopy(this .iv, 0, TEMP2, 0, this .iv.length);
164: System.arraycopy(TEMP1, 0, TEMP2, this .iv.length, TEMP1.length);
165:
166: // Reverse the order of the octets in TEMP2 and call the result TEMP3.
167: byte[] TEMP3 = new byte[TEMP2.length];
168:
169: for (int i = 0; i < TEMP2.length; i++) {
170: TEMP3[i] = TEMP2[TEMP2.length - (i + 1)];
171: }
172:
173: // Encrypt TEMP3 in CBC mode using the KEK and an initialization vector
174: // of 0x 4a dd a2 2c 79 e8 21 05. The resulting cipher text is the desired
175: // result. It is 40 octets long if a 168 bit key is being wrapped.
176: ParametersWithIV param2 = new ParametersWithIV(this .param, IV2);
177:
178: this .engine.init(true, param2);
179:
180: for (int i = 0; i < noOfBlocks + 1; i++) {
181: int currentBytePos = i * engine.getBlockSize();
182:
183: engine.processBlock(TEMP3, currentBytePos, TEMP3,
184: currentBytePos);
185: }
186:
187: return TEMP3;
188: }
189:
190: /**
191: * Method unwrap
192: *
193: * @param in
194: * @param inOff
195: * @param inLen
196: * @return the unwrapped bytes.
197: * @throws InvalidCipherTextException
198: */
199: public byte[] unwrap(byte[] in, int inOff, int inLen)
200: throws InvalidCipherTextException {
201: if (forWrapping) {
202: throw new IllegalStateException("Not set for unwrapping");
203: }
204:
205: if (in == null) {
206: throw new InvalidCipherTextException(
207: "Null pointer as ciphertext");
208: }
209:
210: if (inLen % engine.getBlockSize() != 0) {
211: throw new InvalidCipherTextException(
212: "Ciphertext not multiple of "
213: + engine.getBlockSize());
214: }
215:
216: /*
217: // Check if the length of the cipher text is reasonable given the key
218: // type. It must be 40 bytes for a 168 bit key and either 32, 40, or
219: // 48 bytes for a 128, 192, or 256 bit key. If the length is not supported
220: // or inconsistent with the algorithm for which the key is intended,
221: // return error.
222: //
223: // we do not accept 168 bit keys. it has to be 192 bit.
224: int lengthA = (estimatedKeyLengthInBit / 8) + 16;
225: int lengthB = estimatedKeyLengthInBit % 8;
226:
227: if ((lengthA != keyToBeUnwrapped.length) || (lengthB != 0)) {
228: throw new XMLSecurityException("empty");
229: }
230: */
231:
232: // Decrypt the cipher text with TRIPLedeS in CBC mode using the KEK
233: // and an initialization vector (IV) of 0x4adda22c79e82105. Call the output TEMP3.
234: ParametersWithIV param2 = new ParametersWithIV(this .param, IV2);
235:
236: this .engine.init(false, param2);
237:
238: byte TEMP3[] = new byte[inLen];
239:
240: System.arraycopy(in, inOff, TEMP3, 0, inLen);
241:
242: for (int i = 0; i < (TEMP3.length / engine.getBlockSize()); i++) {
243: int currentBytePos = i * engine.getBlockSize();
244:
245: engine.processBlock(TEMP3, currentBytePos, TEMP3,
246: currentBytePos);
247: }
248:
249: // Reverse the order of the octets in TEMP3 and call the result TEMP2.
250: byte[] TEMP2 = new byte[TEMP3.length];
251:
252: for (int i = 0; i < TEMP3.length; i++) {
253: TEMP2[i] = TEMP3[TEMP3.length - (i + 1)];
254: }
255:
256: // Decompose TEMP2 into IV, the first 8 octets, and TEMP1, the remaining octets.
257: this .iv = new byte[8];
258:
259: byte[] TEMP1 = new byte[TEMP2.length - 8];
260:
261: System.arraycopy(TEMP2, 0, this .iv, 0, 8);
262: System.arraycopy(TEMP2, 8, TEMP1, 0, TEMP2.length - 8);
263:
264: // Decrypt TEMP1 using TRIPLedeS in CBC mode using the KEK and the IV
265: // found in the previous step. Call the result WKCKS.
266: this .paramPlusIV = new ParametersWithIV(this .param, this .iv);
267:
268: this .engine.init(false, this .paramPlusIV);
269:
270: byte[] WKCKS = new byte[TEMP1.length];
271:
272: System.arraycopy(TEMP1, 0, WKCKS, 0, TEMP1.length);
273:
274: for (int i = 0; i < (WKCKS.length / engine.getBlockSize()); i++) {
275: int currentBytePos = i * engine.getBlockSize();
276:
277: engine.processBlock(WKCKS, currentBytePos, WKCKS,
278: currentBytePos);
279: }
280:
281: // Decompose WKCKS. CKS is the last 8 octets and WK, the wrapped key, are
282: // those octets before the CKS.
283: byte[] result = new byte[WKCKS.length - 8];
284: byte[] CKStoBeVerified = new byte[8];
285:
286: System.arraycopy(WKCKS, 0, result, 0, WKCKS.length - 8);
287: System
288: .arraycopy(WKCKS, WKCKS.length - 8, CKStoBeVerified, 0,
289: 8);
290:
291: // Calculate a CMS Key Checksum, (section 5.6.1), over the WK and compare
292: // with the CKS extracted in the above step. If they are not equal, return error.
293: if (!checkCMSKeyChecksum(result, CKStoBeVerified)) {
294: throw new InvalidCipherTextException(
295: "Checksum inside ciphertext is corrupted");
296: }
297:
298: // WK is the wrapped key, now extracted for use in data decryption.
299: return result;
300: }
301:
302: /**
303: * Some key wrap algorithms make use of the Key Checksum defined
304: * in CMS [CMS-Algorithms]. This is used to provide an integrity
305: * check value for the key being wrapped. The algorithm is
306: *
307: * - Compute the 20 octet SHA-1 hash on the key being wrapped.
308: * - Use the first 8 octets of this hash as the checksum value.
309: *
310: * @param key
311: * @return the CMS checksum.
312: * @throws RuntimeException
313: * @see http://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum
314: */
315: private byte[] calculateCMSKeyChecksum(byte[] key) {
316: byte[] result = new byte[8];
317:
318: sha1.update(key, 0, key.length);
319: sha1.doFinal(digest, 0);
320:
321: System.arraycopy(digest, 0, result, 0, 8);
322:
323: return result;
324: }
325:
326: /**
327: * @param key
328: * @param checksum
329: * @return true if okay, false otherwise.
330: * @see http://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum
331: */
332: private boolean checkCMSKeyChecksum(byte[] key, byte[] checksum) {
333: byte[] calculatedChecksum = calculateCMSKeyChecksum(key);
334:
335: if (checksum.length != calculatedChecksum.length) {
336: return false;
337: }
338:
339: for (int i = 0; i != checksum.length; i++) {
340: if (checksum[i] != calculatedChecksum[i]) {
341: return false;
342: }
343: }
344:
345: return true;
346: }
347: }
|