001: /*
002: * Copyright 2004-2007 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: /*
027: * "@(#)AesDkCrypto.java 1.17 07/05/05 SMI"
028: */
029:
030: package sun.security.krb5.internal.crypto.dk;
031:
032: import javax.crypto.Cipher;
033: import javax.crypto.Mac;
034: import javax.crypto.SecretKeyFactory;
035: import javax.crypto.SecretKey;
036: import javax.crypto.spec.SecretKeySpec;
037: import javax.crypto.spec.DESedeKeySpec;
038: import javax.crypto.spec.IvParameterSpec;
039: import javax.crypto.spec.PBEKeySpec;
040: import java.security.spec.KeySpec;
041: import java.security.GeneralSecurityException;
042: import sun.security.krb5.KrbCryptoException;
043: import sun.security.krb5.Confounder;
044: import sun.security.krb5.internal.crypto.KeyUsage;
045: import java.util.Arrays;
046:
047: /**
048: * This class provides the implementation of AES Encryption for Kerberos
049: * as defined RFC 3962.
050: * http://www.ietf.org/rfc/rfc3962.txt
051: *
052: * Algorithm profile described in [KCRYPTO]:
053: * +--------------------------------------------------------------------+
054: * | protocol key format 128- or 256-bit string |
055: * | |
056: * | string-to-key function PBKDF2+DK with variable |
057: * | iteration count (see |
058: * | above) |
059: * | |
060: * | default string-to-key parameters 00 00 10 00 |
061: * | |
062: * | key-generation seed length key size |
063: * | |
064: * | random-to-key function identity function |
065: * | |
066: * | hash function, H SHA-1 |
067: * | |
068: * | HMAC output size, h 12 octets (96 bits) |
069: * | |
070: * | message block size, m 1 octet |
071: * | |
072: * | encryption/decryption functions, AES in CBC-CTS mode |
073: * | E and D (cipher block size 16 |
074: * | octets), with next to |
075: * | last block as CBC-style |
076: * | ivec |
077: * +--------------------------------------------------------------------+
078: *
079: * Supports AES128 and AES256
080: *
081: * @author Seema Malkani
082: * @version 1.17, 05/05/07
083: */
084:
085: public class AesDkCrypto extends DkCrypto {
086:
087: private static final boolean debug = false;
088:
089: private static final int BLOCK_SIZE = 16;
090: private static final int DEFAULT_ITERATION_COUNT = 4096;
091: private static final byte[] ZERO_IV = new byte[] { 0, 0, 0, 0, 0,
092: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
093: private static final int hashSize = 96 / 8;
094: private final int keyLength;
095:
096: public AesDkCrypto(int length) {
097: keyLength = length;
098: }
099:
100: protected int getKeySeedLength() {
101: return keyLength; // bits; AES key material
102: }
103:
104: public byte[] stringToKey(char[] password, String salt,
105: byte[] s2kparams) throws GeneralSecurityException {
106:
107: byte[] saltUtf8 = null;
108: try {
109: saltUtf8 = salt.getBytes("UTF-8");
110: return stringToKey(password, saltUtf8, s2kparams);
111: } catch (Exception e) {
112: return null;
113: } finally {
114: if (saltUtf8 != null) {
115: Arrays.fill(saltUtf8, (byte) 0);
116: }
117: }
118: }
119:
120: private byte[] stringToKey(char[] secret, byte[] salt, byte[] params)
121: throws GeneralSecurityException {
122:
123: int iter_count = DEFAULT_ITERATION_COUNT;
124: if (params != null) {
125: if (params.length != 4) {
126: throw new RuntimeException(
127: "Invalid parameter to stringToKey");
128: }
129: iter_count = readBigEndian(params, 0, 4);
130: }
131:
132: byte[] tmpKey = randomToKey(PBKDF2(secret, salt, iter_count,
133: getKeySeedLength()));
134: byte[] result = dk(tmpKey, KERBEROS_CONSTANT);
135: return result;
136: }
137:
138: protected byte[] randomToKey(byte[] in) {
139: // simple identity operation
140: return in;
141: }
142:
143: protected Cipher getCipher(byte[] key, byte[] ivec, int mode)
144: throws GeneralSecurityException {
145:
146: // IV
147: if (ivec == null) {
148: ivec = ZERO_IV;
149: }
150: SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
151: Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
152: IvParameterSpec encIv = new IvParameterSpec(ivec, 0,
153: ivec.length);
154: cipher.init(mode, secretKey, encIv);
155: return cipher;
156: }
157:
158: // get an instance of the AES Cipher in CTS mode
159: public int getChecksumLength() {
160: return hashSize; // bytes
161: }
162:
163: /**
164: * Get the truncated HMAC
165: */
166: protected byte[] getHmac(byte[] key, byte[] msg)
167: throws GeneralSecurityException {
168:
169: SecretKey keyKi = new SecretKeySpec(key, "HMAC");
170: Mac m = Mac.getInstance("HmacSHA1");
171: m.init(keyKi);
172:
173: // generate hash
174: byte[] hash = m.doFinal(msg);
175:
176: // truncate hash
177: byte[] output = new byte[hashSize];
178: System.arraycopy(hash, 0, output, 0, hashSize);
179: return output;
180: }
181:
182: /**
183: * Calculate the checksum
184: */
185: public byte[] calculateChecksum(byte[] baseKey, int usage,
186: byte[] input, int start, int len)
187: throws GeneralSecurityException {
188:
189: if (!KeyUsage.isValid(usage)) {
190: throw new GeneralSecurityException(
191: "Invalid key usage number: " + usage);
192: }
193:
194: // Derive keys
195: byte[] constant = new byte[5];
196: constant[0] = (byte) ((usage >> 24) & 0xff);
197: constant[1] = (byte) ((usage >> 16) & 0xff);
198: constant[2] = (byte) ((usage >> 8) & 0xff);
199: constant[3] = (byte) (usage & 0xff);
200:
201: constant[4] = (byte) 0x99;
202:
203: byte[] Kc = dk(baseKey, constant); // Checksum key
204: if (debug) {
205: System.err.println("usage: " + usage);
206: traceOutput("input", input, start, Math.min(len, 32));
207: traceOutput("constant", constant, 0, constant.length);
208: traceOutput("baseKey", baseKey, 0, baseKey.length);
209: traceOutput("Kc", Kc, 0, Kc.length);
210: }
211:
212: try {
213: // Generate checksum
214: // H1 = HMAC(Kc, input)
215: byte[] hmac = getHmac(Kc, input);
216: if (debug) {
217: traceOutput("hmac", hmac, 0, hmac.length);
218: }
219: if (hmac.length == getChecksumLength()) {
220: return hmac;
221: } else if (hmac.length > getChecksumLength()) {
222: byte[] buf = new byte[getChecksumLength()];
223: System.arraycopy(hmac, 0, buf, 0, buf.length);
224: return buf;
225: } else {
226: throw new GeneralSecurityException(
227: "checksum size too short: " + hmac.length
228: + "; expecting : "
229: + getChecksumLength());
230: }
231: } finally {
232: Arrays.fill(Kc, 0, Kc.length, (byte) 0);
233: }
234: }
235:
236: /**
237: * Performs encryption using derived key; adds confounder.
238: */
239: public byte[] encrypt(byte[] baseKey, int usage, byte[] ivec,
240: byte[] new_ivec, byte[] plaintext, int start, int len)
241: throws GeneralSecurityException, KrbCryptoException {
242:
243: if (!KeyUsage.isValid(usage)) {
244: throw new GeneralSecurityException(
245: "Invalid key usage number: " + usage);
246: }
247: byte[] output = encryptCTS(baseKey, usage, ivec, new_ivec,
248: plaintext, start, len, true);
249: return output;
250: }
251:
252: /**
253: * Performs encryption using derived key; does not add confounder.
254: */
255: public byte[] encryptRaw(byte[] baseKey, int usage, byte[] ivec,
256: byte[] plaintext, int start, int len)
257: throws GeneralSecurityException, KrbCryptoException {
258:
259: if (!KeyUsage.isValid(usage)) {
260: throw new GeneralSecurityException(
261: "Invalid key usage number: " + usage);
262: }
263: byte[] output = encryptCTS(baseKey, usage, ivec, null,
264: plaintext, start, len, false);
265: return output;
266: }
267:
268: /**
269: * @param baseKey key from which keys are to be derived using usage
270: * @param ciphertext E(Ke, conf | plaintext | padding, ivec) | H1[1..h]
271: */
272: public byte[] decrypt(byte[] baseKey, int usage, byte[] ivec,
273: byte[] ciphertext, int start, int len)
274: throws GeneralSecurityException {
275:
276: if (!KeyUsage.isValid(usage)) {
277: throw new GeneralSecurityException(
278: "Invalid key usage number: " + usage);
279: }
280: byte[] output = decryptCTS(baseKey, usage, ivec, ciphertext,
281: start, len, true);
282: return output;
283: }
284:
285: /**
286: * Decrypts data using specified key and initial vector.
287: * @param baseKey encryption key to use
288: * @param ciphertext encrypted data to be decrypted
289: * @param usage ignored
290: */
291: public byte[] decryptRaw(byte[] baseKey, int usage, byte[] ivec,
292: byte[] ciphertext, int start, int len)
293: throws GeneralSecurityException {
294:
295: if (!KeyUsage.isValid(usage)) {
296: throw new GeneralSecurityException(
297: "Invalid key usage number: " + usage);
298: }
299: byte[] output = decryptCTS(baseKey, usage, ivec, ciphertext,
300: start, len, false);
301: return output;
302: }
303:
304: /**
305: * Encrypt AES in CBC-CTS mode using derived keys.
306: */
307: private byte[] encryptCTS(byte[] baseKey, int usage, byte[] ivec,
308: byte[] new_ivec, byte[] plaintext, int start, int len,
309: boolean confounder_exists) throws GeneralSecurityException,
310: KrbCryptoException {
311:
312: byte[] Ke = null;
313: byte[] Ki = null;
314:
315: if (debug) {
316: System.err.println("usage: " + usage);
317: if (ivec != null) {
318: traceOutput("old_state.ivec", ivec, 0, ivec.length);
319: }
320: traceOutput("plaintext", plaintext, start, Math
321: .min(len, 32));
322: traceOutput("baseKey", baseKey, 0, baseKey.length);
323: }
324:
325: try {
326: // derive Encryption key
327: byte[] constant = new byte[5];
328: constant[0] = (byte) ((usage >> 24) & 0xff);
329: constant[1] = (byte) ((usage >> 16) & 0xff);
330: constant[2] = (byte) ((usage >> 8) & 0xff);
331: constant[3] = (byte) (usage & 0xff);
332: constant[4] = (byte) 0xaa;
333: Ke = dk(baseKey, constant); // Encryption key
334:
335: byte[] toBeEncrypted = null;
336: if (confounder_exists) {
337: byte[] confounder = Confounder.bytes(BLOCK_SIZE);
338: toBeEncrypted = new byte[confounder.length + len];
339: System.arraycopy(confounder, 0, toBeEncrypted, 0,
340: confounder.length);
341: System.arraycopy(plaintext, start, toBeEncrypted,
342: confounder.length, len);
343: } else {
344: toBeEncrypted = new byte[len];
345: System.arraycopy(plaintext, start, toBeEncrypted, 0,
346: len);
347: }
348:
349: // encryptedData + HMAC
350: byte[] output = new byte[toBeEncrypted.length + hashSize];
351:
352: // AES in JCE
353: Cipher cipher = Cipher.getInstance("AES/CTS/NoPadding");
354: SecretKeySpec secretKey = new SecretKeySpec(Ke, "AES");
355: IvParameterSpec encIv = new IvParameterSpec(ivec, 0,
356: ivec.length);
357: cipher.init(Cipher.ENCRYPT_MODE, secretKey, encIv);
358: cipher.doFinal(toBeEncrypted, 0, toBeEncrypted.length,
359: output);
360:
361: // Derive integrity key
362: constant[4] = (byte) 0x55;
363: Ki = dk(baseKey, constant);
364: if (debug) {
365: traceOutput("constant", constant, 0, constant.length);
366: traceOutput("Ki", Ki, 0, Ke.length);
367: }
368:
369: // Generate checksum
370: // H1 = HMAC(Ki, conf | plaintext | pad)
371: byte[] hmac = getHmac(Ki, toBeEncrypted);
372:
373: // encryptedData + HMAC
374: System.arraycopy(hmac, 0, output, toBeEncrypted.length,
375: hmac.length);
376: return output;
377: } finally {
378: if (Ke != null) {
379: Arrays.fill(Ke, 0, Ke.length, (byte) 0);
380: }
381: if (Ki != null) {
382: Arrays.fill(Ki, 0, Ki.length, (byte) 0);
383: }
384: }
385: }
386:
387: /**
388: * Decrypt AES in CBC-CTS mode using derived keys.
389: */
390: private byte[] decryptCTS(byte[] baseKey, int usage, byte[] ivec,
391: byte[] ciphertext, int start, int len,
392: boolean confounder_exists) throws GeneralSecurityException {
393:
394: byte[] Ke = null;
395: byte[] Ki = null;
396:
397: try {
398: // Derive encryption key
399: byte[] constant = new byte[5];
400: constant[0] = (byte) ((usage >> 24) & 0xff);
401: constant[1] = (byte) ((usage >> 16) & 0xff);
402: constant[2] = (byte) ((usage >> 8) & 0xff);
403: constant[3] = (byte) (usage & 0xff);
404:
405: constant[4] = (byte) 0xaa;
406: Ke = dk(baseKey, constant); // Encryption key
407:
408: if (debug) {
409: System.err.println("usage: " + usage);
410: if (ivec != null) {
411: traceOutput("old_state.ivec", ivec, 0, ivec.length);
412: }
413: traceOutput("ciphertext", ciphertext, start, Math.min(
414: len, 32));
415: traceOutput("constant", constant, 0, constant.length);
416: traceOutput("baseKey", baseKey, 0, baseKey.length);
417: traceOutput("Ke", Ke, 0, Ke.length);
418: }
419:
420: // Decrypt [confounder | plaintext ] (without checksum)
421:
422: // AES in JCE
423: Cipher cipher = Cipher.getInstance("AES/CTS/NoPadding");
424: SecretKeySpec secretKey = new SecretKeySpec(Ke, "AES");
425: IvParameterSpec encIv = new IvParameterSpec(ivec, 0,
426: ivec.length);
427: cipher.init(Cipher.DECRYPT_MODE, secretKey, encIv);
428: byte[] plaintext = cipher.doFinal(ciphertext, start, len
429: - hashSize);
430:
431: if (debug) {
432: traceOutput("AES PlainText", plaintext, 0, Math.min(
433: plaintext.length, 32));
434: }
435:
436: // Derive integrity key
437: constant[4] = (byte) 0x55;
438: Ki = dk(baseKey, constant); // Integrity key
439: if (debug) {
440: traceOutput("constant", constant, 0, constant.length);
441: traceOutput("Ki", Ki, 0, Ke.length);
442: }
443:
444: // Verify checksum
445: // H1 = HMAC(Ki, conf | plaintext | pad)
446: byte[] calculatedHmac = getHmac(Ki, plaintext);
447: int hmacOffset = start + len - hashSize;
448: if (debug) {
449: traceOutput("calculated Hmac", calculatedHmac, 0,
450: calculatedHmac.length);
451: traceOutput("message Hmac", ciphertext, hmacOffset,
452: hashSize);
453: }
454: boolean cksumFailed = false;
455: if (calculatedHmac.length >= hashSize) {
456: for (int i = 0; i < hashSize; i++) {
457: if (calculatedHmac[i] != ciphertext[hmacOffset + i]) {
458: cksumFailed = true;
459: System.err.println("Checksum failed !");
460: break;
461: }
462: }
463: }
464: if (cksumFailed) {
465: throw new GeneralSecurityException("Checksum failed");
466: }
467:
468: if (confounder_exists) {
469: // Get rid of confounder
470: // [ confounder | plaintext ]
471: byte[] output = new byte[plaintext.length - BLOCK_SIZE];
472: System.arraycopy(plaintext, BLOCK_SIZE, output, 0,
473: output.length);
474: return output;
475: } else {
476: return plaintext;
477: }
478: } finally {
479: if (Ke != null) {
480: Arrays.fill(Ke, 0, Ke.length, (byte) 0);
481: }
482: if (Ki != null) {
483: Arrays.fill(Ki, 0, Ki.length, (byte) 0);
484: }
485: }
486: }
487:
488: /*
489: * Invoke the PKCS#5 PBKDF2 algorithm
490: */
491: private static byte[] PBKDF2(char[] secret, byte[] salt, int count,
492: int keyLength) throws GeneralSecurityException {
493:
494: PBEKeySpec keySpec = new PBEKeySpec(secret, salt, count,
495: keyLength);
496: SecretKeyFactory skf = SecretKeyFactory
497: .getInstance("PBKDF2WithHmacSHA1");
498: SecretKey key = skf.generateSecret(keySpec);
499: byte[] result = key.getEncoded();
500:
501: return result;
502: }
503:
504: public static final int readBigEndian(byte[] data, int pos, int size) {
505: int retVal = 0;
506: int shifter = (size - 1) * 8;
507: while (size > 0) {
508: retVal += (data[pos] & 0xff) << shifter;
509: shifter -= 8;
510: pos++;
511: size--;
512: }
513: return retVal;
514: }
515:
516: }
|