Skip to content

Commit ba5d2f1

Browse files
committed
fix(crypto): Serialize key usage during encrypt/decrypt to prevent AEADBadTagException
1 parent 388dcdb commit ba5d2f1

File tree

2 files changed

+49
-25
lines changed

2 files changed

+49
-25
lines changed

safebox/src/androidTest/java/com/harrytmthy/safebox/SafeBoxTest.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,26 @@ class SafeBoxTest {
223223
assertEquals(-1, safeBox.getInt("0", -1))
224224
}
225225

226+
@Test
227+
fun apply_then_commit_whenRepeatedManyTimes_shouldReturnCorrectValues() {
228+
safeBox = createSafeBox(ioDispatcher = Dispatchers.IO)
229+
230+
safeBox.edit().apply {
231+
repeat(100) {
232+
putInt(it.toString(), it)
233+
if (it % 2 == 0) {
234+
apply()
235+
} else {
236+
commit()
237+
}
238+
}
239+
}
240+
241+
repeat(100) {
242+
assertEquals(it, safeBox.getInt(it.toString(), -1))
243+
}
244+
}
245+
226246
private fun createSafeBox(
227247
ioDispatcher: CoroutineDispatcher = UnconfinedTestDispatcher(),
228248
stateListener: SafeBoxStateListener? = null,

safebox/src/main/java/com/harrytmthy/safebox/cryptography/ChaCha20CipherProvider.kt

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -40,34 +40,38 @@ internal class ChaCha20CipherProvider(
4040

4141
private val cipherPool = SingletonCipherPoolProvider.getChaCha20CipherPool()
4242

43-
override fun encrypt(plaintext: ByteArray): ByteArray {
44-
val iv = if (deterministic) {
45-
MessageDigest.getInstance(DIGEST_SHA256).digest(plaintext).copyOf(IV_SIZE)
46-
} else {
47-
SecureRandomProvider.generate(IV_SIZE)
48-
}
49-
val paramSpec = AEADParameterSpec(iv, MAC_SIZE_BITS)
50-
val key = keyProvider.getOrCreateKey()
51-
val encrypted = cipherPool.withCipher { cipher ->
52-
cipher.init(Cipher.ENCRYPT_MODE, key, paramSpec)
53-
cipher.doFinal(plaintext)
43+
private val cipherLock = Any()
44+
45+
override fun encrypt(plaintext: ByteArray): ByteArray =
46+
synchronized(cipherLock) {
47+
val iv = if (deterministic) {
48+
MessageDigest.getInstance(DIGEST_SHA256).digest(plaintext).copyOf(IV_SIZE)
49+
} else {
50+
SecureRandomProvider.generate(IV_SIZE)
51+
}
52+
val paramSpec = AEADParameterSpec(iv, MAC_SIZE_BITS)
53+
val key = keyProvider.getOrCreateKey()
54+
val encrypted = cipherPool.withCipher { cipher ->
55+
cipher.init(Cipher.ENCRYPT_MODE, key, paramSpec)
56+
cipher.doFinal(plaintext)
57+
}
58+
(key as? SafeSecretKey)?.releaseHeapCopy()
59+
iv + encrypted
5460
}
55-
(key as? SafeSecretKey)?.releaseHeapCopy()
56-
return iv + encrypted
57-
}
5861

59-
override fun decrypt(ciphertext: ByteArray): ByteArray {
60-
val iv = ciphertext.copyOfRange(0, IV_SIZE)
61-
val actual = ciphertext.copyOfRange(IV_SIZE, ciphertext.size)
62-
val paramSpec = AEADParameterSpec(iv, MAC_SIZE_BITS)
63-
val key = keyProvider.getOrCreateKey()
64-
val plaintext = cipherPool.withCipher { cipher ->
65-
cipher.init(Cipher.DECRYPT_MODE, key, paramSpec)
66-
cipher.doFinal(actual)
62+
override fun decrypt(ciphertext: ByteArray): ByteArray =
63+
synchronized(cipherLock) {
64+
val iv = ciphertext.copyOfRange(0, IV_SIZE)
65+
val actual = ciphertext.copyOfRange(IV_SIZE, ciphertext.size)
66+
val paramSpec = AEADParameterSpec(iv, MAC_SIZE_BITS)
67+
val key = keyProvider.getOrCreateKey()
68+
val plaintext = cipherPool.withCipher { cipher ->
69+
cipher.init(Cipher.DECRYPT_MODE, key, paramSpec)
70+
cipher.doFinal(actual)
71+
}
72+
(key as? SafeSecretKey)?.releaseHeapCopy()
73+
plaintext
6774
}
68-
(key as? SafeSecretKey)?.releaseHeapCopy()
69-
return plaintext
70-
}
7175

7276
override fun destroyKey() {
7377
keyProvider.destroyKey()

0 commit comments

Comments
 (0)