From e3b8cea04d43d9aafec544f56aa46ccf691a575d Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 1 Feb 2016 15:21:33 +0100 Subject: performance: cache session keys per compatible S2K configuration --- .../SessionKeySecretKeyDecryptorBuilder.java | 107 +++++++++++++++++++++ .../keychain/pgp/CanonicalizedSecretKey.java | 27 +++++- .../keychain/pgp/ComparableS2K.java | 91 ++++++++++++++++++ .../keychain/pgp/PgpDecryptVerifyOperation.java | 2 + .../keychain/util/Passphrase.java | 45 ++++++++- 5 files changed, 266 insertions(+), 6 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/SessionKeySecretKeyDecryptorBuilder.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/ComparableS2K.java (limited to 'OpenKeychain') diff --git a/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/SessionKeySecretKeyDecryptorBuilder.java b/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/SessionKeySecretKeyDecryptorBuilder.java new file mode 100644 index 000000000..49282230f --- /dev/null +++ b/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/SessionKeySecretKeyDecryptorBuilder.java @@ -0,0 +1,107 @@ +package org.spongycastle.openpgp.operator.jcajce; + + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Provider; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.spec.IvParameterSpec; +import org.spongycastle.bcpg.S2K; +import org.spongycastle.jcajce.util.DefaultJcaJceHelper; +import org.spongycastle.jcajce.util.NamedJcaJceHelper; +import org.spongycastle.jcajce.util.ProviderJcaJceHelper; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider; + + +public class SessionKeySecretKeyDecryptorBuilder +{ + private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); + private PGPDigestCalculatorProvider calculatorProvider; + + private JcaPGPDigestCalculatorProviderBuilder calculatorProviderBuilder; + + public SessionKeySecretKeyDecryptorBuilder() + { + this.calculatorProviderBuilder = new JcaPGPDigestCalculatorProviderBuilder(); + } + + public SessionKeySecretKeyDecryptorBuilder(PGPDigestCalculatorProvider calculatorProvider) + { + this.calculatorProvider = calculatorProvider; + } + + public SessionKeySecretKeyDecryptorBuilder setProvider(Provider provider) + { + this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + + if (calculatorProviderBuilder != null) + { + calculatorProviderBuilder.setProvider(provider); + } + + return this; + } + + public SessionKeySecretKeyDecryptorBuilder setProvider(String providerName) + { + this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + + if (calculatorProviderBuilder != null) + { + calculatorProviderBuilder.setProvider(providerName); + } + + return this; + } + + public PBESecretKeyDecryptor build(final byte[] sessionKey) + throws PGPException + { + if (calculatorProvider == null) + { + calculatorProvider = calculatorProviderBuilder.build(); + } + + return new PBESecretKeyDecryptor(null, calculatorProvider) + { + @Override + public byte[] makeKeyFromPassPhrase(int keyAlgorithm, S2K s2k) throws PGPException { + return sessionKey; + } + + public byte[] recoverKeyData(int encAlgorithm, byte[] key, byte[] iv, byte[] keyData, int keyOff, int keyLen) + throws PGPException + { + try + { + Cipher c = helper.createCipher(PGPUtil.getSymmetricCipherName(encAlgorithm) + "/CFB/NoPadding"); + + c.init(Cipher.DECRYPT_MODE, PGPUtil.makeSymmetricKey(encAlgorithm, key), new IvParameterSpec(iv)); + + return c.doFinal(keyData, keyOff, keyLen); + } + catch (IllegalBlockSizeException e) + { + throw new PGPException("illegal block size: " + e.getMessage(), e); + } + catch (BadPaddingException e) + { + throw new PGPException("bad padding: " + e.getMessage(), e); + } + catch (InvalidAlgorithmParameterException e) + { + throw new PGPException("invalid parameter: " + e.getMessage(), e); + } + catch (InvalidKeyException e) + { + throw new PGPException("invalid key: " + e.getMessage(), e); + } + } + }; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java index 7394c07c3..7f2a00617 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java @@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.pgp; import org.spongycastle.bcpg.S2K; +import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; import org.spongycastle.openpgp.PGPException; import org.spongycastle.openpgp.PGPPrivateKey; import org.spongycastle.openpgp.PGPSecretKey; @@ -33,6 +34,7 @@ import org.spongycastle.openpgp.operator.jcajce.JcaPGPKeyConverter; import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder; import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder; +import org.spongycastle.openpgp.operator.jcajce.SessionKeySecretKeyDecryptorBuilder; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; @@ -145,13 +147,12 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { // Otherwise, it's just a regular ol' passphrase return SecretKeyType.PASSPHRASE; } - } /** * Returns true on right passphrase */ - public boolean unlock(Passphrase passphrase) throws PgpGeneralException { + public boolean unlock(final Passphrase passphrase) throws PgpGeneralException { // handle keys on OpenPGP cards like they were unlocked S2K s2k = mSecretKey.getS2K(); if (s2k != null @@ -163,8 +164,26 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { // try to extract keys using the passphrase try { - PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( - Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.getCharArray()); + + int keyEncryptionAlgorithm = mSecretKey.getKeyEncryptionAlgorithm(); + if (keyEncryptionAlgorithm == SymmetricKeyAlgorithmTags.NULL) { + mPrivateKey = mSecretKey.extractPrivateKey(null); + mPrivateKeyState = PRIVATE_KEY_STATE_UNLOCKED; + return true; + } + + byte[] sessionKey; + sessionKey = passphrase.getCachedSessionKeyForAlgorithm(keyEncryptionAlgorithm, s2k); + if (sessionKey == null) { + PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( + Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.getCharArray()); + // this operation is EXPENSIVE, so we cache its result in the passed Passphrase object! + sessionKey = keyDecryptor.makeKeyFromPassPhrase(keyEncryptionAlgorithm, s2k); + passphrase.addCachedSessionKey(keyEncryptionAlgorithm, s2k, sessionKey); + } + + PBESecretKeyDecryptor keyDecryptor = new SessionKeySecretKeyDecryptorBuilder() + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(sessionKey); mPrivateKey = mSecretKey.extractPrivateKey(keyDecryptor); mPrivateKeyState = PRIVATE_KEY_STATE_UNLOCKED; } catch (PGPException e) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/ComparableS2K.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/ComparableS2K.java new file mode 100644 index 000000000..b10f77739 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/ComparableS2K.java @@ -0,0 +1,91 @@ +package org.sufficientlysecure.keychain.pgp; + + +import java.util.Arrays; + +import android.os.Parcel; +import android.os.Parcelable; + +import org.spongycastle.bcpg.S2K; + + +public class ComparableS2K implements Parcelable { + + int encryptionAlgorithm; + int s2kType; + int s2kHashAlgo; + long s2kItCount; + byte[] s2kIV; + + Integer cachedHashCode; + + public ComparableS2K(int encryptionAlgorithm, S2K s2k) { + this.encryptionAlgorithm = encryptionAlgorithm; + this.s2kType = s2k.getType(); + this.s2kHashAlgo = s2k.getHashAlgorithm(); + this.s2kItCount = s2k.getIterationCount(); + this.s2kIV = s2k.getIV(); + } + + protected ComparableS2K(Parcel in) { + encryptionAlgorithm = in.readInt(); + s2kType = in.readInt(); + s2kHashAlgo = in.readInt(); + s2kItCount = in.readLong(); + s2kIV = in.createByteArray(); + } + + @Override + public int hashCode() { + if (cachedHashCode == null) { + cachedHashCode = encryptionAlgorithm; + cachedHashCode *= 31 * s2kType; + cachedHashCode *= 31 * s2kHashAlgo; + cachedHashCode *= (int) (31 * s2kItCount); + cachedHashCode *= 31 * Arrays.hashCode(s2kIV); + } + + return cachedHashCode; + } + + @Override + public boolean equals(Object o) { + boolean isComparableS2K = o instanceof ComparableS2K; + if (!isComparableS2K) { + return false; + } + ComparableS2K other = (ComparableS2K) o; + return encryptionAlgorithm == other.encryptionAlgorithm + && s2kType == other.s2kType + && s2kHashAlgo == other.s2kHashAlgo + && s2kItCount == other.s2kItCount + && Arrays.equals(s2kIV, other.s2kIV); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(encryptionAlgorithm); + dest.writeInt(s2kType); + dest.writeInt(s2kHashAlgo); + dest.writeLong(s2kItCount); + dest.writeByteArray(s2kIV); + } + + public static final Creator CREATOR = new Creator() { + @Override + public ComparableS2K createFromParcel(Parcel in) { + return new ComparableS2K(in); + } + + @Override + public ComparableS2K[] newArray(int size) { + return new ComparableS2K[size]; + } + }; + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java index ea7465209..79a7a8fe1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java @@ -610,6 +610,8 @@ public class PgpDecryptVerifyOperation extends BaseOperation mCachedSessionKeys; /** * According to http://stackoverflow.com/a/15844273 EditText is not using String internally @@ -87,8 +93,18 @@ public class Passphrase implements Parcelable { return mPassphrase.length; } - public char charAt(int index) { - return mPassphrase[index]; + public byte[] getCachedSessionKeyForAlgorithm(int keyEncryptionAlgorithm, S2K s2k) { + if (mCachedSessionKeys == null) { + return null; + } + return mCachedSessionKeys.get(new ComparableS2K(keyEncryptionAlgorithm, s2k)); + } + + public void addCachedSessionKey(int keyEncryptionAlgorithm, S2K s2k, byte[] sessionKey) { + if (mCachedSessionKeys == null) { + mCachedSessionKeys = new HashMap<>(); + } + mCachedSessionKeys.put(new ComparableS2K(keyEncryptionAlgorithm, s2k), sessionKey); } /** @@ -98,6 +114,12 @@ public class Passphrase implements Parcelable { if (mPassphrase != null) { Arrays.fill(mPassphrase, ' '); } + if (mCachedSessionKeys == null) { + return; + } + for (byte[] cachedSessionKey : mCachedSessionKeys.values()) { + Arrays.fill(cachedSessionKey, (byte) 0); + } } @Override @@ -144,10 +166,29 @@ public class Passphrase implements Parcelable { private Passphrase(Parcel source) { mPassphrase = source.createCharArray(); + int size = source.readInt(); + if (size == 0) { + return; + } + mCachedSessionKeys = new HashMap<>(size); + for (int i = 0; i < size; i++) { + ComparableS2K cachedS2K = source.readParcelable(getClass().getClassLoader()); + byte[] cachedSessionKey = source.createByteArray(); + mCachedSessionKeys.put(cachedS2K, cachedSessionKey); + } } public void writeToParcel(Parcel dest, int flags) { dest.writeCharArray(mPassphrase); + if (mCachedSessionKeys == null || mCachedSessionKeys.isEmpty()) { + dest.writeInt(0); + return; + } + dest.writeInt(mCachedSessionKeys.size()); + for (Entry entry : mCachedSessionKeys.entrySet()) { + dest.writeParcelable(entry.getKey(), 0); + dest.writeByteArray(entry.getValue()); + } } public static final Creator CREATOR = new Creator() { -- cgit v1.2.3 From b1ea1261425e05d7eaa803e6ea72c1f0bbb5ae32 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 1 Feb 2016 15:22:36 +0100 Subject: performance: avoid expensive getSecretKeyType call, use cached where possible --- .../keychain/operations/CertifyOperation.java | 28 ++-- .../keychain/pgp/CanonicalizedSecretKey.java | 4 +- .../keychain/pgp/CanonicalizedSecretKeyRing.java | 14 -- .../keychain/pgp/PgpDecryptVerifyOperation.java | 142 +++++++++++---------- .../keychain/pgp/PgpSignEncryptOperation.java | 19 +-- .../keychain/provider/CachedPublicKeyRing.java | 2 +- .../keychain/provider/ProviderHelper.java | 8 +- .../keychain/ui/PassphraseDialogActivity.java | 80 +++++++----- 8 files changed, 152 insertions(+), 145 deletions(-) (limited to 'OpenKeychain') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java index 4ad75fde1..79b42ecc4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java @@ -38,6 +38,7 @@ import org.sufficientlysecure.keychain.pgp.PgpCertifyOperation.PgpCertifyResult; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; import org.sufficientlysecure.keychain.service.CertifyActionsParcel; @@ -75,24 +76,22 @@ public class CertifyOperation extends BaseOperation { // Retrieve and unlock secret key CanonicalizedSecretKey certificationKey; + long masterKeyId = parcel.mMasterKeyId; try { log.add(LogType.MSG_CRT_MASTER_FETCH, 1); - CanonicalizedSecretKeyRing secretKeyRing = - mProviderHelper.getCanonicalizedSecretKeyRing(parcel.mMasterKeyId); - log.add(LogType.MSG_CRT_UNLOCK, 1); - certificationKey = secretKeyRing.getSecretKey(); + CachedPublicKeyRing cachedPublicKeyRing = mProviderHelper.getCachedPublicKeyRing(masterKeyId); Passphrase passphrase; - switch (certificationKey.getSecretKeyType()) { + switch (cachedPublicKeyRing.getSecretKeyType(masterKeyId)) { case PIN: case PATTERN: case PASSPHRASE: passphrase = cryptoInput.getPassphrase(); if (passphrase == null) { try { - passphrase = getCachedPassphrase(certificationKey.getKeyId(), certificationKey.getKeyId()); + passphrase = getCachedPassphrase(masterKeyId, masterKeyId); } catch (PassphraseCacheInterface.NoSecretKeyException ignored) { // treat as a cache miss for error handling purposes } @@ -100,10 +99,7 @@ public class CertifyOperation extends BaseOperation { if (passphrase == null) { return new CertifyResult(log, - RequiredInputParcel.createRequiredSignPassphrase( - certificationKey.getKeyId(), - certificationKey.getKeyId(), - null), + RequiredInputParcel.createRequiredSignPassphrase(masterKeyId, masterKeyId, null), cryptoInput ); } @@ -123,7 +119,14 @@ public class CertifyOperation extends BaseOperation { return new CertifyResult(CertifyResult.RESULT_ERROR, log); } - if (!certificationKey.unlock(passphrase)) { + // Get actual secret key + CanonicalizedSecretKeyRing secretKeyRing = + mProviderHelper.getCanonicalizedSecretKeyRing(parcel.mMasterKeyId); + certificationKey = secretKeyRing.getSecretKey(); + + log.add(LogType.MSG_CRT_UNLOCK, 1); + boolean unlockSuccessful = certificationKey.unlock(passphrase); + if (!unlockSuccessful) { log.add(LogType.MSG_CRT_ERROR_UNLOCK, 2); return new CertifyResult(CertifyResult.RESULT_ERROR, log); } @@ -142,8 +145,7 @@ public class CertifyOperation extends BaseOperation { int certifyOk = 0, certifyError = 0, uploadOk = 0, uploadError = 0; NfcSignOperationsBuilder allRequiredInput = new NfcSignOperationsBuilder( - cryptoInput.getSignatureTime(), certificationKey.getKeyId(), - certificationKey.getKeyId()); + cryptoInput.getSignatureTime(), masterKeyId, masterKeyId); // Work through all requested certifications for (CertifyAction action : parcel.mCertifyActions) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java index 7f2a00617..95a0d41cc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java @@ -120,7 +120,9 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { } - public SecretKeyType getSecretKeyType() { + // This method can potentially take a LONG time (i.e. seconds), so it should only + // ever be called by ProviderHelper to be cached in the database. + public SecretKeyType getSecretKeyTypeSuperExpensive() { S2K s2k = mSecretKey.getS2K(); if (s2k != null && s2k.getType() == S2K.GNU_DUMMY_S2K) { // divert to card is special diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java index 97b5fa6fe..1df16254f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java @@ -70,20 +70,6 @@ public class CanonicalizedSecretKeyRing extends CanonicalizedKeyRing { return new CanonicalizedSecretKey(this, mRing.getSecretKey(id)); } - /** Returns the key id which should be used for signing. - * - * This method returns keys which are actually available (ie. secret available, and not stripped, - * revoked, or expired), hence only works on keyrings where a secret key is available! - */ - public long getSecretSignId() throws PgpGeneralException { - for(CanonicalizedSecretKey key : secretKeyIterator()) { - if (key.canSign() && key.isValid() && key.getSecretKeyType().isUsable()) { - return key.getKeyId(); - } - } - throw new PgpGeneralException("no valid signing key available"); - } - public IterableIterator secretKeyIterator() { final Iterator it = mRing.getSecretKeys(); return new IterableIterator<>(new Iterator() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java index 79a7a8fe1..3fc020aa7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java @@ -64,6 +64,8 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.LogTyp import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; +import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; @@ -539,7 +541,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation() { + new AsyncTask() { + @Override - protected Boolean doInBackground(Void... params) { + protected CanonicalizedSecretKey doInBackground(Void... params) { try { - // wait some 100ms here, give the user time to appreciate the progress bar - try { - Thread.sleep(100); - } catch (InterruptedException e) { - // never mind + long timeBeforeOperation = System.currentTimeMillis(); + + Long subKeyId = mRequiredInput.getSubKeyId(); + CanonicalizedSecretKeyRing secretKeyRing = + new ProviderHelper(getActivity()).getCanonicalizedSecretKeyRing( + KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId)); + CanonicalizedSecretKey secretKeyToUnlock = + secretKeyRing.getSecretKey(subKeyId); + + // this is the operation may take a very long time (100ms to several seconds!) + boolean unlockSucceeded = secretKeyToUnlock.unlock(passphrase); + + // if it didn't take that long, give the user time to appreciate the progress bar + long operationTime = System.currentTimeMillis() -timeBeforeOperation; + if (operationTime < 100) { + try { + Thread.sleep(100 -operationTime); + } catch (InterruptedException e) { + // ignore + } } - // make sure this unlocks - return mSecretRing.getSecretKey(mRequiredInput.getSubKeyId()).unlock(passphrase); - } catch (PgpGeneralException e) { + + return unlockSucceeded ? secretKeyToUnlock : null; + } catch (NotFoundException | PgpGeneralException e) { Toast.makeText(getActivity(), R.string.error_could_not_extract_private_key, Toast.LENGTH_SHORT).show(); getActivity().setResult(RESULT_CANCELED); dismiss(); getActivity().finish(); - return false; + return null; } } /** Handle a good or bad passphrase. This happens in the UI thread! */ @Override - protected void onPostExecute(Boolean result) { + protected void onPostExecute(CanonicalizedSecretKey result) { super.onPostExecute(result); // if we were cancelled in the meantime, the result isn't relevant anymore @@ -437,7 +447,7 @@ public class PassphraseDialogActivity extends FragmentActivity { } // if the passphrase was wrong, reset and re-enable the dialogue - if (!result) { + if (result == null) { mPassphraseEditText.setText(""); mPassphraseEditText.setError(getString(R.string.wrong_passphrase)); mLayout.setDisplayedChild(0); @@ -455,8 +465,8 @@ public class PassphraseDialogActivity extends FragmentActivity { try { PassphraseCacheService.addCachedPassphrase(getActivity(), - mSecretRing.getMasterKeyId(), mRequiredInput.getSubKeyId(), passphrase, - mSecretRing.getPrimaryUserIdWithFallback(), timeToLiveSeconds); + mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId(), passphrase, + result.getRing().getPrimaryUserIdWithFallback(), timeToLiveSeconds); } catch (PgpKeyNotFoundException e) { Log.e(Constants.TAG, "adding of a passphrase failed", e); } -- cgit v1.2.3 From 6a7652c0d76f1ac9f3ba989ac2387f14e8853014 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 1 Feb 2016 17:32:17 +0100 Subject: performance: fix unit tests --- .../operations/PromoteKeyOperationTest.java | 6 +-- .../keychain/pgp/PgpKeyOperationTest.java | 2 +- .../keychain/provider/InteropTest.java | 58 ++++++++++++++++------ .../keychain/provider/ProviderHelperSaveTest.java | 8 +-- 4 files changed, 51 insertions(+), 23 deletions(-) (limited to 'OpenKeychain') diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperationTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperationTest.java index 4c46f69cf..a2f28a94f 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperationTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperationTest.java @@ -145,7 +145,7 @@ public class PromoteKeyOperationTest { for (CanonicalizedSecretKey key : ring.secretKeyIterator()) { Assert.assertEquals("all subkeys must be divert-to-card", - SecretKeyType.DIVERT_TO_CARD, key.getSecretKeyType()); + SecretKeyType.DIVERT_TO_CARD, key.getSecretKeyTypeSuperExpensive()); Assert.assertArrayEquals("all subkeys must have correct iv", aid, key.getIv()); } @@ -176,12 +176,12 @@ public class PromoteKeyOperationTest { for (CanonicalizedSecretKey key : ring.secretKeyIterator()) { if (key.getKeyId() == keyId) { Assert.assertEquals("subkey must be divert-to-card", - SecretKeyType.DIVERT_TO_CARD, key.getSecretKeyType()); + SecretKeyType.DIVERT_TO_CARD, key.getSecretKeyTypeSuperExpensive()); Assert.assertArrayEquals("subkey must have correct iv", aid, key.getIv()); } else { Assert.assertEquals("some subkeys must be gnu dummy", - SecretKeyType.GNU_DUMMY, key.getSecretKeyType()); + SecretKeyType.GNU_DUMMY, key.getSecretKeyTypeSuperExpensive()); } } diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java index 082a4923e..391588fac 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java @@ -1254,7 +1254,7 @@ public class PgpKeyOperationTest { modified.getEncoded(), false, 0); Assert.assertEquals("secret key type should be 'pin' after this", SecretKeyType.PIN, - secretRing.getSecretKey().getSecretKeyType()); + secretRing.getSecretKey().getSecretKeyTypeSuperExpensive()); // need to sleep for a sec, so the timestamp changes for notation data Thread.sleep(1000); diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java index b48c5ac4e..91d819400 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java @@ -30,6 +30,7 @@ import org.openintents.openpgp.OpenPgpSignatureResult; import org.robolectric.RobolectricGradleTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowLog; import org.spongycastle.jce.provider.BouncyCastleProvider; import org.sufficientlysecure.keychain.WorkaroundBuildConfig; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; @@ -37,10 +38,12 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.Operat import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; +import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; @@ -64,6 +67,7 @@ public class InteropTest { @BeforeClass public static void setUpOnce() throws Exception { Security.insertProviderAt(new BouncyCastleProvider(), 1); + ShadowLog.stream = System.out; } @Test @@ -243,21 +247,45 @@ public class InteropTest { KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(verify.getMasterKeyId()) : null; ProviderHelper helper = new ProviderHelper(RuntimeEnvironment.application) { - @Override - public CanonicalizedPublicKeyRing getCanonicalizedPublicKeyRing(Uri q) - throws NotFoundException { - Assert.assertEquals(msg + ": query should be for verification key", - q, verifyUri); - return verify; - } - @Override - public CanonicalizedSecretKeyRing getCanonicalizedSecretKeyRing(Uri q) - throws NotFoundException { - Assert.assertEquals(msg + ": query should be for the decryption key", - q, decryptUri); - return decrypt; - } - }; + + @Override + public CachedPublicKeyRing getCachedPublicKeyRing(Uri queryUri) throws PgpKeyNotFoundException { + Assert.assertEquals(msg + ": query should be for the decryption key", queryUri, decryptUri); + return new CachedPublicKeyRing(this, queryUri) { + @Override + public long getMasterKeyId() throws PgpKeyNotFoundException { + return decrypt.getMasterKeyId(); + } + + @Override + public SecretKeyType getSecretKeyType(long keyId) throws NotFoundException { + return decrypt.getSecretKey(keyId).getSecretKeyTypeSuperExpensive(); + } + }; + } + + @Override + public CanonicalizedPublicKeyRing getCanonicalizedPublicKeyRing(Uri q) + throws NotFoundException { + Assert.assertEquals(msg + ": query should be for verification key", q, verifyUri); + return verify; + } + + @Override + public CanonicalizedSecretKeyRing getCanonicalizedSecretKeyRing(Uri q) + throws NotFoundException { + Assert.assertEquals(msg + ": query should be for the decryption key", q, decryptUri); + return decrypt; + } + + @Override + public CanonicalizedSecretKeyRing getCanonicalizedSecretKeyRing(long masterKeyId) + throws NotFoundException { + Assert.assertEquals(msg + ": query should be for the decryption key", + masterKeyId, decrypt.getMasterKeyId()); + return decrypt; + } + }; return new PgpDecryptVerifyOperation(RuntimeEnvironment.application, helper, null) { @Override diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/ProviderHelperSaveTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/ProviderHelperSaveTest.java index 60cbe098c..b607f8255 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/ProviderHelperSaveTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/ProviderHelperSaveTest.java @@ -153,7 +153,7 @@ public class ProviderHelperSaveTest { Assert.assertEquals("first subkey should be of type sign+certify", KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA, (int) key.getKeyUsage()); Assert.assertEquals("first subkey should be divert-to-card", - SecretKeyType.DIVERT_TO_CARD, key.getSecretKeyType()); + SecretKeyType.DIVERT_TO_CARD, key.getSecretKeyTypeSuperExpensive()); Assert.assertTrue("canCertify() should be true", key.canCertify()); Assert.assertTrue("canSign() should be true", key.canSign()); @@ -168,7 +168,7 @@ public class ProviderHelperSaveTest { Assert.assertEquals("second subkey should be of type authenticate", KeyFlags.AUTHENTICATION, (int) key.getKeyUsage()); Assert.assertEquals("second subkey should be divert-to-card", - SecretKeyType.DIVERT_TO_CARD, key.getSecretKeyType()); + SecretKeyType.DIVERT_TO_CARD, key.getSecretKeyTypeSuperExpensive()); Assert.assertTrue("canAuthenticate() should be true", key.canAuthenticate()); // cached @@ -182,7 +182,7 @@ public class ProviderHelperSaveTest { Assert.assertEquals("first subkey should be of type encrypt (both types)", KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, (int) key.getKeyUsage()); Assert.assertEquals("third subkey should be divert-to-card", - SecretKeyType.DIVERT_TO_CARD, key.getSecretKeyType()); + SecretKeyType.DIVERT_TO_CARD, key.getSecretKeyTypeSuperExpensive()); Assert.assertTrue("canEncrypt() should be true", key.canEncrypt()); // cached @@ -237,7 +237,7 @@ public class ProviderHelperSaveTest { Assert.assertTrue("master key should have sign flag", ring.getPublicKey().canSign()); Assert.assertTrue("master key should have encrypt flag", ring.getPublicKey().canEncrypt()); - signId = ring.getSecretSignId(); + signId = mProviderHelper.getCachedPublicKeyRing(masterKeyId).getSecretSignId(); Assert.assertNotEquals("encrypt id should not be 0", 0, signId); Assert.assertNotEquals("encrypt key should be different from master key", masterKeyId, signId); } -- cgit v1.2.3 From 3bf6a00250684a48db00d2437615d014bbbca5b4 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 5 Feb 2016 14:58:43 +0100 Subject: performance: use more canonical hashCode implementation --- .../java/org/sufficientlysecure/keychain/pgp/ComparableS2K.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'OpenKeychain') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/ComparableS2K.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/ComparableS2K.java index b10f77739..5c92008e5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/ComparableS2K.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/ComparableS2K.java @@ -39,10 +39,10 @@ public class ComparableS2K implements Parcelable { public int hashCode() { if (cachedHashCode == null) { cachedHashCode = encryptionAlgorithm; - cachedHashCode *= 31 * s2kType; - cachedHashCode *= 31 * s2kHashAlgo; - cachedHashCode *= (int) (31 * s2kItCount); - cachedHashCode *= 31 * Arrays.hashCode(s2kIV); + cachedHashCode = 31 * cachedHashCode + s2kType; + cachedHashCode = 31 * cachedHashCode + s2kHashAlgo; + cachedHashCode = 31 * cachedHashCode + (int) (s2kItCount ^ (s2kItCount >>> 32)); + cachedHashCode = 31 * cachedHashCode + Arrays.hashCode(s2kIV); } return cachedHashCode; -- cgit v1.2.3 From 01b165ea88a032f31b8c2ff07351d3f893f6413d Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 10 Feb 2016 17:08:00 +0100 Subject: performance: add license headers and some documentation --- .../SessionKeySecretKeyDecryptorBuilder.java | 9 +++++ .../keychain/pgp/CanonicalizedSecretKey.java | 14 +++++--- .../keychain/pgp/ComparableS2K.java | 40 +++++++++++++++++++--- .../keychain/pgp/PgpDecryptVerifyOperation.java | 1 + .../keychain/util/Passphrase.java | 22 +++++++++--- 5 files changed, 72 insertions(+), 14 deletions(-) (limited to 'OpenKeychain') diff --git a/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/SessionKeySecretKeyDecryptorBuilder.java b/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/SessionKeySecretKeyDecryptorBuilder.java index 49282230f..36fe06dfa 100644 --- a/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/SessionKeySecretKeyDecryptorBuilder.java +++ b/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/SessionKeySecretKeyDecryptorBuilder.java @@ -1,3 +1,9 @@ +/** + * Copyright (c) 2016 Vincent Breitmoser + * + * Licensed under the Bouncy Castle License (MIT license). See LICENSE file for details. + */ + package org.spongycastle.openpgp.operator.jcajce; @@ -18,6 +24,9 @@ import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider; +/** This is a builder for a special PBESecretKeyDecryptor which is parametrized by a + * fixed session key, which is used in place of the one obtained from a passphrase. + */ public class SessionKeySecretKeyDecryptorBuilder { private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java index 95a0d41cc..012a7e4e6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java @@ -38,6 +38,7 @@ import org.spongycastle.openpgp.operator.jcajce.SessionKeySecretKeyDecryptorBuil import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; +import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Passphrase; @@ -120,8 +121,13 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { } - // This method can potentially take a LONG time (i.e. seconds), so it should only - // ever be called by ProviderHelper to be cached in the database. + /** This method returns the SecretKeyType for this secret key, testing for an empty + * passphrase in the process. + * + * This method can potentially take a LONG time (i.e. seconds), so it should only + * ever be called by {@link ProviderHelper} for the purpose of caching its output + * in the database. + */ public SecretKeyType getSecretKeyTypeSuperExpensive() { S2K s2k = mSecretKey.getS2K(); if (s2k != null && s2k.getType() == S2K.GNU_DUMMY_S2K) { @@ -175,13 +181,13 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { } byte[] sessionKey; - sessionKey = passphrase.getCachedSessionKeyForAlgorithm(keyEncryptionAlgorithm, s2k); + sessionKey = passphrase.getCachedSessionKeyForParameters(keyEncryptionAlgorithm, s2k); if (sessionKey == null) { PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.getCharArray()); // this operation is EXPENSIVE, so we cache its result in the passed Passphrase object! sessionKey = keyDecryptor.makeKeyFromPassPhrase(keyEncryptionAlgorithm, s2k); - passphrase.addCachedSessionKey(keyEncryptionAlgorithm, s2k, sessionKey); + passphrase.addCachedSessionKeyForParameters(keyEncryptionAlgorithm, s2k, sessionKey); } PBESecretKeyDecryptor keyDecryptor = new SessionKeySecretKeyDecryptorBuilder() diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/ComparableS2K.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/ComparableS2K.java index 5c92008e5..31faa233c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/ComparableS2K.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/ComparableS2K.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2016 Vincent Breitmoser + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.sufficientlysecure.keychain.pgp; @@ -9,13 +26,26 @@ import android.os.Parcelable; import org.spongycastle.bcpg.S2K; +/** This is an immutable and parcelable class which stores the full s2k parametrization + * of an encrypted secret key, i.e. all fields of the {@link S2K} class (type, hash algo, + * iteration count, iv) plus the encryptionAlgorithm. This class is intended to be used + * as key in a HashMap for session key caching purposes, and overrides the + * {@link #hashCode} and {@link #equals} methods in a suitable way. + * + * Note that although it is a rather unlikely scenario that secret keys of the same key + * are encrypted with different ciphers, the encryption algorithm still determines the + * length of the specific session key and thus needs to be considered for purposes of + * session key caching. + * + * @see org.spongycastle.bcpg.S2K + */ public class ComparableS2K implements Parcelable { - int encryptionAlgorithm; - int s2kType; - int s2kHashAlgo; - long s2kItCount; - byte[] s2kIV; + private final int encryptionAlgorithm; + private final int s2kType; + private final int s2kHashAlgo; + private final long s2kItCount; + private final byte[] s2kIV; Integer cachedHashCode; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java index 3fc020aa7..f7a69612a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java @@ -1,6 +1,7 @@ /* * Copyright (C) 2012-2014 Dominik Schürmann * Copyright (C) 2010-2014 Thialfihar + * Copyright (C) 2015-2016 Vincent Breitmoser * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Passphrase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Passphrase.java index bb54f8024..d47aefdfd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Passphrase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Passphrase.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2015 Dominik Schürmann + * Copyright (C) 2016 Vincent Breitmoser * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -32,8 +33,13 @@ import java.util.Map.Entry; /** - * Passwords should not be stored as Strings in memory. - * This class wraps a char[] that can be erased after it is no longer used. + * This class wraps a char[] array that is overwritten before the object is freed, to avoid + * keeping passphrases in memory as much as possible. + * + * In addition to the raw passphrases, this class can cache the session key output of an applied + * S2K algorithm for a given set of S2K parameters. Since S2K operations are very expensive, this + * mechanism should be used to cache session keys whenever possible. + * * See also: *

* http://docs.oracle.com/javase/6/docs/technotes/guides/security/crypto/CryptoSpec.html#PBEEx @@ -43,7 +49,7 @@ import java.util.Map.Entry; */ public class Passphrase implements Parcelable { private char[] mPassphrase; - HashMap mCachedSessionKeys; + private HashMap mCachedSessionKeys; /** * According to http://stackoverflow.com/a/15844273 EditText is not using String internally @@ -93,14 +99,20 @@ public class Passphrase implements Parcelable { return mPassphrase.length; } - public byte[] getCachedSessionKeyForAlgorithm(int keyEncryptionAlgorithm, S2K s2k) { + /** @return A cached session key, or null if none exists for the given parameters. */ + public byte[] getCachedSessionKeyForParameters(int keyEncryptionAlgorithm, S2K s2k) { if (mCachedSessionKeys == null) { return null; } return mCachedSessionKeys.get(new ComparableS2K(keyEncryptionAlgorithm, s2k)); } - public void addCachedSessionKey(int keyEncryptionAlgorithm, S2K s2k, byte[] sessionKey) { + /** Adds a session key for a set of s2k parameters to this Passphrase object's + * cache. The caller should make sure that the supplied session key is the result + * of an S2K operation applied to exactly the passphrase stored by this object + * with the given parameters. + */ + public void addCachedSessionKeyForParameters(int keyEncryptionAlgorithm, S2K s2k, byte[] sessionKey) { if (mCachedSessionKeys == null) { mCachedSessionKeys = new HashMap<>(); } -- cgit v1.2.3