blob: 31919fc51f5b6e83d2181fd41b942ba6ce95b169 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2004-2007 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26/*
27 */
28
29package sun.security.krb5.internal.crypto.dk;
30
31import javax.crypto.Cipher;
32import javax.crypto.Mac;
33import javax.crypto.SecretKeyFactory;
34import javax.crypto.SecretKey;
35import javax.crypto.spec.SecretKeySpec;
36import javax.crypto.spec.DESedeKeySpec;
37import javax.crypto.spec.IvParameterSpec;
38import javax.crypto.spec.PBEKeySpec;
39import java.security.spec.KeySpec;
40import java.security.GeneralSecurityException;
41import sun.security.krb5.KrbCryptoException;
42import sun.security.krb5.Confounder;
43import sun.security.krb5.internal.crypto.KeyUsage;
44import java.util.Arrays;
45
46/**
47 * This class provides the implementation of AES Encryption for Kerberos
48 * as defined RFC 3962.
49 * http://www.ietf.org/rfc/rfc3962.txt
50 *
51 * Algorithm profile described in [KCRYPTO]:
52 * +--------------------------------------------------------------------+
53 * | protocol key format 128- or 256-bit string |
54 * | |
55 * | string-to-key function PBKDF2+DK with variable |
56 * | iteration count (see |
57 * | above) |
58 * | |
59 * | default string-to-key parameters 00 00 10 00 |
60 * | |
61 * | key-generation seed length key size |
62 * | |
63 * | random-to-key function identity function |
64 * | |
65 * | hash function, H SHA-1 |
66 * | |
67 * | HMAC output size, h 12 octets (96 bits) |
68 * | |
69 * | message block size, m 1 octet |
70 * | |
71 * | encryption/decryption functions, AES in CBC-CTS mode |
72 * | E and D (cipher block size 16 |
73 * | octets), with next to |
74 * | last block as CBC-style |
75 * | ivec |
76 * +--------------------------------------------------------------------+
77 *
78 * Supports AES128 and AES256
79 *
80 * @author Seema Malkani
81 */
82
83public class AesDkCrypto extends DkCrypto {
84
85 private static final boolean debug = false;
86
87 private static final int BLOCK_SIZE = 16;
88 private static final int DEFAULT_ITERATION_COUNT = 4096;
89 private static final byte[] ZERO_IV = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0,
90 0, 0, 0, 0, 0, 0, 0, 0 };
91 private static final int hashSize = 96/8;
92 private final int keyLength;
93
94 public AesDkCrypto(int length) {
95 keyLength = length;
96 }
97
98 protected int getKeySeedLength() {
99 return keyLength; // bits; AES key material
100 }
101
102 public byte[] stringToKey(char[] password, String salt, byte[] s2kparams)
103 throws GeneralSecurityException {
104
105 byte[] saltUtf8 = null;
106 try {
107 saltUtf8 = salt.getBytes("UTF-8");
108 return stringToKey(password, saltUtf8, s2kparams);
109 } catch (Exception e) {
110 return null;
111 } finally {
112 if (saltUtf8 != null) {
113 Arrays.fill(saltUtf8, (byte)0);
114 }
115 }
116 }
117
118 private byte[] stringToKey(char[] secret, byte[] salt, byte[] params)
119 throws GeneralSecurityException {
120
121 int iter_count = DEFAULT_ITERATION_COUNT;
122 if (params != null) {
123 if (params.length != 4) {
124 throw new RuntimeException("Invalid parameter to stringToKey");
125 }
126 iter_count = readBigEndian(params, 0, 4);
127 }
128
129 byte[] tmpKey = randomToKey(PBKDF2(secret, salt, iter_count,
130 getKeySeedLength()));
131 byte[] result = dk(tmpKey, KERBEROS_CONSTANT);
132 return result;
133 }
134
135 protected byte[] randomToKey(byte[] in) {
136 // simple identity operation
137 return in;
138 }
139
140 protected Cipher getCipher(byte[] key, byte[] ivec, int mode)
141 throws GeneralSecurityException {
142
143 // IV
144 if (ivec == null) {
145 ivec = ZERO_IV;
146 }
147 SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
148 Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
149 IvParameterSpec encIv = new IvParameterSpec(ivec, 0, ivec.length);
150 cipher.init(mode, secretKey, encIv);
151 return cipher;
152 }
153
154 // get an instance of the AES Cipher in CTS mode
155 public int getChecksumLength() {
156 return hashSize; // bytes
157 }
158
159 /**
160 * Get the truncated HMAC
161 */
162 protected byte[] getHmac(byte[] key, byte[] msg)
163 throws GeneralSecurityException {
164
165 SecretKey keyKi = new SecretKeySpec(key, "HMAC");
166 Mac m = Mac.getInstance("HmacSHA1");
167 m.init(keyKi);
168
169 // generate hash
170 byte[] hash = m.doFinal(msg);
171
172 // truncate hash
173 byte[] output = new byte[hashSize];
174 System.arraycopy(hash, 0, output, 0, hashSize);
175 return output;
176 }
177
178 /**
179 * Calculate the checksum
180 */
181 public byte[] calculateChecksum(byte[] baseKey, int usage, byte[] input,
182 int start, int len) throws GeneralSecurityException {
183
184 if (!KeyUsage.isValid(usage)) {
185 throw new GeneralSecurityException("Invalid key usage number: "
186 + usage);
187 }
188
189 // Derive keys
190 byte[] constant = new byte[5];
191 constant[0] = (byte) ((usage>>24)&0xff);
192 constant[1] = (byte) ((usage>>16)&0xff);
193 constant[2] = (byte) ((usage>>8)&0xff);
194 constant[3] = (byte) (usage&0xff);
195
196 constant[4] = (byte) 0x99;
197
198 byte[] Kc = dk(baseKey, constant); // Checksum key
199 if (debug) {
200 System.err.println("usage: " + usage);
201 traceOutput("input", input, start, Math.min(len, 32));
202 traceOutput("constant", constant, 0, constant.length);
203 traceOutput("baseKey", baseKey, 0, baseKey.length);
204 traceOutput("Kc", Kc, 0, Kc.length);
205 }
206
207 try {
208 // Generate checksum
209 // H1 = HMAC(Kc, input)
210 byte[] hmac = getHmac(Kc, input);
211 if (debug) {
212 traceOutput("hmac", hmac, 0, hmac.length);
213 }
214 if (hmac.length == getChecksumLength()) {
215 return hmac;
216 } else if (hmac.length > getChecksumLength()) {
217 byte[] buf = new byte[getChecksumLength()];
218 System.arraycopy(hmac, 0, buf, 0, buf.length);
219 return buf;
220 } else {
221 throw new GeneralSecurityException("checksum size too short: " +
222 hmac.length + "; expecting : " + getChecksumLength());
223 }
224 } finally {
225 Arrays.fill(Kc, 0, Kc.length, (byte)0);
226 }
227 }
228
229 /**
230 * Performs encryption using derived key; adds confounder.
231 */
232 public byte[] encrypt(byte[] baseKey, int usage,
233 byte[] ivec, byte[] new_ivec, byte[] plaintext, int start, int len)
234 throws GeneralSecurityException, KrbCryptoException {
235
236 if (!KeyUsage.isValid(usage)) {
237 throw new GeneralSecurityException("Invalid key usage number: "
238 + usage);
239 }
240 byte[] output = encryptCTS(baseKey, usage, ivec, new_ivec, plaintext,
241 start, len, true);
242 return output;
243 }
244
245 /**
246 * Performs encryption using derived key; does not add confounder.
247 */
248 public byte[] encryptRaw(byte[] baseKey, int usage,
249 byte[] ivec, byte[] plaintext, int start, int len)
250 throws GeneralSecurityException, KrbCryptoException {
251
252 if (!KeyUsage.isValid(usage)) {
253 throw new GeneralSecurityException("Invalid key usage number: "
254 + usage);
255 }
256 byte[] output = encryptCTS(baseKey, usage, ivec, null, plaintext,
257 start, len, false);
258 return output;
259 }
260
261 /**
262 * @param baseKey key from which keys are to be derived using usage
263 * @param ciphertext E(Ke, conf | plaintext | padding, ivec) | H1[1..h]
264 */
265 public byte[] decrypt(byte[] baseKey, int usage, byte[] ivec,
266 byte[] ciphertext, int start, int len) throws GeneralSecurityException {
267
268 if (!KeyUsage.isValid(usage)) {
269 throw new GeneralSecurityException("Invalid key usage number: "
270 + usage);
271 }
272 byte[] output = decryptCTS(baseKey, usage, ivec, ciphertext,
273 start, len, true);
274 return output;
275 }
276
277 /**
278 * Decrypts data using specified key and initial vector.
279 * @param baseKey encryption key to use
280 * @param ciphertext encrypted data to be decrypted
281 * @param usage ignored
282 */
283 public byte[] decryptRaw(byte[] baseKey, int usage, byte[] ivec,
284 byte[] ciphertext, int start, int len)
285 throws GeneralSecurityException {
286
287 if (!KeyUsage.isValid(usage)) {
288 throw new GeneralSecurityException("Invalid key usage number: "
289 + usage);
290 }
291 byte[] output = decryptCTS(baseKey, usage, ivec, ciphertext,
292 start, len, false);
293 return output;
294 }
295
296 /**
297 * Encrypt AES in CBC-CTS mode using derived keys.
298 */
299 private byte[] encryptCTS(byte[] baseKey, int usage, byte[] ivec,
300 byte[] new_ivec, byte[] plaintext, int start, int len,
301 boolean confounder_exists)
302 throws GeneralSecurityException, KrbCryptoException {
303
304 byte[] Ke = null;
305 byte[] Ki = null;
306
307 if (debug) {
308 System.err.println("usage: " + usage);
309 if (ivec != null) {
310 traceOutput("old_state.ivec", ivec, 0, ivec.length);
311 }
312 traceOutput("plaintext", plaintext, start, Math.min(len, 32));
313 traceOutput("baseKey", baseKey, 0, baseKey.length);
314 }
315
316 try {
317 // derive Encryption key
318 byte[] constant = new byte[5];
319 constant[0] = (byte) ((usage>>24)&0xff);
320 constant[1] = (byte) ((usage>>16)&0xff);
321 constant[2] = (byte) ((usage>>8)&0xff);
322 constant[3] = (byte) (usage&0xff);
323 constant[4] = (byte) 0xaa;
324 Ke = dk(baseKey, constant); // Encryption key
325
326 byte[] toBeEncrypted = null;
327 if (confounder_exists) {
328 byte[] confounder = Confounder.bytes(BLOCK_SIZE);
329 toBeEncrypted = new byte[confounder.length + len];
330 System.arraycopy(confounder, 0, toBeEncrypted,
331 0, confounder.length);
332 System.arraycopy(plaintext, start, toBeEncrypted,
333 confounder.length, len);
334 } else {
335 toBeEncrypted = new byte[len];
336 System.arraycopy(plaintext, start, toBeEncrypted, 0, len);
337 }
338
339 // encryptedData + HMAC
340 byte[] output = new byte[toBeEncrypted.length + hashSize];
341
342 // AES in JCE
343 Cipher cipher = Cipher.getInstance("AES/CTS/NoPadding");
344 SecretKeySpec secretKey = new SecretKeySpec(Ke, "AES");
345 IvParameterSpec encIv = new IvParameterSpec(ivec, 0, ivec.length);
346 cipher.init(Cipher.ENCRYPT_MODE, secretKey, encIv);
347 cipher.doFinal(toBeEncrypted, 0, toBeEncrypted.length, output);
348
349 // Derive integrity key
350 constant[4] = (byte) 0x55;
351 Ki = dk(baseKey, constant);
352 if (debug) {
353 traceOutput("constant", constant, 0, constant.length);
354 traceOutput("Ki", Ki, 0, Ke.length);
355 }
356
357 // Generate checksum
358 // H1 = HMAC(Ki, conf | plaintext | pad)
359 byte[] hmac = getHmac(Ki, toBeEncrypted);
360
361 // encryptedData + HMAC
362 System.arraycopy(hmac, 0, output, toBeEncrypted.length,
363 hmac.length);
364 return output;
365 } finally {
366 if (Ke != null) {
367 Arrays.fill(Ke, 0, Ke.length, (byte) 0);
368 }
369 if (Ki != null) {
370 Arrays.fill(Ki, 0, Ki.length, (byte) 0);
371 }
372 }
373 }
374
375 /**
376 * Decrypt AES in CBC-CTS mode using derived keys.
377 */
378 private byte[] decryptCTS(byte[] baseKey, int usage, byte[] ivec,
379 byte[] ciphertext, int start, int len, boolean confounder_exists)
380 throws GeneralSecurityException {
381
382 byte[] Ke = null;
383 byte[] Ki = null;
384
385 try {
386 // Derive encryption key
387 byte[] constant = new byte[5];
388 constant[0] = (byte) ((usage>>24)&0xff);
389 constant[1] = (byte) ((usage>>16)&0xff);
390 constant[2] = (byte) ((usage>>8)&0xff);
391 constant[3] = (byte) (usage&0xff);
392
393 constant[4] = (byte) 0xaa;
394 Ke = dk(baseKey, constant); // Encryption key
395
396 if (debug) {
397 System.err.println("usage: " + usage);
398 if (ivec != null) {
399 traceOutput("old_state.ivec", ivec, 0, ivec.length);
400 }
401 traceOutput("ciphertext", ciphertext, start, Math.min(len, 32));
402 traceOutput("constant", constant, 0, constant.length);
403 traceOutput("baseKey", baseKey, 0, baseKey.length);
404 traceOutput("Ke", Ke, 0, Ke.length);
405 }
406
407 // Decrypt [confounder | plaintext ] (without checksum)
408
409 // AES in JCE
410 Cipher cipher = Cipher.getInstance("AES/CTS/NoPadding");
411 SecretKeySpec secretKey = new SecretKeySpec(Ke, "AES");
412 IvParameterSpec encIv = new IvParameterSpec(ivec, 0, ivec.length);
413 cipher.init(Cipher.DECRYPT_MODE, secretKey, encIv);
414 byte[] plaintext = cipher.doFinal(ciphertext, start, len-hashSize);
415
416 if (debug) {
417 traceOutput("AES PlainText", plaintext, 0,
418 Math.min(plaintext.length, 32));
419 }
420
421 // Derive integrity key
422 constant[4] = (byte) 0x55;
423 Ki = dk(baseKey, constant); // Integrity key
424 if (debug) {
425 traceOutput("constant", constant, 0, constant.length);
426 traceOutput("Ki", Ki, 0, Ke.length);
427 }
428
429 // Verify checksum
430 // H1 = HMAC(Ki, conf | plaintext | pad)
431 byte[] calculatedHmac = getHmac(Ki, plaintext);
432 int hmacOffset = start + len - hashSize;
433 if (debug) {
434 traceOutput("calculated Hmac", calculatedHmac,
435 0, calculatedHmac.length);
436 traceOutput("message Hmac", ciphertext, hmacOffset, hashSize);
437 }
438 boolean cksumFailed = false;
439 if (calculatedHmac.length >= hashSize) {
440 for (int i = 0; i < hashSize; i++) {
441 if (calculatedHmac[i] != ciphertext[hmacOffset+i]) {
442 cksumFailed = true;
443 System.err.println("Checksum failed !");
444 break;
445 }
446 }
447 }
448 if (cksumFailed) {
449 throw new GeneralSecurityException("Checksum failed");
450 }
451
452 if (confounder_exists) {
453 // Get rid of confounder
454 // [ confounder | plaintext ]
455 byte[] output = new byte[plaintext.length - BLOCK_SIZE];
456 System.arraycopy(plaintext, BLOCK_SIZE, output,
457 0, output.length);
458 return output;
459 } else {
460 return plaintext;
461 }
462 } finally {
463 if (Ke != null) {
464 Arrays.fill(Ke, 0, Ke.length, (byte) 0);
465 }
466 if (Ki != null) {
467 Arrays.fill(Ki, 0, Ki.length, (byte) 0);
468 }
469 }
470 }
471
472 /*
473 * Invoke the PKCS#5 PBKDF2 algorithm
474 */
475 private static byte[] PBKDF2(char[] secret, byte[] salt,
476 int count, int keyLength) throws GeneralSecurityException {
477
478 PBEKeySpec keySpec = new PBEKeySpec(secret, salt, count, keyLength);
479 SecretKeyFactory skf =
480 SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
481 SecretKey key = skf.generateSecret(keySpec);
482 byte[] result = key.getEncoded();
483
484 return result;
485 }
486
487 public static final int readBigEndian(byte[] data, int pos, int size) {
488 int retVal = 0;
489 int shifter = (size-1)*8;
490 while (size > 0) {
491 retVal += (data[pos] & 0xff) << shifter;
492 shifter -= 8;
493 pos++;
494 size--;
495 }
496 return retVal;
497 }
498
499}