/**
* Copyright 2005-2014 Restlet
*
* The contents of this file are subject to the terms of one of the following
* open source licenses: Apache 2.0 or or EPL 1.0 (the "Licenses"). You can
* select the license that you prefer but you may not use this file except in
* compliance with one of these Licenses.
*
* You can obtain a copy of the Apache 2.0 license at
* http://www.opensource.org/licenses/apache-2.0
*
* You can obtain a copy of the EPL 1.0 license at
* http://www.opensource.org/licenses/eclipse-1.0
*
* See the Licenses for the specific language governing permissions and
* limitations under the Licenses.
*
* Alternatively, you can obtain a royalty free commercial license with less
* limitations, transferable or non-transferable, directly at
* http://restlet.com/products/restlet-framework
*
* Restlet is a registered trademark of Restlet S.A.S.
*/
package org.restlet.ext.crypto;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.restlet.data.Digest;
import org.restlet.engine.util.Base64;
/**
* Security data manipulation utilities.
*
* @author Jerome Louvel
*/
public class DigestUtils {
/**
* General regex pattern to extract comma separated name-value components.
* This pattern captures one name and value per match(), and is repeatedly
* applied to the input string to extract all components. Must handle both
* quoted and unquoted values as RFC2617 isn't consistent in this respect.
* Pattern is immutable and thread-safe so reuse one static instance.
*/
private static final char[] HEXDIGITS = "0123456789abcdef".toCharArray();
/**
* Returns the digest of the target string. Target is decoded to bytes using
* the US-ASCII charset. Supports MD5 and SHA-1 algorithms.
*
* @param target
* The string to encode.
* @param algorithm
* The digest algorithm to use.
* @return The digest of the target string.
*/
public static char[] digest(char[] target, String algorithm) {
return DigestUtils.digest(new String(target), algorithm).toCharArray();
}
/**
* Returns the digest of the target string. Target is decoded to bytes using
* the US-ASCII charset. Supports MD5 and SHA-1 algorithms.
*
* @param target
* The string to encode.
* @param algorithm
* The digest algorithm to use.
* @return The digest of the target string.
*/
public static String digest(String target, String algorithm) {
if (Digest.ALGORITHM_MD5.equals(algorithm)) {
return toMd5(target);
} else if (Digest.ALGORITHM_SHA_1.equals(algorithm)) {
return toSha1(target);
}
throw new IllegalArgumentException("Unsupported algorithm.");
};
/**
* Converts a source string to its HMAC/SHA-1 value.
*
* @param source
* The source string to convert.
* @param secretKey
* The secret key to use for conversion.
* @return The HMac value of the source string.
*/
public static byte[] toHMacSha1(String source, byte[] secretKey) {
byte[] result = null;
try {
// Create the HMAC/SHA1 key
SecretKeySpec signingKey = new SecretKeySpec(secretKey, "HmacSHA1");
// Create the message authentication code (MAC)
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(signingKey);
// Compute the HMAC value
result = mac.doFinal(source.getBytes());
} catch (NoSuchAlgorithmException nsae) {
throw new RuntimeException(
"Could not find the SHA-1 algorithm. HMac conversion failed.",
nsae);
} catch (InvalidKeyException ike) {
throw new RuntimeException(
"Invalid key exception detected. HMac conversion failed.",
ike);
}
return result;
}
/**
* Converts a source string to its HMAC/SHA-1 value.
*
* @param source
* The source string to convert.
* @param secretKey
* The secret key to use for conversion.
* @return The HMac value of the source string.
*/
public static byte[] toHMacSha1(String source, String secretKey) {
return toHMacSha1(source, secretKey.getBytes());
}
/**
* Converts a source string to its HMAC/SHA256 value.
*
* @param source
* The source string to convert.
* @param secretKey
* The secret key to use for conversion.
* @return The HMac value of the source string.
*/
public static byte[] toHMacSha256(String source, byte[] secretKey) {
byte[] result = null;
try {
// Create the HMAC/SHA256 key
SecretKeySpec signingKey = new SecretKeySpec(secretKey,
"HmacSHA256");
// Create the message authentication code (MAC)
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(signingKey);
// Compute the HMAC value
result = mac.doFinal(source.getBytes("UTF-8"));
} catch (NoSuchAlgorithmException nsae) {
throw new RuntimeException(
"Could not find the SHA256 algorithm. HMac conversion failed.",
nsae);
} catch (InvalidKeyException ike) {
throw new RuntimeException(
"Invalid key exception detected. HMac conversion failed.",
ike);
} catch (IllegalStateException ise) {
throw new RuntimeException(
"IIllegal state exception detected. HMac conversion failed.",
ise);
} catch (UnsupportedEncodingException uee) {
throw new RuntimeException(
"Unsuported encoding UTF-8. HMac conversion failed.", uee);
}
return result;
}
/**
* Converts a source string to its HMAC/SHA256 value.
*
* @param source
* The source string to convert.
* @param secretKey
* The secret key to use for conversion.
* @return The HMac value of the source string.
*/
public static byte[] toHMacSha256(String source, String secretKey) {
return toHMacSha256(source, secretKey.getBytes());
}
/**
* Return the HTTP DIGEST hashed secret. It concatenates the identifier,
* realm and secret, separated by a comma and digest them using MD5.
*
* @param identifier
* The user identifier to hash.
* @param secret
* The user secret.
* @param realm
* The authentication realm.
* @return A hash of the user name, realm, and password, specified as A1 in
* section 3.2.2.2 of RFC2617, or null if the identifier has no
* corresponding secret.
*/
public static String toHttpDigest(String identifier, char[] secret,
String realm) {
if (secret != null) {
return toMd5(identifier + ":" + realm + ":" + new String(secret));
}
return null;
}
/**
* Returns the MD5 digest of the target string. Target is decoded to bytes
* using the US-ASCII charset. The returned hexadecimal String always
* contains 32 lowercase alphanumeric characters. For example, if target is
* "HelloWorld", this method returns "68e109f0f40ca72a15e05cc22786f8e6".
*
* @param target
* The string to encode.
* @return The MD5 digest of the target string.
*/
public static String toMd5(String target) {
try {
return toMd5(target, "US-ASCII");
} catch (UnsupportedEncodingException uee) {
// Unlikely, US-ASCII comes with every JVM
throw new RuntimeException(
"US-ASCII is an unsupported encoding, unable to compute MD5");
}
}
/**
* Returns the MD5 digest of target string. Target is decoded to bytes using
* the named charset. The returned hexadecimal String always contains 32
* lowercase alphanumeric characters. For example, if target is
* "HelloWorld", this method returns "68e109f0f40ca72a15e05cc22786f8e6".
*
* @param target
* The string to encode.
* @param charsetName
* The character set.
* @return The MD5 digest of the target string.
*
* @throws UnsupportedEncodingException
*/
public static String toMd5(String target, String charsetName)
throws UnsupportedEncodingException {
try {
final byte[] md5 = MessageDigest.getInstance("MD5").digest(
target.getBytes(charsetName));
final char[] md5Chars = new char[32];
int i = 0;
for (final byte b : md5) {
md5Chars[i++] = HEXDIGITS[(b >> 4) & 0xF];
md5Chars[i++] = HEXDIGITS[b & 0xF];
}
return new String(md5Chars);
} catch (NoSuchAlgorithmException nsae) {
throw new RuntimeException(
"No MD5 algorithm, unable to compute MD5");
}
}
/**
* Returns the SHA1 digest of the target string. Target is decoded to bytes
* using the US-ASCII charset.
*
* @param target
* The string to encode.
* @return The MD5 digest of the target string.
*/
public static String toSha1(String target) {
try {
return toSha1(target, "US-ASCII");
} catch (UnsupportedEncodingException uee) {
// Unlikely, US-ASCII comes with every JVM
throw new RuntimeException(
"US-ASCII is an unsupported encoding, unable to compute SHA1");
}
}
/**
* Returns the SHA1 digest of target string. Target is decoded to bytes
* using the named charset.
*
* @param target
* The string to encode.
* @param charsetName
* The character set.
* @return The SHA1 digest of the target string.
*
* @throws UnsupportedEncodingException
*/
public static String toSha1(String target, String charsetName)
throws UnsupportedEncodingException {
try {
return Base64.encode(
MessageDigest.getInstance("SHA1").digest(
target.getBytes(charsetName)), false);
} catch (NoSuchAlgorithmException nsae) {
throw new RuntimeException(
"No SHA1 algorithm, unable to compute SHA1");
}
}
/**
* Private constructor to ensure that the class acts as a true utility class
* i.e. it isn't instantiable and extensible.
*/
private DigestUtils() {
}
}