/*
* Copyright (c) 2004, 2008, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
*/
package sun.security.krb5.internal.crypto.dk;
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.SecretKeyFactory;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.DESedeKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import java.security.spec.KeySpec;
import java.security.GeneralSecurityException;
import sun.security.krb5.KrbCryptoException;
import sun.security.krb5.Confounder;
import sun.security.krb5.internal.crypto.KeyUsage;
import java.util.Arrays;
/**
* This class provides the implementation of AES Encryption for Kerberos
* as defined RFC 3962.
* http://www.ietf.org/rfc/rfc3962.txt
*
* Algorithm profile described in [KCRYPTO]:
* +--------------------------------------------------------------------+
* | protocol key format 128- or 256-bit string |
* | |
* | string-to-key function PBKDF2+DK with variable |
* | iteration count (see |
* | above) |
* | |
* | default string-to-key parameters 00 00 10 00 |
* | |
* | key-generation seed length key size |
* | |
* | random-to-key function identity function |
* | |
* | hash function, H SHA-1 |
* | |
* | HMAC output size, h 12 octets (96 bits) |
* | |
* | message block size, m 1 octet |
* | |
* | encryption/decryption functions, AES in CBC-CTS mode |
* | E and D (cipher block size 16 |
* | octets), with next to |
* | last block as CBC-style |
* | ivec |
* +--------------------------------------------------------------------+
*
* Supports AES128 and AES256
*
* @author Seema Malkani
*/
public class AesDkCrypto extends DkCrypto {
private static final boolean debug = false;
private static final int BLOCK_SIZE = 16;
private static final int DEFAULT_ITERATION_COUNT = 4096;
private static final byte[] ZERO_IV = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0 };
private static final int hashSize = 96/8;
private final int keyLength;
public AesDkCrypto(int length) {
keyLength = length;
}
protected int getKeySeedLength() {
return keyLength; // bits; AES key material
}
public byte[] stringToKey(char[] password, String salt, byte[] s2kparams)
throws GeneralSecurityException {
byte[] saltUtf8 = null;
try {
saltUtf8 = salt.getBytes("UTF-8");
return stringToKey(password, saltUtf8, s2kparams);
} catch (Exception e) {
return null;
} finally {
if (saltUtf8 != null) {
Arrays.fill(saltUtf8, (byte)0);
}
}
}
private byte[] stringToKey(char[] secret, byte[] salt, byte[] params)
throws GeneralSecurityException {
int iter_count = DEFAULT_ITERATION_COUNT;
if (params != null) {
if (params.length != 4) {
throw new RuntimeException("Invalid parameter to stringToKey");
}
iter_count = readBigEndian(params, 0, 4);
}
byte[] tmpKey = randomToKey(PBKDF2(secret, salt, iter_count,
getKeySeedLength()));
byte[] result = dk(tmpKey, KERBEROS_CONSTANT);
return result;
}
protected byte[] randomToKey(byte[] in) {
// simple identity operation
return in;
}
protected Cipher getCipher(byte[] key, byte[] ivec, int mode)
throws GeneralSecurityException {
// IV
if (ivec == null) {
ivec = ZERO_IV;
}
SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
IvParameterSpec encIv = new IvParameterSpec(ivec, 0, ivec.length);
cipher.init(mode, secretKey, encIv);
return cipher;
}
// get an instance of the AES Cipher in CTS mode
public int getChecksumLength() {
return hashSize; // bytes
}
/**
* Get the truncated HMAC
*/
protected byte[] getHmac(byte[] key, byte[] msg)
throws GeneralSecurityException {
SecretKey keyKi = new SecretKeySpec(key, "HMAC");
Mac m = Mac.getInstance("HmacSHA1");
m.init(keyKi);
// generate hash
byte[] hash = m.doFinal(msg);
// truncate hash
byte[] output = new byte[hashSize];
System.arraycopy(hash, 0, output, 0, hashSize);
return output;
}
/**
* Calculate the checksum
*/
public byte[] calculateChecksum(byte[] baseKey, int usage, byte[] input,
int start, int len) throws GeneralSecurityException {
if (!KeyUsage.isValid(usage)) {
throw new GeneralSecurityException("Invalid key usage number: "
+ usage);
}
// Derive keys
byte[] constant = new byte[5];
constant[0] = (byte) ((usage>>24)&0xff);
constant[1] = (byte) ((usage>>16)&0xff);
constant[2] = (byte) ((usage>>8)&0xff);
constant[3] = (byte) (usage&0xff);
constant[4] = (byte) 0x99;
byte[] Kc = dk(baseKey, constant); // Checksum key
if (debug) {
System.err.println("usage: " + usage);
traceOutput("input", input, start, Math.min(len, 32));
traceOutput("constant", constant, 0, constant.length);
traceOutput("baseKey", baseKey, 0, baseKey.length);
traceOutput("Kc", Kc, 0, Kc.length);
}
try {
// Generate checksum
// H1 = HMAC(Kc, input)
byte[] hmac = getHmac(Kc, input);
if (debug) {
traceOutput("hmac", hmac, 0, hmac.length);
}
if (hmac.length == getChecksumLength()) {
return hmac;
} else if (hmac.length > getChecksumLength()) {
byte[] buf = new byte[getChecksumLength()];
System.arraycopy(hmac, 0, buf, 0, buf.length);
return buf;
} else {
throw new GeneralSecurityException("checksum size too short: " +
hmac.length + "; expecting : " + getChecksumLength());
}
} finally {
Arrays.fill(Kc, 0, Kc.length, (byte)0);
}
}
/**
* Performs encryption using derived key; adds confounder.
*/
public byte[] encrypt(byte[] baseKey, int usage,
byte[] ivec, byte[] new_ivec, byte[] plaintext, int start, int len)
throws GeneralSecurityException, KrbCryptoException {
if (!KeyUsage.isValid(usage)) {
throw new GeneralSecurityException("Invalid key usage number: "
+ usage);
}
byte[] output = encryptCTS(baseKey, usage, ivec, new_ivec, plaintext,
start, len, true);
return output;
}
/**
* Performs encryption using derived key; does not add confounder.
*/
public byte[] encryptRaw(byte[] baseKey, int usage,
byte[] ivec, byte[] plaintext, int start, int len)
throws GeneralSecurityException, KrbCryptoException {
if (!KeyUsage.isValid(usage)) {
throw new GeneralSecurityException("Invalid key usage number: "
+ usage);
}
byte[] output = encryptCTS(baseKey, usage, ivec, null, plaintext,
start, len, false);
return output;
}
/**
* @param baseKey key from which keys are to be derived using usage
* @param ciphertext E(Ke, conf | plaintext | padding, ivec) | H1[1..h]
*/
public byte[] decrypt(byte[] baseKey, int usage, byte[] ivec,
byte[] ciphertext, int start, int len) throws GeneralSecurityException {
if (!KeyUsage.isValid(usage)) {
throw new GeneralSecurityException("Invalid key usage number: "
+ usage);
}
byte[] output = decryptCTS(baseKey, usage, ivec, ciphertext,
start, len, true);
return output;
}
/**
* Decrypts data using specified key and initial vector.
* @param baseKey encryption key to use
* @param ciphertext encrypted data to be decrypted
* @param usage ignored
*/
public byte[] decryptRaw(byte[] baseKey, int usage, byte[] ivec,
byte[] ciphertext, int start, int len)
throws GeneralSecurityException {
if (!KeyUsage.isValid(usage)) {
throw new GeneralSecurityException("Invalid key usage number: "
+ usage);
}
byte[] output = decryptCTS(baseKey, usage, ivec, ciphertext,
start, len, false);
return output;
}
/**
* Encrypt AES in CBC-CTS mode using derived keys.
*/
private byte[] encryptCTS(byte[] baseKey, int usage, byte[] ivec,
byte[] new_ivec, byte[] plaintext, int start, int len,
boolean confounder_exists)
throws GeneralSecurityException, KrbCryptoException {
byte[] Ke = null;
byte[] Ki = null;
if (debug) {
System.err.println("usage: " + usage);
if (ivec != null) {
traceOutput("old_state.ivec", ivec, 0, ivec.length);
}
traceOutput("plaintext", plaintext, start, Math.min(len, 32));
traceOutput("baseKey", baseKey, 0, baseKey.length);
}
try {
// derive Encryption key
byte[] constant = new byte[5];
constant[0] = (byte) ((usage>>24)&0xff);
constant[1] = (byte) ((usage>>16)&0xff);
constant[2] = (byte) ((usage>>8)&0xff);
constant[3] = (byte) (usage&0xff);
constant[4] = (byte) 0xaa;
Ke = dk(baseKey, constant); // Encryption key
byte[] toBeEncrypted = null;
if (confounder_exists) {
byte[] confounder = Confounder.bytes(BLOCK_SIZE);
toBeEncrypted = new byte[confounder.length + len];
System.arraycopy(confounder, 0, toBeEncrypted,
0, confounder.length);
System.arraycopy(plaintext, start, toBeEncrypted,
confounder.length, len);
} else {
toBeEncrypted = new byte[len];
System.arraycopy(plaintext, start, toBeEncrypted, 0, len);
}
// encryptedData + HMAC
byte[] output = new byte[toBeEncrypted.length + hashSize];
// AES in JCE
Cipher cipher = Cipher.getInstance("AES/CTS/NoPadding");
SecretKeySpec secretKey = new SecretKeySpec(Ke, "AES");
IvParameterSpec encIv = new IvParameterSpec(ivec, 0, ivec.length);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, encIv);
cipher.doFinal(toBeEncrypted, 0, toBeEncrypted.length, output);
// Derive integrity key
constant[4] = (byte) 0x55;
Ki = dk(baseKey, constant);
if (debug) {
traceOutput("constant", constant, 0, constant.length);
traceOutput("Ki", Ki, 0, Ke.length);
}
// Generate checksum
// H1 = HMAC(Ki, conf | plaintext | pad)
byte[] hmac = getHmac(Ki, toBeEncrypted);
// encryptedData + HMAC
System.arraycopy(hmac, 0, output, toBeEncrypted.length,
hmac.length);
return output;
} finally {
if (Ke != null) {
Arrays.fill(Ke, 0, Ke.length, (byte) 0);
}
if (Ki != null) {
Arrays.fill(Ki, 0, Ki.length, (byte) 0);
}
}
}
/**
* Decrypt AES in CBC-CTS mode using derived keys.
*/
private byte[] decryptCTS(byte[] baseKey, int usage, byte[] ivec,
byte[] ciphertext, int start, int len, boolean confounder_exists)
throws GeneralSecurityException {
byte[] Ke = null;
byte[] Ki = null;
try {
// Derive encryption key
byte[] constant = new byte[5];
constant[0] = (byte) ((usage>>24)&0xff);
constant[1] = (byte) ((usage>>16)&0xff);
constant[2] = (byte) ((usage>>8)&0xff);
constant[3] = (byte) (usage&0xff);
constant[4] = (byte) 0xaa;
Ke = dk(baseKey, constant); // Encryption key
if (debug) {
System.err.println("usage: " + usage);
if (ivec != null) {
traceOutput("old_state.ivec", ivec, 0, ivec.length);
}
traceOutput("ciphertext", ciphertext, start, Math.min(len, 32));
traceOutput("constant", constant, 0, constant.length);
traceOutput("baseKey", baseKey, 0, baseKey.length);
traceOutput("Ke", Ke, 0, Ke.length);
}
// Decrypt [confounder | plaintext ] (without checksum)
// AES in JCE
Cipher cipher = Cipher.getInstance("AES/CTS/NoPadding");
SecretKeySpec secretKey = new SecretKeySpec(Ke, "AES");
IvParameterSpec encIv = new IvParameterSpec(ivec, 0, ivec.length);
cipher.init(Cipher.DECRYPT_MODE, secretKey, encIv);
byte[] plaintext = cipher.doFinal(ciphertext, start, len-hashSize);
if (debug) {
traceOutput("AES PlainText", plaintext, 0,
Math.min(plaintext.length, 32));
}
// Derive integrity key
constant[4] = (byte) 0x55;
Ki = dk(baseKey, constant); // Integrity key
if (debug) {
traceOutput("constant", constant, 0, constant.length);
traceOutput("Ki", Ki, 0, Ke.length);
}
// Verify checksum
// H1 = HMAC(Ki, conf | plaintext | pad)
byte[] calculatedHmac = getHmac(Ki, plaintext);
int hmacOffset = start + len - hashSize;
if (debug) {
traceOutput("calculated Hmac", calculatedHmac,
0, calculatedHmac.length);
traceOutput("message Hmac", ciphertext, hmacOffset, hashSize);
}
boolean cksumFailed = false;
if (calculatedHmac.length >= hashSize) {
for (int i = 0; i < hashSize; i++) {
if (calculatedHmac[i] != ciphertext[hmacOffset+i]) {
cksumFailed = true;
if (debug) {
System.err.println("Checksum failed !");
}
break;
}
}
}
if (cksumFailed) {
throw new GeneralSecurityException("Checksum failed");
}
if (confounder_exists) {
// Get rid of confounder
// [ confounder | plaintext ]
byte[] output = new byte[plaintext.length - BLOCK_SIZE];
System.arraycopy(plaintext, BLOCK_SIZE, output,
0, output.length);
return output;
} else {
return plaintext;
}
} finally {
if (Ke != null) {
Arrays.fill(Ke, 0, Ke.length, (byte) 0);
}
if (Ki != null) {
Arrays.fill(Ki, 0, Ki.length, (byte) 0);
}
}
}
/*
* Invoke the PKCS#5 PBKDF2 algorithm
*/
private static byte[] PBKDF2(char[] secret, byte[] salt,
int count, int keyLength) throws GeneralSecurityException {
PBEKeySpec keySpec = new PBEKeySpec(secret, salt, count, keyLength);
SecretKeyFactory skf =
SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
SecretKey key = skf.generateSecret(keySpec);
byte[] result = key.getEncoded();
return result;
}
public static final int readBigEndian(byte[] data, int pos, int size) {
int retVal = 0;
int shifter = (size-1)*8;
while (size > 0) {
retVal += (data[pos] & 0xff) << shifter;
shifter -= 8;
pos++;
size--;
}
return retVal;
}
}