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