/*
* Copyright 2013 MovingBlocks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.terasology.identity;
import org.terasology.math.TeraMath;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
/**
* Generates secrets using the TLS p_hash method. This is used in two situations:
* <ol>
* <li>To generate the master secret from the premaster secret</li>
* <li>To generate keys for symmetric encryption</li>
* </ol>
*/
public final class SecretGenerator {
/**
* Label for generating master secrets
*/
public static final String MASTER_SECRET_LABEL = "master secret";
/**
* Label when generating a key from a master secret
*/
public static final String KEY_EXPANSION = "key expansion";
/**
* The standard length of a master secret
*/
public static final int MASTER_SECRET_LENGTH = 48;
private static final String MD5_HASH_ALGORITHM = "HmacMD5";
private static final String SHA1_HASH_ALGORITHM = "HmacSHA1";
private SecretGenerator() {
}
/**
* Generates a secret from another secret, a seed, and a label
*
* @param secret
* @param label
* @param seed
* @param targetLength The desired length of the generated secret.
* @return The generated secret
*/
public static byte[] generate(byte[] secret, String label, byte[] seed, int targetLength) {
// Split the secret
int partLength = TeraMath.ceilToInt(secret.length / 2.0f);
byte[] part1 = Arrays.copyOfRange(secret, 0, partLength);
byte[] part2 = Arrays.copyOfRange(secret, secret.length - partLength, secret.length);
byte[] labelBytes = label.getBytes(Charset.forName("US-ASCII"));
byte[] combinedLabelSeed = new byte[labelBytes.length + seed.length];
System.arraycopy(labelBytes, 0, combinedLabelSeed, 0, labelBytes.length);
System.arraycopy(seed, 0, combinedLabelSeed, labelBytes.length, seed.length);
// MD5 the first half of the secret
byte[] md5Result = phashMD5(part1, combinedLabelSeed, targetLength);
// SHA1 the second half of the secret
byte[] sha1Result = phashSHA1(part2, combinedLabelSeed, targetLength);
byte[] masterSecret = new byte[md5Result.length];
for (int i = 0; i < masterSecret.length; ++i) {
masterSecret[i] = (byte) (md5Result[i] ^ sha1Result[i]);
}
return masterSecret;
}
public static byte[] phashMD5(byte[] secret, byte[] seed, int targetLength) {
return phash(secret, seed, MD5_HASH_ALGORITHM, targetLength);
}
public static byte[] phashSHA1(byte[] secret, byte[] seed, int targetLength) {
return phash(secret, seed, SHA1_HASH_ALGORITHM, targetLength);
}
private static byte[] phash(byte[] secret, byte[] seed, String algorithm, int targetLength) {
SecretKeySpec signingKey = new SecretKeySpec(secret, algorithm);
try {
Mac mac = Mac.getInstance(algorithm);
mac.init(signingKey);
// Compute the hmac on input data bytes
byte[] prevHash = mac.doFinal(seed);
byte[] result = new byte[targetLength];
int lengthGenerated = 0;
while (lengthGenerated < targetLength) {
byte[] value = new byte[prevHash.length + secret.length];
System.arraycopy(prevHash, 0, value, 0, prevHash.length);
System.arraycopy(secret, 0, value, prevHash.length, secret.length);
prevHash = mac.doFinal(value);
System.arraycopy(prevHash, 0, result, lengthGenerated, Math.min(prevHash.length, targetLength - lengthGenerated));
lengthGenerated += prevHash.length;
}
return result;
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(algorithm + " not supported, required for authentication", e);
} catch (InvalidKeyException e) {
throw new RuntimeException("Error computing master secret", e);
}
}
}