ICMGenerator.java 12.6 KB
Newer Older
1
/* ICMGenerator.java --
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
   Copyright (C) 2001, 2002, 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.prng;

import gnu.java.security.Registry;
import gnu.java.security.prng.BasePRNG;
import gnu.java.security.prng.LimitReachedException;
import gnu.javax.crypto.cipher.CipherFactory;
45
import gnu.javax.crypto.cipher.IBlockCipher;
46 47 48 49 50 51 52

import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.util.HashMap;
import java.util.Map;

/**
53 54 55 56 57
 * Counter Mode is a way to define a pseudorandom keystream generator using a
 * block cipher. The keystream can be used for additive encryption, key
 * derivation, or any other application requiring pseudorandom data.
 * <p>
 * In ICM, the keystream is logically broken into segments. Each segment is
58 59
 * identified with a segment index, and the segments have equal lengths. This
 * segmentation makes ICM especially appropriate for securing packet-based
60 61 62 63 64 65
 * protocols.
 * <p>
 * This implementation adheres to the definition of the ICM keystream generation
 * function that allows for any symetric key block cipher algorithm
 * (initialisation parameter <code>gnu.crypto.prng.icm.cipher.name</code>
 * taken to be an instance of {@link java.lang.String}) to be used. If such a
66 67 68 69
 * parameter is not defined/included in the initialisation <code>Map</code>,
 * then the "Rijndael" algorithm is used. Furthermore, if the initialisation
 * parameter <code>gnu.crypto.cipher.block.size</code> (taken to be a instance
 * of {@link java.lang.Integer}) is missing or undefined in the initialisation
70 71 72
 * <code>Map</code>, then the cipher's <em>default</em> block size is used.
 * <p>
 * The practical limits and constraints of such generator are:
73
 * <ul>
74 75 76 77 78 79 80
 * <li>The number of blocks in any segment <b>MUST NOT</b> exceed <code>
 *    256 ** BLOCK_INDEX_LENGTH</code>.
 * The number of segments <b>MUST NOT</b> exceed
 * <code>256 ** SEGMENT_INDEX_LENGTH</code>. These restrictions ensure the
 * uniqueness of each block cipher input.</li>
 * <li>Each segment contains <code>SEGMENT_LENGTH</code> octets; this value
 * <b>MUST NOT</b> exceed the value <code>(256 ** BLOCK_INDEX_LENGTH) *
81
 *    BLOCK_LENGTH</code>.</li>
82 83 84 85 86
 * <li>The sum of <code>SEGMENT_INDEX_LENGTH</code> and
 * <code>BLOCK_INDEX_LENGTH</code> <b>MUST NOT</b> exceed <code>BLOCK_LENGTH
 *    / 2</code>.
 * This requirement protects the ICM keystream generator from potentially
 * failing to be pseudorandom.</li>
87
 * </ul>
88 89
 * <p>
 * <b>NOTE</b>: Rijndael is used as the default symmetric key block cipher
90 91
 * algorithm because, with its default block and key sizes, it is the AES. Yet
 * being Rijndael, the algorithm offers more versatile block and key sizes which
92 93 94
 * may prove to be useful for generating <em>longer</em> key streams.
 * <p>
 * References:
95
 * <ol>
96 97 98
 * <li><a
 * href="http://www.ietf.org/internet-drafts/draft-mcgrew-saag-icm-00.txt">
 * Integer Counter Mode</a>, David A. McGrew.</li>
99 100
 * </ol>
 */
101 102 103
public class ICMGenerator
    extends BasePRNG
    implements Cloneable
104 105 106 107
{
  /** Property name of underlying block cipher for this ICM generator. */
  public static final String CIPHER = "gnu.crypto.prng.icm.cipher.name";
  /** Property name of ICM's block index length. */
108 109
  public static final String BLOCK_INDEX_LENGTH =
      "gnu.crypto.prng.icm.block.index.length";
110
  /** Property name of ICM's segment index length. */
111 112
  public static final String SEGMENT_INDEX_LENGTH =
      "gnu.crypto.prng.icm.segment.index.length";
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
  /** Property name of ICM's offset. */
  public static final String OFFSET = "gnu.crypto.prng.icm.offset";
  /** Property name of ICM's segment index. */
  public static final String SEGMENT_INDEX = "gnu.crypto.prng.icm.segment.index";
  /** The integer value 256 as a BigInteger. */
  private static final BigInteger TWO_FIFTY_SIX = new BigInteger("256");
  /** The underlying cipher implementation. */
  private IBlockCipher cipher;
  /** This keystream block index length in bytes. */
  private int blockNdxLength = -1;
  /** This keystream segment index length in bytes. */
  private int segmentNdxLength = -1;
  /** The index of the next block for a given keystream segment. */
  private BigInteger blockNdx = BigInteger.ZERO;
  /** The segment index for this keystream. */
  private BigInteger segmentNdx;
  /** The initial counter for a given keystream segment. */
  private BigInteger C0;

  /** Trivial 0-arguments constructor. */
  public ICMGenerator()
  {
    super(Registry.ICM_PRNG);
  }

138 139 140 141
  // Conceptually, ICM is a keystream generator that takes a secret key and a
  // segment index as an input and then outputs a keystream segment. The
  // segmentation lends itself to packet encryption, as each keystream segment
  // can be used to encrypt a distinct packet.
142
  //
143 144
  // An ICM key consists of the block cipher key and an Offset. The Offset is
  // an integer with BLOCK_LENGTH octets...
145 146 147 148 149 150
  public void setup(Map attributes)
  {
    // find out which cipher algorithm to use
    boolean newCipher = true;
    String underlyingCipher = (String) attributes.get(CIPHER);
    if (underlyingCipher == null)
151 152 153 154 155 156 157 158
      if (cipher == null) // happy birthday
        // ensure we have a reliable implementation of this cipher
        cipher = CipherFactory.getInstance(Registry.RIJNDAEL_CIPHER);
      else
        // we already have one. use it as is
        newCipher = false;
    else // ensure we have a reliable implementation of this cipher
      cipher = CipherFactory.getInstance(underlyingCipher);
159 160 161 162 163

    // find out what block size we should use it in
    int cipherBlockSize = 0;
    Integer bs = (Integer) attributes.get(IBlockCipher.CIPHER_BLOCK_SIZE);
    if (bs != null)
164
      cipherBlockSize = bs.intValue();
165 166
    else
      {
167 168 169
        if (newCipher) // assume we'll use its default block size
          cipherBlockSize = cipher.defaultBlockSize();
        // else use as is
170 171 172 173
      }
    // get the key material
    byte[] key = (byte[]) attributes.get(IBlockCipher.KEY_MATERIAL);
    if (key == null)
174
      throw new IllegalArgumentException(IBlockCipher.KEY_MATERIAL);
175 176
    // now initialise the cipher
    HashMap map = new HashMap();
177 178
    if (cipherBlockSize != 0) // only needed if new or changed
      map.put(IBlockCipher.CIPHER_BLOCK_SIZE, Integer.valueOf(cipherBlockSize));
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
    map.put(IBlockCipher.KEY_MATERIAL, key);
    try
      {
        cipher.init(map);
      }
    catch (InvalidKeyException x)
      {
        throw new IllegalArgumentException(IBlockCipher.KEY_MATERIAL);
      }
    // at this point we have an initialised (new or otherwise) cipher
    // ensure that remaining params make sense
    cipherBlockSize = cipher.currentBlockSize();
    BigInteger counterRange = TWO_FIFTY_SIX.pow(cipherBlockSize);
    // offset, like the underlying cipher key is not cloneable
    // always look for it and throw an exception if it's not there
    Object obj = attributes.get(OFFSET);
    // allow either a byte[] or a BigInteger
    BigInteger r;
    if (obj instanceof BigInteger)
198 199
      r = (BigInteger) obj;
    else // assume byte[]. should be same length as cipher block size
200 201 202
      {
        byte[] offset = (byte[]) obj;
        if (offset.length != cipherBlockSize)
203
          throw new IllegalArgumentException(OFFSET);
204 205 206 207 208 209 210 211
        r = new BigInteger(1, offset);
      }
    int wantBlockNdxLength = -1; // number of octets in the block index
    Integer i = (Integer) attributes.get(BLOCK_INDEX_LENGTH);
    if (i != null)
      {
        wantBlockNdxLength = i.intValue();
        if (wantBlockNdxLength < 1)
212
          throw new IllegalArgumentException(BLOCK_INDEX_LENGTH);
213 214 215 216 217 218 219
      }
    int wantSegmentNdxLength = -1; // number of octets in the segment index
    i = (Integer) attributes.get(SEGMENT_INDEX_LENGTH);
    if (i != null)
      {
        wantSegmentNdxLength = i.intValue();
        if (wantSegmentNdxLength < 1)
220
          throw new IllegalArgumentException(SEGMENT_INDEX_LENGTH);
221 222 223 224
      }
    // if both are undefined check if it's a reuse
    if ((wantBlockNdxLength == -1) && (wantSegmentNdxLength == -1))
      {
225 226 227 228
        if (blockNdxLength == -1) // new instance
          throw new IllegalArgumentException(BLOCK_INDEX_LENGTH + ", "
                                             + SEGMENT_INDEX_LENGTH);
        // else reuse old values
229
      }
230 231
    else // only one is undefined, set it to BLOCK_LENGTH/2 minus the other
      {
232 233
        int limit = cipherBlockSize / 2;
        if (wantBlockNdxLength == -1)
234
          wantBlockNdxLength = limit - wantSegmentNdxLength;
235
        else if (wantSegmentNdxLength == -1)
236
          wantSegmentNdxLength = limit - wantBlockNdxLength;
237
        else if ((wantSegmentNdxLength + wantBlockNdxLength) > limit)
238 239
          throw new IllegalArgumentException(BLOCK_INDEX_LENGTH + ", "
                                             + SEGMENT_INDEX_LENGTH);
240 241 242 243 244 245 246 247
        // save new values
        blockNdxLength = wantBlockNdxLength;
        segmentNdxLength = wantSegmentNdxLength;
      }
    // get the segment index as a BigInteger
    BigInteger s = (BigInteger) attributes.get(SEGMENT_INDEX);
    if (s == null)
      {
248 249
        if (segmentNdx == null) // segment index was never set
          throw new IllegalArgumentException(SEGMENT_INDEX);
250 251
        // reuse; check if still valid
        if (segmentNdx.compareTo(TWO_FIFTY_SIX.pow(segmentNdxLength)) > 0)
252
          throw new IllegalArgumentException(SEGMENT_INDEX);
253 254 255 256
      }
    else
      {
        if (s.compareTo(TWO_FIFTY_SIX.pow(segmentNdxLength)) > 0)
257
          throw new IllegalArgumentException(SEGMENT_INDEX);
258 259 260 261 262 263
        segmentNdx = s;
      }
    // The initial counter of the keystream segment with segment index s is
    // defined as follows, where r denotes the Offset:
    //
    // C[0] = (s * (256^BLOCK_INDEX_LENGTH) + r) modulo (256^BLOCK_LENGTH)
264 265
    C0 = segmentNdx.multiply(TWO_FIFTY_SIX.pow(blockNdxLength))
                   .add(r).modPow(BigInteger.ONE, counterRange);
266 267 268 269 270 271
    try
      {
        fillBlock();
      }
    catch (LimitReachedException impossible)
      {
272 273
        throw (InternalError)
          new InternalError().initCause(impossible);
274
      }
275 276 277 278 279
  }

  public void fillBlock() throws LimitReachedException
  {
    if (C0 == null)
280
      throw new IllegalStateException();
281
    if (blockNdx.compareTo(TWO_FIFTY_SIX.pow(blockNdxLength)) >= 0)
282
      throw new LimitReachedException();
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
    int cipherBlockSize = cipher.currentBlockSize();
    BigInteger counterRange = TWO_FIFTY_SIX.pow(cipherBlockSize);
    // encrypt the counter for the current blockNdx
    // C[i] = (C[0] + i) modulo (256^BLOCK_LENGTH).
    BigInteger Ci = C0.add(blockNdx).modPow(BigInteger.ONE, counterRange);
    buffer = Ci.toByteArray();
    int limit = buffer.length;
    if (limit < cipherBlockSize)
      {
        byte[] data = new byte[cipherBlockSize];
        System.arraycopy(buffer, 0, data, cipherBlockSize - limit, limit);
        buffer = data;
      }
    else if (limit > cipherBlockSize)
      {
        byte[] data = new byte[cipherBlockSize];
        System.arraycopy(buffer, limit - cipherBlockSize, data, 0,
                         cipherBlockSize);
        buffer = data;
      }
    cipher.encryptBlock(buffer, 0, buffer, 0);
    blockNdx = blockNdx.add(BigInteger.ONE); // increment blockNdx
  }
306
}