Merge "Refactor Android Keystore CipherSpi base class." into mnc-dev
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java
index aed5875..03be759 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java
@@ -77,17 +77,17 @@
 
         // javax.crypto.Cipher
         putSymmetricCipherImpl("AES/ECB/NoPadding",
-                PACKAGE_NAME + ".AndroidKeyStoreCipherSpi$AES$ECB$NoPadding");
+                PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$ECB$NoPadding");
         putSymmetricCipherImpl("AES/ECB/PKCS7Padding",
-                PACKAGE_NAME + ".AndroidKeyStoreCipherSpi$AES$ECB$PKCS7Padding");
+                PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$ECB$PKCS7Padding");
 
         putSymmetricCipherImpl("AES/CBC/NoPadding",
-                PACKAGE_NAME + ".AndroidKeyStoreCipherSpi$AES$CBC$NoPadding");
+                PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$CBC$NoPadding");
         putSymmetricCipherImpl("AES/CBC/PKCS7Padding",
-                PACKAGE_NAME + ".AndroidKeyStoreCipherSpi$AES$CBC$PKCS7Padding");
+                PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$CBC$PKCS7Padding");
 
         putSymmetricCipherImpl("AES/CTR/NoPadding",
-                PACKAGE_NAME + ".AndroidKeyStoreCipherSpi$AES$CTR$NoPadding");
+                PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$CTR$NoPadding");
     }
 
     private void putMacImpl(String algorithm, String implClass) {
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpi.java
deleted file mode 100644
index 27df5e7..0000000
--- a/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpi.java
+++ /dev/null
@@ -1,685 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.security.keystore;
-
-import android.os.IBinder;
-import android.security.KeyStore;
-import android.security.KeyStoreException;
-import android.security.keymaster.KeymasterArguments;
-import android.security.keymaster.KeymasterDefs;
-import android.security.keymaster.OperationResult;
-import android.security.keystore.KeyProperties;
-
-import java.security.AlgorithmParameters;
-import java.security.GeneralSecurityException;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.Key;
-import java.security.NoSuchAlgorithmException;
-import java.security.ProviderException;
-import java.security.SecureRandom;
-import java.security.spec.AlgorithmParameterSpec;
-import java.security.spec.InvalidParameterSpecException;
-import java.util.Arrays;
-
-import javax.crypto.AEADBadTagException;
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.CipherSpi;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.ShortBufferException;
-import javax.crypto.spec.IvParameterSpec;
-
-/**
- * Base class for {@link CipherSpi} providing Android KeyStore backed ciphers.
- *
- * @hide
- */
-public abstract class AndroidKeyStoreCipherSpi extends CipherSpi
-        implements KeyStoreCryptoOperation {
-
-    public abstract static class AES extends AndroidKeyStoreCipherSpi {
-        protected AES(int keymasterBlockMode, int keymasterPadding, boolean ivUsed) {
-            super(KeymasterDefs.KM_ALGORITHM_AES,
-                    keymasterBlockMode,
-                    keymasterPadding,
-                    16,
-                    ivUsed);
-        }
-
-        public abstract static class ECB extends AES {
-            protected ECB(int keymasterPadding) {
-                super(KeymasterDefs.KM_MODE_ECB, keymasterPadding, false);
-            }
-
-            public static class NoPadding extends ECB {
-                public NoPadding() {
-                    super(KeymasterDefs.KM_PAD_NONE);
-                }
-            }
-
-            public static class PKCS7Padding extends ECB {
-                public PKCS7Padding() {
-                    super(KeymasterDefs.KM_PAD_PKCS7);
-                }
-            }
-        }
-
-        public abstract static class CBC extends AES {
-            protected CBC(int keymasterPadding) {
-                super(KeymasterDefs.KM_MODE_CBC, keymasterPadding, true);
-            }
-
-            public static class NoPadding extends CBC {
-                public NoPadding() {
-                    super(KeymasterDefs.KM_PAD_NONE);
-                }
-            }
-
-            public static class PKCS7Padding extends CBC {
-                public PKCS7Padding() {
-                    super(KeymasterDefs.KM_PAD_PKCS7);
-                }
-            }
-        }
-
-        public abstract static class CTR extends AES {
-            protected CTR(int keymasterPadding) {
-                super(KeymasterDefs.KM_MODE_CTR, keymasterPadding, true);
-            }
-
-            public static class NoPadding extends CTR {
-                public NoPadding() {
-                    super(KeymasterDefs.KM_PAD_NONE);
-                }
-            }
-        }
-    }
-
-    private final KeyStore mKeyStore;
-    private final int mKeymasterAlgorithm;
-    private final int mKeymasterBlockMode;
-    private final int mKeymasterPadding;
-    private final int mBlockSizeBytes;
-
-    /** Whether this transformation requires an IV. */
-    private final boolean mIvRequired;
-
-    // Fields below are populated by Cipher.init and KeyStore.begin and should be preserved after
-    // doFinal finishes.
-    protected boolean mEncrypting;
-    private AndroidKeyStoreSecretKey mKey;
-    private SecureRandom mRng;
-    private boolean mFirstOperationInitiated;
-    private byte[] mIv;
-    /** Whether the current {@code #mIv} has been used by the underlying crypto operation. */
-    private boolean mIvHasBeenUsed;
-
-    // Fields below must be reset after doFinal
-    private byte[] mAdditionalEntropyForBegin;
-
-    /**
-     * Token referencing this operation inside keystore service. It is initialized by
-     * {@code engineInit} and is invalidated when {@code engineDoFinal} succeeds and one some
-     * error conditions in between.
-     */
-    private IBinder mOperationToken;
-    private long mOperationHandle;
-    private KeyStoreCryptoOperationChunkedStreamer mMainDataStreamer;
-
-    /**
-     * Encountered exception which could not be immediately thrown because it was encountered inside
-     * a method that does not throw checked exception. This exception will be thrown from
-     * {@code engineDoFinal}. Once such an exception is encountered, {@code engineUpdate} and
-     * {@code engineDoFinal} start ignoring input data.
-     */
-    private Exception mCachedException;
-
-    protected AndroidKeyStoreCipherSpi(
-            int keymasterAlgorithm,
-            int keymasterBlockMode,
-            int keymasterPadding,
-            int blockSizeBytes,
-            boolean ivUsed) {
-        mKeyStore = KeyStore.getInstance();
-        mKeymasterAlgorithm = keymasterAlgorithm;
-        mKeymasterBlockMode = keymasterBlockMode;
-        mKeymasterPadding = keymasterPadding;
-        mBlockSizeBytes = blockSizeBytes;
-        mIvRequired = ivUsed;
-    }
-
-    @Override
-    protected void engineInit(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
-        resetAll();
-
-        boolean success = false;
-        try {
-            init(opmode, key, random);
-            initAlgorithmSpecificParameters();
-            try {
-                ensureKeystoreOperationInitialized();
-            } catch (InvalidAlgorithmParameterException e) {
-                throw new InvalidKeyException(e);
-            }
-            success = true;
-        } finally {
-            if (!success) {
-                resetAll();
-            }
-        }
-    }
-
-    @Override
-    protected void engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random)
-            throws InvalidKeyException, InvalidAlgorithmParameterException {
-        resetAll();
-
-        boolean success = false;
-        try {
-            init(opmode, key, random);
-            initAlgorithmSpecificParameters(params);
-            ensureKeystoreOperationInitialized();
-            success = true;
-        } finally {
-            if (!success) {
-                resetAll();
-            }
-        }
-    }
-
-    @Override
-    protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params,
-            SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
-        resetAll();
-
-        boolean success = false;
-        try {
-            init(opmode, key, random);
-            initAlgorithmSpecificParameters(params);
-            ensureKeystoreOperationInitialized();
-            success = true;
-        } finally {
-            if (!success) {
-                resetAll();
-            }
-        }
-    }
-
-    private void init(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
-        if (!(key instanceof AndroidKeyStoreSecretKey)) {
-            throw new InvalidKeyException(
-                    "Unsupported key: " + ((key != null) ? key.getClass().getName() : "null"));
-        }
-        mKey = (AndroidKeyStoreSecretKey) key;
-        mRng = random;
-        mIv = null;
-        mFirstOperationInitiated = false;
-
-        if ((opmode != Cipher.ENCRYPT_MODE) && (opmode != Cipher.DECRYPT_MODE)) {
-            throw new UnsupportedOperationException(
-                    "Only ENCRYPT and DECRYPT modes supported. Mode: " + opmode);
-        }
-        mEncrypting = opmode == Cipher.ENCRYPT_MODE;
-    }
-
-    private void resetAll() {
-        IBinder operationToken = mOperationToken;
-        if (operationToken != null) {
-            mOperationToken = null;
-            mKeyStore.abort(operationToken);
-        }
-        mEncrypting = false;
-        mKey = null;
-        mRng = null;
-        mFirstOperationInitiated = false;
-        mIv = null;
-        mIvHasBeenUsed = false;
-        mAdditionalEntropyForBegin = null;
-        mOperationToken = null;
-        mOperationHandle = 0;
-        mMainDataStreamer = null;
-        mCachedException = null;
-    }
-
-    private void resetWhilePreservingInitState() {
-        IBinder operationToken = mOperationToken;
-        if (operationToken != null) {
-            mOperationToken = null;
-            mKeyStore.abort(operationToken);
-        }
-        mOperationHandle = 0;
-        mMainDataStreamer = null;
-        mAdditionalEntropyForBegin = null;
-        mCachedException = null;
-    }
-
-    private void ensureKeystoreOperationInitialized() throws InvalidKeyException,
-            InvalidAlgorithmParameterException {
-        if (mMainDataStreamer != null) {
-            return;
-        }
-        if (mCachedException != null) {
-            return;
-        }
-        if (mKey == null) {
-            throw new IllegalStateException("Not initialized");
-        }
-        if ((mEncrypting) && (mIvRequired) && (mIvHasBeenUsed)) {
-            // IV is being reused for encryption: this violates security best practices.
-            throw new IllegalStateException(
-                    "IV has already been used. Reusing IV in encryption mode violates security best"
-                    + " practices.");
-        }
-
-        KeymasterArguments keymasterInputArgs = new KeymasterArguments();
-        keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm);
-        keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockMode);
-        keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding);
-        addAlgorithmSpecificParametersToBegin(keymasterInputArgs);
-
-        KeymasterArguments keymasterOutputArgs = new KeymasterArguments();
-        OperationResult opResult = mKeyStore.begin(
-                mKey.getAlias(),
-                mEncrypting ? KeymasterDefs.KM_PURPOSE_ENCRYPT : KeymasterDefs.KM_PURPOSE_DECRYPT,
-                true, // permit aborting this operation if keystore runs out of resources
-                keymasterInputArgs,
-                mAdditionalEntropyForBegin,
-                keymasterOutputArgs);
-        mAdditionalEntropyForBegin = null;
-        if (opResult == null) {
-            throw new KeyStoreConnectException();
-        }
-
-        // Store operation token and handle regardless of the error code returned by KeyStore to
-        // ensure that the operation gets aborted immediately if the code below throws an exception.
-        mOperationToken = opResult.token;
-        mOperationHandle = opResult.operationHandle;
-
-        // If necessary, throw an exception due to KeyStore operation having failed.
-        GeneralSecurityException e = KeyStoreCryptoOperationUtils.getExceptionForCipherInit(
-                mKeyStore, mKey, opResult.resultCode);
-        if (e != null) {
-            if (e instanceof InvalidKeyException) {
-                throw (InvalidKeyException) e;
-            } else if (e instanceof InvalidAlgorithmParameterException) {
-                throw (InvalidAlgorithmParameterException) e;
-            } else {
-                throw new ProviderException("Unexpected exception type", e);
-            }
-        }
-
-        if (mOperationToken == null) {
-            throw new ProviderException("Keystore returned null operation token");
-        }
-        if (mOperationHandle == 0) {
-            throw new ProviderException("Keystore returned invalid operation handle");
-        }
-
-        loadAlgorithmSpecificParametersFromBeginResult(keymasterOutputArgs);
-        mFirstOperationInitiated = true;
-        mIvHasBeenUsed = true;
-        mMainDataStreamer = new KeyStoreCryptoOperationChunkedStreamer(
-                new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
-                        mKeyStore, opResult.token));
-    }
-
-    @Override
-    protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) {
-        if (mCachedException != null) {
-            return null;
-        }
-        try {
-            ensureKeystoreOperationInitialized();
-        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
-            mCachedException = e;
-            return null;
-        }
-
-        if (inputLen == 0) {
-            return null;
-        }
-
-        byte[] output;
-        try {
-            output = mMainDataStreamer.update(input, inputOffset, inputLen);
-        } catch (KeyStoreException e) {
-            mCachedException = e;
-            return null;
-        }
-
-        if (output.length == 0) {
-            return null;
-        }
-
-        return output;
-    }
-
-    @Override
-    protected int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output,
-            int outputOffset) throws ShortBufferException {
-        byte[] outputCopy = engineUpdate(input, inputOffset, inputLen);
-        if (outputCopy == null) {
-            return 0;
-        }
-        int outputAvailable = output.length - outputOffset;
-        if (outputCopy.length > outputAvailable) {
-            throw new ShortBufferException("Output buffer too short. Produced: "
-                    + outputCopy.length + ", available: " + outputAvailable);
-        }
-        System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length);
-        return outputCopy.length;
-    }
-
-    @Override
-    protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen)
-            throws IllegalBlockSizeException, BadPaddingException {
-        if (mCachedException != null) {
-            throw (IllegalBlockSizeException)
-                    new IllegalBlockSizeException().initCause(mCachedException);
-        }
-
-        try {
-            ensureKeystoreOperationInitialized();
-        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
-            throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e);
-        }
-
-        byte[] output;
-        try {
-            output = mMainDataStreamer.doFinal(input, inputOffset, inputLen);
-        } catch (KeyStoreException e) {
-            switch (e.getErrorCode()) {
-                case KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH:
-                    throw new IllegalBlockSizeException();
-                case KeymasterDefs.KM_ERROR_INVALID_ARGUMENT:
-                    throw new BadPaddingException();
-                case KeymasterDefs.KM_ERROR_VERIFICATION_FAILED:
-                    throw new AEADBadTagException();
-                default:
-                    throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e);
-            }
-        }
-
-        resetWhilePreservingInitState();
-        return output;
-    }
-
-    @Override
-    protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output,
-            int outputOffset) throws ShortBufferException, IllegalBlockSizeException,
-            BadPaddingException {
-        byte[] outputCopy = engineDoFinal(input, inputOffset, inputLen);
-        if (outputCopy == null) {
-            return 0;
-        }
-        int outputAvailable = output.length - outputOffset;
-        if (outputCopy.length > outputAvailable) {
-            throw new ShortBufferException("Output buffer too short. Produced: "
-                    + outputCopy.length + ", available: " + outputAvailable);
-        }
-        System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length);
-        return outputCopy.length;
-    }
-
-    @Override
-    protected int engineGetBlockSize() {
-        return mBlockSizeBytes;
-    }
-
-    @Override
-    protected byte[] engineGetIV() {
-        return (mIv != null) ? mIv.clone() : null;
-    }
-
-    @Override
-    protected int engineGetOutputSize(int inputLen) {
-        return inputLen + 3 * engineGetBlockSize();
-    }
-
-    @Override
-    protected void engineSetMode(String mode) throws NoSuchAlgorithmException {
-        // This should never be invoked because all algorithms registered with the AndroidKeyStore
-        // provide explicitly specify block mode.
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    protected void engineSetPadding(String arg0) throws NoSuchPaddingException {
-        // This should never be invoked because all algorithms registered with the AndroidKeyStore
-        // provide explicitly specify padding mode.
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void finalize() throws Throwable {
-        try {
-            IBinder operationToken = mOperationToken;
-            if (operationToken != null) {
-                mKeyStore.abort(operationToken);
-            }
-        } finally {
-            super.finalize();
-        }
-    }
-
-    @Override
-    public long getOperationHandle() {
-        return mOperationHandle;
-    }
-
-    // The methods below may need to be overridden by subclasses that use algorithm-specific
-    // parameters.
-
-    /**
-     * Returns algorithm-specific parameters used by this {@code CipherSpi} instance or {@code null}
-     * if no algorithm-specific parameters are used.
-     *
-     * <p>This implementation only handles the IV parameter.
-     */
-    @Override
-    protected AlgorithmParameters engineGetParameters() {
-        if (!mIvRequired) {
-            return null;
-        }
-        if ((mIv != null) && (mIv.length > 0)) {
-            try {
-                AlgorithmParameters params =
-                        AlgorithmParameters.getInstance(KeyProperties.KEY_ALGORITHM_AES);
-                params.init(new IvParameterSpec(mIv));
-                return params;
-            } catch (NoSuchAlgorithmException e) {
-                throw new ProviderException("Failed to obtain AES AlgorithmParameters", e);
-            } catch (InvalidParameterSpecException e) {
-                throw new ProviderException(
-                        "Failed to initialize AES AlgorithmParameters with an IV", e);
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Invoked by {@code engineInit} to initialize algorithm-specific parameters. These parameters
-     * may need to be stored to be reused after {@code doFinal}.
-     *
-     * <p>The default implementation only handles the IV parameters.
-     *
-     * @param params algorithm parameters.
-     *
-     * @throws InvalidAlgorithmParameterException if some/all of the parameters cannot be
-     *         automatically configured and thus {@code Cipher.init} needs to be invoked with
-     *         explicitly provided parameters.
-     */
-    protected void initAlgorithmSpecificParameters(AlgorithmParameterSpec params)
-            throws InvalidAlgorithmParameterException {
-        if (!mIvRequired) {
-            if (params != null) {
-                throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params);
-            }
-            return;
-        }
-
-        // IV is used
-        if (params == null) {
-            if (!mEncrypting) {
-                // IV must be provided by the caller
-                throw new InvalidAlgorithmParameterException(
-                        "IvParameterSpec must be provided when decrypting");
-            }
-            return;
-        }
-        if (!(params instanceof IvParameterSpec)) {
-            throw new InvalidAlgorithmParameterException("Only IvParameterSpec supported");
-        }
-        mIv = ((IvParameterSpec) params).getIV();
-        if (mIv == null) {
-            throw new InvalidAlgorithmParameterException("Null IV in IvParameterSpec");
-        }
-    }
-
-    /**
-     * Invoked by {@code engineInit} to initialize algorithm-specific parameters. These parameters
-     * may need to be stored to be reused after {@code doFinal}.
-     *
-     * <p>The default implementation only handles the IV parameters.
-     *
-     * @param params algorithm parameters.
-     *
-     * @throws InvalidAlgorithmParameterException if some/all of the parameters cannot be
-     *         automatically configured and thus {@code Cipher.init} needs to be invoked with
-     *         explicitly provided parameters.
-     */
-    protected void initAlgorithmSpecificParameters(AlgorithmParameters params)
-            throws InvalidAlgorithmParameterException {
-        if (!mIvRequired) {
-            if (params != null) {
-                throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params);
-            }
-            return;
-        }
-
-        // IV is used
-        if (params == null) {
-            if (!mEncrypting) {
-                // IV must be provided by the caller
-                throw new InvalidAlgorithmParameterException("IV required when decrypting"
-                        + ". Use IvParameterSpec or AlgorithmParameters to provide it.");
-            }
-            return;
-        }
-
-        IvParameterSpec ivSpec;
-        try {
-            ivSpec = params.getParameterSpec(IvParameterSpec.class);
-        } catch (InvalidParameterSpecException e) {
-            if (!mEncrypting) {
-                // IV must be provided by the caller
-                throw new InvalidAlgorithmParameterException("IV required when decrypting"
-                        + ", but not found in parameters: " + params, e);
-            }
-            mIv = null;
-            return;
-        }
-        mIv = ivSpec.getIV();
-        if (mIv == null) {
-            throw new InvalidAlgorithmParameterException("Null IV in AlgorithmParameters");
-        }
-    }
-
-    /**
-     * Invoked by {@code engineInit} to initialize algorithm-specific parameters. These parameters
-     * may need to be stored to be reused after {@code doFinal}.
-     *
-     * <p>The default implementation only handles the IV parameter.
-     *
-     * @throws InvalidKeyException if some/all of the parameters cannot be automatically configured
-     *         and thus {@code Cipher.init} needs to be invoked with explicitly provided parameters.
-     */
-    protected void initAlgorithmSpecificParameters() throws InvalidKeyException {
-        if (!mIvRequired) {
-            return;
-        }
-
-        // IV is used
-        if (!mEncrypting) {
-            throw new InvalidKeyException("IV required when decrypting"
-                    + ". Use IvParameterSpec or AlgorithmParameters to provide it.");
-        }
-    }
-
-    /**
-     * Invoked to add algorithm-specific parameters for the KeyStore's {@code begin} operation.
-     *
-     * <p>The default implementation takes care of the IV.
-     *
-     * @param keymasterArgs keystore/keymaster arguments to be populated with algorithm-specific
-     *        parameters.
-     */
-    protected void addAlgorithmSpecificParametersToBegin(KeymasterArguments keymasterArgs) {
-        if (!mFirstOperationInitiated) {
-            // First begin operation -- see if we need to provide additional entropy for IV
-            // generation.
-            if (mIvRequired) {
-                // IV is needed
-                if ((mIv == null) && (mEncrypting)) {
-                    // IV was not provided by the caller and thus will be generated by keymaster.
-                    // Mix in some additional entropy from the provided SecureRandom.
-                    mAdditionalEntropyForBegin =
-                            KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
-                                    mRng, mBlockSizeBytes);
-                }
-            }
-        }
-
-        if ((mIvRequired) && (mIv != null)) {
-            keymasterArgs.addBlob(KeymasterDefs.KM_TAG_NONCE, mIv);
-        }
-    }
-
-    /**
-     * Invoked by {@code engineInit} to obtain algorithm-specific parameters from the result of the
-     * Keymaster's {@code begin} operation. Some of these parameters may need to be reused after
-     * {@code doFinal} by {@link #addAlgorithmSpecificParametersToBegin(KeymasterArguments)}.
-     *
-     * <p>The default implementation only takes care of the IV.
-     *
-     * @param keymasterArgs keystore/keymaster arguments returned by KeyStore {@code begin}
-     *        operation.
-     */
-    protected void loadAlgorithmSpecificParametersFromBeginResult(
-            KeymasterArguments keymasterArgs) {
-        // NOTE: Keymaster doesn't always return an IV, even if it's used.
-        byte[] returnedIv = keymasterArgs.getBlob(KeymasterDefs.KM_TAG_NONCE, null);
-        if ((returnedIv != null) && (returnedIv.length == 0)) {
-            returnedIv = null;
-        }
-
-        if (mIvRequired) {
-            if (mIv == null) {
-                mIv = returnedIv;
-            } else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) {
-                throw new ProviderException("IV in use differs from provided IV");
-            }
-        } else {
-            if (returnedIv != null) {
-                throw new ProviderException(
-                        "IV in use despite IV not being used by this transformation");
-            }
-        }
-    }
-}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java b/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java
new file mode 100644
index 0000000..4104dcc
--- /dev/null
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java
@@ -0,0 +1,534 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.keystore;
+
+import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.security.KeyStore;
+import android.security.KeyStoreException;
+import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.KeymasterDefs;
+import android.security.keymaster.OperationResult;
+
+import java.nio.ByteBuffer;
+import java.security.AlgorithmParameters;
+import java.security.GeneralSecurityException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.ProviderException;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.AEADBadTagException;
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.CipherSpi;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.ShortBufferException;
+
+/**
+ * Base class for {@link CipherSpi} implementations of Android KeyStore backed ciphers.
+ *
+ * @hide
+ */
+abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStoreCryptoOperation {
+    private final KeyStore mKeyStore;
+
+    // Fields below are populated by Cipher.init and KeyStore.begin and should be preserved after
+    // doFinal finishes.
+    private boolean mEncrypting;
+    private AndroidKeyStoreKey mKey;
+    private SecureRandom mRng;
+
+    /**
+     * Token referencing this operation inside keystore service. It is initialized by
+     * {@code engineInit} and is invalidated when {@code engineDoFinal} succeeds and on some error
+     * conditions in between.
+     */
+    private IBinder mOperationToken;
+    private long mOperationHandle;
+    private KeyStoreCryptoOperationChunkedStreamer mMainDataStreamer;
+
+    /**
+     * Encountered exception which could not be immediately thrown because it was encountered inside
+     * a method that does not throw checked exception. This exception will be thrown from
+     * {@code engineDoFinal}. Once such an exception is encountered, {@code engineUpdate} and
+     * {@code engineDoFinal} start ignoring input data.
+     */
+    private Exception mCachedException;
+
+    AndroidKeyStoreCipherSpiBase() {
+        mKeyStore = KeyStore.getInstance();
+    }
+
+    @Override
+    protected final void engineInit(int opmode, Key key, SecureRandom random)
+            throws InvalidKeyException {
+        resetAll();
+
+        boolean success = false;
+        try {
+            init(opmode, key, random);
+            initAlgorithmSpecificParameters();
+            try {
+                ensureKeystoreOperationInitialized();
+            } catch (InvalidAlgorithmParameterException e) {
+                throw new InvalidKeyException(e);
+            }
+            success = true;
+        } finally {
+            if (!success) {
+                resetAll();
+            }
+        }
+    }
+
+    @Override
+    protected final void engineInit(int opmode, Key key, AlgorithmParameters params,
+            SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
+        resetAll();
+
+        boolean success = false;
+        try {
+            init(opmode, key, random);
+            initAlgorithmSpecificParameters(params);
+            ensureKeystoreOperationInitialized();
+            success = true;
+        } finally {
+            if (!success) {
+                resetAll();
+            }
+        }
+    }
+
+    @Override
+    protected final void engineInit(int opmode, Key key, AlgorithmParameterSpec params,
+            SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
+        resetAll();
+
+        boolean success = false;
+        try {
+            init(opmode, key, random);
+            initAlgorithmSpecificParameters(params);
+            ensureKeystoreOperationInitialized();
+            success = true;
+        } finally {
+            if (!success) {
+                resetAll();
+            }
+        }
+    }
+
+    private void init(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
+        if ((opmode != Cipher.ENCRYPT_MODE) && (opmode != Cipher.DECRYPT_MODE)) {
+            throw new UnsupportedOperationException(
+                    "Only ENCRYPT and DECRYPT modes supported. Mode: " + opmode);
+        }
+        mEncrypting = opmode == Cipher.ENCRYPT_MODE;
+        initKey(opmode, key);
+        if (mKey == null) {
+            throw new ProviderException("initKey did not initialize the key");
+        }
+        mRng = random;
+    }
+
+    /**
+     * Resets this cipher to its pristine pre-init state. This must be equivalent to obtaining a new
+     * cipher instance.
+     *
+     * <p>Subclasses storing additional state should override this method, reset the additional
+     * state, and then chain to superclass.
+     */
+    @CallSuper
+    protected void resetAll() {
+        IBinder operationToken = mOperationToken;
+        if (operationToken != null) {
+            mOperationToken = null;
+            mKeyStore.abort(operationToken);
+        }
+        mEncrypting = false;
+        mKey = null;
+        mRng = null;
+        mOperationToken = null;
+        mOperationHandle = 0;
+        mMainDataStreamer = null;
+        mCachedException = null;
+    }
+
+    /**
+     * Resets this cipher while preserving the initialized state. This must be equivalent to
+     * rolling back the cipher's state to just after the most recent {@code engineInit} completed
+     * successfully.
+     *
+     * <p>Subclasses storing additional post-init state should override this method, reset the
+     * additional state, and then chain to superclass.
+     */
+    @CallSuper
+    protected void resetWhilePreservingInitState() {
+        IBinder operationToken = mOperationToken;
+        if (operationToken != null) {
+            mOperationToken = null;
+            mKeyStore.abort(operationToken);
+        }
+        mOperationHandle = 0;
+        mMainDataStreamer = null;
+        mCachedException = null;
+    }
+
+    private void ensureKeystoreOperationInitialized() throws InvalidKeyException,
+            InvalidAlgorithmParameterException {
+        if (mMainDataStreamer != null) {
+            return;
+        }
+        if (mCachedException != null) {
+            return;
+        }
+        if (mKey == null) {
+            throw new IllegalStateException("Not initialized");
+        }
+
+        KeymasterArguments keymasterInputArgs = new KeymasterArguments();
+        addAlgorithmSpecificParametersToBegin(keymasterInputArgs);
+        byte[] additionalEntropy = KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
+                mRng, getAdditionalEntropyAmountForBegin());
+
+        KeymasterArguments keymasterOutputArgs = new KeymasterArguments();
+        OperationResult opResult = mKeyStore.begin(
+                mKey.getAlias(),
+                mEncrypting ? KeymasterDefs.KM_PURPOSE_ENCRYPT : KeymasterDefs.KM_PURPOSE_DECRYPT,
+                true, // permit aborting this operation if keystore runs out of resources
+                keymasterInputArgs,
+                additionalEntropy,
+                keymasterOutputArgs);
+        if (opResult == null) {
+            throw new KeyStoreConnectException();
+        }
+
+        // Store operation token and handle regardless of the error code returned by KeyStore to
+        // ensure that the operation gets aborted immediately if the code below throws an exception.
+        mOperationToken = opResult.token;
+        mOperationHandle = opResult.operationHandle;
+
+        // If necessary, throw an exception due to KeyStore operation having failed.
+        GeneralSecurityException e = KeyStoreCryptoOperationUtils.getExceptionForCipherInit(
+                mKeyStore, mKey, opResult.resultCode);
+        if (e != null) {
+            if (e instanceof InvalidKeyException) {
+                throw (InvalidKeyException) e;
+            } else if (e instanceof InvalidAlgorithmParameterException) {
+                throw (InvalidAlgorithmParameterException) e;
+            } else {
+                throw new ProviderException("Unexpected exception type", e);
+            }
+        }
+
+        if (mOperationToken == null) {
+            throw new ProviderException("Keystore returned null operation token");
+        }
+        if (mOperationHandle == 0) {
+            throw new ProviderException("Keystore returned invalid operation handle");
+        }
+
+        loadAlgorithmSpecificParametersFromBeginResult(keymasterOutputArgs);
+        mMainDataStreamer = new KeyStoreCryptoOperationChunkedStreamer(
+                new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
+                        mKeyStore, opResult.token));
+    }
+
+    @Override
+    protected final byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) {
+        if (mCachedException != null) {
+            return null;
+        }
+        try {
+            ensureKeystoreOperationInitialized();
+        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
+            mCachedException = e;
+            return null;
+        }
+
+        if (inputLen == 0) {
+            return null;
+        }
+
+        byte[] output;
+        try {
+            output = mMainDataStreamer.update(input, inputOffset, inputLen);
+        } catch (KeyStoreException e) {
+            mCachedException = e;
+            return null;
+        }
+
+        if (output.length == 0) {
+            return null;
+        }
+
+        return output;
+    }
+
+    @Override
+    protected final int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output,
+            int outputOffset) throws ShortBufferException {
+        byte[] outputCopy = engineUpdate(input, inputOffset, inputLen);
+        if (outputCopy == null) {
+            return 0;
+        }
+        int outputAvailable = output.length - outputOffset;
+        if (outputCopy.length > outputAvailable) {
+            throw new ShortBufferException("Output buffer too short. Produced: "
+                    + outputCopy.length + ", available: " + outputAvailable);
+        }
+        System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length);
+        return outputCopy.length;
+    }
+
+    @Override
+    protected final int engineUpdate(ByteBuffer input, ByteBuffer output)
+            throws ShortBufferException {
+        return super.engineUpdate(input, output);
+    }
+
+    @Override
+    protected final void engineUpdateAAD(byte[] input, int inputOffset, int inputLen) {
+        super.engineUpdateAAD(input, inputOffset, inputLen);
+    }
+
+    @Override
+    protected final void engineUpdateAAD(ByteBuffer src) {
+        super.engineUpdateAAD(src);
+    }
+
+    @Override
+    protected final byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen)
+            throws IllegalBlockSizeException, BadPaddingException {
+        if (mCachedException != null) {
+            throw (IllegalBlockSizeException)
+                    new IllegalBlockSizeException().initCause(mCachedException);
+        }
+
+        try {
+            ensureKeystoreOperationInitialized();
+        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
+            throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e);
+        }
+
+        byte[] output;
+        try {
+            output = mMainDataStreamer.doFinal(input, inputOffset, inputLen);
+        } catch (KeyStoreException e) {
+            switch (e.getErrorCode()) {
+                case KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH:
+                    throw new IllegalBlockSizeException();
+                case KeymasterDefs.KM_ERROR_INVALID_ARGUMENT:
+                    throw new BadPaddingException();
+                case KeymasterDefs.KM_ERROR_VERIFICATION_FAILED:
+                    throw new AEADBadTagException();
+                default:
+                    throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e);
+            }
+        }
+
+        resetWhilePreservingInitState();
+        return output;
+    }
+
+    @Override
+    protected final int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output,
+            int outputOffset) throws ShortBufferException, IllegalBlockSizeException,
+            BadPaddingException {
+        byte[] outputCopy = engineDoFinal(input, inputOffset, inputLen);
+        if (outputCopy == null) {
+            return 0;
+        }
+        int outputAvailable = output.length - outputOffset;
+        if (outputCopy.length > outputAvailable) {
+            throw new ShortBufferException("Output buffer too short. Produced: "
+                    + outputCopy.length + ", available: " + outputAvailable);
+        }
+        System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length);
+        return outputCopy.length;
+    }
+
+    @Override
+    protected final int engineDoFinal(ByteBuffer input, ByteBuffer output)
+            throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
+        return super.engineDoFinal(input, output);
+    }
+
+    @Override
+    protected final byte[] engineWrap(Key key)
+            throws IllegalBlockSizeException, InvalidKeyException {
+        return super.engineWrap(key);
+    }
+
+    @Override
+    protected final Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm,
+            int wrappedKeyType) throws InvalidKeyException, NoSuchAlgorithmException {
+        return super.engineUnwrap(wrappedKey, wrappedKeyAlgorithm, wrappedKeyType);
+    }
+
+    @Override
+    protected final void engineSetMode(String mode) throws NoSuchAlgorithmException {
+        // This should never be invoked because all algorithms registered with the AndroidKeyStore
+        // provide explicitly specify block mode.
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    protected final void engineSetPadding(String arg0) throws NoSuchPaddingException {
+        // This should never be invoked because all algorithms registered with the AndroidKeyStore
+        // provide explicitly specify padding mode.
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    protected final int engineGetKeySize(Key key) throws InvalidKeyException {
+        throw new UnsupportedOperationException();
+    }
+
+    @CallSuper
+    @Override
+    public void finalize() throws Throwable {
+        try {
+            IBinder operationToken = mOperationToken;
+            if (operationToken != null) {
+                mKeyStore.abort(operationToken);
+            }
+        } finally {
+            super.finalize();
+        }
+    }
+
+    @Override
+    public final long getOperationHandle() {
+        return mOperationHandle;
+    }
+
+    protected final void setKey(@NonNull AndroidKeyStoreKey key) {
+        mKey = key;
+    }
+
+    /**
+     * Returns {@code true} if this cipher is initialized for encryption, {@code false} if this
+     * cipher is initialized for decryption.
+     */
+    protected final boolean isEncrypting() {
+        return mEncrypting;
+    }
+
+    @NonNull
+    protected final KeyStore getKeyStore() {
+        return mKeyStore;
+    }
+
+    // The methods below need to be implemented by subclasses.
+
+    /**
+     * Initializes this cipher with the provided key.
+     *
+     * @throws InvalidKeyException if the {@code key} is not suitable for this cipher in the
+     *         specified {@code opmode}.
+     *
+     * @see #setKey(AndroidKeyStoreKey)
+     */
+    protected abstract void initKey(int opmode, @Nullable Key key) throws InvalidKeyException;
+
+    /**
+     * Returns algorithm-specific parameters used by this cipher or {@code null} if no
+     * algorithm-specific parameters are used.
+     */
+    @Nullable
+    @Override
+    protected abstract AlgorithmParameters engineGetParameters();
+
+    /**
+     * Invoked by {@code engineInit} to initialize algorithm-specific parameters when no additional
+     * initialization parameters were provided.
+     *
+     * @throws InvalidKeyException if this cipher cannot be configured based purely on the provided
+     *         key and needs additional parameters to be provided to {@code Cipher.init}.
+     */
+    protected abstract void initAlgorithmSpecificParameters() throws InvalidKeyException;
+
+    /**
+     * Invoked by {@code engineInit} to initialize algorithm-specific parameters when additional
+     * parameters were provided.
+     *
+     * @param params additional algorithm parameters or {@code null} if not specified.
+     *
+     * @throws InvalidAlgorithmParameterException if there is insufficient information to configure
+     *         this cipher or if the provided parameters are not suitable for this cipher.
+     */
+    protected abstract void initAlgorithmSpecificParameters(
+            @Nullable AlgorithmParameterSpec params) throws InvalidAlgorithmParameterException;
+
+    /**
+     * Invoked by {@code engineInit} to initialize algorithm-specific parameters when additional
+     * parameters were provided.
+     *
+     * @param params additional algorithm parameters or {@code null} if not specified.
+     *
+     * @throws InvalidAlgorithmParameterException if there is insufficient information to configure
+     *         this cipher or if the provided parameters are not suitable for this cipher.
+     */
+    protected abstract void initAlgorithmSpecificParameters(@Nullable AlgorithmParameters params)
+            throws InvalidAlgorithmParameterException;
+
+    /**
+     * Returns the amount of additional entropy (in bytes) to be provided to the KeyStore's
+     * {@code begin} operation.
+     *
+     * <p>For decryption, this should be {@code 0} because decryption should not be consuming any
+     * entropy. For encryption, this value should match (or exceed) the amount of Shannon entropy of
+     * the ciphertext produced by this cipher assuming the key, the plaintext, and all explicitly
+     * provided parameters to {@code Cipher.init} are known. For example, for AES CBC encryption
+     * with an explicitly provided IV this should be {@code 0}, whereas for the case where IV is
+     * generated by the KeyStore's {@code begin} operation this should be {@code 16}. For RSA with
+     * OAEP this should be the size of the OAEP hash output. For RSA with PKCS#1 padding this should
+     * be the size of the padding string or could be raised (for simplicity) to the size of the
+     * modulus.
+     */
+    protected abstract int getAdditionalEntropyAmountForBegin();
+
+    /**
+     * Invoked to add algorithm-specific parameters for the KeyStore's {@code begin} operation.
+     *
+     * @param keymasterArgs keystore/keymaster arguments to be populated with algorithm-specific
+     *        parameters.
+     */
+    protected abstract void addAlgorithmSpecificParametersToBegin(
+            @NonNull KeymasterArguments keymasterArgs);
+
+    /**
+     * Invoked to obtain algorithm-specific parameters from the result of the KeyStore's
+     * {@code begin} operation.
+     *
+     * <p>Some parameters, such as IV, are not required to be provided to {@code Cipher.init}. Such
+     * parameters, if not provided, must be generated by KeyStore and returned to the user of
+     * {@code Cipher} and potentially reused after {@code doFinal}.
+     *
+     * @param keymasterArgs keystore/keymaster arguments returned by KeyStore {@code begin}
+     *        operation.
+     */
+    protected abstract void loadAlgorithmSpecificParametersFromBeginResult(
+            @NonNull KeymasterArguments keymasterArgs);
+}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreUnauthenticatedAESCipherSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreUnauthenticatedAESCipherSpi.java
new file mode 100644
index 0000000..47cd1d1
--- /dev/null
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreUnauthenticatedAESCipherSpi.java
@@ -0,0 +1,292 @@
+package android.security.keystore;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.KeymasterDefs;
+
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.ProviderException;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidParameterSpecException;
+import java.util.Arrays;
+
+import javax.crypto.CipherSpi;
+import javax.crypto.spec.IvParameterSpec;
+
+/**
+ * Base class for Android Keystore unauthenticated AES {@link CipherSpi} implementations.
+ *
+ * @hide
+ */
+class AndroidKeyStoreUnauthenticatedAESCipherSpi extends AndroidKeyStoreCipherSpiBase {
+
+    abstract static class ECB extends AndroidKeyStoreUnauthenticatedAESCipherSpi {
+        protected ECB(int keymasterPadding) {
+            super(KeymasterDefs.KM_MODE_ECB, keymasterPadding, false);
+        }
+
+        public static class NoPadding extends ECB {
+            public NoPadding() {
+                super(KeymasterDefs.KM_PAD_NONE);
+            }
+        }
+
+        public static class PKCS7Padding extends ECB {
+            public PKCS7Padding() {
+                super(KeymasterDefs.KM_PAD_PKCS7);
+            }
+        }
+    }
+
+    abstract static class CBC extends AndroidKeyStoreUnauthenticatedAESCipherSpi {
+        protected CBC(int keymasterPadding) {
+            super(KeymasterDefs.KM_MODE_CBC, keymasterPadding, true);
+        }
+
+        public static class NoPadding extends CBC {
+            public NoPadding() {
+                super(KeymasterDefs.KM_PAD_NONE);
+            }
+        }
+
+        public static class PKCS7Padding extends CBC {
+            public PKCS7Padding() {
+                super(KeymasterDefs.KM_PAD_PKCS7);
+            }
+        }
+    }
+
+    abstract static class CTR extends AndroidKeyStoreUnauthenticatedAESCipherSpi {
+        protected CTR(int keymasterPadding) {
+            super(KeymasterDefs.KM_MODE_CTR, keymasterPadding, true);
+        }
+
+        public static class NoPadding extends CTR {
+            public NoPadding() {
+                super(KeymasterDefs.KM_PAD_NONE);
+            }
+        }
+    }
+
+    private static final int BLOCK_SIZE_BYTES = 16;
+
+    private final int mKeymasterBlockMode;
+    private final int mKeymasterPadding;
+    /** Whether this transformation requires an IV. */
+    private final boolean mIvRequired;
+
+    private byte[] mIv;
+
+    /** Whether the current {@code #mIv} has been used by the underlying crypto operation. */
+    private boolean mIvHasBeenUsed;
+
+    AndroidKeyStoreUnauthenticatedAESCipherSpi(
+            int keymasterBlockMode,
+            int keymasterPadding,
+            boolean ivRequired) {
+        mKeymasterBlockMode = keymasterBlockMode;
+        mKeymasterPadding = keymasterPadding;
+        mIvRequired = ivRequired;
+    }
+
+    @Override
+    protected final void resetAll() {
+        mIv = null;
+        mIvHasBeenUsed = false;
+        super.resetAll();
+    }
+
+    @Override
+    protected final void resetWhilePreservingInitState() {
+        super.resetWhilePreservingInitState();
+    }
+
+    @Override
+    protected final void initKey(int opmode, Key key) throws InvalidKeyException {
+        if (!(key instanceof AndroidKeyStoreSecretKey)) {
+            throw new InvalidKeyException(
+                    "Unsupported key: " + ((key != null) ? key.getClass().getName() : "null"));
+        }
+        if (!KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(key.getAlgorithm())) {
+            throw new InvalidKeyException(
+                    "Unsupported key algorithm: " + key.getAlgorithm() + ". Only " +
+                    KeyProperties.KEY_ALGORITHM_AES + " supported");
+        }
+        setKey((AndroidKeyStoreSecretKey) key);
+    }
+
+    @Override
+    protected final void initAlgorithmSpecificParameters() throws InvalidKeyException {
+        if (!mIvRequired) {
+            return;
+        }
+
+        // IV is used
+        if (!isEncrypting()) {
+            throw new InvalidKeyException("IV required when decrypting"
+                    + ". Use IvParameterSpec or AlgorithmParameters to provide it.");
+        }
+    }
+
+    @Override
+    protected final void initAlgorithmSpecificParameters(AlgorithmParameterSpec params)
+            throws InvalidAlgorithmParameterException {
+        if (!mIvRequired) {
+            if (params != null) {
+                throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params);
+            }
+            return;
+        }
+
+        // IV is used
+        if (params == null) {
+            if (!isEncrypting()) {
+                // IV must be provided by the caller
+                throw new InvalidAlgorithmParameterException(
+                        "IvParameterSpec must be provided when decrypting");
+            }
+            return;
+        }
+        if (!(params instanceof IvParameterSpec)) {
+            throw new InvalidAlgorithmParameterException("Only IvParameterSpec supported");
+        }
+        mIv = ((IvParameterSpec) params).getIV();
+        if (mIv == null) {
+            throw new InvalidAlgorithmParameterException("Null IV in IvParameterSpec");
+        }
+    }
+
+    @Override
+    protected final void initAlgorithmSpecificParameters(AlgorithmParameters params)
+            throws InvalidAlgorithmParameterException {
+        if (!mIvRequired) {
+            if (params != null) {
+                throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params);
+            }
+            return;
+        }
+
+        // IV is used
+        if (params == null) {
+            if (!isEncrypting()) {
+                // IV must be provided by the caller
+                throw new InvalidAlgorithmParameterException("IV required when decrypting"
+                        + ". Use IvParameterSpec or AlgorithmParameters to provide it.");
+            }
+            return;
+        }
+
+        IvParameterSpec ivSpec;
+        try {
+            ivSpec = params.getParameterSpec(IvParameterSpec.class);
+        } catch (InvalidParameterSpecException e) {
+            if (!isEncrypting()) {
+                // IV must be provided by the caller
+                throw new InvalidAlgorithmParameterException("IV required when decrypting"
+                        + ", but not found in parameters: " + params, e);
+            }
+            mIv = null;
+            return;
+        }
+        mIv = ivSpec.getIV();
+        if (mIv == null) {
+            throw new InvalidAlgorithmParameterException("Null IV in AlgorithmParameters");
+        }
+    }
+
+    @Override
+    protected final int getAdditionalEntropyAmountForBegin() {
+        if ((mIvRequired) && (mIv == null) && (isEncrypting())) {
+            // IV will need to be generated
+            return BLOCK_SIZE_BYTES;
+        }
+
+        return 0;
+    }
+
+    @Override
+    protected final void addAlgorithmSpecificParametersToBegin(
+            @NonNull KeymasterArguments keymasterArgs) {
+        if ((isEncrypting()) && (mIvRequired) && (mIvHasBeenUsed)) {
+            // IV is being reused for encryption: this violates security best practices.
+            throw new IllegalStateException(
+                    "IV has already been used. Reusing IV in encryption mode violates security best"
+                    + " practices.");
+        }
+
+        keymasterArgs.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
+        keymasterArgs.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockMode);
+        keymasterArgs.addInt(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding);
+        if ((mIvRequired) && (mIv != null)) {
+            keymasterArgs.addBlob(KeymasterDefs.KM_TAG_NONCE, mIv);
+        }
+    }
+
+    @Override
+    protected final void loadAlgorithmSpecificParametersFromBeginResult(
+            @NonNull KeymasterArguments keymasterArgs) {
+        mIvHasBeenUsed = true;
+
+        // NOTE: Keymaster doesn't always return an IV, even if it's used.
+        byte[] returnedIv = keymasterArgs.getBlob(KeymasterDefs.KM_TAG_NONCE, null);
+        if ((returnedIv != null) && (returnedIv.length == 0)) {
+            returnedIv = null;
+        }
+
+        if (mIvRequired) {
+            if (mIv == null) {
+                mIv = returnedIv;
+            } else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) {
+                throw new ProviderException("IV in use differs from provided IV");
+            }
+        } else {
+            if (returnedIv != null) {
+                throw new ProviderException(
+                        "IV in use despite IV not being used by this transformation");
+            }
+        }
+    }
+
+    @Override
+    protected final int engineGetBlockSize() {
+        return BLOCK_SIZE_BYTES;
+    }
+
+    @Override
+    protected final int engineGetOutputSize(int inputLen) {
+        return inputLen + 3 * BLOCK_SIZE_BYTES;
+    }
+
+    @Override
+    protected final byte[] engineGetIV() {
+        return ArrayUtils.cloneIfNotEmpty(mIv);
+    }
+
+    @Nullable
+    @Override
+    protected final AlgorithmParameters engineGetParameters() {
+        if (!mIvRequired) {
+            return null;
+        }
+        if ((mIv != null) && (mIv.length > 0)) {
+            try {
+                AlgorithmParameters params = AlgorithmParameters.getInstance("AES");
+                params.init(new IvParameterSpec(mIv));
+                return params;
+            } catch (NoSuchAlgorithmException e) {
+                throw new ProviderException(
+                        "Failed to obtain AES AlgorithmParameters", e);
+            } catch (InvalidParameterSpecException e) {
+                throw new ProviderException(
+                        "Failed to initialize AES AlgorithmParameters with an IV",
+                        e);
+            }
+        }
+        return null;
+    }
+}
diff --git a/keystore/java/android/security/keystore/ArrayUtils.java b/keystore/java/android/security/keystore/ArrayUtils.java
index 81be3848..26172d2 100644
--- a/keystore/java/android/security/keystore/ArrayUtils.java
+++ b/keystore/java/android/security/keystore/ArrayUtils.java
@@ -32,6 +32,10 @@
         return ((array != null) && (array.length > 0)) ? array.clone() : array;
     }
 
+    public static byte[] cloneIfNotEmpty(byte[] array) {
+        return ((array != null) && (array.length > 0)) ? array.clone() : array;
+    }
+
     public static byte[] concat(byte[] arr1, byte[] arr2) {
         return concat(arr1, 0, (arr1 != null) ? arr1.length : 0,
                 arr2, 0, (arr2 != null) ? arr2.length : 0);
diff --git a/keystore/java/android/security/keystore/KeyStoreCryptoOperationUtils.java b/keystore/java/android/security/keystore/KeyStoreCryptoOperationUtils.java
index 6ae76f1..27c1b2a 100644
--- a/keystore/java/android/security/keystore/KeyStoreCryptoOperationUtils.java
+++ b/keystore/java/android/security/keystore/KeyStoreCryptoOperationUtils.java
@@ -19,6 +19,8 @@
 import android.security.KeyStore;
 import android.security.keymaster.KeymasterDefs;
 
+import libcore.util.EmptyArray;
+
 import java.security.GeneralSecurityException;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
@@ -94,6 +96,9 @@
      *        RNG.
      */
     static byte[] getRandomBytesToMixIntoKeystoreRng(SecureRandom rng, int sizeBytes) {
+        if (sizeBytes <= 0) {
+            return EmptyArray.BYTE;
+        }
         if (rng == null) {
             rng = getRng();
         }