# 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 + T engineGetParameterSpec(Class paramSpec) + throws InvalidParameterSpecException { + + if (GCMParameterSpec.class.isAssignableFrom(paramSpec)) { + return paramSpec.cast(new GCMParameterSpec(tLen * 8, iv)); + } else { + throw new InvalidParameterSpecException + ("Inappropriate parameter specification"); + } + } + + protected byte[] engineGetEncoded() throws IOException { + DerOutputStream out = new DerOutputStream(); + DerOutputStream bytes = new DerOutputStream(); + + bytes.putOctetString(iv); + bytes.putInteger(tLen); + out.write(DerValue.tag_Sequence, bytes); + return out.toByteArray(); + } + + protected byte[] engineGetEncoded(String encodingMethod) + throws IOException { + return engineGetEncoded(); + } + + /* + * Returns a formatted string describing the parameters. + */ + protected String engineToString() { + String LINE_SEP = System.getProperty("line.separator"); + HexDumpEncoder encoder = new HexDumpEncoder(); + StringBuilder sb + = new StringBuilder(LINE_SEP + " iv:" + LINE_SEP + "[" + + encoder.encodeBuffer(iv) + "]"); + + sb.append(LINE_SEP + "tLen(bits):" + LINE_SEP + tLen*8 + LINE_SEP); + return sb.toString(); + } +} diff -r cf5d2d5094cc -r bcb241432928 jdk/src/share/classes/com/sun/crypto/provider/GCTR.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/share/classes/com/sun/crypto/provider/GCTR.java Mon Jan 07 14:40:07 2013 -0800 @@ -0,0 +1,144 @@ +/* + * 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.security.*; +import javax.crypto.*; +import static com.sun.crypto.provider.AESConstants.AES_BLOCK_SIZE; + +/** + * This class represents the GCTR function defined in NIST 800-38D + * under section 6.5. It needs to be constructed w/ an initialized + * cipher object, and initial counter block(ICB). Given an input X + * of arbitrary length, it processes and returns an output which has + * the same length as X. + * + *

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