package gnu.crypto.mac;
// ----------------------------------------------------------------------------
// $Id: UHash32.java,v 1.7 2005/10/06 04:24:16 rsdio 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.,
// 51 Franklin Street, Fifth Floor,
// Boston, MA 02110-1301
// 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.cipher.IBlockCipher;
import gnu.crypto.prng.IRandom;
import gnu.crypto.prng.LimitReachedException;
import gnu.crypto.prng.UMacGenerator;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.util.HashMap;
import java.util.Map;
/**
* <p><i>UHASH</i> is a keyed hash function, which takes as input a string of
* arbitrary length, and produces as output a string of fixed length (such as 8
* bytes). The actual output length depends on the parameter UMAC-OUTPUT-LEN.</p>
*
* <p><i>UHASH</i> has been shown to be <i>epsilon-ASU</i> ("Almost Strongly
* Universal"), where epsilon is a small (parameter-dependent) real number.
* Informally, saying that a keyed hash function is <i>epsilon-ASU</i> means
* that for any two distinct fixed input strings, the two outputs of the hash
* function with a random key "look almost like a pair of random strings". The
* number epsilon measures how non-random the output strings may be.</p>
*
* <i>UHASH</i> has been designed to be fast by exploiting several architectural
* features of modern commodity processors. It was specifically designed for use
* in <i>UMAC</i>. But <i>UHASH</i> is useful beyond that domain, and can be
* easily adopted for other purposes.</p>
*
* <i>UHASH</i> does its work in three layers. First, a hash function called
* <code>NH</code> is used to compress input messages into strings which are
* typically many times smaller than the input message. Second, the compressed
* message is hashed with an optimized <i>polynomial hash function</i> into a
* fixed-length 16-byte string. Finally, the 16-byte string is hashed using an
* <i>inner-product hash</i> into a string of length WORD-LEN bytes. These three
* layers are repeated (with a modified key) until the outputs total
* UMAC-OUTPUT-LEN bytes.</p>
*
* <p>References:</p>
*
* <ol>
* <li><a href="http://www.ietf.org/internet-drafts/draft-krovetz-umac-01.txt">
* UMAC</a>: Message Authentication Code using Universal Hashing.<br>
* T. Krovetz, J. Black, S. Halevi, A. Hevia, H. Krawczyk, and P. Rogaway.</li>
* </ol>
*
* @version $Revision: 1.7 $
*/
public class UHash32 extends BaseMac {
// Constants and variables
// -------------------------------------------------------------------------
// UMAC prime values
private static final BigInteger PRIME_19 = BigInteger.valueOf(0x7FFFFL);
private static final BigInteger PRIME_32 = BigInteger.valueOf(0xFFFFFFFBL);
private static final BigInteger PRIME_36 = BigInteger.valueOf(0xFFFFFFFFBL);
private static final BigInteger PRIME_64 = new BigInteger(1,
new byte[] {
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xC5
});
private static final BigInteger PRIME_128 = new BigInteger(1,
new byte[] {
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x61
});
static final BigInteger TWO = BigInteger.valueOf(2L);
static final long BOUNDARY = TWO.shiftLeft(17).longValue();
// 2**64 - 2**32
static final BigInteger LOWER_RANGE = TWO.pow(64).subtract(TWO.pow(32));
// 2**128 - 2**96
static final BigInteger UPPER_RANGE = TWO.pow(128).subtract(TWO.pow(96));
static final byte[] ALL_ZEROES = new byte[32];
int streams;
L1Hash32[] l1hash;
// Constructor(s)
// -------------------------------------------------------------------------
/** Trivial 0-arguments constructor. */
public UHash32() {
super("uhash32");
}
/**
* <p>Private constructor for cloning purposes.</p>
*
* @param that the instance to clone.
*/
private UHash32(UHash32 that) {
this();
this.streams = that.streams;
if (that.l1hash != null) {
// this.l1hash = new L1Hash32[that.l1hash.length];
this.l1hash = new L1Hash32[that.streams];
// for (int i = 0; i < that.l1hash.length; i++) {
for (int i = 0; i < that.streams; i++) {
if (that.l1hash[i] != null) {
this.l1hash[i] = (L1Hash32) that.l1hash[i].clone();
}
}
}
}
// Class methods
// -------------------------------------------------------------------------
/**
* <p>The prime numbers used in UMAC are:</p>
* <pre>
* +-----+--------------------+---------------------------------------+
* | x | prime(x) [Decimal] | prime(x) [Hexadecimal] |
* +-----+--------------------+---------------------------------------+
* | 19 | 2^19 - 1 | 0x0007FFFF |
* | 32 | 2^32 - 5 | 0xFFFFFFFB |
* | 36 | 2^36 - 5 | 0x0000000F FFFFFFFB |
* | 64 | 2^64 - 59 | 0xFFFFFFFF FFFFFFC5 |
* | 128 | 2^128 - 159 | 0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFF61 |
* +-----+--------------------+---------------------------------------+
*</pre>
*
* @param n a number of bits.
* @return the largest prime number less than 2**n.
*/
static final BigInteger prime(int n) {
switch (n) {
case 19: return PRIME_19;
case 32: return PRIME_32;
case 36: return PRIME_36;
case 64: return PRIME_64;
case 128: return PRIME_128;
default: throw new IllegalArgumentException("Undefined prime("+String.valueOf(n)+")");
}
}
// Instance methods
// -------------------------------------------------------------------------
// java.lang.Cloneable interface implementation ----------------------------
public Object clone() {
return new UHash32(this);
}
// gnu.crypto.mac.IMac interface implementation ----------------------------
public int macSize() {
return UMac32.OUTPUT_LEN;
}
public void init(Map attributes)
throws InvalidKeyException, IllegalStateException {
byte[] K = (byte[]) attributes.get(MAC_KEY_MATERIAL);
if (K == null) {
throw new InvalidKeyException("Null Key");
}
if (K.length != UMac32.KEY_LEN) {
throw new InvalidKeyException("Invalid Key length: "+String.valueOf(K.length));
}
// Calculate iterations needed to make UMAC-OUTPUT-LEN bytes
streams = (UMac32.OUTPUT_LEN + 3) / 4;
// Define total key needed for all iterations using UMacGenerator.
// L1Key and L3Key1 both reuse most key between iterations.
IRandom kdf1 = new UMacGenerator();
IRandom kdf2 = new UMacGenerator();
IRandom kdf3 = new UMacGenerator();
IRandom kdf4 = new UMacGenerator();
Map map = new HashMap();
map.put(IBlockCipher.KEY_MATERIAL, K);
map.put(UMacGenerator.INDEX, new Integer(0));
kdf1.init(map);
map.put(UMacGenerator.INDEX, new Integer(1));
kdf2.init(map);
map.put(UMacGenerator.INDEX, new Integer(2));
kdf3.init(map);
map.put(UMacGenerator.INDEX, new Integer(3));
kdf4.init(map);
// need to generate all bytes for use later in a Toepliz construction
byte[] L1Key = new byte[UMac32.L1_KEY_LEN + (streams - 1) * 16];
try {
kdf1.nextBytes(L1Key, 0, L1Key.length);
} catch (LimitReachedException x) {
x.printStackTrace(System.err);
throw new RuntimeException("KDF for L1Key reached limit");
}
l1hash = new L1Hash32[streams];
for (int i = 0; i < streams; i++) {
byte[] k1 = new byte[UMac32.L1_KEY_LEN];
System.arraycopy(L1Key, i * 16, k1, 0, UMac32.L1_KEY_LEN);
byte[] k2 = new byte[24];
try {
kdf2.nextBytes(k2, 0, 24);
} catch (LimitReachedException x) {
x.printStackTrace(System.err);
throw new RuntimeException("KDF for L2Key reached limit");
}
byte[] k31 = new byte[64];
try {
kdf3.nextBytes(k31, 0, 64);
} catch (LimitReachedException x) {
x.printStackTrace(System.err);
throw new RuntimeException("KDF for L3Key1 reached limit");
}
byte[] k32 = new byte[4];
try {
kdf4.nextBytes(k32, 0, 4);
} catch (LimitReachedException x) {
x.printStackTrace(System.err);
throw new RuntimeException("KDF for L3Key2 reached limit");
}
L1Hash32 mac = new L1Hash32();
mac.init(k1, k2, k31, k32);
l1hash[i] = mac;
}
}
public void update(byte b) {
for (int i = 0; i < streams; i++) {
l1hash[i].update(b);
}
}
public void update(byte[] b, int offset, int len) {
for (int i = 0; i < len; i++) {
this.update(b[offset + i]);
}
}
public byte[] digest() {
byte[] result = new byte[UMac32.OUTPUT_LEN];
for (int i = 0; i < streams; i++) {
byte[] partialResult = l1hash[i].digest();
System.arraycopy(partialResult, 0, result, 4*i, 4);
}
reset();
return result;
}
public void reset() {
for (int i = 0; i < streams; i++) {
l1hash[i].reset();
}
}
public boolean selfTest() {
return true;
}
// helper methods ----------------------------------------------------------
// Inner classes
// =========================================================================
/**
* First hash stage of the UHash32 algorithm.
*/
class L1Hash32 implements Cloneable {
// Constants and variables
// ----------------------------------------------------------------------
private int[] key; // key material as an array of 32-bit ints
private byte[] buffer; // work buffer L1_KEY_LEN long
private int count; // meaningful bytes in buffer
private ByteArrayOutputStream Y;
// private byte[] y;
private long totalCount;
private L2Hash32 l2hash;
private L3Hash32 l3hash;
// Constructor(s)
// ----------------------------------------------------------------------
/** Trivial 0-arguments constructor. */
L1Hash32() {
super();
key = new int[UMac32.L1_KEY_LEN / 4];
buffer = new byte[UMac32.L1_KEY_LEN];
count = 0;
Y = new ByteArrayOutputStream();
totalCount = 0L;
}
/**
* <p>Private constructor for cloning purposes.</p>
*
* @param that the instance to clone.
*/
private L1Hash32(L1Hash32 that) {
this();
System.arraycopy(that.key, 0, this.key, 0, that.key.length);
System.arraycopy(that.buffer, 0, this.buffer, 0, that.count);
this.count = that.count;
byte[] otherY = that.Y.toByteArray();
this.Y.write(otherY, 0, otherY.length);
this.totalCount = that.totalCount;
if (that.l2hash != null) {
this.l2hash = (L2Hash32) that.l2hash.clone();
}
if (that.l3hash != null) {
this.l3hash = (L3Hash32) that.l3hash.clone();
}
}
// Class methods
// ----------------------------------------------------------------------
// Instance methods
// ----------------------------------------------------------------------
// java.lang.Cloneable interface implementation -------------------------
public Object clone() {
return new L1Hash32(this);
}
// other instance methods -----------------------------------------------
public void init(byte[] k1, byte[] k2, byte[] k31, byte[] k32) {
for (int i = 0, j = 0; i < (UMac32.L1_KEY_LEN / 4); i++) {
key[i] = k1[j++] << 24 |
(k1[j++] & 0xFF) << 16 |
(k1[j++] & 0xFF) << 8 |
(k1[j++] & 0xFF);
}
l2hash = new L2Hash32(k2);
l3hash = new L3Hash32(k31, k32);
}
public void update(byte b) {
// Break M into L1_KEY_LEN byte chunks (final chunk may be shorter)
// Let M_1, M_2, ..., M_t be strings so that M = M_1 || M_2 || .. ||
// M_t, and length(M_i) = L1_KEY_LEN for all 0 < i < t.
// For each chunk, except the last: endian-adjust, NH hash
// and add bit-length. Use results to build Y.
buffer[count] = b;
count++;
totalCount++;
if (count >= UMac32.L1_KEY_LEN) {
byte[] y = nh32(UMac32.L1_KEY_LEN);
Y.write(y, 0, 8);
count = 0;
// For each iteration, extract key and three-layer hash.
// If length(M) <= L1_KEY_LEN, then skip L2-HASH.
if (Y.size() == 16) { // we already hashed twice L1_KEY_LEN
byte[] A = Y.toByteArray();
Y.reset();
l2hash.update(A, 0, 16);
}
}
}
public byte[] digest() {
// For the last chunk: pad to 32-byte boundary, endian-adjust,
// NH hash and add bit-length. Concatenate the result to Y.
if (count != 0) {
if (count % 32 != 0) {
int limit = 32 * ((count + 31) / 32);
System.arraycopy(ALL_ZEROES, 0, buffer, count, limit - count);
count += limit - count;
}
byte[] y = nh32(count);
Y.write(y, 0, 8);
}
byte[] A = Y.toByteArray();
Y.reset();
byte[] B;
if (totalCount <= UMac32.L1_KEY_LEN) {
// we might have 'update'd the bytes already. check
if (A.length == 0) { // we did
B = l2hash.digest();
} else { // did not
B = new byte[16];
System.arraycopy(A, 0, B, 8, 8);
}
} else {
if (A.length != 0) {
l2hash.update(A, 0, A.length);
}
B = l2hash.digest();
}
byte[] result = l3hash.digest(B);
reset();
return result;
}
public void reset() {
count = 0;
Y.reset();
totalCount = 0L;
if (l2hash != null) {
l2hash.reset();
}
}
// helper methods -------------------------------------------------------
/**
* 5.1 NH-32: NH hashing with a 32-bit word size.
*
* @param len count of bytes, divisible by 32, in buffer to process
* @return Y, string of length 8 bytes.
*/
private byte[] nh32(int len) {
// Break M and K into 4-byte chunks
int t = len / 4;
// Let M_1, M_2, ..., M_t be 4-byte strings
// so that M = M_1 || M_2 || .. || M_t.
// Let K_1, K_2, ..., K_t be 4-byte strings
// so that K_1 || K_2 || .. || K_t is a prefix of K.
int[] m = new int[t];
int i;
int j = 0;
for (i = 0, j = 0; i < t; i++) {
m[i] = buffer[j++] << 24 |
(buffer[j++] & 0xFF) << 16 |
(buffer[j++] & 0xFF) << 8 |
(buffer[j++] & 0xFF);
}
// Perform NH hash on the chunks, pairing words for multiplication
// which are 4 apart to accommodate vector-parallelism.
long result = len * 8L;
for (i = 0; i < t; i += 8) {
result += ((m[i+0] + key[i+0]) & 0xFFFFFFFFL) * ((m[i+4] + key[i+4]) & 0xFFFFFFFFL);
result += ((m[i+1] + key[i+1]) & 0xFFFFFFFFL) * ((m[i+5] + key[i+5]) & 0xFFFFFFFFL);
result += ((m[i+2] + key[i+2]) & 0xFFFFFFFFL) * ((m[i+6] + key[i+6]) & 0xFFFFFFFFL);
result += ((m[i+3] + key[i+3]) & 0xFFFFFFFFL) * ((m[i+7] + key[i+7]) & 0xFFFFFFFFL);
}
return new byte[] {
(byte)(result >>> 56), (byte)(result >>> 48),
(byte)(result >>> 40), (byte)(result >>> 32),
(byte)(result >>> 24), (byte)(result >>> 16),
(byte)(result >>> 8), (byte) result
};
}
}
// =========================================================================
/**
* <p>Second hash stage of the UHash32 algorithm.</p>
*
* 5.4 L2-HASH-32: Second-layer hash.<p>
* <ul>
* <li>Input:<br>
* K string of length 24 bytes.<br>
* M string of length less than 2^64 bytes.</li>
* <li>Returns:<br>
* Y, string of length 16 bytes.</li>
* </ul>
*/
class L2Hash32 implements Cloneable {
// Constants and variables
// ----------------------------------------------------------------------
private BigInteger k64, k128;
private BigInteger y;
private boolean highBound;
private long bytesSoFar;
private ByteArrayOutputStream buffer;
// Constructor(s)
// ----------------------------------------------------------------------
L2Hash32(byte[] K) {
super();
if (K.length != 24) {
throw new ExceptionInInitializerError("K length is not 24");
}
// Extract keys and restrict to special key-sets
// Mask64 = uint2str(0x01FFFFFF01FFFFFF, 8);
// Mask128 = uint2str(0x01FFFFFF01FFFFFF01FFFFFF01FFFFFF, 16);
// k64 = str2uint(K[1..8] and Mask64);
// k128 = str2uint(K[9..24] and Mask128);
int i = 0;
k64 = new BigInteger(1, new byte[] {
(byte)(K[i++] & 0x01), (byte)(K[i++] & 0xFF),
(byte)(K[i++] & 0xFF), (byte)(K[i++] & 0xFF),
(byte)(K[i++] & 0x01), (byte)(K[i++] & 0xFF),
(byte)(K[i++] & 0xFF), (byte)(K[i++] & 0xFF)
});
k128 = new BigInteger(1, new byte[] {
(byte)(K[i++] & 0x01), (byte)(K[i++] & 0xFF),
(byte)(K[i++] & 0xFF), (byte)(K[i++] & 0xFF),
(byte)(K[i++] & 0x01), (byte)(K[i++] & 0xFF),
(byte)(K[i++] & 0xFF), (byte)(K[i++] & 0xFF),
(byte)(K[i++] & 0x01), (byte)(K[i++] & 0xFF),
(byte)(K[i++] & 0xFF), (byte)(K[i++] & 0xFF),
(byte)(K[i++] & 0x01), (byte)(K[i++] & 0xFF),
(byte)(K[i++] & 0xFF), (byte)(K[i++] & 0xFF)
});
y = BigInteger.ONE;
highBound = false;
bytesSoFar = 0L;
}
private L2Hash32(L2Hash32 that) {
super();
this.k64 = that.k64;
this.k128 = that.k128;
this.y = that.y;
this.highBound = that.highBound;
this.bytesSoFar = that.bytesSoFar;
if (that.buffer != null) {
byte[] thatbuffer = that.buffer.toByteArray();
this.buffer = new ByteArrayOutputStream();
this.buffer.write(thatbuffer, 0, thatbuffer.length);
}
}
// Class methods
// ----------------------------------------------------------------------
// Instance methods
// ----------------------------------------------------------------------
// java.lang.Cloneable interface implementation -------------------------
public Object clone() {
return new L2Hash32(this);
}
// other instance methods -----------------------------------------------
// this is called with either 8-bytes or 16-bytes
void update(byte[] b, int offset, int len) {
if (len == 0) {
return;
}
if (!highBound) { // do the first (only?) 8-bytes
poly(64, LOWER_RANGE, k64, b, offset, 8);
bytesSoFar += 8L;
highBound = (bytesSoFar > BOUNDARY);
if (highBound) { // if we just crossed the limit then process y
poly(128, UPPER_RANGE, k128, yTo16bytes(), 0, 16);
buffer = new ByteArrayOutputStream();
}
// do the rest if any
update(b, offset + 8, len -8);
} else { // we're already beyond the 2**17 bytes size limit
// process in chuncks of 16
buffer.write(b, offset, len);
if (buffer.size() > 16) {
byte[] bb = buffer.toByteArray();
poly(128, UPPER_RANGE, k128, bb, 0, 16);
if (bb.length > 16) {
buffer.write(bb, 16, bb.length - 16);
}
}
}
}
byte[] digest() {
// If M no more than 2^17 bytes, hash under 64-bit prime,
// otherwise, hash first 2^17 bytes under 64-bit prime and
// remainder under 128-bit prime.
if (!highBound) { // y is up-to-date
// do nothing
} else { // we may have some bytes in buffer
byte[] bb = buffer.toByteArray();
byte[] lastBlock = new byte[16];
System.arraycopy(bb, 0, lastBlock, 0, bb.length);
lastBlock[bb.length] = (byte) 0x80;
poly(128, UPPER_RANGE, k128, lastBlock, 0, 16);
}
byte[] result = yTo16bytes();
reset();
return result;
}
void reset() {
y = BigInteger.ONE;
highBound = false;
bytesSoFar = 0L;
if (buffer != null) {
buffer.reset();
}
}
// helper methods -------------------------------------------------------
private byte[] yTo16bytes() {
byte[] yy = y.toByteArray();
byte[] result = new byte[16];
if (yy.length > 16) {
System.arraycopy(yy, yy.length - 16, result, 0, 16);
} else {
System.arraycopy(yy, 0, result, 16 - yy.length, yy.length);
}
return result;
}
/**
* 5.3 POLY: Polynomial hash
* Function Name: POLY
*
* @param wordbits positive integer divisible by 8: called with 64 or 128.
* @param maxwordrange positive integer less than 2**wordbits.
* @param k integer in the range 0 .. prime(wordbits) - 1.
* @param M string with length divisible by (wordbits / 8) bytes.
* return y, integer in the range 0 .. prime(wordbits) - 1.
*/
private void poly(int wordbits, BigInteger maxwordrange, BigInteger k,
byte[] M, int off, int len) {
byte[] mag = new byte[len];
System.arraycopy(M, off, mag, 0, len);
// Define constants used for fixing out-of-range words
// int wordbytes = wordbits / 8;
BigInteger p = prime(wordbits);
BigInteger offset = TWO.pow(wordbits).subtract(p); // 2^wordbits - p;
BigInteger marker = p.subtract(BigInteger.ONE);
// Break M into chunks of length wordbytes bytes
// long n = M.length / wordbytes;
// Let M_1, M_2, ..., M_n be strings of length wordbytes bytes
// so that M = M_1 || M_2 || .. || M_n
// For each input word, compare it with maxwordrange. If larger
// then hash the words 'marker' and (m - offset), both in range.
// for (int i = 0; i < n; i++) {
BigInteger m = new BigInteger(1, mag);
if (m.compareTo(maxwordrange) >= 0) { // m >= maxwordrange
y = y.multiply(k).add(marker).mod(p); // (k * y + marker) % p;
y = y.multiply(k).add(m.subtract(offset)).mod(p); // (k * y + (m - offset)) % p;
} else {
y = y.multiply(k).add(m).mod(p); // (k * y + m) % p;
}
// }
// return y;
}
}
// =========================================================================
/**
* Third hash stage of the UHash32 algorithm.
*
* Input:
* K1 string of length 64 bytes.
* K2 string of length 4 bytes.
* M string of length 16 bytes.
* Returns:
* Y, string of length 4 bytes.
*/
class L3Hash32 implements Cloneable {
// Constants and variables
// ----------------------------------------------------------------------
private static final long PRIME_36 = 0x0000000FFFFFFFFBL;
private int[] k = new int[9];
// Constructor(s)
// ----------------------------------------------------------------------
/**
*
* @param K1 string of length 64 bytes.
* @param K2 string of length 4 bytes.
*/
L3Hash32(byte[] K1, byte[] K2) {
super();
// pre-conditions
if (K1.length != 64) {
throw new ExceptionInInitializerError("K1 length is not 64");
}
if (K2.length != 4) {
throw new ExceptionInInitializerError("K2 length is not 4");
}
// Break K1 into 8 chunks and convert to integers
// int i = 0;
// for (int j = 0; i < 8; ) {
for (int i = 0, j = 0; i < 8; i++) {
long kk = (K1[j++] & 0xFFL) << 56 |
(K1[j++] & 0xFFL) << 48 |
(K1[j++] & 0xFFL) << 40 |
(K1[j++] & 0xFFL) << 32 |
(K1[j++] & 0xFFL) << 24 |
(K1[j++] & 0xFFL) << 16 |
(K1[j++] & 0xFFL) << 8 |
(K1[j++] & 0xFFL);
// k[i++] = (int)(kk % PRIME_36);
k[i] = (int)(kk % PRIME_36);
}
// k[i] = K2[0] << 24 | (K2[1] & 0xFF) << 16 | (K2[2] & 0xFF) << 8 | (K2[3] & 0xFF);
k[8] = K2[0] << 24 | (K2[1] & 0xFF) << 16 | (K2[2] & 0xFF) << 8 | (K2[3] & 0xFF);
}
private L3Hash32(int[] k) {
super();
this.k = k;
}
// Class methods
// ----------------------------------------------------------------------
// Instance methods
// ----------------------------------------------------------------------
// java.lang.Cloneable interface implementation -------------------------
public Object clone() {
return new L3Hash32((int[]) k.clone());
}
// other instance methods -----------------------------------------------
/**
* @param M string of length 16 bytes.
* @return Y, string of length 4 bytes.
*/
byte[] digest(byte[] M) {
if (M.length != 16) {
throw new IllegalArgumentException("M length is not 16");
}
long m, y = 0L;
for (int i = 0, j = 0; i < 8; i++) {
// Break M into 8 chunks and convert to integers
m = (M[j++] & 0xFFL) << 8 | (M[j++] & 0xFFL);
// Inner-product hash, extract last 32 bits and affine-translate
// y = (m_1 * k_1 + ... + m_8 * k_8) mod prime(36);
// y = y mod 2^32;
y += (m * (k[i] & 0xFFFFFFFFL)) % PRIME_36;
}
int Y = ((int) y) ^ k[8];
return new byte[] {(byte)(Y >>> 24), (byte)(Y >>> 16), (byte)(Y >>> 8), (byte) Y};
}
}
}