Skip to content

Commit 39ae46f

Browse files
BowlerrDorianMazur
andauthored
refactor: Improve Cipher Management with Thread-Safe Caching (#738)
* refactor: Improve Cipher Management with Thread-Safe Caching * fix: remove not needed code * refactor: cleaned up CipherCache and added comments - Added an optional "prefix" parameter to support multiple cache keys per transformation. - Replaced manual cache initialization with getOrPut on a synchronized ThreadLocal map. - Enhanced code readability with detailed KDoc comments. --------- Co-authored-by: MazurDorian <[email protected]>
1 parent f484248 commit 39ae46f

File tree

4 files changed

+53
-31
lines changed

4 files changed

+53
-31
lines changed

android/src/main/java/com/oblador/keychain/KeychainModule.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule
1313
import com.facebook.react.bridge.ReactMethod
1414
import com.facebook.react.bridge.ReadableMap
1515
import com.facebook.react.module.annotations.ReactModule
16+
import com.oblador.keychain.cipherStorage.CipherCache
1617
import com.oblador.keychain.cipherStorage.CipherStorage
1718
import com.oblador.keychain.cipherStorage.CipherStorage.DecryptionResult
1819
import com.oblador.keychain.cipherStorage.CipherStorageBase
@@ -156,6 +157,8 @@ class KeychainModule(reactContext: ReactApplicationContext) :
156157
if (coroutineScope.isActive) {
157158
coroutineScope.cancel("$KEYCHAIN_MODULE has been destroyed.")
158159
}
160+
// Clean up cipher cache
161+
CipherCache.clearCache()
159162
}
160163

161164
/** {@inheritDoc} */
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package com.oblador.keychain.cipherStorage
2+
3+
import android.util.Log
4+
import java.security.NoSuchAlgorithmException
5+
import javax.crypto.Cipher
6+
import javax.crypto.NoSuchPaddingException
7+
8+
/**
9+
* Thread-safe cache for Cipher instances to improve performance by reusing existing instances.
10+
* Uses ThreadLocal storage to maintain separate caches for different threads.
11+
*/
12+
object CipherCache {
13+
private val LOG_TAG = CipherCache::class.java.simpleName
14+
15+
private val cipherCache = ThreadLocal<MutableMap<String, Cipher>>()
16+
17+
/**
18+
* Gets or creates a Cipher instance for the specified transformation.
19+
* This method is thread-safe and caches Cipher instances per thread.
20+
*
21+
* @param transformation The name of the transformation, e.g., "AES/CBC/PKCS7Padding"
22+
* @param prefix An optional identifier added to the cache key to distinguish between different uses of the same transformation.
23+
* The prefix only affects how the cipher is stored in the cache and does not modify the cipher's behavior.
24+
* @return A Cipher instance for the requested transformation
25+
* @throws NoSuchAlgorithmException if the transformation algorithm is not available
26+
* @throws NoSuchPaddingException if the padding scheme is not available
27+
*/
28+
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class)
29+
fun getCipher(transformation: String, prefix: String? = null): Cipher {
30+
return synchronized(this) {
31+
val cacheKey = prefix?.let { "${it}_$transformation" } ?: transformation
32+
(cipherCache.get() ?: mutableMapOf<String, Cipher>().also { cipherCache.set(it) })
33+
.getOrPut(cacheKey) { Cipher.getInstance(transformation) }
34+
}
35+
}
36+
37+
/**
38+
* Clears the cipher cache for the current thread.
39+
* This should be called when the ciphers are no longer needed to free up resources.
40+
*/
41+
fun clearCache() {
42+
try {
43+
cipherCache.remove()
44+
} catch (e: Exception) {
45+
Log.w(LOG_TAG, "Failed to clear cipher cache: ${e.message}")
46+
}
47+
}
48+
}

android/src/main/java/com/oblador/keychain/cipherStorage/CipherStorageBase.kt

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,6 @@ abstract class CipherStorageBase(protected val applicationContext: Context) : Ci
7979

8080
// region Members
8181

82-
/** Get cached instance of cipher. Get instance operation is slow. */
83-
@Transient
84-
protected var cachedCipher: Cipher? = null
85-
8682
/** Cached instance of the Keystore. */
8783
@Transient
8884
protected var cachedKeyStore: KeyStore? = null
@@ -164,14 +160,7 @@ abstract class CipherStorageBase(protected val applicationContext: Context) : Ci
164160
/** Get cipher instance and cache it for any next call. */
165161
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class)
166162
fun getCachedInstance(): Cipher {
167-
if (cachedCipher == null) {
168-
synchronized(this) {
169-
if (cachedCipher == null) {
170-
cachedCipher = Cipher.getInstance(getEncryptionTransformation())
171-
}
172-
}
173-
}
174-
return cachedCipher!!
163+
return CipherCache.getCipher(getEncryptionTransformation())
175164
}
176165

177166
/** Check requirements to the security level. */
@@ -466,24 +455,6 @@ abstract class CipherStorageBase(protected val applicationContext: Context) : Ci
466455

467456
// endregion
468457

469-
// region Testing
470-
471-
/** Override internal cipher instance cache. */
472-
@VisibleForTesting
473-
fun setCipher(cipher: Cipher): CipherStorageBase {
474-
cachedCipher = cipher
475-
return this
476-
}
477-
478-
/** Override the keystore instance cache. */
479-
@VisibleForTesting
480-
fun setKeyStore(keystore: KeyStore): CipherStorageBase {
481-
cachedKeyStore = keystore
482-
return this
483-
}
484-
485-
// endregion
486-
487458
// region Nested declarations
488459

489460
/** Generic cipher initialization. */

android/src/main/java/com/oblador/keychain/cipherStorage/CipherStorageKeystoreAesGcm.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ class CipherStorageKeystoreAesGcm(
6666
/** AES. */
6767
override fun getEncryptionAlgorithm(): String = ALGORITHM_AES
6868

69-
/** AES/CBC/PKCS7Padding */
69+
/** AES/GCM/NoPadding */
7070
override fun getEncryptionTransformation(): String = ENCRYPTION_TRANSFORMATION
7171

7272
// endregion

0 commit comments

Comments
 (0)