gnu.crypto.mac.HMac Maven / Gradle / Ivy
The newest version!
package gnu.crypto.mac;
// ----------------------------------------------------------------------------
// $Id: HMac.java,v 1.8 2003/09/28 04:53:38 raif Exp $
//
// Copyright (C) 2001, 2002, 2003, Free Software Foundation, Inc.
//
// This file is part of GNU Crypto.
//
// GNU Crypto is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2, or (at your option)
// any later version.
//
// GNU Crypto 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 for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; see the file COPYING. If not, write to the
//
// Free Software Foundation Inc.,
// 59 Temple Place - Suite 330,
// Boston, MA 02111-1307
// USA
//
// Linking this library statically or dynamically with other modules is
// making a combined work based on this library. Thus, the terms and
// conditions of the GNU General Public License cover the whole
// combination.
//
// As a special exception, the copyright holders of this library give
// you permission to link this library with independent modules to
// produce an executable, regardless of the license terms of these
// independent modules, and to copy and distribute the resulting
// executable under terms of your choice, provided that you also meet,
// for each linked independent module, the terms and conditions of the
// license of that module. An independent module is a module which is
// not derived from or based on this library. If you modify this
// library, you may extend this exception to your version of the
// library, but you are not obligated to do so. If you do not wish to
// do so, delete this exception statement from your version.
// ----------------------------------------------------------------------------
import gnu.crypto.Registry;
import gnu.crypto.hash.IMessageDigest;
import gnu.crypto.hash.MD5;
import gnu.crypto.util.Util;
import java.security.InvalidKeyException;
import java.util.HashMap;
import java.util.Map;
/**
* The implementation of the HMAC (Keyed-Hash Message Authentication
* Code).
*
* HMAC can be used in combination with any iterated cryptographic
* hash function. HMAC also uses a secret key for calculation and
* verification of the message authentication values. The main goals behind this
* construction are
*
*
* - To use, without modifications, available hash functions. In
* particular, hash functions that perform well in software, and for which
* code is freely and widely available.
*
* - To preserve the original performance of the hash function without
* incurring a significant degradation.
*
* - To use and handle keys in a simple way.
*
* - To have a well understood cryptographic analysis of the strength of
* the authentication mechanism based on reasonable assumptions on the
* underlying hash function.
*
* - To allow for easy replaceability of the underlying hash function in
* case that faster or more secure hash functions are found or required.
*
*
* References:
*
*
* - RFC 2104HMAC:
* Keyed-Hashing for Message Authentication.
* H. Krawczyk, M. Bellare, and R. Canetti.
*
*
* @version $Revision: 1.8 $
*/
public class HMac extends BaseMac {
// Constants and variables
// -------------------------------------------------------------------------
public static final String USE_WITH_PKCS5_V2 = "gnu.crypto.hmac.pkcs5";
private static final byte IPAD_BYTE = 0x36;
private static final byte OPAD_BYTE = 0x5C;
/** caches the result of the correctness test, once executed. */
private static Boolean valid;
protected int macSize;
protected int blockSize;
protected IMessageDigest ipadHash;
protected IMessageDigest opadHash;
protected byte[] ipad;
// Constructor(s)
// -------------------------------------------------------------------------
/**
* Trivial constructor for use by concrete subclasses.
*
* @param underlyingHash the underlying hash algorithm instance.
*/
protected HMac(IMessageDigest underlyingHash) {
super(Registry.HMAC_NAME_PREFIX + underlyingHash.name(), underlyingHash);
this.blockSize = underlyingHash.blockSize();
this.macSize = underlyingHash.hashSize();
ipadHash = opadHash = null;
}
// Class methods
// -------------------------------------------------------------------------
// Instance methods
// -------------------------------------------------------------------------
// java.lang.Cloneable interface implementation ----------------------------
public Object clone() {
HMac result = new HMac((IMessageDigest) this.underlyingHash.clone());
result.truncatedSize = this.truncatedSize;
if (this.ipadHash != null) {
result.ipadHash = (IMessageDigest) this.ipadHash.clone();
}
if (this.opadHash != null) {
result.opadHash = (IMessageDigest) this.opadHash.clone();
}
if (this.ipad != null) {
result.ipad = (byte[]) this.ipad.clone();
}
return result;
}
// implementation of abstract methods in BaseMac ---------------------------
public void init(Map attributes)
throws InvalidKeyException, IllegalStateException {
Integer ts = (Integer) attributes.get(TRUNCATED_SIZE);
truncatedSize = (ts == null ? macSize : ts.intValue());
if (truncatedSize < (macSize / 2)) {
throw new IllegalArgumentException("Truncated size too small");
} else if (truncatedSize < 10) {
throw new IllegalArgumentException("Truncated size less than 80 bits");
}
// we dont use/save the key outside this method
byte[] K = (byte[]) attributes.get(MAC_KEY_MATERIAL);
if (K == null) { // take it as an indication to re-use previous key if set
if (ipadHash == null) {
throw new InvalidKeyException("Null key");
}
// we already went through the motions; ie. up to step #4. re-use
underlyingHash = (IMessageDigest) ipadHash.clone();
return;
}
// for HMACs used in key-derivation functions (e.g. PBKDF2) the key
// material need not be >= the (output) block size of the underlying
// algorithm
Boolean pkcs5 = (Boolean) attributes.get(USE_WITH_PKCS5_V2);
if (pkcs5 == null) {
pkcs5 = Boolean.FALSE;
}
if (K.length < macSize && !pkcs5.booleanValue()) {
throw new InvalidKeyException("Key too short");
}
if (K.length > blockSize) {
// (0) replace K with HASH(K) if K is larger than the hash's
// block size. Then pad with zeros until it is the correct
// size (the next `if').
underlyingHash.update(K, 0, K.length);
K = underlyingHash.digest();
}
if (K.length < blockSize) {
// (1) append zeros to the end of K to create a B byte string
// (e.g., if K is of length 20 bytes and B=64, then K will be
// appended with 44 zero bytes 0x00)
int limit = (K.length > blockSize) ? blockSize : K.length;
byte[] newK = new byte[blockSize];
System.arraycopy(K, 0, newK, 0, limit);
K = newK;
}
underlyingHash.reset();
opadHash = (IMessageDigest) underlyingHash.clone();
if (ipad == null) {
ipad = new byte[blockSize];
}
// (2) XOR (bitwise exclusive-OR) the B byte string computed in step
// (1) with ipad
// (3) append the stream of data 'text' to the B byte string resulting
// from step (2)
// (4) apply H to the stream generated in step (3)
for (int i = 0; i < blockSize; i++) {
// underlyingHash.update((byte)(K[i] ^ IPAD_BYTE));
ipad[i] = (byte)(K[i] ^ IPAD_BYTE);
}
for (int i = 0; i < blockSize; i++) {
opadHash.update((byte)(K[i] ^ OPAD_BYTE));
}
underlyingHash.update(ipad, 0, blockSize);
ipadHash = (IMessageDigest) underlyingHash.clone();
K = null;
}
public void reset() {
super.reset();
if (ipad != null) {
underlyingHash.update(ipad, 0, blockSize);
ipadHash = (IMessageDigest) underlyingHash.clone();
}
}
public byte[] digest() {
if (ipadHash == null) {
throw new IllegalStateException("HMAC not initialised");
}
byte[] out = underlyingHash.digest();
// (5) XOR (bitwise exclusive-OR) the B byte string computed in
// step (1) with opad
underlyingHash = (IMessageDigest) opadHash.clone();
// (6) append the H result from step (4) to the B byte string
// resulting from step (5)
underlyingHash.update(out, 0, macSize);
// (7) apply H to the stream generated in step (6) and output
// the result
out = underlyingHash.digest(); // which also resets the underlying hash
// truncate and return
if (truncatedSize == macSize)
return out;
byte[] result = new byte[truncatedSize];
System.arraycopy(out, 0, result, 0, truncatedSize);
return result;
}
public boolean selfTest() {
if (valid == null) {
try {
IMac mac = new HMac(new MD5()); // use rfc-2104 test vectors
String tv1 = "9294727A3638BB1C13F48EF8158BFC9D";
String tv3 = "56BE34521D144C88DBB8C733F0E8B3F6";
byte[] k1 = new byte[] {
0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B,
0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B
};
byte[] k3 = new byte[] {
(byte) 0xAA, (byte) 0xAA, (byte) 0xAA, (byte) 0xAA,
(byte) 0xAA, (byte) 0xAA, (byte) 0xAA, (byte) 0xAA,
(byte) 0xAA, (byte) 0xAA, (byte) 0xAA, (byte) 0xAA,
(byte) 0xAA, (byte) 0xAA, (byte) 0xAA, (byte) 0xAA
};
byte[] data = new byte[50];
for (int i = 0; i < 50; ) {
data[i++] = (byte) 0xDD;
}
HashMap map = new HashMap();
// test vector #1
map.put(MAC_KEY_MATERIAL, k1);
mac.init(map);
mac.update("Hi There".getBytes("ASCII"), 0, 8);
if (!tv1.equals(Util.toString(mac.digest()))) {
valid = Boolean.FALSE;
}
// test #2 is not used since it causes a "Key too short" exception
// test vector #3
map.put(MAC_KEY_MATERIAL, k3);
mac.init(map);
mac.update(data, 0, 50);
if (!tv3.equals(Util.toString(mac.digest()))) {
valid = Boolean.FALSE;
}
valid = Boolean.TRUE;
} catch (Exception x) {
x.printStackTrace(System.err);
valid = Boolean.FALSE;
}
}
return valid.booleanValue();
}
}