/* * 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 {@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 * {@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 * {@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. */ @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 {@code engineInit} completed * successfully. * *
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. */ @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 {@code null}. * * @returns 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. */ protected final void setKeymasterPurposeOverride(int keymasterPurpose) { mKeymasterPurposeOverride = keymasterPurpose; } protected final int getKeymasterPurposeOverride() { return mKeymasterPurposeOverride; } /** * 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 {@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. This amount of entropy is typically what's consumed to generate * random parameters, such as IV. * *
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 * {@code finish} operation. This amount of entropy is typically what's consumed by encryption * padding scheme. * *
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 {@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. * *
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); }