/**
* Copyright (c) 2009--2014 Red Hat, Inc.
*
* This software is licensed to you under the GNU General Public License,
* version 2 (GPLv2). There is NO WARRANTY for this software, express or
* implied, including the implied warranties of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
* along with this software; if not, see
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
*
* Red Hat trademarks are not licensed under GPLv2. No permission is
* granted to use or replicate Red Hat trademarks that are incorporated
* in this software or its documentation.
*/
package com.redhat.rhn.common.security;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
/**
* A class to generate Hashed Message Authentication Codes.
*
* @version $Rev$
*/
public class HMAC {
private HMAC() {
}
private static final String HEXCHARS = "0123456789abcdef";
/**
* Convert a byte array to a hex string of the format
* "1f 30 b7". package protected so that SessionSwap can use it.
* @param a The byte array to convert
* @return the resulting hex string
*/
public static String byteArrayToHex(byte[] a) {
int hn, ln, cx;
StringBuilder buf = new StringBuilder(a.length * 2);
for (cx = 0; cx < a.length; cx++) {
hn = ((a[cx]) & 0x00ff) / 16;
ln = (a[cx]) & 0x000f;
buf.append(HEXCHARS.charAt(hn));
buf.append(HEXCHARS.charAt(ln));
}
return buf.toString();
}
/**
* Generate an HMAC hash for the given text and key using SHA1 as the
* hash function.
* @param text The text to hash
* @param key The key to use when generating the hash.
* @return The resulting hash string
*/
public static String sha1(String text, String key) {
try {
SecretKey skey = new SecretKeySpec(key.getBytes(), "HMACSHA1");
Mac mac = Mac.getInstance(skey.getAlgorithm());
mac.init(skey);
mac.update(text.getBytes());
return byteArrayToHex(mac.doFinal());
}
catch (NoSuchAlgorithmException e) {
throw new IllegalArgumentException("No such alg: " + e);
}
catch (InvalidKeyException e) {
throw new IllegalArgumentException("Invalid key: " + e);
}
}
/**
* Generate an HMAC hash for the given text and key using MD5 as the
* hash function.
* @param text The text to hash
* @param key The key to use when generating the hash.
* @return The resulting hash string
* TODO: Make this use the JDK MD5 algorithm as above vs
* the custom one below.
*/
public static String md5(String text, String key) {
return generate(text, key, "MD5");
}
private static String generate(String text, String key, String algorithm) {
MessageDigest msgDigest = null;
try {
msgDigest = MessageDigest.getInstance(algorithm);
}
catch (NoSuchAlgorithmException e) {
throw new IllegalArgumentException("Algorithm " + algorithm +
" not found");
}
byte[] keyBytes;
/* if key is longer than 64 bytes reset it to key=MD5(key) */
if (key.length() > 64) {
msgDigest.update(key.getBytes());
keyBytes = msgDigest.digest();
msgDigest.reset();
}
else {
keyBytes = key.getBytes();
}
byte[] temp = new byte[64];
for (int i = 0; i < 64; i++) {
if (i < keyBytes.length) {
temp[i] = keyBytes[i];
}
else {
temp[i] = 0;
}
}
keyBytes = temp;
/*
* the HMAC_MD5 transform looks like:
*
* MD5(K XOR opad, MD5(K XOR ipad, text))
*
* where K is an n byte key
* ipad is the byte 0x36 repeated 64 times
* opad is the byte 0x5c repeated 64 times
* and text is the data being protected
*/
/* start out by storing key in pads */
byte[] iPad = new byte[64];
byte[] oPad = new byte[64];
/* XOR key with ipad and opad values */
for (int i = 0; i < 64; i++) {
iPad[i] = (byte)(keyBytes[i] ^ 0x36);
oPad[i] = (byte)(keyBytes[i] ^ 0x5c);
}
msgDigest.update(iPad);
msgDigest.update(text.getBytes());
byte[] digest = msgDigest.digest();
msgDigest.reset();
msgDigest.update(oPad);
msgDigest.update(digest);
byte[] res = msgDigest.digest();
return byteArrayToHex(res);
}
}