502 lines
18 KiB
Java
502 lines
18 KiB
Java
/*
|
|
* 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;
|
|
}
|
|
|
|
}
|