|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  */ | 
|  |  | 
|  | package com.sun.crypto.provider; | 
|  |  | 
|  | import java.util.Arrays; | 
|  | import java.util.Locale; | 
|  |  | 
|  | import java.security.*; | 
|  | import java.security.spec.*; | 
|  | import javax.crypto.*; | 
|  | import javax.crypto.spec.*; | 
|  | import javax.crypto.BadPaddingException; | 
|  |  | 
|  | /** | 
|  |  * This class represents the symmetric algorithms in its various modes | 
|  |  * (<code>ECB</code>, <code>CFB</code>, <code>OFB</code>, <code>CBC</code>, | 
|  |  * <code>PCBC</code>, <code>CTR</code>, and <code>CTS</code>) and | 
|  |  * padding schemes (<code>PKCS5Padding</code>, <code>NoPadding</code>, | 
|  |  * <code>ISO10126Padding</code>). | 
|  |  * | 
|  |  * @author Gigi Ankeny | 
|  |  * @author Jan Luehe | 
|  |  * @see ElectronicCodeBook | 
|  |  * @see CipherFeedback | 
|  |  * @see OutputFeedback | 
|  |  * @see CipherBlockChaining | 
|  |  * @see PCBC | 
|  |  * @see CounterMode | 
|  |  * @see CipherTextStealing | 
|  |  */ | 
|  |  | 
|  | final class CipherCore { | 
|  |  | 
|  |      | 
|  |  | 
|  |      */ | 
|  |     private byte[] buffer = null; | 
|  |  | 
|  |      | 
|  |  | 
|  |      */ | 
|  |     private int blockSize = 0; | 
|  |  | 
|  |      | 
|  |  | 
|  |      */ | 
|  |     private int unitBytes = 0; | 
|  |  | 
|  |      | 
|  |  | 
|  |      */ | 
|  |     private int buffered = 0; | 
|  |  | 
|  |      | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     private int minBytes = 0; | 
|  |  | 
|  |      | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     private int diffBlocksize = 0; | 
|  |  | 
|  |      | 
|  |  | 
|  |      */ | 
|  |     private Padding padding = null; | 
|  |  | 
|  |      | 
|  |  | 
|  |      */ | 
|  |     private FeedbackCipher cipher = null; | 
|  |  | 
|  |      | 
|  |  | 
|  |      */ | 
|  |     private int cipherMode = ECB_MODE; | 
|  |  | 
|  |      | 
|  |  | 
|  |      */ | 
|  |     private boolean decrypting = false; | 
|  |  | 
|  |      | 
|  |  | 
|  |      */ | 
|  |     private static final int ECB_MODE = 0; | 
|  |     private static final int CBC_MODE = 1; | 
|  |     private static final int CFB_MODE = 2; | 
|  |     private static final int OFB_MODE = 3; | 
|  |     private static final int PCBC_MODE = 4; | 
|  |     private static final int CTR_MODE = 5; | 
|  |     private static final int CTS_MODE = 6; | 
|  |     static final int GCM_MODE = 7; | 
|  |  | 
|  |      | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     private boolean requireReinit = false; | 
|  |     private byte[] lastEncKey = null; | 
|  |     private byte[] lastEncIv = null; | 
|  |  | 
|  |      | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     CipherCore(SymmetricCipher impl, int blkSize) { | 
|  |         blockSize = blkSize; | 
|  |         unitBytes = blkSize; | 
|  |         diffBlocksize = blkSize; | 
|  |  | 
|  |          | 
|  |  | 
|  |  | 
|  |  | 
|  |          */ | 
|  |         buffer = new byte[blockSize*2]; | 
|  |  | 
|  |          | 
|  |         cipher = new ElectronicCodeBook(impl); | 
|  |         padding = new PKCS5Padding(blockSize); | 
|  |     } | 
|  |  | 
|  |      | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     void setMode(String mode) throws NoSuchAlgorithmException { | 
|  |         if (mode == null) | 
|  |             throw new NoSuchAlgorithmException("null mode"); | 
|  |  | 
|  |         String modeUpperCase = mode.toUpperCase(Locale.ENGLISH); | 
|  |  | 
|  |         if (modeUpperCase.equals("ECB")) { | 
|  |             return; | 
|  |         } | 
|  |  | 
|  |         SymmetricCipher rawImpl = cipher.getEmbeddedCipher(); | 
|  |         if (modeUpperCase.equals("CBC")) { | 
|  |             cipherMode = CBC_MODE; | 
|  |             cipher = new CipherBlockChaining(rawImpl); | 
|  |         } else if (modeUpperCase.equals("CTS")) { | 
|  |             cipherMode = CTS_MODE; | 
|  |             cipher = new CipherTextStealing(rawImpl); | 
|  |             minBytes = blockSize+1; | 
|  |             padding = null; | 
|  |         } else if (modeUpperCase.equals("CTR")) { | 
|  |             cipherMode = CTR_MODE; | 
|  |             cipher = new CounterMode(rawImpl); | 
|  |             unitBytes = 1; | 
|  |             padding = null; | 
|  |         }  else if (modeUpperCase.equals("GCM")) { | 
|  |              | 
|  |             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")) { | 
|  |             cipherMode = OFB_MODE; | 
|  |             unitBytes = getNumOfUnit(mode, "OFB".length(), blockSize); | 
|  |             cipher = new OutputFeedback(rawImpl, unitBytes); | 
|  |         } else if (modeUpperCase.equals("PCBC")) { | 
|  |             cipherMode = PCBC_MODE; | 
|  |             cipher = new PCBC(rawImpl); | 
|  |         } | 
|  |         else { | 
|  |             throw new NoSuchAlgorithmException("Cipher mode: " + mode | 
|  |                                                + " not found"); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |      | 
|  |  | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     int getMode() { | 
|  |         return cipherMode; | 
|  |     } | 
|  |  | 
|  |     private static int getNumOfUnit(String mode, int offset, int blockSize) | 
|  |         throws NoSuchAlgorithmException { | 
|  |         int result = blockSize;  | 
|  |         if (mode.length() > offset) { | 
|  |             int numInt; | 
|  |             try { | 
|  |                 Integer num = Integer.valueOf(mode.substring(offset)); | 
|  |                 numInt = num.intValue(); | 
|  |                 result = numInt >> 3; | 
|  |             } catch (NumberFormatException e) { | 
|  |                 throw new NoSuchAlgorithmException | 
|  |                     ("Algorithm mode: " + mode + " not implemented"); | 
|  |             } | 
|  |             if ((numInt % 8 != 0) || (result > blockSize)) { | 
|  |                 throw new NoSuchAlgorithmException | 
|  |                     ("Invalid algorithm mode: " + mode); | 
|  |             } | 
|  |         } | 
|  |         return result; | 
|  |     } | 
|  |  | 
|  |      | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     void setPadding(String paddingScheme) | 
|  |         throws NoSuchPaddingException | 
|  |     { | 
|  |         if (paddingScheme == null) { | 
|  |             throw new NoSuchPaddingException("null padding"); | 
|  |         } | 
|  |         if (paddingScheme.equalsIgnoreCase("NoPadding")) { | 
|  |             padding = null; | 
|  |         } else if (paddingScheme.equalsIgnoreCase("ISO10126Padding")) { | 
|  |             padding = new ISO10126Padding(blockSize); | 
|  |         } else if (!paddingScheme.equalsIgnoreCase("PKCS5Padding")) { | 
|  |             throw new NoSuchPaddingException("Padding: " + paddingScheme | 
|  |                                              + " not implemented"); | 
|  |         } | 
|  |         if ((padding != null) && | 
|  |             ((cipherMode == CTR_MODE) || (cipherMode == CTS_MODE) | 
|  |              || (cipherMode == GCM_MODE))) { | 
|  |             padding = null; | 
|  |             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"); | 
|  |             } | 
|  |         } | 
|  |     } | 
|  |  | 
|  |      | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     int getOutputSize(int inputLen) { | 
|  |          | 
|  |         return getOutputSizeByOperation(inputLen, true); | 
|  |     } | 
|  |  | 
|  |     private int getOutputSizeByOperation(int inputLen, boolean isDoFinal) { | 
|  |         int totalLen = Math.addExact(buffered, cipher.getBufferedLength()); | 
|  |         totalLen = Math.addExact(totalLen, inputLen); | 
|  |         switch (cipherMode) { | 
|  |         case GCM_MODE: | 
|  |             if (isDoFinal) { | 
|  |                 int tagLen = ((GaloisCounterMode) cipher).getTagLen(); | 
|  |                 if (!decrypting) { | 
|  |                     totalLen = Math.addExact(totalLen, tagLen); | 
|  |                 } else { | 
|  |                     totalLen -= tagLen; | 
|  |                 } | 
|  |             } | 
|  |             if (totalLen < 0) { | 
|  |                 totalLen = 0; | 
|  |             } | 
|  |             break; | 
|  |         default: | 
|  |             if (padding != null && !decrypting) { | 
|  |                 if (unitBytes != blockSize) { | 
|  |                     if (totalLen < diffBlocksize) { | 
|  |                         totalLen = diffBlocksize; | 
|  |                     } else { | 
|  |                         int residue = (totalLen - diffBlocksize) % blockSize; | 
|  |                         totalLen = Math.addExact(totalLen, (blockSize - residue)); | 
|  |                     } | 
|  |                 } else { | 
|  |                     totalLen = Math.addExact(totalLen, padding.padLength(totalLen)); | 
|  |                 } | 
|  |             } | 
|  |             break; | 
|  |         } | 
|  |         return totalLen; | 
|  |     } | 
|  |  | 
|  |      | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     byte[] getIV() { | 
|  |         byte[] iv = cipher.getIV(); | 
|  |         return (iv == null) ? null : iv.clone(); | 
|  |     } | 
|  |  | 
|  |      | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     AlgorithmParameters getParameters(String algName) { | 
|  |         if (cipherMode == ECB_MODE) { | 
|  |             return null; | 
|  |         } | 
|  |         AlgorithmParameters params = null; | 
|  |         AlgorithmParameterSpec spec; | 
|  |         byte[] iv = getIV(); | 
|  |         if (iv == null) { | 
|  |              | 
|  |             if (cipherMode == GCM_MODE) { | 
|  |                 iv = new byte[GaloisCounterMode.DEFAULT_IV_LEN]; | 
|  |             } else { | 
|  |                 iv = new byte[blockSize]; | 
|  |             } | 
|  |             SunJCE.getRandom().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.getInstance()); | 
|  |             params.init(spec); | 
|  |         } catch (NoSuchAlgorithmException nsae) { | 
|  |              | 
|  |             throw new RuntimeException("Cannot find " + algName + | 
|  |                 " AlgorithmParameters implementation in SunJCE provider"); | 
|  |         } catch (InvalidParameterSpecException ipse) { | 
|  |              | 
|  |             throw new RuntimeException(spec.getClass() + " not supported"); | 
|  |         } | 
|  |         return params; | 
|  |     } | 
|  |  | 
|  |      | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     void init(int opmode, Key key, SecureRandom random) | 
|  |             throws InvalidKeyException { | 
|  |         try { | 
|  |             init(opmode, key, (AlgorithmParameterSpec)null, random); | 
|  |         } catch (InvalidAlgorithmParameterException e) { | 
|  |             throw new InvalidKeyException(e.getMessage()); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |      | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     void init(int opmode, Key key, AlgorithmParameterSpec params, | 
|  |             SecureRandom random) | 
|  |             throws InvalidKeyException, InvalidAlgorithmParameterException { | 
|  |         decrypting = (opmode == Cipher.DECRYPT_MODE) | 
|  |                   || (opmode == Cipher.UNWRAP_MODE); | 
|  |  | 
|  |         byte[] keyBytes = getKeyBytes(key); | 
|  |         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); | 
|  |                 } | 
|  |             } | 
|  |         } | 
|  |         if (cipherMode == ECB_MODE) { | 
|  |             if (ivBytes != null) { | 
|  |                 throw new InvalidAlgorithmParameterException | 
|  |                                                 ("ECB mode cannot use IV"); | 
|  |             } | 
|  |         } else if (ivBytes == null)  { | 
|  |             if (decrypting) { | 
|  |                 throw new InvalidAlgorithmParameterException("Parameters " | 
|  |                                                              + "missing"); | 
|  |             } | 
|  |  | 
|  |             if (random == null) { | 
|  |                 random = SunJCE.getRandom(); | 
|  |             } | 
|  |             if (cipherMode == GCM_MODE) { | 
|  |                 ivBytes = new byte[GaloisCounterMode.DEFAULT_IV_LEN]; | 
|  |             } else { | 
|  |                 ivBytes = new byte[blockSize]; | 
|  |             } | 
|  |             random.nextBytes(ivBytes); | 
|  |         } | 
|  |  | 
|  |         buffered = 0; | 
|  |         diffBlocksize = blockSize; | 
|  |  | 
|  |         String algorithm = key.getAlgorithm(); | 
|  |  | 
|  |          | 
|  |         if (cipherMode == GCM_MODE) { | 
|  |             if(tagLen == -1) { | 
|  |                 tagLen = GaloisCounterMode.DEFAULT_TAG_LEN; | 
|  |             } | 
|  |             if (decrypting) { | 
|  |                 minBytes = tagLen; | 
|  |             } else { | 
|  |                  | 
|  |                 requireReinit = | 
|  |                     Arrays.equals(ivBytes, lastEncIv) && | 
|  |                     MessageDigest.isEqual(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); | 
|  |         } | 
|  |          | 
|  |         requireReinit = false; | 
|  |     } | 
|  |  | 
|  |     void init(int opmode, Key key, AlgorithmParameters params, | 
|  |               SecureRandom random) | 
|  |         throws InvalidKeyException, InvalidAlgorithmParameterException { | 
|  |         AlgorithmParameterSpec spec = null; | 
|  |         String paramType = null; | 
|  |         if (params != null) { | 
|  |             try { | 
|  |                 if (cipherMode == GCM_MODE) { | 
|  |                     paramType = "GCM"; | 
|  |                     spec = params.getParameterSpec(GCMParameterSpec.class); | 
|  |                 } else { | 
|  |                     // NOTE: RC2 parameters are always handled through | 
|  |                     // init(..., AlgorithmParameterSpec,...) method, so | 
|  |                      | 
|  |                     paramType = "IV"; | 
|  |                     spec = params.getParameterSpec(IvParameterSpec.class); | 
|  |                 } | 
|  |             } catch (InvalidParameterSpecException ipse) { | 
|  |                 throw new InvalidAlgorithmParameterException | 
|  |                     ("Wrong parameter type: " + paramType + " expected"); | 
|  |             } | 
|  |         } | 
|  |         init(opmode, key, spec, random); | 
|  |     } | 
|  |  | 
|  |      | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     static byte[] getKeyBytes(Key key) throws InvalidKeyException { | 
|  |         if (key == null) { | 
|  |             throw new InvalidKeyException("No key given"); | 
|  |         } | 
|  |          | 
|  |         if (!"RAW".equalsIgnoreCase(key.getFormat())) { | 
|  |             throw new InvalidKeyException("Wrong format: RAW bytes needed"); | 
|  |         } | 
|  |         byte[] keyBytes = key.getEncoded(); | 
|  |         if (keyBytes == null) { | 
|  |             throw new InvalidKeyException("RAW key bytes missing"); | 
|  |         } | 
|  |         return keyBytes; | 
|  |     } | 
|  |  | 
|  |  | 
|  |      | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     byte[] update(byte[] input, int inputOffset, int inputLen) { | 
|  |         checkReinit(); | 
|  |  | 
|  |         byte[] output = null; | 
|  |         try { | 
|  |             output = new byte[getOutputSizeByOperation(inputLen, false)]; | 
|  |             int len = update(input, inputOffset, inputLen, output, | 
|  |                              0); | 
|  |             if (len == output.length) { | 
|  |                 return output; | 
|  |             } else { | 
|  |                 byte[] copy = Arrays.copyOf(output, len); | 
|  |                 if (decrypting) { | 
|  |                      | 
|  |                     Arrays.fill(output, (byte) 0x00); | 
|  |                 } | 
|  |                 return copy; | 
|  |             } | 
|  |         } catch (ShortBufferException e) { | 
|  |              | 
|  |             throw new ProviderException("Unexpected exception", e); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |      | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     int update(byte[] input, int inputOffset, int inputLen, byte[] output, | 
|  |                int outputOffset) throws ShortBufferException { | 
|  |         checkReinit(); | 
|  |  | 
|  |          | 
|  |         int len = Math.addExact(buffered, inputLen); | 
|  |         len -= minBytes; | 
|  |         if (padding != null && decrypting) { | 
|  |              | 
|  |             len -= blockSize; | 
|  |         } | 
|  |          | 
|  |         len = (len > 0 ? (len - (len % unitBytes)) : 0); | 
|  |  | 
|  |          | 
|  |         if ((output == null) || | 
|  |             ((output.length - outputOffset) < len)) { | 
|  |             throw new ShortBufferException("Output buffer must be " | 
|  |                                            + "(at least) " + len | 
|  |                                            + " bytes long"); | 
|  |         } | 
|  |  | 
|  |         int outLen = 0; | 
|  |         if (len != 0) {  | 
|  |             if ((input == output) | 
|  |                  && (outputOffset - inputOffset < inputLen) | 
|  |                  && (inputOffset - outputOffset < buffer.length)) { | 
|  |                 // copy 'input' out to avoid its content being | 
|  |                  | 
|  |                 input = Arrays.copyOfRange(input, inputOffset, | 
|  |                     Math.addExact(inputOffset, inputLen)); | 
|  |                 inputOffset = 0; | 
|  |             } | 
|  |             if (len <= buffered) { | 
|  |                  | 
|  |                 if (decrypting) { | 
|  |                     outLen = cipher.decrypt(buffer, 0, len, output, outputOffset); | 
|  |                 } else { | 
|  |                     outLen = cipher.encrypt(buffer, 0, len, output, outputOffset); | 
|  |                 } | 
|  |                 buffered -= len; | 
|  |                 if (buffered != 0) { | 
|  |                     System.arraycopy(buffer, len, buffer, 0, buffered); | 
|  |                 } | 
|  |             } else {  | 
|  |                 int inputConsumed = len - buffered; | 
|  |                 int temp; | 
|  |                 if (buffered > 0) { | 
|  |                     int bufferCapacity = buffer.length - buffered; | 
|  |                     if (bufferCapacity != 0) { | 
|  |                         temp = Math.min(bufferCapacity, inputConsumed); | 
|  |                         if (unitBytes != blockSize) { | 
|  |                             temp -= (Math.addExact(buffered, temp) % unitBytes); | 
|  |                         } | 
|  |                         System.arraycopy(input, inputOffset, buffer, buffered, temp); | 
|  |                         inputOffset = Math.addExact(inputOffset, temp); | 
|  |                         inputConsumed -= temp; | 
|  |                         inputLen -= temp; | 
|  |                         buffered = Math.addExact(buffered, temp); | 
|  |                     } | 
|  |                     // process 'buffer'. When finished we can null out 'buffer' | 
|  |                      | 
|  |                     if (decrypting) { | 
|  |                          outLen = cipher.decrypt(buffer, 0, buffered, output, outputOffset); | 
|  |                     } else { | 
|  |                          outLen = cipher.encrypt(buffer, 0, buffered, output, outputOffset); | 
|  |                           | 
|  |                          Arrays.fill(buffer, (byte) 0x00); | 
|  |                     } | 
|  |                     outputOffset = Math.addExact(outputOffset, outLen); | 
|  |                     buffered = 0; | 
|  |                 } | 
|  |                 if (inputConsumed > 0) {  | 
|  |                     if (decrypting) { | 
|  |                         outLen += cipher.decrypt(input, inputOffset, inputConsumed, | 
|  |                             output, outputOffset); | 
|  |                     } else { | 
|  |                         outLen += cipher.encrypt(input, inputOffset, inputConsumed, | 
|  |                             output, outputOffset); | 
|  |                     } | 
|  |                     inputOffset += inputConsumed; | 
|  |                     inputLen -= inputConsumed; | 
|  |                 } | 
|  |             } | 
|  |             // Let's keep track of how many bytes are needed to make | 
|  |             // the total input length a multiple of blocksize when | 
|  |              | 
|  |             if (unitBytes != blockSize) { | 
|  |                 if (len < diffBlocksize) { | 
|  |                     diffBlocksize -= len; | 
|  |                 } else { | 
|  |                     diffBlocksize = blockSize - | 
|  |                         ((len - diffBlocksize) % blockSize); | 
|  |                 } | 
|  |             } | 
|  |         } | 
|  |          | 
|  |         if (inputLen > 0) { | 
|  |             System.arraycopy(input, inputOffset, buffer, buffered, | 
|  |                              inputLen); | 
|  |             buffered = Math.addExact(buffered, inputLen); | 
|  |         } | 
|  |         return outLen; | 
|  |     } | 
|  |  | 
|  |      | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     byte[] doFinal(byte[] input, int inputOffset, int inputLen) | 
|  |         throws IllegalBlockSizeException, BadPaddingException { | 
|  |         try { | 
|  |             checkReinit(); | 
|  |             byte[] output = new byte[getOutputSizeByOperation(inputLen, true)]; | 
|  |             byte[] finalBuf = prepareInputBuffer(input, inputOffset, | 
|  |                     inputLen, output, 0); | 
|  |             int finalOffset = (finalBuf == input) ? inputOffset : 0; | 
|  |             int finalBufLen = (finalBuf == input) ? inputLen : finalBuf.length; | 
|  |  | 
|  |             int outLen = fillOutputBuffer(finalBuf, finalOffset, output, 0, | 
|  |                     finalBufLen, input); | 
|  |  | 
|  |             endDoFinal(); | 
|  |             if (outLen < output.length) { | 
|  |                 byte[] copy = Arrays.copyOf(output, outLen); | 
|  |                 if (decrypting) { | 
|  |                      | 
|  |                     Arrays.fill(output, (byte) 0x00); | 
|  |                 } | 
|  |                 return copy; | 
|  |             } else { | 
|  |                 return output; | 
|  |             } | 
|  |         } catch (ShortBufferException e) { | 
|  |              | 
|  |             throw new ProviderException("Unexpected exception", e); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |      | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     int doFinal(byte[] input, int inputOffset, int inputLen, byte[] output, | 
|  |                 int outputOffset) | 
|  |         throws IllegalBlockSizeException, ShortBufferException, | 
|  |                BadPaddingException { | 
|  |         checkReinit(); | 
|  |  | 
|  |         int estOutSize = getOutputSizeByOperation(inputLen, true); | 
|  |         int outputCapacity = checkOutputCapacity(output, outputOffset, | 
|  |                 estOutSize); | 
|  |         int offset = decrypting ? 0 : outputOffset;  | 
|  |         byte[] finalBuf = prepareInputBuffer(input, inputOffset, | 
|  |                 inputLen, output, outputOffset); | 
|  |         byte[] outWithPadding = null;  | 
|  |  | 
|  |         int finalOffset = (finalBuf == input) ? inputOffset : 0; | 
|  |         int finalBufLen = (finalBuf == input) ? inputLen : finalBuf.length; | 
|  |  | 
|  |         if (decrypting) { | 
|  |             // if the size of specified output buffer is less than | 
|  |             // the length of the cipher text, then the current | 
|  |             // content of cipher has to be preserved in order for | 
|  |             // users to retry the call with a larger buffer in the | 
|  |              | 
|  |             if (outputCapacity < estOutSize) { | 
|  |                 cipher.save(); | 
|  |             } | 
|  |             // create temporary output buffer so that only "real" | 
|  |              | 
|  |             outWithPadding = new byte[estOutSize]; | 
|  |         } | 
|  |         byte[] outBuffer = decrypting ? outWithPadding : output; | 
|  |  | 
|  |         int outLen = fillOutputBuffer(finalBuf, finalOffset, outBuffer, | 
|  |                 offset, finalBufLen, input); | 
|  |  | 
|  |         if (decrypting) { | 
|  |  | 
|  |             if (outputCapacity < outLen) { | 
|  |                  | 
|  |                 cipher.restore(); | 
|  |                 throw new ShortBufferException("Output buffer too short: " | 
|  |                                                + (outputCapacity) | 
|  |                                                + " bytes given, " + outLen | 
|  |                                                + " bytes needed"); | 
|  |             } | 
|  |              | 
|  |             System.arraycopy(outWithPadding, 0, output, outputOffset, outLen); | 
|  |              | 
|  |             Arrays.fill(outWithPadding, (byte) 0x00); | 
|  |         } | 
|  |         endDoFinal(); | 
|  |         return outLen; | 
|  |     } | 
|  |  | 
|  |     private void endDoFinal() { | 
|  |         buffered = 0; | 
|  |         diffBlocksize = blockSize; | 
|  |         if (cipherMode != ECB_MODE) { | 
|  |             cipher.reset(); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     private int unpad(int outLen, byte[] outWithPadding) | 
|  |             throws BadPaddingException { | 
|  |         int padStart = padding.unpad(outWithPadding, 0, outLen); | 
|  |         if (padStart < 0) { | 
|  |             throw new BadPaddingException("Given final block not " + | 
|  |             "properly padded. Such issues can arise if a bad key " + | 
|  |             "is used during decryption."); | 
|  |         } | 
|  |         outLen = padStart; | 
|  |         return outLen; | 
|  |     } | 
|  |  | 
|  |     private byte[] prepareInputBuffer(byte[] input, int inputOffset, | 
|  |                       int inputLen, byte[] output, int outputOffset) | 
|  |                       throws IllegalBlockSizeException, ShortBufferException { | 
|  |          | 
|  |         int len = Math.addExact(buffered, inputLen); | 
|  |          | 
|  |         int totalLen = Math.addExact(len, cipher.getBufferedLength()); | 
|  |         int paddingLen = 0; | 
|  |          | 
|  |         if (unitBytes != blockSize) { | 
|  |             if (totalLen < diffBlocksize) { | 
|  |                 paddingLen = diffBlocksize - totalLen; | 
|  |             } else { | 
|  |                 paddingLen = blockSize - | 
|  |                     ((totalLen - diffBlocksize) % blockSize); | 
|  |             } | 
|  |         } else if (padding != null) { | 
|  |             paddingLen = padding.padLength(totalLen); | 
|  |         } | 
|  |  | 
|  |         if (decrypting && (padding != null) && | 
|  |             (paddingLen > 0) && (paddingLen != blockSize)) { | 
|  |             throw new IllegalBlockSizeException | 
|  |                 ("Input length must be multiple of " + blockSize + | 
|  |                  " when decrypting with padded cipher"); | 
|  |         } | 
|  |  | 
|  |          | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |          */ | 
|  |         if ((buffered != 0) || (!decrypting && padding != null) || | 
|  |             ((input == output) | 
|  |               && (outputOffset - inputOffset < inputLen) | 
|  |               && (inputOffset - outputOffset < buffer.length))) { | 
|  |             byte[] finalBuf; | 
|  |             if (decrypting || padding == null) { | 
|  |                 paddingLen = 0; | 
|  |             } | 
|  |             finalBuf = new byte[Math.addExact(len, paddingLen)]; | 
|  |             if (buffered != 0) { | 
|  |                 System.arraycopy(buffer, 0, finalBuf, 0, buffered); | 
|  |                 if (!decrypting) { | 
|  |                     // done with input buffer. We should zero out the | 
|  |                      | 
|  |                     Arrays.fill(buffer, (byte) 0x00); | 
|  |                 } | 
|  |             } | 
|  |             if (inputLen != 0) { | 
|  |                 System.arraycopy(input, inputOffset, finalBuf, | 
|  |                         buffered, inputLen); | 
|  |             } | 
|  |             if (paddingLen != 0) { | 
|  |                 padding.padWithLen(finalBuf, Math.addExact(buffered, inputLen), paddingLen); | 
|  |             } | 
|  |             return finalBuf; | 
|  |         } | 
|  |         return input; | 
|  |     } | 
|  |  | 
|  |     private int fillOutputBuffer(byte[] finalBuf, int finalOffset, | 
|  |                                  byte[] output, int outOfs, int finalBufLen, | 
|  |                                  byte[] input) | 
|  |             throws ShortBufferException, BadPaddingException, | 
|  |             IllegalBlockSizeException { | 
|  |         int len; | 
|  |         try { | 
|  |             len = finalNoPadding(finalBuf, finalOffset, output, | 
|  |                     outOfs, finalBufLen); | 
|  |             if (decrypting && padding != null) { | 
|  |                 len = unpad(len, output); | 
|  |             } | 
|  |             return len; | 
|  |         } finally { | 
|  |             if (!decrypting) { | 
|  |                  | 
|  |                 requireReinit = (cipherMode == GCM_MODE); | 
|  |                 if (finalBuf != input) { | 
|  |                      | 
|  |                     Arrays.fill(finalBuf, (byte) 0x00); | 
|  |                 } | 
|  |             } | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     private int checkOutputCapacity(byte[] output, int outputOffset, | 
|  |                             int estOutSize) throws ShortBufferException { | 
|  |         // check output buffer capacity. | 
|  |         // if we are decrypting with padding applied, we can perform this | 
|  |         // check only after we have determined how many padding bytes there | 
|  |          | 
|  |         int outputCapacity = output.length - outputOffset; | 
|  |         int minOutSize = decrypting ? (estOutSize - blockSize) : estOutSize; | 
|  |         if ((output == null) || (outputCapacity < minOutSize)) { | 
|  |             throw new ShortBufferException("Output buffer must be " | 
|  |                 + "(at least) " + minOutSize + " bytes long"); | 
|  |         } | 
|  |         return outputCapacity; | 
|  |     } | 
|  |  | 
|  |     private void checkReinit() { | 
|  |         if (requireReinit) { | 
|  |             throw new IllegalStateException | 
|  |                 ("Must use either different key or iv for GCM encryption"); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     private int finalNoPadding(byte[] in, int inOfs, byte[] out, int outOfs, | 
|  |                                int len) | 
|  |         throws IllegalBlockSizeException, AEADBadTagException, | 
|  |         ShortBufferException { | 
|  |  | 
|  |         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) { | 
|  |             outLen = cipher.decryptFinal(in, inOfs, len, out, outOfs); | 
|  |         } else { | 
|  |             outLen = cipher.encryptFinal(in, inOfs, len, out, outOfs); | 
|  |         } | 
|  |         return outLen; | 
|  |     } | 
|  |  | 
|  |     // Note: Wrap() and Unwrap() are the same in | 
|  |     // each of SunJCE CipherSpi implementation classes. | 
|  |     // They are duplicated due to export control requirements: | 
|  |     // All CipherSpi implementation must be final. | 
|  |      | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     byte[] wrap(Key key) | 
|  |         throws IllegalBlockSizeException, InvalidKeyException { | 
|  |         byte[] result = null; | 
|  |  | 
|  |         try { | 
|  |             byte[] encodedKey = key.getEncoded(); | 
|  |             if ((encodedKey == null) || (encodedKey.length == 0)) { | 
|  |                 throw new InvalidKeyException("Cannot get an encoding of " + | 
|  |                                               "the key to be wrapped"); | 
|  |             } | 
|  |             result = doFinal(encodedKey, 0, encodedKey.length); | 
|  |         } catch (BadPaddingException e) { | 
|  |             // Should never happen | 
|  |         } | 
|  |         return result; | 
|  |     } | 
|  |  | 
|  |      | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     Key unwrap(byte[] wrappedKey, String wrappedKeyAlgorithm, | 
|  |                int wrappedKeyType) | 
|  |         throws InvalidKeyException, NoSuchAlgorithmException { | 
|  |         byte[] encodedKey; | 
|  |         try { | 
|  |             encodedKey = doFinal(wrappedKey, 0, wrappedKey.length); | 
|  |         } catch (BadPaddingException ePadding) { | 
|  |             throw new InvalidKeyException("The wrapped key is not padded " + | 
|  |                                           "correctly"); | 
|  |         } catch (IllegalBlockSizeException eBlockSize) { | 
|  |             throw new InvalidKeyException("The wrapped key does not have " + | 
|  |                                           "the correct length"); | 
|  |         } | 
|  |         return ConstructKeys.constructKey(encodedKey, wrappedKeyAlgorithm, | 
|  |                                           wrappedKeyType); | 
|  |     } | 
|  |  | 
|  |      | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     void updateAAD(byte[] src, int offset, int len) { | 
|  |         checkReinit(); | 
|  |         cipher.updateAAD(src, offset, len); | 
|  |     } | 
|  | } |