package de.persosim.simulator.crypto; import static org.globaltester.logging.BasicLogger.DEBUG; import static org.globaltester.logging.BasicLogger.log; import java.io.IOException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.util.Arrays; import org.globaltester.cryptoprovider.Crypto; import de.persosim.simulator.utils.HexString; import de.persosim.simulator.utils.Utils; /** * @author slutters * * Implementation of Key Derivation Function (KDF) accoring to TR-03110 A.2.3. Key Derivation Function */ public class KeyDerivationFunction { public static final String[] DIGEST_ORDER = new String[]{"SHA-1", "SHA-256"}; public static final byte[] COUNTER_ENC = new byte[]{(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01}; public static final byte[] COUNTER_MAC = new byte[]{(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02}; public static final byte[] COUNTER_PI = new byte[]{(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x03}; /*--------------------------------------------------------------------------------*/ protected MessageDigest messageDigest; protected int keyLengthInBytes; /*--------------------------------------------------------------------------------*/ /** * * @param messageDigest the message digest to be used * @param keyLengthInBytes key length in byte */ public KeyDerivationFunction(MessageDigest messageDigest, int keyLengthInBytes) { if(messageDigest == null) {throw new NullPointerException();} if(keyLengthInBytes < 0) {throw new IllegalArgumentException("key length must be >= 0");} if(messageDigest.getDigestLength() < keyLengthInBytes) {throw new IllegalArgumentException("key length must be smaller than or equal to digest length");} this.messageDigest = messageDigest; this.keyLengthInBytes = keyLengthInBytes; } public KeyDerivationFunction(int keyLengthInBytes) { if(keyLengthInBytes < 0) {throw new IllegalArgumentException("key length must be >= 0");} if(keyLengthInBytes > 32) {throw new IllegalArgumentException("key length must be <= 32");} this.keyLengthInBytes = keyLengthInBytes; try { if(keyLengthInBytes <= 16) { this.messageDigest = MessageDigest.getInstance(DIGEST_ORDER[0], Crypto.getCryptoProvider()); } else{ this.messageDigest = MessageDigest.getInstance(DIGEST_ORDER[1], Crypto.getCryptoProvider()); } } catch (NoSuchAlgorithmException e) { /* this is not supposed to happen */ e.printStackTrace(); } } /*--------------------------------------------------------------------------------*/ /** * Returns key material derived derived from SHA1(secret||nonce||counter) stripped to key length * @param secret the common secret shared by PICC and ICD * @param nonce optional nonce, may be null * @param counter a counter * @return key material derived derived from SHA1(secret||nonce||counter) stripped to key length */ public byte[] deriveKey(byte[] secret, byte[] nonce, byte[] counter) { int inputLength; byte[] input, digest; if(secret == null) {throw new NullPointerException();} if(counter == null) {throw new NullPointerException();} inputLength = secret.length + counter.length; if(nonce != null) { inputLength += nonce.length; log(KeyDerivationFunction.class, "deriving key from secret \"" + HexString.encode(secret) + "\", nonce \"" + HexString.encode(nonce) + "\" and counter \"" + HexString.encode(counter) + "\"", DEBUG); } else{ log(KeyDerivationFunction.class, "deriving key from secret \"" + HexString.encode(secret) + "\", no nonce and counter \"" + HexString.encode(counter) + "\"", DEBUG); } if(inputLength <= 0) { throw new IllegalArgumentException("KDF input length must be > 0"); } input = new byte[inputLength]; if(nonce == null) { input = Utils.concatByteArrays(secret, counter); } else{ input = Utils.concatByteArrays(secret, nonce, counter); } log(KeyDerivationFunction.class, "message digest input is: " + HexString.encode(input), DEBUG); log(KeyDerivationFunction.class, "message digest algorithm is: " + messageDigest.getAlgorithm() + " of " + keyLengthInBytes + " bytes length", DEBUG); digest = this.messageDigest.digest(input); log(KeyDerivationFunction.class, "message digest result is: " + HexString.encode(digest), DEBUG); return Arrays.copyOf(digest, this.keyLengthInBytes); } /** * Returns key material derived derived from SHA1(secret||nonce||COUNTER_ENC) stripped to key length * @param secret the common secret shared by PICC and ICD * @param nonce optional nonce, may be null * @return key material derived derived from SHA1(secret||nonce||COUNTER_ENC) stripped to key length * @throws IOException * @throws NoSuchProviderException * @throws NoSuchAlgorithmException */ public byte[] deriveENC(byte[] secret, byte[] nonce) { return this.deriveKey(secret, nonce, COUNTER_ENC); } /** * Returns key material derived derived from SHA1(secret||COUNTER_ENC) stripped to key length * @param secret the common secret shared by PICC and ICD * @return key material derived derived from SHA1(secret||COUNTER_ENC) stripped to key length * @throws IOException * @throws NoSuchProviderException * @throws NoSuchAlgorithmException */ public byte[] deriveENC(byte[] secret) { return this.deriveENC(secret, null); } /** * Returns key material derived derived from SHA1(secret||nonce||COUNTER_MAC) stripped to key length * @param secret the common secret shared by PICC and ICD * @param nonce optional nonce, may be null * @return key material derived derived from SHA1(secret||nonce||COUNTER_MAC) stripped to key length * @throws IOException * @throws NoSuchProviderException * @throws NoSuchAlgorithmException */ public byte[] deriveMAC(byte[] secret, byte[] nonce) { return this.deriveKey(secret, nonce, COUNTER_MAC); } /** * Returns key material derived derived from SHA1(secret||COUNTER_MAC) stripped to key length * @param secret the common secret shared by PICC and ICD * @return key material derived derived from SHA1(secret||COUNTER_MAC) stripped to key length * @throws IOException * @throws NoSuchProviderException * @throws NoSuchAlgorithmException */ public byte[] deriveMAC(byte[] secret) { return this.deriveMAC(secret, null); } /** * Returns key material derived derived from SHA1(secret||COUNTER_PI) stripped to key length * @param secret the common secret shared by PICC and ICD * @return key material derived derived from SHA1(secret||COUNTER_PI) stripped to key length * @throws IOException * @throws NoSuchProviderException * @throws NoSuchAlgorithmException */ public byte[] derivePI(byte[] secret) { return this.deriveKey(secret, null, COUNTER_PI); } /*--------------------------------------------------------------------------------*/ /** * @return the digest */ public MessageDigest getDigest() { return messageDigest; } /** * @param digest the digest to set */ public void setDigest(MessageDigest digest) { if(digest == null) { throw new NullPointerException(); } if(digest.getDigestLength() < this.keyLengthInBytes) { throw new IllegalArgumentException("length of computable message digest too small for key length"); } this.messageDigest = digest; } /** * @return the keyLength */ public int getKeyLengthInBytes() { return keyLengthInBytes; } /** * @param keyLengthInBytes the keyLength to set */ public void setKeyLengthInBytes(int keyLengthInBytes) { if(keyLengthInBytes < 0) { throw new IllegalArgumentException("key length must be >= 0"); } if(this.messageDigest.getDigestLength() < keyLengthInBytes) { throw new IllegalArgumentException("key length too big for message digest"); } this.keyLengthInBytes = keyLengthInBytes; } }