1/* 2 * Copyright (c) 2004, 2008, Oracle and/or its affiliates. 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * 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 if (debug) { 444 System.err.println("Checksum failed !"); 445 } 446 break; 447 } 448 } 449 } 450 if (cksumFailed) { 451 throw new GeneralSecurityException("Checksum failed"); 452 } 453 454 if (confounder_exists) { 455 // Get rid of confounder 456 // [ confounder | plaintext ] 457 byte[] output = new byte[plaintext.length - BLOCK_SIZE]; 458 System.arraycopy(plaintext, BLOCK_SIZE, output, 459 0, output.length); 460 return output; 461 } else { 462 return plaintext; 463 } 464 } finally { 465 if (Ke != null) { 466 Arrays.fill(Ke, 0, Ke.length, (byte) 0); 467 } 468 if (Ki != null) { 469 Arrays.fill(Ki, 0, Ki.length, (byte) 0); 470 } 471 } 472 } 473 474 /* 475 * Invoke the PKCS#5 PBKDF2 algorithm 476 */ 477 private static byte[] PBKDF2(char[] secret, byte[] salt, 478 int count, int keyLength) throws GeneralSecurityException { 479 480 PBEKeySpec keySpec = new PBEKeySpec(secret, salt, count, keyLength); 481 SecretKeyFactory skf = 482 SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); 483 SecretKey key = skf.generateSecret(keySpec); 484 byte[] result = key.getEncoded(); 485 486 return result; 487 } 488 489 public static final int readBigEndian(byte[] data, int pos, int size) { 490 int retVal = 0; 491 int shifter = (size-1)*8; 492 while (size > 0) { 493 retVal += (data[pos] & 0xff) << shifter; 494 shifter -= 8; 495 pos++; 496 size--; 497 } 498 return retVal; 499 } 500 501} 502