Skip to content

Commit 10c71fc

Browse files
authored
Add ability to use custom IV in AES/GCM (#38)
1 parent 8b7eefe commit 10c71fc

File tree

8 files changed

+233
-51
lines changed

8 files changed

+233
-51
lines changed

cryptography-core/api/cryptography-core.api

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,37 @@ public final class dev/whyoleg/cryptography/algorithms/AES$GCM$Companion : dev/w
129129
}
130130

131131
public abstract interface class dev/whyoleg/cryptography/algorithms/AES$GCM$Key : dev/whyoleg/cryptography/algorithms/AES$Key {
132-
public abstract fun cipher-6q1zMKY (I)Ldev/whyoleg/cryptography/operations/AuthenticatedCipher;
133-
public static synthetic fun cipher-6q1zMKY$default (Ldev/whyoleg/cryptography/algorithms/AES$GCM$Key;IILjava/lang/Object;)Ldev/whyoleg/cryptography/operations/AuthenticatedCipher;
132+
public abstract fun cipher-6q1zMKY (I)Ldev/whyoleg/cryptography/algorithms/AES$IvAuthenticatedCipher;
133+
public static synthetic fun cipher-6q1zMKY$default (Ldev/whyoleg/cryptography/algorithms/AES$GCM$Key;IILjava/lang/Object;)Ldev/whyoleg/cryptography/algorithms/AES$IvAuthenticatedCipher;
134+
}
135+
136+
public abstract interface class dev/whyoleg/cryptography/algorithms/AES$IvAuthenticatedCipher : dev/whyoleg/cryptography/algorithms/AES$IvAuthenticatedDecryptor, dev/whyoleg/cryptography/algorithms/AES$IvAuthenticatedEncryptor {
137+
}
138+
139+
public abstract interface class dev/whyoleg/cryptography/algorithms/AES$IvAuthenticatedDecryptor : dev/whyoleg/cryptography/operations/AuthenticatedDecryptor {
140+
public fun decrypt (Lkotlinx/io/bytestring/ByteString;Lkotlinx/io/bytestring/ByteString;Lkotlinx/io/bytestring/ByteString;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
141+
public fun decrypt ([B[B[BLkotlin/coroutines/Continuation;)Ljava/lang/Object;
142+
public static synthetic fun decrypt$default (Ldev/whyoleg/cryptography/algorithms/AES$IvAuthenticatedDecryptor;Lkotlinx/io/bytestring/ByteString;Lkotlinx/io/bytestring/ByteString;Lkotlinx/io/bytestring/ByteString;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
143+
public static synthetic fun decrypt$default (Ldev/whyoleg/cryptography/algorithms/AES$IvAuthenticatedDecryptor;[B[B[BLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
144+
public static synthetic fun decrypt$suspendImpl (Ldev/whyoleg/cryptography/algorithms/AES$IvAuthenticatedDecryptor;Lkotlinx/io/bytestring/ByteString;Lkotlinx/io/bytestring/ByteString;Lkotlinx/io/bytestring/ByteString;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
145+
public static synthetic fun decrypt$suspendImpl (Ldev/whyoleg/cryptography/algorithms/AES$IvAuthenticatedDecryptor;[B[B[BLkotlin/coroutines/Continuation;)Ljava/lang/Object;
146+
public fun decryptBlocking (Lkotlinx/io/bytestring/ByteString;Lkotlinx/io/bytestring/ByteString;Lkotlinx/io/bytestring/ByteString;)Lkotlinx/io/bytestring/ByteString;
147+
public abstract fun decryptBlocking ([B[B[B)[B
148+
public static synthetic fun decryptBlocking$default (Ldev/whyoleg/cryptography/algorithms/AES$IvAuthenticatedDecryptor;Lkotlinx/io/bytestring/ByteString;Lkotlinx/io/bytestring/ByteString;Lkotlinx/io/bytestring/ByteString;ILjava/lang/Object;)Lkotlinx/io/bytestring/ByteString;
149+
public static synthetic fun decryptBlocking$default (Ldev/whyoleg/cryptography/algorithms/AES$IvAuthenticatedDecryptor;[B[B[BILjava/lang/Object;)[B
150+
}
151+
152+
public abstract interface class dev/whyoleg/cryptography/algorithms/AES$IvAuthenticatedEncryptor : dev/whyoleg/cryptography/operations/AuthenticatedEncryptor {
153+
public fun encrypt (Lkotlinx/io/bytestring/ByteString;Lkotlinx/io/bytestring/ByteString;Lkotlinx/io/bytestring/ByteString;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
154+
public fun encrypt ([B[B[BLkotlin/coroutines/Continuation;)Ljava/lang/Object;
155+
public static synthetic fun encrypt$default (Ldev/whyoleg/cryptography/algorithms/AES$IvAuthenticatedEncryptor;Lkotlinx/io/bytestring/ByteString;Lkotlinx/io/bytestring/ByteString;Lkotlinx/io/bytestring/ByteString;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
156+
public static synthetic fun encrypt$default (Ldev/whyoleg/cryptography/algorithms/AES$IvAuthenticatedEncryptor;[B[B[BLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
157+
public static synthetic fun encrypt$suspendImpl (Ldev/whyoleg/cryptography/algorithms/AES$IvAuthenticatedEncryptor;Lkotlinx/io/bytestring/ByteString;Lkotlinx/io/bytestring/ByteString;Lkotlinx/io/bytestring/ByteString;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
158+
public static synthetic fun encrypt$suspendImpl (Ldev/whyoleg/cryptography/algorithms/AES$IvAuthenticatedEncryptor;[B[B[BLkotlin/coroutines/Continuation;)Ljava/lang/Object;
159+
public fun encryptBlocking (Lkotlinx/io/bytestring/ByteString;Lkotlinx/io/bytestring/ByteString;Lkotlinx/io/bytestring/ByteString;)Lkotlinx/io/bytestring/ByteString;
160+
public abstract fun encryptBlocking ([B[B[B)[B
161+
public static synthetic fun encryptBlocking$default (Ldev/whyoleg/cryptography/algorithms/AES$IvAuthenticatedEncryptor;Lkotlinx/io/bytestring/ByteString;Lkotlinx/io/bytestring/ByteString;Lkotlinx/io/bytestring/ByteString;ILjava/lang/Object;)Lkotlinx/io/bytestring/ByteString;
162+
public static synthetic fun encryptBlocking$default (Ldev/whyoleg/cryptography/algorithms/AES$IvAuthenticatedEncryptor;[B[B[BILjava/lang/Object;)[B
134163
}
135164

136165
public abstract interface class dev/whyoleg/cryptography/algorithms/AES$IvCipher : dev/whyoleg/cryptography/algorithms/AES$IvDecryptor, dev/whyoleg/cryptography/algorithms/AES$IvEncryptor {

cryptography-core/api/cryptography-core.klib.api

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,28 @@ abstract interface <#A: dev.whyoleg.cryptography.algorithms/AES.Key> dev.whyoleg
5757
open fun <get-id>(): dev.whyoleg.cryptography/CryptographyAlgorithmId<dev.whyoleg.cryptography.algorithms/AES.GCM> // dev.whyoleg.cryptography.algorithms/AES.GCM.id.<get-id>|<get-id>(){}[0]
5858

5959
abstract interface Key : dev.whyoleg.cryptography.algorithms/AES.Key { // dev.whyoleg.cryptography.algorithms/AES.GCM.Key|null[0]
60-
abstract fun cipher(dev.whyoleg.cryptography/BinarySize = ...): dev.whyoleg.cryptography.operations/AuthenticatedCipher // dev.whyoleg.cryptography.algorithms/AES.GCM.Key.cipher|cipher(dev.whyoleg.cryptography.BinarySize){}[0]
60+
abstract fun cipher(dev.whyoleg.cryptography/BinarySize = ...): dev.whyoleg.cryptography.algorithms/AES.IvAuthenticatedCipher // dev.whyoleg.cryptography.algorithms/AES.GCM.Key.cipher|cipher(dev.whyoleg.cryptography.BinarySize){}[0]
6161
}
6262

6363
final object Companion : dev.whyoleg.cryptography/CryptographyAlgorithmId<dev.whyoleg.cryptography.algorithms/AES.GCM> // dev.whyoleg.cryptography.algorithms/AES.GCM.Companion|null[0]
6464
}
6565

66+
abstract interface IvAuthenticatedCipher : dev.whyoleg.cryptography.algorithms/AES.IvAuthenticatedDecryptor, dev.whyoleg.cryptography.algorithms/AES.IvAuthenticatedEncryptor // dev.whyoleg.cryptography.algorithms/AES.IvAuthenticatedCipher|null[0]
67+
68+
abstract interface IvAuthenticatedDecryptor : dev.whyoleg.cryptography.operations/AuthenticatedDecryptor { // dev.whyoleg.cryptography.algorithms/AES.IvAuthenticatedDecryptor|null[0]
69+
abstract fun decryptBlocking(kotlin/ByteArray, kotlin/ByteArray, kotlin/ByteArray? = ...): kotlin/ByteArray // dev.whyoleg.cryptography.algorithms/AES.IvAuthenticatedDecryptor.decryptBlocking|decryptBlocking(kotlin.ByteArray;kotlin.ByteArray;kotlin.ByteArray?){}[0]
70+
open fun decryptBlocking(kotlinx.io.bytestring/ByteString, kotlinx.io.bytestring/ByteString, kotlinx.io.bytestring/ByteString? = ...): kotlinx.io.bytestring/ByteString // dev.whyoleg.cryptography.algorithms/AES.IvAuthenticatedDecryptor.decryptBlocking|decryptBlocking(kotlinx.io.bytestring.ByteString;kotlinx.io.bytestring.ByteString;kotlinx.io.bytestring.ByteString?){}[0]
71+
open suspend fun decrypt(kotlin/ByteArray, kotlin/ByteArray, kotlin/ByteArray? = ...): kotlin/ByteArray // dev.whyoleg.cryptography.algorithms/AES.IvAuthenticatedDecryptor.decrypt|decrypt(kotlin.ByteArray;kotlin.ByteArray;kotlin.ByteArray?){}[0]
72+
open suspend fun decrypt(kotlinx.io.bytestring/ByteString, kotlinx.io.bytestring/ByteString, kotlinx.io.bytestring/ByteString? = ...): kotlinx.io.bytestring/ByteString // dev.whyoleg.cryptography.algorithms/AES.IvAuthenticatedDecryptor.decrypt|decrypt(kotlinx.io.bytestring.ByteString;kotlinx.io.bytestring.ByteString;kotlinx.io.bytestring.ByteString?){}[0]
73+
}
74+
75+
abstract interface IvAuthenticatedEncryptor : dev.whyoleg.cryptography.operations/AuthenticatedEncryptor { // dev.whyoleg.cryptography.algorithms/AES.IvAuthenticatedEncryptor|null[0]
76+
abstract fun encryptBlocking(kotlin/ByteArray, kotlin/ByteArray, kotlin/ByteArray? = ...): kotlin/ByteArray // dev.whyoleg.cryptography.algorithms/AES.IvAuthenticatedEncryptor.encryptBlocking|encryptBlocking(kotlin.ByteArray;kotlin.ByteArray;kotlin.ByteArray?){}[0]
77+
open fun encryptBlocking(kotlinx.io.bytestring/ByteString, kotlinx.io.bytestring/ByteString, kotlinx.io.bytestring/ByteString? = ...): kotlinx.io.bytestring/ByteString // dev.whyoleg.cryptography.algorithms/AES.IvAuthenticatedEncryptor.encryptBlocking|encryptBlocking(kotlinx.io.bytestring.ByteString;kotlinx.io.bytestring.ByteString;kotlinx.io.bytestring.ByteString?){}[0]
78+
open suspend fun encrypt(kotlin/ByteArray, kotlin/ByteArray, kotlin/ByteArray? = ...): kotlin/ByteArray // dev.whyoleg.cryptography.algorithms/AES.IvAuthenticatedEncryptor.encrypt|encrypt(kotlin.ByteArray;kotlin.ByteArray;kotlin.ByteArray?){}[0]
79+
open suspend fun encrypt(kotlinx.io.bytestring/ByteString, kotlinx.io.bytestring/ByteString, kotlinx.io.bytestring/ByteString? = ...): kotlinx.io.bytestring/ByteString // dev.whyoleg.cryptography.algorithms/AES.IvAuthenticatedEncryptor.encrypt|encrypt(kotlinx.io.bytestring.ByteString;kotlinx.io.bytestring.ByteString;kotlinx.io.bytestring.ByteString?){}[0]
80+
}
81+
6682
abstract interface IvCipher : dev.whyoleg.cryptography.algorithms/AES.IvDecryptor, dev.whyoleg.cryptography.algorithms/AES.IvEncryptor // dev.whyoleg.cryptography.algorithms/AES.IvCipher|null[0]
6783

6884
abstract interface IvDecryptor : dev.whyoleg.cryptography.operations/Decryptor { // dev.whyoleg.cryptography.algorithms/AES.IvDecryptor|null[0]

cryptography-core/src/commonMain/kotlin/algorithms/AES.kt

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public interface AES<K : AES.Key> : CryptographyAlgorithm {
7979

8080
@SubclassOptInRequired(CryptographyProviderApi::class)
8181
public interface Key : AES.Key {
82-
public fun cipher(tagSize: BinarySize = 128.bits): AuthenticatedCipher
82+
public fun cipher(tagSize: BinarySize = 128.bits): IvAuthenticatedCipher
8383
}
8484
}
8585

@@ -119,4 +119,41 @@ public interface AES<K : AES.Key> : CryptographyAlgorithm {
119119
public fun decryptBlocking(iv: ByteString, ciphertext: ByteString): ByteString =
120120
decryptBlocking(iv.asByteArray(), ciphertext.asByteArray()).asByteString()
121121
}
122+
123+
@SubclassOptInRequired(CryptographyProviderApi::class)
124+
public interface IvAuthenticatedCipher : IvAuthenticatedEncryptor, IvAuthenticatedDecryptor
125+
126+
@SubclassOptInRequired(CryptographyProviderApi::class)
127+
public interface IvAuthenticatedEncryptor : AuthenticatedEncryptor {
128+
@DelicateCryptographyApi
129+
public suspend fun encrypt(iv: ByteArray, plaintext: ByteArray, associatedData: ByteArray? = null): ByteArray = encryptBlocking(iv, plaintext, associatedData)
130+
131+
@DelicateCryptographyApi
132+
public fun encryptBlocking(iv: ByteArray, plaintext: ByteArray, associatedData: ByteArray? = null): ByteArray
133+
134+
@DelicateCryptographyApi
135+
public suspend fun encrypt(iv: ByteString, plaintext: ByteString, associatedData: ByteString? = null): ByteString =
136+
encrypt(iv.asByteArray(), plaintext.asByteArray(), associatedData?.toByteArray()).asByteString()
137+
138+
@DelicateCryptographyApi
139+
public fun encryptBlocking(iv: ByteString, plaintext: ByteString, associatedData: ByteString? = null): ByteString =
140+
encryptBlocking(iv.asByteArray(), plaintext.asByteArray(), associatedData?.toByteArray()).asByteString()
141+
}
142+
143+
@SubclassOptInRequired(CryptographyProviderApi::class)
144+
public interface IvAuthenticatedDecryptor : AuthenticatedDecryptor {
145+
@DelicateCryptographyApi
146+
public suspend fun decrypt(iv: ByteArray, ciphertext: ByteArray, associatedData: ByteArray? = null): ByteArray = decryptBlocking(iv, ciphertext, associatedData)
147+
148+
@DelicateCryptographyApi
149+
public fun decryptBlocking(iv: ByteArray, ciphertext: ByteArray, associatedData: ByteArray? = null): ByteArray
150+
151+
@DelicateCryptographyApi
152+
public suspend fun decrypt(iv: ByteString, ciphertext: ByteString, associatedData: ByteString? = null): ByteString =
153+
decrypt(iv.asByteArray(), ciphertext.asByteArray(), associatedData?.toByteArray()).asByteString()
154+
155+
@DelicateCryptographyApi
156+
public fun decryptBlocking(iv: ByteString, ciphertext: ByteString, associatedData: ByteString? = null): ByteString =
157+
decryptBlocking(iv.asByteArray(), ciphertext.asByteArray(), associatedData?.toByteArray()).asByteString()
158+
}
122159
}

cryptography-providers-tests/src/commonMain/kotlin/compatibility/AesCbcCompatibilityTest.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,7 @@ abstract class AesCbcCompatibilityTest(provider: CryptographyProvider) :
2424
private data class CipherParameters(
2525
val padding: Boolean,
2626
val iv: ByteStringAsString?,
27-
) : TestParameters {
28-
override fun toString(): String = "CipherParameters(padding=${padding}, iv.size=${iv?.size})"
29-
}
27+
) : TestParameters
3028

3129
override suspend fun CompatibilityTestScope<AES.CBC>.generate(isStressTest: Boolean) {
3230
val cipherIterations = when {

cryptography-providers-tests/src/commonMain/kotlin/compatibility/AesGcmCompatibilityTest.kt

Lines changed: 62 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ abstract class AesGcmCompatibilityTest(provider: CryptographyProvider) :
2020
AesBasedCompatibilityTest<AES.GCM.Key, AES.GCM>(AES.GCM, provider) {
2121

2222
@Serializable
23-
private data class CipherParameters(val tagSizeBits: Int) : TestParameters
23+
private data class CipherParameters(
24+
val tagSizeBits: Int,
25+
val iv: ByteStringAsString?,
26+
) : TestParameters
2427

2528
override suspend fun CompatibilityTestScope<AES.GCM>.generate(isStressTest: Boolean) {
2629
val associatedDataIterations = when {
@@ -31,17 +34,28 @@ abstract class AesGcmCompatibilityTest(provider: CryptographyProvider) :
3134
isStressTest -> 10
3235
else -> 5
3336
}
37+
val ivIterations = when {
38+
isStressTest -> 10
39+
else -> 5
40+
}
3441

42+
val tagSizes = listOf(96, 128)
3543

36-
val tagSizes = listOf(96, 128).map { tagSizeBits ->
37-
val id = api.ciphers.saveParameters(CipherParameters(tagSizeBits))
38-
id to tagSizeBits.bits
44+
val parametersList = buildList {
45+
tagSizes.forEach { tagSize ->
46+
// size of IV = 12
47+
(List(ivIterations) { ByteString(CryptographyRandom.nextBytes(12)) } + listOf(null)).forEach { iv ->
48+
val parameters = CipherParameters(tagSize, iv)
49+
val id = api.ciphers.saveParameters(parameters)
50+
add(id to parameters)
51+
}
52+
}
3953
}
4054

4155
generateKeys(isStressTest) { key, keyReference, _ ->
42-
tagSizes.forEach { (cipherParametersId, tagSize) ->
43-
logger.log { "tagSize = $tagSize" }
44-
val cipher = key.cipher(tagSize)
56+
parametersList.forEach { (cipherParametersId, parameters) ->
57+
logger.log { "parameters = $parameters" }
58+
val cipher = key.cipher(parameters.tagSizeBits.bits)
4559
repeat(associatedDataIterations) { adIndex ->
4660
val associatedDataSize = if (adIndex == 0) null else CryptographyRandom.nextInt(maxAssociatedDataSize)
4761
logger.log { "associatedData.size = $associatedDataSize" }
@@ -50,10 +64,21 @@ abstract class AesGcmCompatibilityTest(provider: CryptographyProvider) :
5064
val plaintextSize = CryptographyRandom.nextInt(maxPlaintextSize)
5165
logger.log { "plaintext.size = $plaintextSize" }
5266
val plaintext = ByteString(CryptographyRandom.nextBytes(plaintextSize))
53-
val ciphertext = cipher.encrypt(plaintext, associatedData)
54-
logger.log { "ciphertext.size = ${ciphertext.size}" }
5567

56-
assertContentEquals(plaintext, cipher.decrypt(ciphertext, associatedData), "Initial Decrypt")
68+
val ciphertext = when (val iv = parameters.iv) {
69+
null -> {
70+
val ciphertext = cipher.encrypt(plaintext, associatedData)
71+
logger.log { "ciphertext.size = ${ciphertext.size}" }
72+
assertContentEquals(plaintext, cipher.decrypt(ciphertext, associatedData), "Initial Decrypt")
73+
ciphertext
74+
}
75+
else -> {
76+
val ciphertext = cipher.resetIv(context).encrypt(iv, plaintext, associatedData)
77+
logger.log { "ciphertext.size = ${ciphertext.size}" }
78+
assertContentEquals(plaintext, cipher.decrypt(iv, ciphertext, associatedData), "Initial Decrypt")
79+
ciphertext
80+
}
81+
}
5782

5883
api.ciphers.saveData(
5984
cipherParametersId,
@@ -68,18 +93,38 @@ abstract class AesGcmCompatibilityTest(provider: CryptographyProvider) :
6893
override suspend fun CompatibilityTestScope<AES.GCM>.validate() {
6994
val keys = validateKeys()
7095

71-
api.ciphers.getParameters<CipherParameters> { (tagSize), parametersId, _ ->
96+
api.ciphers.getParameters<CipherParameters> { (tagSize, iv), parametersId, _ ->
7297
api.ciphers.getData<AuthenticatedCipherData>(parametersId) { (keyReference, associatedData, plaintext, ciphertext), _, _ ->
7398
keys[keyReference]?.forEach { key ->
7499
val cipher = key.cipher(tagSize.bits)
75-
assertContentEquals(plaintext, cipher.decrypt(ciphertext, associatedData), "Decrypt")
76-
assertContentEquals(
77-
plaintext,
78-
cipher.decrypt(cipher.encrypt(plaintext, associatedData), associatedData),
79-
"Encrypt-Decrypt"
80-
)
100+
101+
when (iv) {
102+
null -> {
103+
assertContentEquals(plaintext, cipher.decrypt(ciphertext, associatedData), "Decrypt")
104+
assertContentEquals(
105+
plaintext,
106+
cipher.decrypt(cipher.encrypt(plaintext, associatedData), associatedData),
107+
"Encrypt-Decrypt"
108+
)
109+
}
110+
else -> {
111+
assertContentEquals(plaintext, cipher.decrypt(iv, ciphertext, associatedData), "Decrypt")
112+
assertContentEquals(
113+
plaintext,
114+
cipher.decrypt(iv, cipher.resetIv(context).encrypt(iv, plaintext, associatedData), associatedData),
115+
"Encrypt-Decrypt"
116+
)
117+
}
118+
}
81119
}
82120
}
83121
}
84122
}
85123
}
124+
125+
// GCM mode on JDK has a check which tries to prevent reuse of the same IV with the same key.
126+
// we need to set random IV first to be able to reuse IV for different plaintext for the same key
127+
private suspend fun AES.IvAuthenticatedCipher.resetIv(context: TestContext): AES.IvAuthenticatedCipher {
128+
if (context.provider.isJdk) encrypt(ByteString())
129+
return this
130+
}

0 commit comments

Comments
 (0)