/* UMac32.java -- Copyright (C) 2001, 2002, 2003, 2006 Free Software Foundation, Inc. This file is a part of GNU Classpath. GNU Classpath 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 of the License, or (at your option) any later version. GNU Classpath 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 GNU Classpath; if not, write to the Free Software Foundation, Inc., 51 Franklin St, 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. */ package gnu.javax.crypto.mac; import gnu.java.security.Registry; import gnu.java.security.prng.IRandom; import gnu.java.security.prng.LimitReachedException; import gnu.java.security.util.Util; import gnu.javax.crypto.cipher.CipherFactory; import gnu.javax.crypto.cipher.IBlockCipher; import gnu.javax.crypto.prng.UMacGenerator; import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.security.InvalidKeyException; import java.util.HashMap; import java.util.Map; /** * The implementation of the UMAC (Universal Message Authentication * Code). *
* The UMAC algorithms described are parameterized. This means * that various low-level choices, like the endian convention and the underlying * cryptographic primitive, have not been fixed. One must choose values for * these parameters before the authentication tag generated by UMAC (for * a given message, key, and nonce) becomes fully-defined. In this document we * provide two collections of parameter settings, and have named the sets * UMAC16 and UMAC32. The parameter sets have been chosen based * on experimentation and provide good performance on a wide variety of * processors. UMAC16 is designed to excel on processors which provide * small-scale SIMD parallelism of the type found in Intel's MMX and Motorola's * AltiVec instruction sets, while UMAC32 is designed to do well on * processors with good 32- and 64- bit support. UMAC32 may take * advantage of SIMD parallelism in future processors. *
* UMAC has been designed to allow implementations which accommodate * on-line authentication. This means that pieces of the message may be * presented to UMAC at different times (but in correct order) and an * on-line implementation will be able to process the message correctly without * the need to buffer more than a few dozen bytes of the message. For * simplicity, the algorithms in this specification are presented as if the * entire message being authenticated were available at once. *
* To authenticate a message, Msg
, one first applies the
* universal hash function, resulting in a string which is typically much
* shorter than the original message. The pseudorandom function is applied to a
* nonce, and the result is used in the manner of a Vernam cipher: the
* authentication tag is the xor of the output from the hash function and the
* output from the pseudorandom function. Thus, an authentication tag is
* generated as
*
* AuthTag = f(Nonce) xor h(Msg) **
* Here f
is the pseudorandom function shared between the sender
* and the receiver, and h is a universal hash function shared by the sender and
* the receiver. In UMAC, a shared key is used to key the pseudorandom
* function f
, and then f
is used for both tag
* generation and internally to generate all of the bits needed by the universal
* hash function.
*
* The universal hash function that we use is called UHASH
. It
* combines several software-optimized algorithms into a multi-layered
* structure. The algorithm is moderately complex. Some of this complexity comes
* from extensive speed optimizations.
*
* For the pseudorandom function we use the block cipher of the Advanced * Encryption Standard (AES). *
* The UMAC32 parameters, considered in this implementation are: *
* UMAC32 * ------ * WORD-LEN 4 * UMAC-OUTPUT-LEN 8 * L1-KEY-LEN 1024 * UMAC-KEY-LEN 16 * ENDIAN-FAVORITE BIG * * L1-OPERATIONS-SIGN UNSIGNED **
* Please note that this UMAC32 differs from the one described in the paper by * the ENDIAN-FAVORITE value. *
* References: *
* For convenience, this implementation accepts that not both parameters be * always specified. *
* This method throws an exception if no Key Material is specified in * the input map, and there is no previously set/defined Key Material * (from an earlier invocation of this method). If a Key Material can * be used, but no Nonce Material is defined or previously * set/defined, then a default value of all-zeroes shall be used. * * @param attributes one or both of required parameters. * @throws InvalidKeyException the key material specified is not of the * correct length. */ public void init(Map attributes) throws InvalidKeyException, IllegalStateException { byte[] key = (byte[]) attributes.get(MAC_KEY_MATERIAL); byte[] n = (byte[]) attributes.get(NONCE_MATERIAL); boolean newKey = (key != null); boolean newNonce = (n != null); if (newKey) { if (key.length != KEY_LEN) throw new InvalidKeyException("Key length: " + String.valueOf(key.length)); K = key; } else { if (K == null) throw new InvalidKeyException("Null Key"); } if (newNonce) { if (n.length < 1 || n.length > 16) throw new IllegalArgumentException("Invalid Nonce length: " + String.valueOf(n.length)); if (n.length < 16) // pad with zeroes { byte[] newN = new byte[16]; System.arraycopy(n, 0, newN, 0, n.length); nonce = newN; } else nonce = n; nonceReuseCount = BigInteger.ZERO; } else if (nonce == null) // use all-0 nonce if 1st time { nonce = new byte[16]; nonceReuseCount = BigInteger.ZERO; } else if (! newKey) // increment nonce if still below max count { nonceReuseCount = nonceReuseCount.add(BigInteger.ONE); if (nonceReuseCount.compareTo(MAX_NONCE_ITERATIONS) >= 0) { // limit reached. we SHOULD have a key throw new InvalidKeyException("Null Key and unusable old Nonce"); } BigInteger N = new BigInteger(1, nonce); N = N.add(BigInteger.ONE).mod(MAX_NONCE_ITERATIONS); n = N.toByteArray(); if (n.length == 16) nonce = n; else if (n.length < 16) { nonce = new byte[16]; System.arraycopy(n, 0, nonce, 16 - n.length, n.length); } else { nonce = new byte[16]; System.arraycopy(n, n.length - 16, nonce, 0, 16); } } else // do nothing, re-use old nonce value nonceReuseCount = BigInteger.ZERO; if (uhash32 == null) uhash32 = new UHash32(); Map map = new HashMap(); map.put(MAC_KEY_MATERIAL, K); uhash32.init(map); } public void update(byte b) { uhash32.update(b); } public void update(byte[] b, int offset, int len) { uhash32.update(b, offset, len); } public byte[] digest() { byte[] result = uhash32.digest(); byte[] pad = pdf(); // pdf(K, nonce); for (int i = 0; i < OUTPUT_LEN; i++) result[i] = (byte)(result[i] ^ pad[i]); return result; } public void reset() { if (uhash32 != null) uhash32.reset(); } public boolean selfTest() { if (valid == null) { byte[] key; try { key = "abcdefghijklmnop".getBytes("ASCII"); } catch (UnsupportedEncodingException x) { throw new RuntimeException("ASCII not supported"); } byte[] nonce = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 }; UMac32 mac = new UMac32(); Map attributes = new HashMap(); attributes.put(MAC_KEY_MATERIAL, key); attributes.put(NONCE_MATERIAL, nonce); try { mac.init(attributes); } catch (InvalidKeyException x) { x.printStackTrace(System.err); return false; } byte[] data = new byte[128]; data[0] = (byte) 0x80; mac.update(data, 0, 128); byte[] result = mac.digest(); valid = Boolean.valueOf(TV1.equals(Util.toString(result))); } return valid.booleanValue(); } /** * @return byte array of length 8 (or OUTPUT_LEN) bytes. */ private byte[] pdf() { // Make Nonce 16 bytes by prepending zeroes. done (see init()) // one AES invocation is enough for more than one PDF invocation // number of index bits needed = 1 // Extract index bits and zero low bits of Nonce BigInteger Nonce = new BigInteger(1, nonce); int nlowbitsnum = Nonce.testBit(0) ? 1 : 0; Nonce = Nonce.clearBit(0); // Generate subkey, AES and extract indexed substring IRandom kdf = new UMacGenerator(); Map map = new HashMap(); map.put(IBlockCipher.KEY_MATERIAL, K); map.put(UMacGenerator.INDEX, Integer.valueOf(128)); kdf.init(map); byte[] Kp = new byte[KEY_LEN]; try { kdf.nextBytes(Kp, 0, KEY_LEN); } catch (IllegalStateException x) { x.printStackTrace(System.err); throw new RuntimeException(String.valueOf(x)); } catch (LimitReachedException x) { x.printStackTrace(System.err); throw new RuntimeException(String.valueOf(x)); } IBlockCipher aes = CipherFactory.getInstance(Registry.AES_CIPHER); map.put(IBlockCipher.KEY_MATERIAL, Kp); try { aes.init(map); } catch (InvalidKeyException x) { x.printStackTrace(System.err); throw new RuntimeException(String.valueOf(x)); } catch (IllegalStateException x) { x.printStackTrace(System.err); throw new RuntimeException(String.valueOf(x)); } byte[] T = new byte[16]; aes.encryptBlock(nonce, 0, T, 0); byte[] result = new byte[OUTPUT_LEN]; System.arraycopy(T, nlowbitsnum, result, 0, OUTPUT_LEN); return result; } }