# HG changeset patch # User valeriep # Date 1357598407 28800 # Node ID bcb2414329283bfe72c37874bd4134cad7355e84 # Parent 6a494f8ba5b5ce22bbfe4994b6a020e630d760d4# Parent cf5d2d5094ccc1e68cc238f15845492c7f4c1dc0 Merge diff -r cf5d2d5094cc -r bcb241432928 jdk/src/share/classes/com/sun/crypto/provider/AESCipher.java --- a/jdk/src/share/classes/com/sun/crypto/provider/AESCipher.java Mon Jan 07 13:19:03 2013 -0800 +++ b/jdk/src/share/classes/com/sun/crypto/provider/AESCipher.java Mon Jan 07 14:40:07 2013 -0800 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -30,6 +30,7 @@ import javax.crypto.*; import javax.crypto.spec.*; import javax.crypto.BadPaddingException; +import java.nio.ByteBuffer; /** * This class implements the AES algorithm in its various modes @@ -127,6 +128,21 @@ super(32, "CFB", "NOPADDING"); } } + public static final class AES128_GCM_NoPadding extends OidImpl { + public AES128_GCM_NoPadding() { + super(16, "GCM", "NOPADDING"); + } + } + public static final class AES192_GCM_NoPadding extends OidImpl { + public AES192_GCM_NoPadding() { + super(24, "GCM", "NOPADDING"); + } + } + public static final class AES256_GCM_NoPadding extends OidImpl { + public AES256_GCM_NoPadding() { + super(32, "GCM", "NOPADDING"); + } + } // utility method used by AESCipher and AESWrapCipher static final void checkKeySize(Key key, int fixedKeySize) @@ -531,4 +547,79 @@ return core.unwrap(wrappedKey, wrappedKeyAlgorithm, wrappedKeyType); } + + /** + * Continues a multi-part update of the Additional Authentication + * Data (AAD), using a subset of the provided buffer. + *
+ * Calls to this method provide AAD to the cipher when operating in + * modes such as AEAD (GCM/CCM). If this cipher is operating in + * either GCM or CCM mode, all AAD must be supplied before beginning + * operations on the ciphertext (via the {@code update} and {@code + * doFinal} methods). + * + * @param src the buffer containing the AAD + * @param offset the offset in {@code src} where the AAD input starts + * @param len the number of AAD bytes + * + * @throws IllegalStateException if this cipher is in a wrong state + * (e.g., has not been initialized), does not accept AAD, or if + * operating in either GCM or CCM mode and one of the {@code update} + * methods has already been called for the active + * encryption/decryption operation + * @throws UnsupportedOperationException if this method + * has not been overridden by an implementation + * + * @since 1.8 + */ + @Override + protected void engineUpdateAAD(byte[] src, int offset, int len) { + core.updateAAD(src, offset, len); + } + + /** + * Continues a multi-part update of the Additional Authentication + * Data (AAD). + *
+ * Calls to this method provide AAD to the cipher when operating in + * modes such as AEAD (GCM/CCM). If this cipher is operating in + * either GCM or CCM mode, all AAD must be supplied before beginning + * operations on the ciphertext (via the {@code update} and {@code + * doFinal} methods). + *
+ * All {@code src.remaining()} bytes starting at
+ * {@code src.position()} are processed.
+ * Upon return, the input buffer's position will be equal
+ * to its limit; its limit will not have changed.
+ *
+ * @param src the buffer containing the AAD
+ *
+ * @throws IllegalStateException if this cipher is in a wrong state
+ * (e.g., has not been initialized), does not accept AAD, or if
+ * operating in either GCM or CCM mode and one of the {@code update}
+ * methods has already been called for the active
+ * encryption/decryption operation
+ * @throws UnsupportedOperationException if this method
+ * has not been overridden by an implementation
+ *
+ * @since 1.8
+ */
+ @Override
+ protected void engineUpdateAAD(ByteBuffer src) {
+ if (src != null) {
+ int aadLen = src.limit() - src.position();
+ if (aadLen != 0) {
+ if (src.hasArray()) {
+ int aadOfs = src.arrayOffset() + src.position();
+ core.updateAAD(src.array(), aadOfs, aadLen);
+ src.position(src.limit());
+ } else {
+ byte[] aad = new byte[aadLen];
+ src.get(aad);
+ core.updateAAD(aad, 0, aadLen);
+ }
+ }
+ }
+ }
}
+
diff -r cf5d2d5094cc -r bcb241432928 jdk/src/share/classes/com/sun/crypto/provider/CipherCore.java
--- a/jdk/src/share/classes/com/sun/crypto/provider/CipherCore.java Mon Jan 07 13:19:03 2013 -0800
+++ b/jdk/src/share/classes/com/sun/crypto/provider/CipherCore.java Mon Jan 07 14:40:07 2013 -0800
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002, 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -25,6 +25,7 @@
package com.sun.crypto.provider;
+import java.util.Arrays;
import java.util.Locale;
import java.security.*;
@@ -59,7 +60,7 @@
private byte[] buffer = null;
/*
- * internal buffer
+ * block size of cipher in bytes
*/
private int blockSize = 0;
@@ -76,10 +77,12 @@
/*
* minimum number of bytes in the buffer required for
* FeedbackCipher.encryptFinal()/decryptFinal() call.
- * update() must buffer this many bytes before before starting
+ * update() must buffer this many bytes before starting
* to encrypt/decrypt data.
- * currently, only CTS mode has a non-zero value due to its special
- * handling on the last two blocks (the last one may be incomplete).
+ * currently, only the following cases have non-zero values:
+ * 1) CTS mode - due to its special handling on the last two blocks
+ * (the last one may be incomplete).
+ * 2) GCM mode + decryption - due to its trailing tag bytes
*/
private int minBytes = 0;
@@ -121,6 +124,24 @@
private static final int PCBC_MODE = 4;
private static final int CTR_MODE = 5;
private static final int CTS_MODE = 6;
+ private static final int GCM_MODE = 7;
+
+ /*
+ * variables used for performing the GCM (key+iv) uniqueness check.
+ * To use GCM mode safely, the cipher object must be re-initialized
+ * with a different combination of key + iv values for each
+ * encryption operation. However, checking all past key + iv values
+ * isn't feasible. Thus, we only do a per-instance check of the
+ * key + iv values used in previous encryption.
+ * For decryption operations, no checking is necessary.
+ * NOTE: this key+iv check have to be done inside CipherCore class
+ * since CipherCore class buffers potential tag bytes in GCM mode
+ * and may not call GaloisCounterMode when there isn't sufficient
+ * input to process.
+ */
+ private boolean requireReinit = false;
+ private byte[] lastEncKey = null;
+ private byte[] lastEncIv = null;
/**
* Creates an instance of CipherCore with default ECB mode and
@@ -149,7 +170,7 @@
* @param mode the cipher mode
*
* @exception NoSuchAlgorithmException if the requested cipher mode does
- * not exist
+ * not exist for this cipher
*/
void setMode(String mode) throws NoSuchAlgorithmException {
if (mode == null)
@@ -165,30 +186,34 @@
if (modeUpperCase.equals("CBC")) {
cipherMode = CBC_MODE;
cipher = new CipherBlockChaining(rawImpl);
- }
- else if (modeUpperCase.equals("CTS")) {
+ } else if (modeUpperCase.equals("CTS")) {
cipherMode = CTS_MODE;
cipher = new CipherTextStealing(rawImpl);
minBytes = blockSize+1;
padding = null;
- }
- else if (modeUpperCase.equals("CTR")) {
+ } else if (modeUpperCase.equals("CTR")) {
cipherMode = CTR_MODE;
cipher = new CounterMode(rawImpl);
unitBytes = 1;
padding = null;
- }
- else if (modeUpperCase.startsWith("CFB")) {
+ } else if (modeUpperCase.startsWith("GCM")) {
+ // can only be used for block ciphers w/ 128-bit block size
+ if (blockSize != 16) {
+ throw new NoSuchAlgorithmException
+ ("GCM mode can only be used for AES cipher");
+ }
+ cipherMode = GCM_MODE;
+ cipher = new GaloisCounterMode(rawImpl);
+ padding = null;
+ } else if (modeUpperCase.startsWith("CFB")) {
cipherMode = CFB_MODE;
unitBytes = getNumOfUnit(mode, "CFB".length(), blockSize);
cipher = new CipherFeedback(rawImpl, unitBytes);
- }
- else if (modeUpperCase.startsWith("OFB")) {
+ } else if (modeUpperCase.startsWith("OFB")) {
cipherMode = OFB_MODE;
unitBytes = getNumOfUnit(mode, "OFB".length(), blockSize);
cipher = new OutputFeedback(rawImpl, unitBytes);
- }
- else if (modeUpperCase.equals("PCBC")) {
+ } else if (modeUpperCase.equals("PCBC")) {
cipherMode = PCBC_MODE;
cipher = new PCBC(rawImpl);
}
@@ -219,6 +244,7 @@
return result;
}
+
/**
* Sets the padding mechanism of this cipher.
*
@@ -242,11 +268,27 @@
+ " not implemented");
}
if ((padding != null) &&
- ((cipherMode == CTR_MODE) || (cipherMode == CTS_MODE))) {
+ ((cipherMode == CTR_MODE) || (cipherMode == CTS_MODE)
+ || (cipherMode == GCM_MODE))) {
padding = null;
- throw new NoSuchPaddingException
- ((cipherMode == CTR_MODE? "CTR":"CTS") +
- " mode must be used with NoPadding");
+ String modeStr = null;
+ switch (cipherMode) {
+ case CTR_MODE:
+ modeStr = "CTR";
+ break;
+ case GCM_MODE:
+ modeStr = "GCM";
+ break;
+ case CTS_MODE:
+ modeStr = "CTS";
+ break;
+ default:
+ // should never happen
+ }
+ if (modeStr != null) {
+ throw new NoSuchPaddingException
+ (modeStr + " mode must be used with NoPadding");
+ }
}
}
@@ -257,7 +299,7 @@
* inputLen
(in bytes).
*
*
This call takes into account any unprocessed (buffered) data from a
- * previous update
call, and padding.
+ * previous update
call, padding, and AEAD tagging.
*
*
The actual output length of the next update
or
* doFinal
call may be smaller than the length returned by
@@ -270,23 +312,60 @@
int getOutputSize(int inputLen) {
int totalLen = buffered + inputLen;
- if (padding == null)
- return totalLen;
+ // GCM: this call may be for either update() or doFinal(), so have to
+ // return the larger value of both
+ // Encryption: based on doFinal value: inputLen + tag
+ // Decryption: based on update value: inputLen
+ if (!decrypting && (cipherMode == GCM_MODE)) {
+ return (totalLen + ((GaloisCounterMode) cipher).getTagLen());
+ }
- if (decrypting)
+ if (padding == null) {
return totalLen;
+ }
+
+ if (decrypting) {
+ return totalLen;
+ }
if (unitBytes != blockSize) {
- if (totalLen < diffBlocksize)
+ if (totalLen < diffBlocksize) {
return diffBlocksize;
- else
+ } else {
return (totalLen + blockSize -
((totalLen - diffBlocksize) % blockSize));
+ }
} else {
return totalLen + padding.padLength(totalLen);
}
}
+ private int getOutputSizeByOperation(int inputLen, boolean isDoFinal) {
+ int totalLen = 0;
+ switch (cipherMode) {
+ case GCM_MODE:
+ totalLen = buffered + inputLen;
+ if (isDoFinal) {
+ int tagLen = ((GaloisCounterMode) cipher).getTagLen();
+ if (decrypting) {
+ // need to get the actual value from cipher??
+ // deduct tagLen
+ totalLen -= tagLen;
+ } else {
+ totalLen += tagLen;
+ }
+ }
+ if (totalLen < 0) {
+ totalLen = 0;
+ }
+ break;
+ default:
+ totalLen = getOutputSize(inputLen);
+ break;
+ }
+ return totalLen;
+ }
+
/**
* Returns the initialization vector (IV) in a new buffer.
*
@@ -318,34 +397,49 @@
* does not use any parameters.
*/
AlgorithmParameters getParameters(String algName) {
+ if (cipherMode == ECB_MODE) {
+ return null;
+ }
AlgorithmParameters params = null;
- if (cipherMode == ECB_MODE) return null;
+ AlgorithmParameterSpec spec;
byte[] iv = getIV();
- if (iv != null) {
- AlgorithmParameterSpec ivSpec;
- if (algName.equals("RC2")) {
- RC2Crypt rawImpl = (RC2Crypt) cipher.getEmbeddedCipher();
- ivSpec = new RC2ParameterSpec(rawImpl.getEffectiveKeyBits(),
- iv);
+ if (iv == null) {
+ // generate spec using default value
+ if (cipherMode == GCM_MODE) {
+ iv = new byte[GaloisCounterMode.DEFAULT_IV_LEN];
} else {
- ivSpec = new IvParameterSpec(iv);
+ iv = new byte[blockSize];
}
- try {
- params = AlgorithmParameters.getInstance(algName, "SunJCE");
- } catch (NoSuchAlgorithmException nsae) {
- // should never happen
- throw new RuntimeException("Cannot find " + algName +
- " AlgorithmParameters implementation in SunJCE provider");
- } catch (NoSuchProviderException nspe) {
- // should never happen
- throw new RuntimeException("Cannot find SunJCE provider");
- }
- try {
- params.init(ivSpec);
- } catch (InvalidParameterSpecException ipse) {
- // should never happen
- throw new RuntimeException("IvParameterSpec not supported");
- }
+ SunJCE.RANDOM.nextBytes(iv);
+ }
+ if (cipherMode == GCM_MODE) {
+ algName = "GCM";
+ spec = new GCMParameterSpec
+ (((GaloisCounterMode) cipher).getTagLen()*8, iv);
+ } else {
+ if (algName.equals("RC2")) {
+ RC2Crypt rawImpl = (RC2Crypt) cipher.getEmbeddedCipher();
+ spec = new RC2ParameterSpec
+ (rawImpl.getEffectiveKeyBits(), iv);
+ } else {
+ spec = new IvParameterSpec(iv);
+ }
+ }
+ try {
+ params = AlgorithmParameters.getInstance(algName, "SunJCE");
+ } catch (NoSuchAlgorithmException nsae) {
+ // should never happen
+ throw new RuntimeException("Cannot find " + algName +
+ " AlgorithmParameters implementation in SunJCE provider");
+ } catch (NoSuchProviderException nspe) {
+ // should never happen
+ throw new RuntimeException("Cannot find SunJCE provider");
+ }
+ try {
+ params.init(spec);
+ } catch (InvalidParameterSpecException ipse) {
+ // should never happen
+ throw new RuntimeException(spec.getClass() + " not supported");
}
return params;
}
@@ -420,44 +514,63 @@
|| (opmode == Cipher.UNWRAP_MODE);
byte[] keyBytes = getKeyBytes(key);
-
- byte[] ivBytes;
- if (params == null) {
- ivBytes = null;
- } else if (params instanceof IvParameterSpec) {
- ivBytes = ((IvParameterSpec)params).getIV();
- if ((ivBytes == null) || (ivBytes.length != blockSize)) {
- throw new InvalidAlgorithmParameterException
- ("Wrong IV length: must be " + blockSize +
- " bytes long");
+ int tagLen = -1;
+ byte[] ivBytes = null;
+ if (params != null) {
+ if (cipherMode == GCM_MODE) {
+ if (params instanceof GCMParameterSpec) {
+ tagLen = ((GCMParameterSpec)params).getTLen();
+ if (tagLen < 96 || tagLen > 128 || ((tagLen & 0x07) != 0)) {
+ throw new InvalidAlgorithmParameterException
+ ("Unsupported TLen value; must be one of " +
+ "{128, 120, 112, 104, 96}");
+ }
+ tagLen = tagLen >> 3;
+ ivBytes = ((GCMParameterSpec)params).getIV();
+ } else {
+ throw new InvalidAlgorithmParameterException
+ ("Unsupported parameter: " + params);
+ }
+ } else {
+ if (params instanceof IvParameterSpec) {
+ ivBytes = ((IvParameterSpec)params).getIV();
+ if ((ivBytes == null) || (ivBytes.length != blockSize)) {
+ throw new InvalidAlgorithmParameterException
+ ("Wrong IV length: must be " + blockSize +
+ " bytes long");
+ }
+ } else if (params instanceof RC2ParameterSpec) {
+ ivBytes = ((RC2ParameterSpec)params).getIV();
+ if ((ivBytes != null) && (ivBytes.length != blockSize)) {
+ throw new InvalidAlgorithmParameterException
+ ("Wrong IV length: must be " + blockSize +
+ " bytes long");
+ }
+ } else {
+ throw new InvalidAlgorithmParameterException
+ ("Unsupported parameter: " + params);
+ }
}
- } else if (params instanceof RC2ParameterSpec) {
- ivBytes = ((RC2ParameterSpec)params).getIV();
- if ((ivBytes != null) && (ivBytes.length != blockSize)) {
- throw new InvalidAlgorithmParameterException
- ("Wrong IV length: must be " + blockSize +
- " bytes long");
- }
- } else {
- throw new InvalidAlgorithmParameterException("Wrong parameter "
- + "type: IV "
- + "expected");
}
-
if (cipherMode == ECB_MODE) {
if (ivBytes != null) {
throw new InvalidAlgorithmParameterException
("ECB mode cannot use IV");
}
- } else if (ivBytes == null) {
+ } else if (ivBytes == null) {
if (decrypting) {
throw new InvalidAlgorithmParameterException("Parameters "
+ "missing");
}
+
if (random == null) {
random = SunJCE.RANDOM;
}
- ivBytes = new byte[blockSize];
+ if (cipherMode == GCM_MODE) {
+ ivBytes = new byte[GaloisCounterMode.DEFAULT_IV_LEN];
+ } else {
+ ivBytes = new byte[blockSize];
+ }
random.nextBytes(ivBytes);
}
@@ -466,23 +579,57 @@
String algorithm = key.getAlgorithm();
- cipher.init(decrypting, algorithm, keyBytes, ivBytes);
+ // GCM mode needs additional handling
+ if (cipherMode == GCM_MODE) {
+ if(tagLen == -1) {
+ tagLen = GaloisCounterMode.DEFAULT_TAG_LEN;
+ }
+ if (decrypting) {
+ minBytes = tagLen;
+ } else {
+ // check key+iv for encryption in GCM mode
+ requireReinit =
+ Arrays.equals(ivBytes, lastEncIv) &&
+ Arrays.equals(keyBytes, lastEncKey);
+ if (requireReinit) {
+ throw new InvalidAlgorithmParameterException
+ ("Cannot reuse iv for GCM encryption");
+ }
+ lastEncIv = ivBytes;
+ lastEncKey = keyBytes;
+ }
+ ((GaloisCounterMode) cipher).init
+ (decrypting, algorithm, keyBytes, ivBytes, tagLen);
+ } else {
+ cipher.init(decrypting, algorithm, keyBytes, ivBytes);
+ }
+ // skip checking key+iv from now on until after doFinal()
+ requireReinit = false;
}
void init(int opmode, Key key, AlgorithmParameters params,
SecureRandom random)
throws InvalidKeyException, InvalidAlgorithmParameterException {
- IvParameterSpec ivSpec = null;
+ AlgorithmParameterSpec spec = null;
+ String paramType = null;
if (params != null) {
try {
- ivSpec = params.getParameterSpec(IvParameterSpec.class);
+ if (cipherMode == GCM_MODE) {
+ paramType = "GCM";
+ spec = params.getParameterSpec(GCMParameterSpec.class);
+ } else {
+ // NOTE: RC2 parameters are always handled through
+ // init(..., AlgorithmParameterSpec,...) method, so
+ // we can assume IvParameterSpec type here.
+ paramType = "IV";
+ spec = params.getParameterSpec(IvParameterSpec.class);
+ }
} catch (InvalidParameterSpecException ipse) {
- throw new InvalidAlgorithmParameterException("Wrong parameter "
- + "type: IV "
- + "expected");
+ throw new InvalidAlgorithmParameterException
+ ("Wrong parameter type: " + paramType + " expected");
}
}
- init(opmode, key, ivSpec, random);
+ init(opmode, key, spec, random);
}
/**
@@ -504,6 +651,7 @@
return keyBytes;
}
+
/**
* Continues a multiple-part encryption or decryption operation
* (depending on how this cipher was initialized), processing another data
@@ -524,22 +672,25 @@
* (e.g., has not been initialized)
*/
byte[] update(byte[] input, int inputOffset, int inputLen) {
+ if (requireReinit) {
+ throw new IllegalStateException
+ ("Must use either different key or iv for GCM encryption");
+ }
+
byte[] output = null;
- byte[] out = null;
try {
- output = new byte[getOutputSize(inputLen)];
+ output = new byte[getOutputSizeByOperation(inputLen, false)];
int len = update(input, inputOffset, inputLen, output,
0);
if (len == output.length) {
- out = output;
+ return output;
} else {
- out = new byte[len];
- System.arraycopy(output, 0, out, 0, len);
+ return Arrays.copyOf(output, len);
}
} catch (ShortBufferException e) {
- // never thrown
+ // should never happen
+ throw new ProviderException("Unexpected exception", e);
}
- return out;
}
/**
@@ -567,6 +718,11 @@
*/
int update(byte[] input, int inputOffset, int inputLen, byte[] output,
int outputOffset) throws ShortBufferException {
+ if (requireReinit) {
+ throw new IllegalStateException
+ ("Must use either different key or iv for GCM encryption");
+ }
+
// figure out how much can be sent to crypto function
int len = buffered + inputLen - minBytes;
if (padding != null && decrypting) {
@@ -582,6 +738,7 @@
+ "(at least) " + len
+ " bytes long");
}
+
if (len != 0) {
// there is some work to do
byte[] in = new byte[len];
@@ -600,7 +757,6 @@
System.arraycopy(input, inputOffset, in,
bufferedConsumed, inputConsumed);
}
-
if (decrypting) {
cipher.decrypt(in, 0, len, output, outputOffset);
} else {
@@ -611,11 +767,12 @@
// the total input length a multiple of blocksize when
// padding is applied
if (unitBytes != blockSize) {
- if (len < diffBlocksize)
+ if (len < diffBlocksize) {
diffBlocksize -= len;
- else
+ } else {
diffBlocksize = blockSize -
((len - diffBlocksize) % blockSize);
+ }
}
inputLen -= inputConsumed;
@@ -669,21 +826,18 @@
byte[] doFinal(byte[] input, int inputOffset, int inputLen)
throws IllegalBlockSizeException, BadPaddingException {
byte[] output = null;
- byte[] out = null;
try {
- output = new byte[getOutputSize(inputLen)];
+ output = new byte[getOutputSizeByOperation(inputLen, true)];
int len = doFinal(input, inputOffset, inputLen, output, 0);
if (len < output.length) {
- out = new byte[len];
- if (len != 0)
- System.arraycopy(output, 0, out, 0, len);
+ return Arrays.copyOf(output, len);
} else {
- out = output;
+ return output;
}
} catch (ShortBufferException e) {
// never thrown
+ throw new ProviderException("Unexpected exception", e);
}
- return out;
}
/**
@@ -726,6 +880,10 @@
int outputOffset)
throws IllegalBlockSizeException, ShortBufferException,
BadPaddingException {
+ if (requireReinit) {
+ throw new IllegalStateException
+ ("Must use either different key or iv for GCM encryption");
+ }
// calculate the total input length
int totalLen = buffered + inputLen;
@@ -752,8 +910,9 @@
}
// if encrypting and padding not null, add padding
- if (!decrypting && padding != null)
+ if (!decrypting && padding != null) {
paddedLen += paddingLen;
+ }
// check output buffer capacity.
// if we are decrypting with padding applied, we can perform this
@@ -763,8 +922,8 @@
throw new ShortBufferException("Output buffer is null");
}
int outputCapacity = output.length - outputOffset;
- if (((!decrypting) || (padding == null)) &&
- (outputCapacity < paddedLen) ||
+
+ if (((!decrypting) && (outputCapacity < paddedLen)) ||
(decrypting && (outputCapacity < (paddedLen - blockSize)))) {
throw new ShortBufferException("Output buffer too short: "
+ outputCapacity + " bytes given, "
@@ -812,6 +971,7 @@
}
totalLen = padStart;
}
+
if ((output.length - outputOffset) < totalLen) {
// restore so users can retry with a larger buffer
cipher.restore();
@@ -824,8 +984,13 @@
output[outputOffset + i] = outWithPadding[i];
}
} else { // encrypting
- totalLen = finalNoPadding(finalBuf, finalOffset, output,
- outputOffset, paddedLen);
+ try {
+ totalLen = finalNoPadding(finalBuf, finalOffset, output,
+ outputOffset, paddedLen);
+ } finally {
+ // reset after doFinal() for GCM encryption
+ requireReinit = (cipherMode == GCM_MODE);
+ }
}
buffered = 0;
@@ -836,33 +1001,33 @@
return totalLen;
}
- private int finalNoPadding(byte[] in, int inOff, byte[] out, int outOff,
+ private int finalNoPadding(byte[] in, int inOfs, byte[] out, int outOfs,
int len)
- throws IllegalBlockSizeException
- {
- if (in == null || len == 0)
- return 0;
+ throws IllegalBlockSizeException, AEADBadTagException {
- if ((cipherMode != CFB_MODE) && (cipherMode != OFB_MODE)
- && ((len % unitBytes) != 0) && (cipherMode != CTS_MODE)) {
- if (padding != null) {
- throw new IllegalBlockSizeException
- ("Input length (with padding) not multiple of " +
- unitBytes + " bytes");
- } else {
- throw new IllegalBlockSizeException
- ("Input length not multiple of " + unitBytes
- + " bytes");
- }
+ if ((cipherMode != GCM_MODE) && (in == null || len == 0)) {
+ return 0;
}
-
+ if ((cipherMode != CFB_MODE) && (cipherMode != OFB_MODE) &&
+ (cipherMode != GCM_MODE) &&
+ ((len % unitBytes) != 0) && (cipherMode != CTS_MODE)) {
+ if (padding != null) {
+ throw new IllegalBlockSizeException
+ ("Input length (with padding) not multiple of " +
+ unitBytes + " bytes");
+ } else {
+ throw new IllegalBlockSizeException
+ ("Input length not multiple of " + unitBytes
+ + " bytes");
+ }
+ }
+ int outLen = 0;
if (decrypting) {
- cipher.decryptFinal(in, inOff, len, out, outOff);
+ outLen = cipher.decryptFinal(in, inOfs, len, out, outOfs);
} else {
- cipher.encryptFinal(in, inOff, len, out, outOff);
+ outLen = cipher.encryptFinal(in, inOfs, len, out, outOfs);
}
-
- return len;
+ return outLen;
}
// Note: Wrap() and Unwrap() are the same in
@@ -939,4 +1104,36 @@
return ConstructKeys.constructKey(encodedKey, wrappedKeyAlgorithm,
wrappedKeyType);
}
+
+ /**
+ * Continues a multi-part update of the Additional Authentication
+ * Data (AAD), using a subset of the provided buffer.
+ *
+ * Calls to this method provide AAD to the cipher when operating in
+ * modes such as AEAD (GCM/CCM). If this cipher is operating in
+ * either GCM or CCM mode, all AAD must be supplied before beginning
+ * operations on the ciphertext (via the {@code update} and {@code
+ * doFinal} methods).
+ *
+ * @param src the buffer containing the AAD
+ * @param offset the offset in {@code src} where the AAD input starts
+ * @param len the number of AAD bytes
+ *
+ * @throws IllegalStateException if this cipher is in a wrong state
+ * (e.g., has not been initialized), does not accept AAD, or if
+ * operating in either GCM or CCM mode and one of the {@code update}
+ * methods has already been called for the active
+ * encryption/decryption operation
+ * @throws UnsupportedOperationException if this method
+ * has not been overridden by an implementation
+ *
+ * @since 1.8
+ */
+ void updateAAD(byte[] src, int offset, int len) {
+ if (requireReinit) {
+ throw new IllegalStateException
+ ("Must use either different key or iv for GCM encryption");
+ }
+ cipher.updateAAD(src, offset, len);
+ }
}
diff -r cf5d2d5094cc -r bcb241432928 jdk/src/share/classes/com/sun/crypto/provider/CipherTextStealing.java
--- a/jdk/src/share/classes/com/sun/crypto/provider/CipherTextStealing.java Mon Jan 07 13:19:03 2013 -0800
+++ b/jdk/src/share/classes/com/sun/crypto/provider/CipherTextStealing.java Mon Jan 07 14:40:07 2013 -0800
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2004, 2007, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2004, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -83,9 +83,10 @@
* @param plainLen the length of the input data
* @param cipher the buffer for the result
* @param cipherOffset the offset in cipher
+ * @return the number of bytes placed into cipher
*/
- void encryptFinal(byte[] plain, int plainOffset, int plainLen,
- byte[] cipher, int cipherOffset)
+ int encryptFinal(byte[] plain, int plainOffset, int plainLen,
+ byte[] cipher, int cipherOffset)
throws IllegalBlockSizeException {
if (plainLen < blockSize) {
@@ -134,6 +135,7 @@
embeddedCipher.encryptBlock(tmp2, 0, cipher, cipherOffset);
}
}
+ return plainLen;
}
/**
@@ -158,9 +160,10 @@
* @param cipherLen the length of the input data
* @param plain the buffer for the result
* @param plainOffset the offset in plain
+ * @return the number of bytes placed into plain
*/
- void decryptFinal(byte[] cipher, int cipherOffset, int cipherLen,
- byte[] plain, int plainOffset)
+ int decryptFinal(byte[] cipher, int cipherOffset, int cipherLen,
+ byte[] plain, int plainOffset)
throws IllegalBlockSizeException {
if (cipherLen < blockSize) {
throw new IllegalBlockSizeException("input is too short!");
@@ -211,5 +214,6 @@
}
}
}
+ return cipherLen;
}
}
diff -r cf5d2d5094cc -r bcb241432928 jdk/src/share/classes/com/sun/crypto/provider/FeedbackCipher.java
--- a/jdk/src/share/classes/com/sun/crypto/provider/FeedbackCipher.java Mon Jan 07 13:19:03 2013 -0800
+++ b/jdk/src/share/classes/com/sun/crypto/provider/FeedbackCipher.java Mon Jan 07 14:40:07 2013 -0800
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2007, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -26,7 +26,7 @@
package com.sun.crypto.provider;
import java.security.InvalidKeyException;
-import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.*;
/**
* This class represents a block cipher in one of its modes. It wraps
@@ -150,11 +150,13 @@
* @param plainLen the length of the input data
* @param cipher the buffer for the encryption result
* @param cipherOffset the offset in cipher
+ * @return the number of bytes placed into cipher
*/
- void encryptFinal(byte[] plain, int plainOffset, int plainLen,
- byte[] cipher, int cipherOffset)
+ int encryptFinal(byte[] plain, int plainOffset, int plainLen,
+ byte[] cipher, int cipherOffset)
throws IllegalBlockSizeException {
encrypt(plain, plainOffset, plainLen, cipher, cipherOffset);
+ return plainLen;
}
/**
* Performs decryption operation.
@@ -190,10 +192,40 @@
* @param cipherLen the length of the input data
* @param plain the buffer for the decryption result
* @param plainOffset the offset in plain
+ * @return the number of bytes placed into plain
*/
- void decryptFinal(byte[] cipher, int cipherOffset, int cipherLen,
- byte[] plain, int plainOffset)
- throws IllegalBlockSizeException {
+ int decryptFinal(byte[] cipher, int cipherOffset, int cipherLen,
+ byte[] plain, int plainOffset)
+ throws IllegalBlockSizeException, AEADBadTagException {
decrypt(cipher, cipherOffset, cipherLen, plain, plainOffset);
+ return cipherLen;
}
+
+ /**
+ * Continues a multi-part update of the Additional Authentication
+ * Data (AAD), using a subset of the provided buffer. If this
+ * cipher is operating in either GCM or CCM mode, all AAD must be
+ * supplied before beginning operations on the ciphertext (via the
+ * {@code update} and {@code doFinal} methods).
+ *
+ * NOTE: Given most modes do not accept AAD, default impl for this + * method throws IllegalStateException. + * + * @param src the buffer containing the AAD + * @param offset the offset in {@code src} where the AAD input starts + * @param len the number of AAD bytes + * + * @throws IllegalStateException if this cipher is in a wrong state + * (e.g., has not been initialized), does not accept AAD, or if + * operating in either GCM or CCM mode and one of the {@code update} + * methods has already been called for the active + * encryption/decryption operation + * @throws UnsupportedOperationException if this method + * has not been overridden by an implementation + * + * @since 1.8 + */ + void updateAAD(byte[] src, int offset, int len) { + throw new IllegalStateException("No AAD accepted"); + } } diff -r cf5d2d5094cc -r bcb241432928 jdk/src/share/classes/com/sun/crypto/provider/GCMParameters.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/share/classes/com/sun/crypto/provider/GCMParameters.java Mon Jan 07 14:40:07 2013 -0800 @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.crypto.provider; + +import java.io.IOException; +import java.security.AlgorithmParametersSpi; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.InvalidParameterSpecException; +import javax.crypto.spec.GCMParameterSpec; +import sun.misc.HexDumpEncoder; +import sun.security.util.*; + +/** + * This class implements the parameter set used with + * GCM encryption, which is defined in RFC 5084 as follows: + * + *
+ * GCMParameters ::= SEQUENCE { + * aes-iv OCTET STRING, -- recommended size is 12 octets + * aes-tLen AES-GCM-ICVlen DEFAULT 12 } + * + * AES-GCM-ICVlen ::= INTEGER (12 | 13 | 14 | 15 | 16) + * + *+ * + * @author Valerie Peng + * @since 1.8 + */ +public final class GCMParameters extends AlgorithmParametersSpi { + + // the iv + private byte[] iv; + // the tag length in bytes + private int tLen; + + public GCMParameters() {} + + protected void engineInit(AlgorithmParameterSpec paramSpec) + throws InvalidParameterSpecException { + + if (!(paramSpec instanceof GCMParameterSpec)) { + throw new InvalidParameterSpecException + ("Inappropriate parameter specification"); + } + GCMParameterSpec gps = (GCMParameterSpec) paramSpec; + // need to convert from bits to bytes for ASN.1 encoding + this.tLen = gps.getTLen()/8; + this.iv = gps.getIV(); + } + + protected void engineInit(byte[] encoded) throws IOException { + DerValue val = new DerValue(encoded); + // check if IV or params + if (val.tag == DerValue.tag_Sequence) { + byte[] iv = val.data.getOctetString(); + int tLen; + if (val.data.available() != 0) { + tLen = val.data.getInteger(); + if (tLen < 12 || tLen > 16 ) { + throw new IOException + ("GCM parameter parsing error: unsupported tag len: " + + tLen); + } + if (val.data.available() != 0) { + throw new IOException + ("GCM parameter parsing error: extra data"); + } + } else { + tLen = 12; + } + this.iv = iv.clone(); + this.tLen = tLen; + } else { + throw new IOException("GCM parameter parsing error: no SEQ tag"); + } + } + + protected void engineInit(byte[] encoded, String decodingMethod) + throws IOException { + engineInit(encoded); + } + + protected
This function is used in the implementation of GCM mode. + * + * @since 1.8 + */ +final class GCTR { + + // these fields should not change after the object has been constructed + private final SymmetricCipher aes; + private final byte[] icb; + + // the current counter value + private byte[] counter; + + // needed for save/restore calls + private byte[] counterSave; + + // NOTE: cipher should already be initialized + GCTR(SymmetricCipher cipher, byte[] initialCounterBlk) { + this.aes = cipher; + this.icb = initialCounterBlk; + this.counter = icb.clone(); + } + + // input must be multiples of 128-bit blocks when calling update + int update(byte[] in, int inOfs, int inLen, byte[] out, int outOfs) { + if (inLen - inOfs > in.length) { + throw new RuntimeException("input length out of bound"); + } + if (inLen < 0 || inLen % AES_BLOCK_SIZE != 0) { + throw new RuntimeException("input length unsupported"); + } + if (out.length - outOfs < inLen) { + throw new RuntimeException("output buffer too small"); + } + + byte[] encryptedCntr = new byte[AES_BLOCK_SIZE]; + + int numOfCompleteBlocks = inLen / AES_BLOCK_SIZE; + for (int i = 0; i < numOfCompleteBlocks; i++) { + aes.encryptBlock(counter, 0, encryptedCntr, 0); + for (int n = 0; n < AES_BLOCK_SIZE; n++) { + int index = (i * AES_BLOCK_SIZE + n); + out[outOfs + index] = + (byte) ((in[inOfs + index] ^ encryptedCntr[n])); + } + GaloisCounterMode.increment32(counter); + } + return inLen; + } + + // input can be arbitrary size when calling doFinal + protected int doFinal(byte[] in, int inOfs, int inLen, byte[] out, + int outOfs) throws IllegalBlockSizeException { + try { + if (inLen < 0) { + throw new IllegalBlockSizeException("Negative input size!"); + } else if (inLen > 0) { + int lastBlockSize = inLen % AES_BLOCK_SIZE; + // process the complete blocks first + update(in, inOfs, inLen - lastBlockSize, out, outOfs); + if (lastBlockSize != 0) { + // do the last partial block + byte[] encryptedCntr = new byte[AES_BLOCK_SIZE]; + aes.encryptBlock(counter, 0, encryptedCntr, 0); + + int processed = inLen - lastBlockSize; + for (int n = 0; n < lastBlockSize; n++) { + out[outOfs + processed + n] = + (byte) ((in[inOfs + processed + n] ^ + encryptedCntr[n])); + } + } + } + } finally { + reset(); + } + return inLen; + } + + /** + * Resets the current counter to its initial value. + * This is used after the doFinal() is called so this object can be + * reused w/o explicit re-initialization. + */ + void reset() { + System.arraycopy(icb, 0, counter, 0, icb.length); + } + + /** + * Save the current content of this object. + */ + void save() { + this.counterSave = this.counter.clone(); + } + + /** + * Restores the content of this object to the previous saved one. + */ + void restore() { + this.counter = this.counterSave; + } +} diff -r cf5d2d5094cc -r bcb241432928 jdk/src/share/classes/com/sun/crypto/provider/GHASH.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/share/classes/com/sun/crypto/provider/GHASH.java Mon Jan 07 14:40:07 2013 -0800 @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/* + * (C) Copyright IBM Corp. 2013 + */ + +package com.sun.crypto.provider; + +import java.util.Arrays; +import java.security.*; +import static com.sun.crypto.provider.AESConstants.AES_BLOCK_SIZE; + +/** + * This class represents the GHASH function defined in NIST 800-38D + * under section 6.4. It needs to be constructed w/ a hash subkey, i.e. + * block H. Given input of 128-bit blocks, it will process and output + * a 128-bit block. + * + *
This function is used in the implementation of GCM mode. + * + * @since 1.8 + */ +final class GHASH { + + private static final byte P128 = (byte) 0xe1; //reduction polynomial + + private static boolean getBit(byte[] b, int pos) { + int p = pos / 8; + pos %= 8; + int i = (b[p] >>> (7 - pos)) & 1; + return i != 0; + } + + private static void shift(byte[] b) { + byte temp, temp2; + temp2 = 0; + for (int i = 0; i < b.length; i++) { + temp = (byte) ((b[i] & 0x01) << 7); + b[i] = (byte) ((b[i] & 0xff) >>> 1); + b[i] = (byte) (b[i] | temp2); + temp2 = temp; + } + } + + // Given block X and Y, returns the muliplication of X * Y + private static byte[] blockMult(byte[] x, byte[] y) { + if (x.length != AES_BLOCK_SIZE || y.length != AES_BLOCK_SIZE) { + throw new RuntimeException("illegal input sizes"); + } + byte[] z = new byte[AES_BLOCK_SIZE]; + byte[] v = y.clone(); + // calculate Z1-Z127 and V1-V127 + for (int i = 0; i < 127; i++) { + // Zi+1 = Zi if bit i of x is 0 + if (getBit(x, i)) { + for (int n = 0; n < z.length; n++) { + z[n] ^= v[n]; + } + } + boolean lastBitOfV = getBit(v, 127); + shift(v); + if (lastBitOfV) v[0] ^= P128; + } + // calculate Z128 + if (getBit(x, 127)) { + for (int n = 0; n < z.length; n++) { + z[n] ^= v[n]; + } + } + return z; + } + + // hash subkey H; should not change after the object has been constructed + private final byte[] subkeyH; + + // buffer for storing hash + private byte[] state; + + // variables for save/restore calls + private byte[] stateSave = null; + + /** + * Initializes the cipher in the specified mode with the given key + * and iv. + * + * @param subkeyH the hash subkey + * + * @exception ProviderException if the given key is inappropriate for + * initializing this digest + */ + GHASH(byte[] subkeyH) throws ProviderException { + if ((subkeyH == null) || subkeyH.length != AES_BLOCK_SIZE) { + throw new ProviderException("Internal error"); + } + this.subkeyH = subkeyH; + this.state = new byte[AES_BLOCK_SIZE]; + } + + /** + * Resets the GHASH object to its original state, i.e. blank w/ + * the same subkey H. Used after digest() is called and to re-use + * this object for different data w/ the same H. + */ + void reset() { + Arrays.fill(state, (byte) 0); + } + + /** + * Save the current snapshot of this GHASH object. + */ + void save() { + stateSave = state.clone(); + } + + /** + * Restores this object using the saved snapshot. + */ + void restore() { + state = stateSave; + } + + private void processBlock(byte[] data, int ofs) { + if (data.length - ofs < AES_BLOCK_SIZE) { + throw new RuntimeException("need complete block"); + } + for (int n = 0; n < state.length; n++) { + state[n] ^= data[ofs + n]; + } + state = blockMult(state, subkeyH); + } + + void update(byte[] in) { + update(in, 0, in.length); + } + + void update(byte[] in, int inOfs, int inLen) { + if (inLen - inOfs > in.length) { + throw new RuntimeException("input length out of bound"); + } + if (inLen % AES_BLOCK_SIZE != 0) { + throw new RuntimeException("input length unsupported"); + } + + for (int i = inOfs; i < (inOfs + inLen); i += AES_BLOCK_SIZE) { + processBlock(in, i); + } + } + + byte[] digest() { + try { + return state.clone(); + } finally { + reset(); + } + } +} diff -r cf5d2d5094cc -r bcb241432928 jdk/src/share/classes/com/sun/crypto/provider/GaloisCounterMode.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/share/classes/com/sun/crypto/provider/GaloisCounterMode.java Mon Jan 07 14:40:07 2013 -0800 @@ -0,0 +1,501 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code.S + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.crypto.provider; + +import java.util.Arrays; +import java.io.*; +import java.security.*; +import javax.crypto.*; +import static com.sun.crypto.provider.AESConstants.AES_BLOCK_SIZE; + +/** + * This class represents ciphers in GaloisCounter (GCM) mode. + * + *
This mode currently should only be used w/ AES cipher. + * Although no checking is done here, caller should only + * pass AES Cipher to the constructor. + * + *
NOTE: This class does not deal with buffering or padding. + * + * @since 1.8 + */ +final class GaloisCounterMode extends FeedbackCipher { + + static int DEFAULT_TAG_LEN = AES_BLOCK_SIZE; + static int DEFAULT_IV_LEN = 12; // in bytes + + // buffer for AAD data; if null, meaning update has been called + private ByteArrayOutputStream aadBuffer = new ByteArrayOutputStream(); + private int sizeOfAAD = 0; + + // in bytes; need to convert to bits (default value 128) when needed + private int tagLenBytes = DEFAULT_TAG_LEN; + + // these following 2 fields can only be initialized after init() is + // called, e.g. after cipher key k is set, and STAY UNCHANGED + private byte[] subkeyH = null; + private byte[] preCounterBlock = null; + + private GCTR gctrPAndC = null; + private GHASH ghashAllToS = null; + + // length of total data, i.e. len(C) + private int processed = 0; + + // additional variables for save/restore calls + private byte[] aadBufferSave = null; + private int sizeOfAADSave = 0; + private int processedSave = 0; + + // value must be 16-byte long; used by GCTR and GHASH as well + static void increment32(byte[] value) { + if (value.length != AES_BLOCK_SIZE) { + throw new RuntimeException("Unexpected counter block length"); + } + // start from last byte and only go over 4 bytes, i.e. total 32 bits + int n = value.length - 1; + while ((n >= value.length - 4) && (++value[n] == 0)) { + n--; + } + } + + // ivLen in bits + private static byte[] getLengthBlock(int ivLen) { + byte[] out = new byte[AES_BLOCK_SIZE]; + out[12] = (byte)(ivLen >>> 24); + out[13] = (byte)(ivLen >>> 16); + out[14] = (byte)(ivLen >>> 8); + out[15] = (byte)ivLen; + return out; + } + + // aLen and cLen both in bits + private static byte[] getLengthBlock(int aLen, int cLen) { + byte[] out = new byte[AES_BLOCK_SIZE]; + out[4] = (byte)(aLen >>> 24); + out[5] = (byte)(aLen >>> 16); + out[6] = (byte)(aLen >>> 8); + out[7] = (byte)aLen; + out[12] = (byte)(cLen >>> 24); + out[13] = (byte)(cLen >>> 16); + out[14] = (byte)(cLen >>> 8); + out[15] = (byte)cLen; + return out; + } + + private static byte[] expandToOneBlock(byte[] in, int inOfs, int len) { + if (len > AES_BLOCK_SIZE) { + throw new ProviderException("input " + len + " too long"); + } + if (len == AES_BLOCK_SIZE && inOfs == 0) { + return in; + } else { + byte[] paddedIn = new byte[AES_BLOCK_SIZE]; + System.arraycopy(in, inOfs, paddedIn, 0, len); + return paddedIn; + } + } + + private static byte[] getJ0(byte[] iv, byte[] subkeyH) { + byte[] j0; + if (iv.length == 12) { // 96 bits + j0 = expandToOneBlock(iv, 0, iv.length); + j0[AES_BLOCK_SIZE - 1] = 1; + } else { + GHASH g = new GHASH(subkeyH); + int lastLen = iv.length % AES_BLOCK_SIZE; + if (lastLen != 0) { + g.update(iv, 0, iv.length - lastLen); + byte[] padded = + expandToOneBlock(iv, iv.length - lastLen, lastLen); + g.update(padded); + } else { + g.update(iv); + } + byte[] lengthBlock = getLengthBlock(iv.length*8); + g.update(lengthBlock); + j0 = g.digest(); + } + return j0; + } + + GaloisCounterMode(SymmetricCipher embeddedCipher) { + super(embeddedCipher); + aadBuffer = new ByteArrayOutputStream(); + } + + /** + * Gets the name of the feedback mechanism + * + * @return the name of the feedback mechanism + */ + String getFeedback() { + return "GCM"; + } + + /** + * Resets the cipher object to its original state. + * This is used when doFinal is called in the Cipher class, so that the + * cipher can be reused (with its original key and iv). + */ + void reset() { + if (aadBuffer == null) { + aadBuffer = new ByteArrayOutputStream(); + } else { + aadBuffer.reset(); + } + if (gctrPAndC != null) gctrPAndC.reset(); + if (ghashAllToS != null) ghashAllToS.reset(); + processed = 0; + sizeOfAAD = 0; + } + + /** + * Save the current content of this cipher. + */ + void save() { + processedSave = processed; + sizeOfAADSave = sizeOfAAD; + aadBufferSave = + ((aadBuffer == null || aadBuffer.size() == 0)? + null : aadBuffer.toByteArray()); + if (gctrPAndC != null) gctrPAndC.save(); + if (ghashAllToS != null) ghashAllToS.save(); + } + + /** + * Restores the content of this cipher to the previous saved one. + */ + void restore() { + processed = processedSave; + sizeOfAAD = sizeOfAADSave; + if (aadBuffer != null) { + aadBuffer.reset(); + if (aadBufferSave != null) { + aadBuffer.write(aadBufferSave, 0, aadBufferSave.length); + } + } + if (gctrPAndC != null) gctrPAndC.restore(); + if (ghashAllToS != null) ghashAllToS.restore(); + } + + /** + * Initializes the cipher in the specified mode with the given key + * and iv. + * + * @param decrypting flag indicating encryption or decryption + * @param algorithm the algorithm name + * @param key the key + * @param iv the iv + * @param tagLenBytes the length of tag in bytes + * + * @exception InvalidKeyException if the given key is inappropriate for + * initializing this cipher + */ + void init(boolean decrypting, String algorithm, byte[] key, byte[] iv) + throws InvalidKeyException { + init(decrypting, algorithm, key, iv, DEFAULT_TAG_LEN); + } + + /** + * Initializes the cipher in the specified mode with the given key + * and iv. + * + * @param decrypting flag indicating encryption or decryption + * @param algorithm the algorithm name + * @param key the key + * @param iv the iv + * @param tagLenBytes the length of tag in bytes + * + * @exception InvalidKeyException if the given key is inappropriate for + * initializing this cipher + */ + void init(boolean decrypting, String algorithm, byte[] keyValue, + byte[] ivValue, int tagLenBytes) + throws InvalidKeyException { + if (keyValue == null || ivValue == null) { + throw new InvalidKeyException("Internal error"); + } + + // always encrypt mode for embedded cipher + this.embeddedCipher.init(false, algorithm, keyValue); + this.subkeyH = new byte[AES_BLOCK_SIZE]; + this.embeddedCipher.encryptBlock(new byte[AES_BLOCK_SIZE], 0, + this.subkeyH, 0); + + this.iv = ivValue.clone(); + preCounterBlock = getJ0(iv, subkeyH); + byte[] j0Plus1 = preCounterBlock.clone(); + increment32(j0Plus1); + gctrPAndC = new GCTR(embeddedCipher, j0Plus1); + ghashAllToS = new GHASH(subkeyH); + + this.tagLenBytes = tagLenBytes; + if (aadBuffer == null) { + aadBuffer = new ByteArrayOutputStream(); + } else { + aadBuffer.reset(); + } + processed = 0; + sizeOfAAD = 0; + } + + /** + * Continues a multi-part update of the Additional Authentication + * Data (AAD), using a subset of the provided buffer. If this + * cipher is operating in either GCM or CCM mode, all AAD must be + * supplied before beginning operations on the ciphertext (via the + * {@code update} and {@code doFinal} methods). + *
+ * NOTE: Given most modes do not accept AAD, default impl for this + * method throws IllegalStateException. + * + * @param src the buffer containing the AAD + * @param offset the offset in {@code src} where the AAD input starts + * @param len the number of AAD bytes + * + * @throws IllegalStateException if this cipher is in a wrong state + * (e.g., has not been initialized), does not accept AAD, or if + * operating in either GCM or CCM mode and one of the {@code update} + * methods has already been called for the active + * encryption/decryption operation + * @throws UnsupportedOperationException if this method + * has not been overridden by an implementation + * + * @since 1.8 + */ + void updateAAD(byte[] src, int offset, int len) { + if (aadBuffer != null) { + aadBuffer.write(src, offset, len); + } else { + // update has already been called + throw new IllegalStateException + ("Update has been called; no more AAD data"); + } + } + + // Feed the AAD data to GHASH, pad if necessary + void processAAD() { + if (aadBuffer != null) { + byte[] aad = aadBuffer.toByteArray(); + sizeOfAAD = aad.length; + aadBuffer = null; + + int lastLen = aad.length % AES_BLOCK_SIZE; + if (lastLen != 0) { + ghashAllToS.update(aad, 0, aad.length - lastLen); + byte[] padded = expandToOneBlock(aad, aad.length - lastLen, + lastLen); + ghashAllToS.update(padded); + } else { + ghashAllToS.update(aad); + } + } + } + + // Utility to process the last block; used by encryptFinal and decryptFinal + void doLastBlock(byte[] in, int inOfs, int len, byte[] out, int outOfs, + boolean isEncrypt) throws IllegalBlockSizeException { + // process data in 'in' + gctrPAndC.doFinal(in, inOfs, len, out, outOfs); + processed += len; + + byte[] ct; + int ctOfs; + if (isEncrypt) { + ct = out; + ctOfs = outOfs; + } else { + ct = in; + ctOfs = inOfs; + } + int lastLen = len % AES_BLOCK_SIZE; + if (lastLen != 0) { + ghashAllToS.update(ct, ctOfs, len - lastLen); + byte[] padded = + expandToOneBlock(ct, (ctOfs + len - lastLen), lastLen); + ghashAllToS.update(padded); + } else { + ghashAllToS.update(ct, ctOfs, len); + } + } + + + /** + * Performs encryption operation. + * + *
The input plain text in
, starting at inOff
+ * and ending at (inOff + len - 1)
, is encrypted. The result
+ * is stored in out
, starting at outOfs
.
+ *
+ *
It is the application's responsibility to make sure that
+ * len
is a multiple of the embedded cipher's block size,
+ * otherwise, a ProviderException will be thrown.
+ *
+ *
It is also the application's responsibility to make sure that
+ * init
has been called before this method is called.
+ * (This check is omitted here, to avoid double checking.)
+ *
+ * @param in the buffer with the input data to be encrypted
+ * @param inOfs the offset in in
+ * @param len the length of the input data
+ * @param out the buffer for the result
+ * @param outOfs the offset in out
+ */
+ void encrypt(byte[] in, int inOfs, int len, byte[] out, int outOfs) {
+ processAAD();
+ if (len > 0) {
+ gctrPAndC.update(in, inOfs, len, out, outOfs);
+ processed += len;
+ ghashAllToS.update(out, outOfs, len);
+ }
+ }
+
+ /**
+ * Performs encryption operation for the last time.
+ *
+ *
NOTE: len
may not be multiple of the embedded
+ * cipher's block size for this call.
+ *
+ * @param in the input buffer with the data to be encrypted
+ * @param inOfs the offset in in
+ * @param len the length of the input data
+ * @param out the buffer for the encryption result
+ * @param outOfs the offset in out
+ * @return the number of bytes placed into the out
buffer
+ */
+ int encryptFinal(byte[] in, int inOfs, int len, byte[] out, int outOfs)
+ throws IllegalBlockSizeException {
+ if (out.length - outOfs < (len + tagLenBytes)) {
+ throw new RuntimeException("Output buffer too small");
+ }
+
+ processAAD();
+ if (len > 0) {
+ //ByteUtil.dumpArray(Arrays.copyOfRange(in, inOfs, inOfs + len));
+ doLastBlock(in, inOfs, len, out, outOfs, true);
+ }
+
+ byte[] lengthBlock = getLengthBlock(sizeOfAAD*8, processed*8);
+ ghashAllToS.update(lengthBlock);
+ byte[] s = ghashAllToS.digest();
+ byte[] sOut = new byte[s.length];
+ GCTR gctrForSToTag = new GCTR(embeddedCipher, this.preCounterBlock);
+ gctrForSToTag.doFinal(s, 0, s.length, sOut, 0);
+
+ System.arraycopy(sOut, 0, out, (outOfs + len), tagLenBytes);
+ return (len + tagLenBytes);
+ }
+
+ /**
+ * Performs decryption operation.
+ *
+ *
The input cipher text in
, starting at
+ * inOfs
and ending at (inOfs + len - 1)
,
+ * is decrypted. The result is stored in out
, starting at
+ * outOfs
.
+ *
+ *
It is the application's responsibility to make sure that
+ * len
is a multiple of the embedded cipher's block
+ * size, as any excess bytes are ignored.
+ *
+ *
It is also the application's responsibility to make sure that
+ * init
has been called before this method is called.
+ * (This check is omitted here, to avoid double checking.)
+ *
+ * @param in the buffer with the input data to be decrypted
+ * @param inOfs the offset in in
+ * @param len the length of the input data
+ * @param out the buffer for the result
+ * @param outOfs the offset in out
+ */
+ void decrypt(byte[] in, int inOfs, int len, byte[] out, int outOfs) {
+ processAAD();
+
+ if (len > 0) { // must be at least AES_BLOCK_SIZE bytes long
+ gctrPAndC.update(in, inOfs, len, out, outOfs);
+ processed += len;
+ ghashAllToS.update(in, inOfs, len);
+ }
+ }
+
+ /**
+ * Performs decryption operation for the last time.
+ *
+ *
NOTE: For cipher feedback modes which does not perform
+ * special handling for the last few blocks, this is essentially
+ * the same as encrypt(...)
. Given most modes do
+ * not do special handling, the default impl for this method is
+ * to simply call decrypt(...)
.
+ *
+ * @param in the input buffer with the data to be decrypted
+ * @param inOfs the offset in cipher
+ * @param len the length of the input data
+ * @param out the buffer for the decryption result
+ * @param outOfs the offset in plain
+ * @return the number of bytes placed into the out
buffer
+ */
+ int decryptFinal(byte[] in, int inOfs, int len,
+ byte[] out, int outOfs)
+ throws IllegalBlockSizeException, AEADBadTagException {
+ if (len < tagLenBytes) {
+ throw new RuntimeException("Input buffer too short - need tag");
+ }
+ if (out.length - outOfs < (len - tagLenBytes)) {
+ throw new RuntimeException("Output buffer too small");
+ }
+ processAAD();
+
+ int processedOld = processed;
+ byte[] tag = new byte[tagLenBytes];
+ // get the trailing tag bytes from 'in'
+ System.arraycopy(in, inOfs + len - tagLenBytes, tag, 0, tagLenBytes);
+ len -= tagLenBytes;
+
+ if (len > 0) {
+ doLastBlock(in, inOfs, len, out, outOfs, false);
+ }
+
+ byte[] lengthBlock = getLengthBlock(sizeOfAAD*8, processed*8);
+ ghashAllToS.update(lengthBlock);
+
+ byte[] s = ghashAllToS.digest();
+ byte[] sOut = new byte[s.length];
+ GCTR gctrForSToTag = new GCTR(embeddedCipher, this.preCounterBlock);
+ gctrForSToTag.doFinal(s, 0, s.length, sOut, 0);
+ for (int i = 0; i < tagLenBytes; i++) {
+ if (tag[i] != sOut[i]) {
+ throw new AEADBadTagException("Tag mismatch!");
+ }
+ }
+ return len;
+ }
+
+ // return tag length in bytes
+ int getTagLen() {
+ return this.tagLenBytes;
+ }
+}
diff -r cf5d2d5094cc -r bcb241432928 jdk/src/share/classes/com/sun/crypto/provider/SunJCE.java
--- a/jdk/src/share/classes/com/sun/crypto/provider/SunJCE.java Mon Jan 07 13:19:03 2013 -0800
+++ b/jdk/src/share/classes/com/sun/crypto/provider/SunJCE.java Mon Jan 07 14:40:07 2013 -0800
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -57,6 +57,7 @@
* - ARCFOUR (RC4 compatible)
*
* - Cipher modes ECB, CBC, CFB, OFB, PCBC, CTR, and CTS for all block ciphers
+ * and mode GCM for AES cipher
*
* - Cipher padding ISO10126Padding for non-PKCS#5 block ciphers and
* NoPadding and PKCS5Padding for all block ciphers
@@ -100,7 +101,7 @@
"|CFB8|CFB16|CFB24|CFB32|CFB40|CFB48|CFB56|CFB64" +
"|OFB8|OFB16|OFB24|OFB32|OFB40|OFB48|OFB56|OFB64";
final String BLOCK_MODES128 = BLOCK_MODES +
- "|CFB72|CFB80|CFB88|CFB96|CFB104|CFB112|CFB120|CFB128" +
+ "|GCM|CFB72|CFB80|CFB88|CFB96|CFB104|CFB112|CFB120|CFB128" +
"|OFB72|OFB80|OFB88|OFB96|OFB104|OFB112|OFB120|OFB128";
final String BLOCK_PADS = "NOPADDING|PKCS5PADDING|ISO10126PADDING";
@@ -258,6 +259,9 @@
put("Cipher.AES_128/CFB/NoPadding", "com.sun.crypto.provider.AESCipher$AES128_CFB_NoPadding");
put("Alg.Alias.Cipher.2.16.840.1.101.3.4.1.4", "AES_128/CFB/NoPadding");
put("Alg.Alias.Cipher.OID.2.16.840.1.101.3.4.1.4", "AES_128/CFB/NoPadding");
+ put("Cipher.AES_128/GCM/NoPadding", "com.sun.crypto.provider.AESCipher$AES128_GCM_NoPadding");
+ put("Alg.Alias.Cipher.2.16.840.1.101.3.4.1.6", "AES_128/GCM/NoPadding");
+ put("Alg.Alias.Cipher.OID.2.16.840.1.101.3.4.1.6", "AES_128/GCM/NoPadding");
put("Cipher.AES_192/ECB/NoPadding", "com.sun.crypto.provider.AESCipher$AES192_ECB_NoPadding");
put("Alg.Alias.Cipher.2.16.840.1.101.3.4.1.21", "AES_192/ECB/NoPadding");
@@ -271,7 +275,9 @@
put("Cipher.AES_192/CFB/NoPadding", "com.sun.crypto.provider.AESCipher$AES192_CFB_NoPadding");
put("Alg.Alias.Cipher.2.16.840.1.101.3.4.1.24", "AES_192/CFB/NoPadding");
put("Alg.Alias.Cipher.OID.2.16.840.1.101.3.4.1.24", "AES_192/CFB/NoPadding");
-
+ put("Cipher.AES_192/GCM/NoPadding", "com.sun.crypto.provider.AESCipher$AES192_GCM_NoPadding");
+ put("Alg.Alias.Cipher.2.16.840.1.101.3.4.1.26", "AES_192/GCM/NoPadding");
+ put("Alg.Alias.Cipher.OID.2.16.840.1.101.3.4.1.26", "AES_192/GCM/NoPadding");
put("Cipher.AES_256/ECB/NoPadding", "com.sun.crypto.provider.AESCipher$AES256_ECB_NoPadding");
put("Alg.Alias.Cipher.2.16.840.1.101.3.4.1.41", "AES_256/ECB/NoPadding");
@@ -285,6 +291,9 @@
put("Cipher.AES_256/CFB/NoPadding", "com.sun.crypto.provider.AESCipher$AES256_CFB_NoPadding");
put("Alg.Alias.Cipher.2.16.840.1.101.3.4.1.44", "AES_256/CFB/NoPadding");
put("Alg.Alias.Cipher.OID.2.16.840.1.101.3.4.1.44", "AES_256/CFB/NoPadding");
+ put("Cipher.AES_256/GCM/NoPadding", "com.sun.crypto.provider.AESCipher$AES256_GCM_NoPadding");
+ put("Alg.Alias.Cipher.2.16.840.1.101.3.4.1.46", "AES_256/GCM/NoPadding");
+ put("Alg.Alias.Cipher.OID.2.16.840.1.101.3.4.1.46", "AES_256/GCM/NoPadding");
put("Cipher.AESWrap", "com.sun.crypto.provider.AESWrapCipher$General");
put("Cipher.AESWrap SupportedModes", "ECB");
@@ -509,6 +518,8 @@
put("AlgorithmParameters.AES",
"com.sun.crypto.provider.AESParameters");
put("Alg.Alias.AlgorithmParameters.Rijndael", "AES");
+ put("AlgorithmParameters.GCM",
+ "com.sun.crypto.provider.GCMParameters");
put("AlgorithmParameters.RC2",
diff -r cf5d2d5094cc -r bcb241432928 jdk/src/share/classes/javax/crypto/Cipher.java
--- a/jdk/src/share/classes/javax/crypto/Cipher.java Mon Jan 07 13:19:03 2013 -0800
+++ b/jdk/src/share/classes/javax/crypto/Cipher.java Mon Jan 07 14:40:07 2013 -0800
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -104,17 +104,30 @@
* must be supplied to GCM/CCM implementations (via the {@code
* updateAAD} methods) before the ciphertext is processed (via
* the {@code update} and {@code doFinal} methods).
- *
+ *
+ * Note that GCM mode has a uniqueness requirement on IVs used in + * encryption with a given key. When IVs are repeated for GCM + * encryption, such usages are subject to forgery attacks. Thus, after + * each encryption operation using GCM mode, callers should re-initialize + * the cipher objects with GCM parameters which has a different IV value. *
- * GCMParameterSpec s = new GCMParameterSpec(...); + * GCMParameterSpec s = ...; * cipher.init(..., s); * - * // If the GCMParameterSpec is needed again - * cipher.getParameters().getParameterSpec(GCMParameterSpec.class)); + * // If the GCM parameters were generated by the provider, it can + * // be retrieved by: + * // cipher.getParameters().getParameterSpec(GCMParameterSpec.class); * * cipher.updateAAD(...); // AAD * cipher.update(...); // Multi-part update * cipher.doFinal(...); // conclusion of operation + * + * // Use a different IV value for every encryption + * byte[] newIv = ...; + * s = new GCMParameterSpec(s.getTLen(), newIv); + * cipher.init(..., s); + * ... + * ** Every implementation of the Java platform is required to support * the following standard
Cipher
transformations with the keysizes
diff -r cf5d2d5094cc -r bcb241432928 jdk/src/share/classes/javax/crypto/spec/GCMParameterSpec.java
--- a/jdk/src/share/classes/javax/crypto/spec/GCMParameterSpec.java Mon Jan 07 13:19:03 2013 -0800
+++ b/jdk/src/share/classes/javax/crypto/spec/GCMParameterSpec.java Mon Jan 07 14:40:07 2013 -0800
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -43,7 +43,7 @@
* (Additional Authenticated Data (AAD), Keys, block ciphers,
* plain/ciphertext and authentication tags) are handled in the {@code
* Cipher} class.
- + *
* Please see RFC 5116 * for more information on the Authenticated Encryption with * Associated Data (AEAD) algorithm, and