diff --git a/pgpainless-core/src/main/java/org/bouncycastle/CachingBcPublicKeyDataDecryptorFactory.java b/pgpainless-core/src/main/java/org/bouncycastle/CachingBcPublicKeyDataDecryptorFactory.java deleted file mode 100644 index 510b0938..00000000 --- a/pgpainless-core/src/main/java/org/bouncycastle/CachingBcPublicKeyDataDecryptorFactory.java +++ /dev/null @@ -1,95 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.bouncycastle; - -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPPrivateKey; -import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; -import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory; -import org.bouncycastle.util.encoders.Base64; -import org.bouncycastle.util.encoders.Hex; -import org.pgpainless.decryption_verification.CustomPublicKeyDataDecryptorFactory; -import org.pgpainless.key.SubkeyIdentifier; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.HashMap; -import java.util.Map; - -/** - * Implementation of the {@link PublicKeyDataDecryptorFactory} which caches decrypted session keys. - * That way, if a message needs to be decrypted multiple times, expensive private key operations can be omitted. - * - * This implementation changes the behavior or {@link #recoverSessionData(int, byte[][])} to first return any - * cache hits. - * If no hit is found, the method call is delegated to the underlying {@link PublicKeyDataDecryptorFactory}. - * The result of that is then placed in the cache and returned. - */ -public class CachingBcPublicKeyDataDecryptorFactory - extends BcPublicKeyDataDecryptorFactory - implements CustomPublicKeyDataDecryptorFactory { - - private static final Logger LOGGER = LoggerFactory.getLogger(CachingBcPublicKeyDataDecryptorFactory.class); - - private final Map cachedSessionKeys = new HashMap<>(); - private final SubkeyIdentifier decryptionKey; - - public CachingBcPublicKeyDataDecryptorFactory(PGPPrivateKey privateKey, SubkeyIdentifier decryptionKey) { - super(privateKey); - this.decryptionKey = decryptionKey; - } - - @Override - public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData) throws PGPException { - byte[] sessionKey = lookupSessionKeyData(secKeyData); - if (sessionKey == null) { - LOGGER.debug("Cache miss for encrypted session key " + Hex.toHexString(secKeyData[0])); - sessionKey = costlyRecoverSessionData(keyAlgorithm, secKeyData); - cacheSessionKeyData(secKeyData, sessionKey); - } else { - LOGGER.debug("Cache hit for encrypted session key " + Hex.toHexString(secKeyData[0])); - } - return sessionKey; - } - - public byte[] costlyRecoverSessionData(int keyAlgorithm, byte[][] secKeyData) throws PGPException { - return super.recoverSessionData(keyAlgorithm, secKeyData); - } - - private byte[] lookupSessionKeyData(byte[][] secKeyData) { - String key = toKey(secKeyData); - byte[] sessionKey = cachedSessionKeys.get(key); - return copy(sessionKey); - } - - private void cacheSessionKeyData(byte[][] secKeyData, byte[] sessionKey) { - String key = toKey(secKeyData); - cachedSessionKeys.put(key, copy(sessionKey)); - } - - private static String toKey(byte[][] secKeyData) { - byte[] sk = secKeyData[0]; - String key = Base64.toBase64String(sk); - return key; - } - - private static byte[] copy(byte[] bytes) { - if (bytes == null) { - return null; - } - byte[] copy = new byte[bytes.length]; - System.arraycopy(bytes, 0, copy, 0, copy.length); - return copy; - } - - public void clear() { - cachedSessionKeys.clear(); - } - - @Override - public SubkeyIdentifier getSubkeyIdentifier() { - return decryptionKey; - } -} diff --git a/pgpainless-core/src/main/java/org/bouncycastle/package-info.java b/pgpainless-core/src/main/java/org/bouncycastle/package-info.java deleted file mode 100644 index 565bb5f4..00000000 --- a/pgpainless-core/src/main/java/org/bouncycastle/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Classes which could be upstreamed to BC at some point. - */ -package org.bouncycastle; diff --git a/pgpainless-core/src/main/kotlin/org/bouncycastle/CachingBcPublicKeyDataDecryptorFactory.kt b/pgpainless-core/src/main/kotlin/org/bouncycastle/CachingBcPublicKeyDataDecryptorFactory.kt new file mode 100644 index 00000000..60b860f2 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/CachingBcPublicKeyDataDecryptorFactory.kt @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.bouncycastle + +import org.bouncycastle.openpgp.PGPPrivateKey +import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory +import org.bouncycastle.util.encoders.Base64 +import org.pgpainless.decryption_verification.CustomPublicKeyDataDecryptorFactory +import org.pgpainless.key.SubkeyIdentifier + +/** + * Implementation of the [PublicKeyDataDecryptorFactory] which caches decrypted session keys. + * That way, if a message needs to be decrypted multiple times, expensive private key operations can be omitted. + * + * This implementation changes the behavior or [recoverSessionData] to first return any + * cache hits. + * If no hit is found, the method call is delegated to the underlying [PublicKeyDataDecryptorFactory]. + * The result of that is then placed in the cache and returned. + */ +class CachingBcPublicKeyDataDecryptorFactory( + privateKey: PGPPrivateKey, + override val subkeyIdentifier: SubkeyIdentifier +) : BcPublicKeyDataDecryptorFactory(privateKey), CustomPublicKeyDataDecryptorFactory { + + private val cachedSessions: MutableMap = mutableMapOf() + + override fun recoverSessionData(keyAlgorithm: Int, secKeyData: Array): ByteArray = + lookupSessionKeyData(secKeyData) ?: + costlyRecoverSessionData(keyAlgorithm, secKeyData) + .also { cacheSessionKeyData(secKeyData, it) } + + private fun lookupSessionKeyData(secKeyData: Array): ByteArray? = + cachedSessions[toKey(secKeyData)]?.clone() + + private fun costlyRecoverSessionData(keyAlgorithm: Int, secKeyData: Array): ByteArray = + super.recoverSessionData(keyAlgorithm, secKeyData) + + private fun cacheSessionKeyData(secKeyData: Array, sessionKey: ByteArray) { + cachedSessions[toKey(secKeyData)] = sessionKey.clone() + } + + private fun toKey(secKeyData: Array): String = + Base64.toBase64String(secKeyData[0]) + + fun clear() { + cachedSessions.clear() + } +} \ No newline at end of file