/*
 * 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 libcore.util.EmptyArray;

import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.InvalidParameterException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.ProviderException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

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.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.SecretKeySpec;

Base class for CipherSpi implementations of Android KeyStore backed ciphers.
@hide
/** * 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 int mKeymasterPurposeOverride = -1; private AndroidKeyStoreKey mKey; private SecureRandom mRng;
Token referencing this operation inside keystore service. It is initialized by engineInit and is invalidated when engineDoFinal succeeds and on some error conditions in between.
/** * 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 KeyStoreCryptoOperationStreamer mMainDataStreamer; private KeyStoreCryptoOperationStreamer mAdditionalAuthenticationDataStreamer; private boolean mAdditionalAuthenticationDataStreamerClosed;
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 engineDoFinal. Once such an exception is encountered, engineUpdate and engineDoFinal start ignoring input data.
/** * 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 { switch (opmode) { case Cipher.ENCRYPT_MODE: case Cipher.WRAP_MODE: mEncrypting = true; break; case Cipher.DECRYPT_MODE: case Cipher.UNWRAP_MODE: mEncrypting = false; break; default: throw new InvalidParameterException("Unsupported opmode: " + opmode); } 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.

Subclasses storing additional state should override this method, reset the additional state, and then chain to superclass.

/** * 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) { mKeyStore.abort(operationToken); } mEncrypting = false; mKeymasterPurposeOverride = -1; mKey = null; mRng = null; mOperationToken = null; mOperationHandle = 0; mMainDataStreamer = null; mAdditionalAuthenticationDataStreamer = null; mAdditionalAuthenticationDataStreamerClosed = false; 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 engineInit completed successfully.

Subclasses storing additional post-init state should override this method, reset the additional state, and then chain to superclass.

/** * 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) { mKeyStore.abort(operationToken); } mOperationToken = null; mOperationHandle = 0; mMainDataStreamer = null; mAdditionalAuthenticationDataStreamer = null; mAdditionalAuthenticationDataStreamerClosed = false; 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()); int purpose; if (mKeymasterPurposeOverride != -1) { purpose = mKeymasterPurposeOverride; } else { purpose = mEncrypting ? KeymasterDefs.KM_PURPOSE_ENCRYPT : KeymasterDefs.KM_PURPOSE_DECRYPT; } OperationResult opResult = mKeyStore.begin( mKey.getAlias(), purpose, true, // permit aborting this operation if keystore runs out of resources keymasterInputArgs, additionalEntropy, mKey.getUid()); 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(opResult.outParams); mMainDataStreamer = createMainDataStreamer(mKeyStore, opResult.token); mAdditionalAuthenticationDataStreamer = createAdditionalAuthenticationDataStreamer(mKeyStore, opResult.token); mAdditionalAuthenticationDataStreamerClosed = false; }
Creates a streamer which sends plaintext/ciphertext into the provided KeyStore and receives the corresponding ciphertext/plaintext from the KeyStore.

This implementation returns a working streamer.

/** * Creates a streamer which sends plaintext/ciphertext into the provided KeyStore and receives * the corresponding ciphertext/plaintext from the KeyStore. * * <p>This implementation returns a working streamer. */
@NonNull protected KeyStoreCryptoOperationStreamer createMainDataStreamer( KeyStore keyStore, IBinder operationToken) { return new KeyStoreCryptoOperationChunkedStreamer( new KeyStoreCryptoOperationChunkedStreamer.MainDataStream( keyStore, operationToken)); }
Creates a streamer which sends Additional Authentication Data (AAD) into the KeyStore.

This implementation returns null.

Returns:stream or null if AAD is not supported by this cipher.
/** * Creates a streamer which sends Additional Authentication Data (AAD) into the KeyStore. * * <p>This implementation returns {@code null}. * * @return stream or {@code null} if AAD is not supported by this cipher. */
@Nullable protected KeyStoreCryptoOperationStreamer createAdditionalAuthenticationDataStreamer( @SuppressWarnings("unused") KeyStore keyStore, @SuppressWarnings("unused") IBinder operationToken) { return null; } @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 { flushAAD(); output = mMainDataStreamer.update(input, inputOffset, inputLen); } catch (KeyStoreException e) { mCachedException = e; return null; } if (output.length == 0) { return null; } return output; } private void flushAAD() throws KeyStoreException { if ((mAdditionalAuthenticationDataStreamer != null) && (!mAdditionalAuthenticationDataStreamerClosed)) { byte[] output; try { output = mAdditionalAuthenticationDataStreamer.doFinal( EmptyArray.BYTE, 0, 0, null, // no signature null // no additional entropy needed flushing AAD ); } finally { mAdditionalAuthenticationDataStreamerClosed = true; } if ((output != null) && (output.length > 0)) { throw new ProviderException( "AAD update unexpectedly returned data: " + output.length + " bytes"); } } } @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 { if (input == null) { throw new NullPointerException("input == null"); } if (output == null) { throw new NullPointerException("output == null"); } int inputSize = input.remaining(); byte[] outputArray; if (input.hasArray()) { outputArray = engineUpdate( input.array(), input.arrayOffset() + input.position(), inputSize); input.position(input.position() + inputSize); } else { byte[] inputArray = new byte[inputSize]; input.get(inputArray); outputArray = engineUpdate(inputArray, 0, inputSize); } int outputSize = (outputArray != null) ? outputArray.length : 0; if (outputSize > 0) { int outputBufferAvailable = output.remaining(); try { output.put(outputArray); } catch (BufferOverflowException e) { throw new ShortBufferException( "Output buffer too small. Produced: " + outputSize + ", available: " + outputBufferAvailable); } } return outputSize; } @Override protected final void engineUpdateAAD(byte[] input, int inputOffset, int inputLen) { if (mCachedException != null) { return; } try { ensureKeystoreOperationInitialized(); } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { mCachedException = e; return; } if (mAdditionalAuthenticationDataStreamerClosed) { throw new IllegalStateException( "AAD can only be provided before Cipher.update is invoked"); } if (mAdditionalAuthenticationDataStreamer == null) { throw new IllegalStateException("This cipher does not support AAD"); } byte[] output; try { output = mAdditionalAuthenticationDataStreamer.update(input, inputOffset, inputLen); } catch (KeyStoreException e) { mCachedException = e; return; } if ((output != null) && (output.length > 0)) { throw new ProviderException("AAD update unexpectedly produced output: " + output.length + " bytes"); } } @Override protected final void engineUpdateAAD(ByteBuffer src) { if (src == null) { throw new IllegalArgumentException("src == null"); } if (!src.hasRemaining()) { return; } byte[] input; int inputOffset; int inputLen; if (src.hasArray()) { input = src.array(); inputOffset = src.arrayOffset() + src.position(); inputLen = src.remaining(); src.position(src.limit()); } else { input = new byte[src.remaining()]; inputOffset = 0; inputLen = input.length; src.get(input); } engineUpdateAAD(input, inputOffset, inputLen); } @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 { flushAAD(); byte[] additionalEntropy = KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( mRng, getAdditionalEntropyAmountForFinish()); output = mMainDataStreamer.doFinal( input, inputOffset, inputLen, null, // no signature involved additionalEntropy); } catch (KeyStoreException e) { switch (e.getErrorCode()) { case KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH: throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e); case KeymasterDefs.KM_ERROR_INVALID_ARGUMENT: throw (BadPaddingException) new BadPaddingException().initCause(e); case KeymasterDefs.KM_ERROR_VERIFICATION_FAILED: throw (AEADBadTagException) new AEADBadTagException().initCause(e); 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 { if (input == null) { throw new NullPointerException("input == null"); } if (output == null) { throw new NullPointerException("output == null"); } int inputSize = input.remaining(); byte[] outputArray; if (input.hasArray()) { outputArray = engineDoFinal( input.array(), input.arrayOffset() + input.position(), inputSize); input.position(input.position() + inputSize); } else { byte[] inputArray = new byte[inputSize]; input.get(inputArray); outputArray = engineDoFinal(inputArray, 0, inputSize); } int outputSize = (outputArray != null) ? outputArray.length : 0; if (outputSize > 0) { int outputBufferAvailable = output.remaining(); try { output.put(outputArray); } catch (BufferOverflowException e) { throw new ShortBufferException( "Output buffer too small. Produced: " + outputSize + ", available: " + outputBufferAvailable); } } return outputSize; } @Override protected final byte[] engineWrap(Key key) throws IllegalBlockSizeException, InvalidKeyException { if (mKey == null) { throw new IllegalStateException("Not initilized"); } if (!isEncrypting()) { throw new IllegalStateException( "Cipher must be initialized in Cipher.WRAP_MODE to wrap keys"); } if (key == null) { throw new NullPointerException("key == null"); } byte[] encoded = null; if (key instanceof SecretKey) { if ("RAW".equalsIgnoreCase(key.getFormat())) { encoded = key.getEncoded(); } if (encoded == null) { try { SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(key.getAlgorithm()); SecretKeySpec spec = (SecretKeySpec) keyFactory.getKeySpec( (SecretKey) key, SecretKeySpec.class); encoded = spec.getEncoded(); } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { throw new InvalidKeyException( "Failed to wrap key because it does not export its key material", e); } } } else if (key instanceof PrivateKey) { if ("PKCS8".equalsIgnoreCase(key.getFormat())) { encoded = key.getEncoded(); } if (encoded == null) { try { KeyFactory keyFactory = KeyFactory.getInstance(key.getAlgorithm()); PKCS8EncodedKeySpec spec = keyFactory.getKeySpec(key, PKCS8EncodedKeySpec.class); encoded = spec.getEncoded(); } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { throw new InvalidKeyException( "Failed to wrap key because it does not export its key material", e); } } } else if (key instanceof PublicKey) { if ("X.509".equalsIgnoreCase(key.getFormat())) { encoded = key.getEncoded(); } if (encoded == null) { try { KeyFactory keyFactory = KeyFactory.getInstance(key.getAlgorithm()); X509EncodedKeySpec spec = keyFactory.getKeySpec(key, X509EncodedKeySpec.class); encoded = spec.getEncoded(); } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { throw new InvalidKeyException( "Failed to wrap key because it does not export its key material", e); } } } else { throw new InvalidKeyException("Unsupported key type: " + key.getClass().getName()); } if (encoded == null) { throw new InvalidKeyException( "Failed to wrap key because it does not export its key material"); } try { return engineDoFinal(encoded, 0, encoded.length); } catch (BadPaddingException e) { throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e); } } @Override protected final Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm, int wrappedKeyType) throws InvalidKeyException, NoSuchAlgorithmException { if (mKey == null) { throw new IllegalStateException("Not initilized"); } if (isEncrypting()) { throw new IllegalStateException( "Cipher must be initialized in Cipher.WRAP_MODE to wrap keys"); } if (wrappedKey == null) { throw new NullPointerException("wrappedKey == null"); } byte[] encoded; try { encoded = engineDoFinal(wrappedKey, 0, wrappedKey.length); } catch (IllegalBlockSizeException | BadPaddingException e) { throw new InvalidKeyException("Failed to unwrap key", e); } switch (wrappedKeyType) { case Cipher.SECRET_KEY: { return new SecretKeySpec(encoded, wrappedKeyAlgorithm); // break; } case Cipher.PRIVATE_KEY: { KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm); try { return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encoded)); } catch (InvalidKeySpecException e) { throw new InvalidKeyException( "Failed to create private key from its PKCS#8 encoded form", e); } // break; } case Cipher.PUBLIC_KEY: { KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm); try { return keyFactory.generatePublic(new X509EncodedKeySpec(encoded)); } catch (InvalidKeySpecException e) { throw new InvalidKeyException( "Failed to create public key from its X.509 encoded form", e); } // break; } default: throw new InvalidParameterException( "Unsupported wrappedKeyType: " + 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; }
Overrides the default purpose/type of the crypto operation.
/** * Overrides the default purpose/type of the crypto operation. */
protected final void setKeymasterPurposeOverride(int keymasterPurpose) { mKeymasterPurposeOverride = keymasterPurpose; } protected final int getKeymasterPurposeOverride() { return mKeymasterPurposeOverride; }
Returns true if this cipher is initialized for encryption, false if this cipher is initialized for decryption.
/** * 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; } protected final long getConsumedInputSizeBytes() { if (mMainDataStreamer == null) { throw new IllegalStateException("Not initialized"); } return mMainDataStreamer.getConsumedInputSizeBytes(); } protected final long getProducedOutputSizeBytes() { if (mMainDataStreamer == null) { throw new IllegalStateException("Not initialized"); } return mMainDataStreamer.getProducedOutputSizeBytes(); } static String opmodeToString(int opmode) { switch (opmode) { case Cipher.ENCRYPT_MODE: return "ENCRYPT_MODE"; case Cipher.DECRYPT_MODE: return "DECRYPT_MODE"; case Cipher.WRAP_MODE: return "WRAP_MODE"; case Cipher.UNWRAP_MODE: return "UNWRAP_MODE"; default: return String.valueOf(opmode); } } // The methods below need to be implemented by subclasses.
Initializes this cipher with the provided key.
Throws:
  • InvalidKeyException – if the key is not suitable for this cipher in the specified opmode.
See Also:
/** * 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 null if no algorithm-specific parameters are used.
/** * 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 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 Cipher.init.
/** * 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 engineInit to initialize algorithm-specific parameters when additional parameters were provided.
Params:
  • params – additional algorithm parameters or null if not specified.
Throws:
/** * 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 engineInit to initialize algorithm-specific parameters when additional parameters were provided.
Params:
  • params – additional algorithm parameters or null if not specified.
Throws:
/** * 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 begin operation. This amount of entropy is typically what's consumed to generate random parameters, such as IV.

For decryption, the return value should be 0 because decryption should not be consuming any entropy. For encryption, the value combined with getAdditionalEntropyAmountForFinish() 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 Cipher.init are known. For example, for AES CBC encryption with an explicitly provided IV the return value should be 0, whereas for the case where IV is generated by the KeyStore's begin operation it should be 16.

/** * Returns the amount of additional entropy (in bytes) to be provided to the KeyStore's * {@code begin} operation. This amount of entropy is typically what's consumed to generate * random parameters, such as IV. * * <p>For decryption, the return value should be {@code 0} because decryption should not be * consuming any entropy. For encryption, the value combined with * {@link #getAdditionalEntropyAmountForFinish()} 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 the return value should be {@code 0}, whereas for * the case where IV is generated by the KeyStore's {@code begin} operation it should be * {@code 16}. */
protected abstract int getAdditionalEntropyAmountForBegin();
Returns the amount of additional entropy (in bytes) to be provided to the KeyStore's finish operation. This amount of entropy is typically what's consumed by encryption padding scheme.

For decryption, the return value should be 0 because decryption should not be consuming any entropy. For encryption, the value combined with getAdditionalEntropyAmountForBegin() 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 Cipher.init are known. For example, for RSA with OAEP the return value should be the size of the OAEP hash output. For RSA with PKCS#1 padding the return value should be the size of the padding string or could be raised (for simplicity) to the size of the modulus.

/** * Returns the amount of additional entropy (in bytes) to be provided to the KeyStore's * {@code finish} operation. This amount of entropy is typically what's consumed by encryption * padding scheme. * * <p>For decryption, the return value should be {@code 0} because decryption should not be * consuming any entropy. For encryption, the value combined with * {@link #getAdditionalEntropyAmountForBegin()} 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 RSA with * OAEP the return value should be the size of the OAEP hash output. For RSA with PKCS#1 padding * the return value should be the size of the padding string or could be raised (for simplicity) * to the size of the modulus. */
protected abstract int getAdditionalEntropyAmountForFinish();
Invoked to add algorithm-specific parameters for the KeyStore's begin operation.
Params:
  • keymasterArgs – keystore/keymaster arguments to be populated with algorithm-specific parameters.
/** * 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 begin operation.

Some parameters, such as IV, are not required to be provided to Cipher.init. Such parameters, if not provided, must be generated by KeyStore and returned to the user of Cipher and potentially reused after doFinal.

Params:
  • keymasterArgs – keystore/keymaster arguments returned by KeyStore begin operation.
/** * 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); }