summaryrefslogtreecommitdiff
path: root/libjava/classpath/gnu/javax/crypto/mac/UMac32.java
blob: 6f53424eaa62bced27395300379cd7df2dd546b4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
/* 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 <i>UMAC</i> (Universal Message Authentication
 * Code).
 * <p>
 * The <i>UMAC</i> algorithms described are <i>parameterized</i>. 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 <i>UMAC</i> (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
 * <i>UMAC16</i> and <i>UMAC32</i>. The parameter sets have been chosen based
 * on experimentation and provide good performance on a wide variety of
 * processors. <i>UMAC16</i> 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 <i>UMAC32</i> is designed to do well on
 * processors with good 32- and 64- bit support. <i>UMAC32</i> may take
 * advantage of SIMD parallelism in future processors.
 * <p>
 * <i>UMAC</i> has been designed to allow implementations which accommodate
 * <i>on-line</i> authentication. This means that pieces of the message may be
 * presented to <i>UMAC</i> 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.
 * <p>
 * To authenticate a message, <code>Msg</code>, 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
 * <pre>
 *     AuthTag = f(Nonce) xor h(Msg)
 * </pre>
 * <p>
 * Here <code>f</code> 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 <i>UMAC</i>, a shared key is used to key the pseudorandom
 * function <code>f</code>, and then <code>f</code> is used for both tag
 * generation and internally to generate all of the bits needed by the universal
 * hash function.
 * <p>
 * The universal hash function that we use is called <code>UHASH</code>. 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.
 * <p>
 * For the pseudorandom function we use the block cipher of the <i>Advanced
 * Encryption Standard</i> (AES).
 * <p>
 * The UMAC32 parameters, considered in this implementation are:
 * <pre>
 *                                    UMAC32
 *                                    ------
 *         WORD-LEN                        4
 *         UMAC-OUTPUT-LEN                 8
 *         L1-KEY-LEN                   1024
 *         UMAC-KEY-LEN                   16
 *         ENDIAN-FAVORITE               BIG *
 *         L1-OPERATIONS-SIGN       UNSIGNED
 * </pre>
 * <p>
 * Please note that this UMAC32 differs from the one described in the paper by
 * the <i>ENDIAN-FAVORITE</i> value.
 * <p>
 * References:
 * <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>
 */
public class UMac32
    extends BaseMac
{
  /**
   * Property name of the user-supplied <i>Nonce</i>. The value associated to
   * this property name is taken to be a byte array.
   */
  public static final String NONCE_MATERIAL = "gnu.crypto.umac.nonce.material";
  /** Known test vector. */
  // private static final String TV1 = "3E5A0E09198B0F94";
  // private static final String TV1 = "5FD764A6D3A9FD9D";
  // private static final String TV1 = "48658DE1D9A70304";
  private static final String TV1 = "455ED214A6909F20";
  private static final BigInteger MAX_NONCE_ITERATIONS = BigInteger.ONE.shiftLeft(16 * 8);
  // UMAC32 parameters
  static final int OUTPUT_LEN = 8;
  static final int L1_KEY_LEN = 1024;
  static final int KEY_LEN = 16;
  /** caches the result of the correctness test, once executed. */
  private static Boolean valid;
  private byte[] nonce;
  private UHash32 uhash32;
  private BigInteger nonceReuseCount;
  /** The authentication key for this instance. */
  private transient byte[] K;

  /** Trivial 0-arguments constructor. */
  public UMac32()
  {
    super("umac32");
  }

  /**
   * Private constructor for cloning purposes.
   *
   * @param that the instance to clone.
   */
  private UMac32(UMac32 that)
  {
    this();

    if (that.K != null)
      this.K = (byte[]) that.K.clone();
    if (that.nonce != null)
      this.nonce = (byte[]) that.nonce.clone();
    if (that.uhash32 != null)
      this.uhash32 = (UHash32) that.uhash32.clone();
    this.nonceReuseCount = that.nonceReuseCount;
  }

  public Object clone()
  {
    return new UMac32(this);
  }

  public int macSize()
  {
    return OUTPUT_LEN;
  }

  /**
   * Initialising a <i>UMAC</i> instance consists of defining values for the
   * following parameters:
   * <ol>
   * <li>Key Material: as the value of the attribute entry keyed by
   * {@link #MAC_KEY_MATERIAL}. The value is taken to be a byte array
   * containing the user-specified key material. The length of this array,
   * if/when defined SHOULD be exactly equal to {@link #KEY_LEN}.</li>
   * <li>Nonce Material: as the value of the attribute entry keyed by
   * {@link #NONCE_MATERIAL}. The value is taken to be a byte array containing
   * the user-specified nonce material. The length of this array, if/when
   * defined SHOULD be (a) greater than zero, and (b) less or equal to 16 (the
   * size of the AES block).</li>
   * </ol>
   * <p>
   * For convenience, this implementation accepts that not both parameters be
   * always specified.
   * <ul>
   * <li>If the <i>Key Material</i> is specified, but the <i>Nonce Material</i>
   * is not, then this implementation, re-uses the previously set <i>Nonce
   * Material</i> after (a) converting the bytes to an unsigned integer, (b)
   * incrementing the number by one, and (c) converting it back to 16 bytes.</li>
   * <li>If the <i>Nonce Material</i> is specified, but the <i>Key Material</i>
   * is not, then this implementation re-uses the previously set <i>Key Material</i>.
   * </li>
   * </ul>
   * <p>
   * This method throws an exception if no <i>Key Material</i> is specified in
   * the input map, and there is no previously set/defined <i>Key Material</i>
   * (from an earlier invocation of this method). If a <i>Key Material</i> can
   * be used, but no <i>Nonce Material</i> 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;
  }
}