Skip to content

Commit f28aca3

Browse files
committed
Support encryption/decryption streaming in JDK provider
1 parent c659d66 commit f28aca3

File tree

16 files changed

+360
-184
lines changed

16 files changed

+360
-184
lines changed

cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkAesCbc.kt

Lines changed: 11 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,13 @@ package dev.whyoleg.cryptography.providers.jdk.algorithms
77
import dev.whyoleg.cryptography.*
88
import dev.whyoleg.cryptography.algorithms.*
99
import dev.whyoleg.cryptography.materials.key.*
10-
import dev.whyoleg.cryptography.operations.signature.*
1110
import dev.whyoleg.cryptography.providers.jdk.*
1211
import dev.whyoleg.cryptography.providers.jdk.materials.*
13-
import dev.whyoleg.cryptography.providers.jdk.operations.*
14-
import javax.crypto.spec.*
1512

1613
internal class JdkAesCbc(
1714
private val state: JdkCryptographyState,
1815
) : AES.CBC {
19-
private val keyWrapper: (JSecretKey) -> AES.CBC.Key = { key ->
20-
object : AES.CBC.Key, JdkEncodableKey<AES.Key.Format>(key) {
21-
override fun cipher(padding: Boolean): AES.IvCipher = AesCbcCipher(state, key, padding)
22-
override fun encodeToByteArrayBlocking(format: AES.Key.Format): ByteArray = when (format) {
23-
AES.Key.Format.JWK -> error("$format is not supported")
24-
AES.Key.Format.RAW -> encodeToRaw()
25-
}
26-
}
27-
}
16+
private val keyWrapper: (JSecretKey) -> AES.CBC.Key = { key -> JdkAesCbcKey(state, key) }
2817
private val keyDecoder = JdkSecretKeyDecoder<AES.Key.Format, _>("AES", keyWrapper)
2918

3019
override fun keyDecoder(): KeyDecoder<AES.Key.Format, AES.CBC.Key> = keyDecoder
@@ -33,37 +22,22 @@ internal class JdkAesCbc(
3322
}
3423
}
3524

36-
private const val ivSizeBytes = 16 //bytes for CBC
37-
38-
private class AesCbcCipher(
25+
private class JdkAesCbcKey(
3926
private val state: JdkCryptographyState,
4027
private val key: JSecretKey,
41-
padding: Boolean,
42-
) : AES.IvCipher {
43-
private val cipher = state.cipher(
44-
when {
28+
) : AES.CBC.Key, JdkEncodableKey<AES.Key.Format>(key) {
29+
override fun cipher(padding: Boolean): AES.IvCipher = JdkAesIvCipher(
30+
state = state,
31+
key = key,
32+
ivSize = 16,
33+
algorithm = when {
4534
padding -> "AES/CBC/PKCS5Padding"
4635
else -> "AES/CBC/NoPadding"
4736
}
4837
)
4938

50-
override fun encryptBlocking(plaintext: ByteArray): ByteArray {
51-
val iv = ByteArray(ivSizeBytes).also(state.secureRandom::nextBytes)
52-
return iv + encryptWithIvBlocking(iv, plaintext)
53-
}
54-
55-
override fun encryptWithIvBlocking(iv: ByteArray, plaintext: ByteArray): ByteArray = cipher.use { cipher ->
56-
cipher.init(JCipher.ENCRYPT_MODE, key, IvParameterSpec(iv), state.secureRandom)
57-
cipher.doFinal(plaintext)
58-
}
59-
60-
override fun decryptBlocking(ciphertext: ByteArray): ByteArray = cipher.use { cipher ->
61-
cipher.init(JCipher.DECRYPT_MODE, key, IvParameterSpec(ciphertext, 0, ivSizeBytes), state.secureRandom)
62-
cipher.doFinal(ciphertext, ivSizeBytes, ciphertext.size - ivSizeBytes)
63-
}
64-
65-
override fun decryptWithIvBlocking(iv: ByteArray, ciphertext: ByteArray): ByteArray = cipher.use { cipher ->
66-
cipher.init(JCipher.DECRYPT_MODE, key, IvParameterSpec(iv), state.secureRandom)
67-
cipher.doFinal(ciphertext)
39+
override fun encodeToByteArrayBlocking(format: AES.Key.Format): ByteArray = when (format) {
40+
AES.Key.Format.JWK -> error("$format is not supported")
41+
AES.Key.Format.RAW -> encodeToRaw()
6842
}
6943
}

cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkAesCtr.kt

Lines changed: 14 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,7 @@ import javax.crypto.spec.*
1414
internal class JdkAesCtr(
1515
private val state: JdkCryptographyState,
1616
) : AES.CTR {
17-
private val keyWrapper: (JSecretKey) -> AES.CTR.Key = { key ->
18-
object : AES.CTR.Key, JdkEncodableKey<AES.Key.Format>(key) {
19-
override fun cipher(): AES.IvCipher = AesCtrCipher(state, key)
20-
override fun encodeToByteArrayBlocking(format: AES.Key.Format): ByteArray = when (format) {
21-
AES.Key.Format.JWK -> error("$format is not supported")
22-
AES.Key.Format.RAW -> encodeToRaw()
23-
}
24-
}
25-
}
17+
private val keyWrapper: (JSecretKey) -> AES.CTR.Key = { key -> JdkAesCtrKey(state, key) }
2618
private val keyDecoder = JdkSecretKeyDecoder<AES.Key.Format, _>("AES", keyWrapper)
2719

2820
override fun keyDecoder(): KeyDecoder<AES.Key.Format, AES.CTR.Key> = keyDecoder
@@ -31,31 +23,19 @@ internal class JdkAesCtr(
3123
}
3224
}
3325

34-
private const val ivSizeBytes = 16 //bytes for CTR
35-
36-
private class AesCtrCipher(
26+
private class JdkAesCtrKey(
3727
private val state: JdkCryptographyState,
3828
private val key: JSecretKey,
39-
) : AES.IvCipher {
40-
private val cipher = state.cipher("AES/CTR/NoPadding")
41-
42-
override fun encryptBlocking(plaintext: ByteArray): ByteArray {
43-
val iv = ByteArray(ivSizeBytes).also(state.secureRandom::nextBytes)
44-
return iv + encryptWithIvBlocking(iv, plaintext)
45-
}
46-
47-
override fun encryptWithIvBlocking(iv: ByteArray, plaintext: ByteArray): ByteArray = cipher.use { cipher ->
48-
cipher.init(JCipher.ENCRYPT_MODE, key, IvParameterSpec(iv), state.secureRandom)
49-
cipher.doFinal(plaintext)
29+
) : AES.CTR.Key, JdkEncodableKey<AES.Key.Format>(key) {
30+
override fun cipher(): AES.IvCipher = JdkAesIvCipher(
31+
state = state,
32+
key = key,
33+
ivSize = 16,
34+
algorithm = "AES/CTR/NoPadding"
35+
)
36+
37+
override fun encodeToByteArrayBlocking(format: AES.Key.Format): ByteArray = when (format) {
38+
AES.Key.Format.JWK -> error("$format is not supported")
39+
AES.Key.Format.RAW -> encodeToRaw()
5040
}
51-
52-
override fun decryptBlocking(ciphertext: ByteArray): ByteArray = cipher.use { cipher ->
53-
cipher.init(JCipher.DECRYPT_MODE, key, IvParameterSpec(ciphertext, 0, ivSizeBytes), state.secureRandom)
54-
cipher.doFinal(ciphertext, ivSizeBytes, ciphertext.size - ivSizeBytes)
55-
}
56-
57-
override fun decryptWithIvBlocking(iv: ByteArray, ciphertext: ByteArray): ByteArray = cipher.use { cipher ->
58-
cipher.init(JCipher.DECRYPT_MODE, key, IvParameterSpec(iv), state.secureRandom)
59-
cipher.doFinal(ciphertext)
60-
}
61-
}
41+
}

cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkAesEcb.kt

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,16 @@ import dev.whyoleg.cryptography.*
88
import dev.whyoleg.cryptography.algorithms.*
99
import dev.whyoleg.cryptography.materials.key.*
1010
import dev.whyoleg.cryptography.operations.*
11+
import dev.whyoleg.cryptography.providers.base.operations.*
1112
import dev.whyoleg.cryptography.providers.jdk.*
1213
import dev.whyoleg.cryptography.providers.jdk.materials.*
14+
import dev.whyoleg.cryptography.providers.jdk.operations.*
1315
import javax.crypto.spec.*
1416

1517
internal class JdkAesEcb(
1618
private val state: JdkCryptographyState,
1719
) : AES.ECB {
18-
private val keyWrapper: (JSecretKey) -> AES.ECB.Key = { key ->
19-
object : AES.ECB.Key, JdkEncodableKey<AES.Key.Format>(key) {
20-
override fun cipher(padding: Boolean): Cipher = AesEcbCipher(state, key, padding)
21-
override fun encodeToByteArrayBlocking(format: AES.Key.Format): ByteArray = when (format) {
22-
AES.Key.Format.JWK -> error("$format is not supported")
23-
AES.Key.Format.RAW -> encodeToRaw()
24-
}
25-
}
26-
}
20+
private val keyWrapper: (JSecretKey) -> AES.ECB.Key = { key -> JdkAesEcbKey(state, key) }
2721
private val keyDecoder = JdkSecretKeyDecoder<AES.Key.Format, _>("AES", keyWrapper)
2822

2923
override fun keyDecoder(): KeyDecoder<AES.Key.Format, AES.ECB.Key> = keyDecoder
@@ -32,25 +26,41 @@ internal class JdkAesEcb(
3226
}
3327
}
3428

35-
private class AesEcbCipher(
29+
private class JdkAesEcbKey(
3630
private val state: JdkCryptographyState,
3731
private val key: JSecretKey,
38-
padding: Boolean,
39-
) : Cipher {
40-
private val cipher = state.cipher(
41-
when {
32+
) : AES.ECB.Key, JdkEncodableKey<AES.Key.Format>(key) {
33+
override fun cipher(padding: Boolean): Cipher = JdkAesEcbCipher(
34+
state = state,
35+
key = key,
36+
algorithm = when {
4237
padding -> "AES/ECB/PKCS5Padding"
4338
else -> "AES/ECB/NoPadding"
4439
}
4540
)
4641

47-
override fun encryptBlocking(plaintext: ByteArray): ByteArray = cipher.use { cipher ->
48-
cipher.init(JCipher.ENCRYPT_MODE, key, state.secureRandom)
49-
cipher.doFinal(plaintext)
42+
override fun encodeToByteArrayBlocking(format: AES.Key.Format): ByteArray = when (format) {
43+
AES.Key.Format.JWK -> error("$format is not supported")
44+
AES.Key.Format.RAW -> encodeToRaw()
45+
}
46+
}
47+
48+
private class JdkAesEcbCipher(
49+
private val state: JdkCryptographyState,
50+
private val key: JSecretKey,
51+
algorithm: String,
52+
) : BaseCipher {
53+
private val cipher = state.cipher(algorithm)
54+
55+
override fun createEncryptFunction(): CipherFunction {
56+
return JdkCipherFunction(cipher.borrowResource {
57+
init(JCipher.ENCRYPT_MODE, key, state.secureRandom)
58+
})
5059
}
5160

52-
override fun decryptBlocking(ciphertext: ByteArray): ByteArray = cipher.use { cipher ->
53-
cipher.init(JCipher.DECRYPT_MODE, key, state.secureRandom)
54-
cipher.doFinal(ciphertext)
61+
override fun createDecryptFunction(): CipherFunction {
62+
return JdkCipherFunction(cipher.borrowResource {
63+
init(JCipher.DECRYPT_MODE, key, state.secureRandom)
64+
})
5565
}
5666
}

cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkAesGcm.kt

Lines changed: 40 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,18 @@ import dev.whyoleg.cryptography.*
88
import dev.whyoleg.cryptography.algorithms.*
99
import dev.whyoleg.cryptography.materials.key.*
1010
import dev.whyoleg.cryptography.operations.*
11+
import dev.whyoleg.cryptography.providers.base.algorithms.*
12+
import dev.whyoleg.cryptography.providers.base.operations.*
1113
import dev.whyoleg.cryptography.providers.jdk.*
1214
import dev.whyoleg.cryptography.providers.jdk.algorithms.*
1315
import dev.whyoleg.cryptography.providers.jdk.materials.*
16+
import dev.whyoleg.cryptography.providers.jdk.operations.*
1417
import javax.crypto.spec.*
1518

1619
internal class JdkAesGcm(
1720
private val state: JdkCryptographyState,
1821
) : AES.GCM {
19-
private val keyWrapper: (JSecretKey) -> AES.GCM.Key = { key ->
20-
object : AES.GCM.Key, JdkEncodableKey<AES.Key.Format>(key) {
21-
override fun cipher(tagSize: BinarySize): AES.IvAuthenticatedCipher = AesGcmCipher(state, key, tagSize)
22-
23-
override fun encodeToByteArrayBlocking(format: AES.Key.Format): ByteArray = when (format) {
24-
AES.Key.Format.JWK -> error("$format is not supported")
25-
AES.Key.Format.RAW -> encodeToRaw()
26-
}
27-
}
28-
}
29-
22+
private val keyWrapper: (JSecretKey) -> AES.GCM.Key = { key -> JdkAesGcmKey(state, key) }
3023
private val keyDecoder = JdkSecretKeyDecoder<AES.Key.Format, _>("AES", keyWrapper)
3124

3225
override fun keyDecoder(): KeyDecoder<AES.Key.Format, AES.GCM.Key> = keyDecoder
@@ -36,35 +29,52 @@ internal class JdkAesGcm(
3629
}
3730
}
3831

39-
private const val ivSizeBytes = 12 // bytes for GCM
32+
private class JdkAesGcmKey(
33+
private val state: JdkCryptographyState,
34+
private val key: JSecretKey,
35+
) : AES.GCM.Key, JdkEncodableKey<AES.Key.Format>(key) {
36+
override fun cipher(tagSize: BinarySize): AES.IvAuthenticatedCipher = JdkAesGcmCipher(state, key, tagSize.inBits)
37+
38+
override fun encodeToByteArrayBlocking(format: AES.Key.Format): ByteArray = when (format) {
39+
AES.Key.Format.JWK -> error("$format is not supported")
40+
AES.Key.Format.RAW -> encodeToRaw()
41+
}
42+
}
4043

41-
private class AesGcmCipher(
44+
private class JdkAesGcmCipher(
4245
private val state: JdkCryptographyState,
4346
private val key: JSecretKey,
44-
private val tagSize: BinarySize,
45-
) : AES.IvAuthenticatedCipher {
47+
private val tagSizeBits: Int,
48+
) : BaseAesIvAuthenticatedCipher {
49+
private val ivSize: Int get() = 12
4650
private val cipher = state.cipher("AES/GCM/NoPadding")
4751

48-
override fun encryptBlocking(plaintext: ByteArray, associatedData: ByteArray?): ByteArray {
49-
val iv = ByteArray(ivSizeBytes).also(state.secureRandom::nextBytes)
50-
return iv + encryptWithIvBlocking(iv, plaintext, associatedData)
52+
override fun createEncryptFunction(associatedData: ByteArray?): CipherFunction {
53+
val iv = ByteArray(ivSize).also(state.secureRandom::nextBytes)
54+
return BaseAesImplicitIvEncryptFunction(iv, createEncryptFunctionWithIv(iv, associatedData))
55+
}
56+
57+
override fun createDecryptFunction(associatedData: ByteArray?): CipherFunction {
58+
return BaseAesImplicitIvDecryptFunction(ivSize) { iv, startIndex ->
59+
createDecryptFunctionWithIv(iv, startIndex, associatedData)
60+
}
5161
}
5262

53-
override fun encryptWithIvBlocking(iv: ByteArray, plaintext: ByteArray, associatedData: ByteArray?): ByteArray = cipher.use { cipher ->
54-
cipher.init(JCipher.ENCRYPT_MODE, key, GCMParameterSpec(tagSize.inBits, iv), state.secureRandom)
55-
associatedData?.let(cipher::updateAAD)
56-
cipher.doFinal(plaintext)
63+
override fun createEncryptFunctionWithIv(iv: ByteArray, associatedData: ByteArray?): CipherFunction {
64+
return JdkCipherFunction(cipher.borrowResource {
65+
init(JCipher.ENCRYPT_MODE, key, GCMParameterSpec(tagSizeBits, iv), state.secureRandom)
66+
associatedData?.let(this::updateAAD)
67+
})
5768
}
5869

59-
override fun decryptBlocking(ciphertext: ByteArray, associatedData: ByteArray?): ByteArray = cipher.use { cipher ->
60-
cipher.init(JCipher.DECRYPT_MODE, key, GCMParameterSpec(tagSize.inBits, ciphertext, 0, ivSizeBytes), state.secureRandom)
61-
associatedData?.let(cipher::updateAAD)
62-
cipher.doFinal(ciphertext, ivSizeBytes, ciphertext.size - ivSizeBytes)
70+
private fun createDecryptFunctionWithIv(iv: ByteArray, startIndex: Int, associatedData: ByteArray?): CipherFunction {
71+
return JdkCipherFunction(cipher.borrowResource {
72+
init(JCipher.DECRYPT_MODE, key, GCMParameterSpec(tagSizeBits, iv, startIndex, ivSize), state.secureRandom)
73+
associatedData?.let(this::updateAAD)
74+
})
6375
}
6476

65-
override fun decryptWithIvBlocking(iv: ByteArray, ciphertext: ByteArray, associatedData: ByteArray?): ByteArray = cipher.use { cipher ->
66-
cipher.init(JCipher.DECRYPT_MODE, key, GCMParameterSpec(tagSize.inBits, iv), state.secureRandom)
67-
associatedData?.let(cipher::updateAAD)
68-
cipher.doFinal(ciphertext)
77+
override fun createDecryptFunctionWithIv(iv: ByteArray, associatedData: ByteArray?): CipherFunction {
78+
return createDecryptFunctionWithIv(iv, 0, associatedData)
6979
}
7080
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright (c) 2024 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package dev.whyoleg.cryptography.providers.jdk.algorithms
6+
7+
import dev.whyoleg.cryptography.providers.base.algorithms.*
8+
import dev.whyoleg.cryptography.providers.base.operations.*
9+
import dev.whyoleg.cryptography.providers.jdk.*
10+
import dev.whyoleg.cryptography.providers.jdk.operations.*
11+
import javax.crypto.spec.*
12+
13+
internal class JdkAesIvCipher(
14+
private val state: JdkCryptographyState,
15+
private val key: JSecretKey,
16+
private val ivSize: Int,
17+
algorithm: String,
18+
) : BaseAesIvCipher {
19+
private val cipher = state.cipher(algorithm)
20+
21+
override fun createEncryptFunction(): CipherFunction {
22+
val iv = ByteArray(ivSize).also(state.secureRandom::nextBytes)
23+
return BaseAesImplicitIvEncryptFunction(iv, createEncryptFunctionWithIv(iv))
24+
}
25+
26+
override fun createDecryptFunction(): CipherFunction {
27+
return BaseAesImplicitIvDecryptFunction(ivSize, ::createDecryptFunctionWithIv)
28+
}
29+
30+
override fun createEncryptFunctionWithIv(iv: ByteArray): CipherFunction {
31+
return JdkCipherFunction(cipher.borrowResource {
32+
init(JCipher.ENCRYPT_MODE, key, IvParameterSpec(iv), state.secureRandom)
33+
})
34+
}
35+
36+
private fun createDecryptFunctionWithIv(iv: ByteArray, startIndex: Int): CipherFunction {
37+
return JdkCipherFunction(cipher.borrowResource {
38+
init(JCipher.DECRYPT_MODE, key, IvParameterSpec(iv, startIndex, ivSize), state.secureRandom)
39+
})
40+
}
41+
42+
override fun createDecryptFunctionWithIv(iv: ByteArray): CipherFunction {
43+
return createDecryptFunctionWithIv(iv, 0)
44+
}
45+
}

cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkDigest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ package dev.whyoleg.cryptography.providers.jdk.algorithms
77
import dev.whyoleg.cryptography.*
88
import dev.whyoleg.cryptography.algorithms.*
99
import dev.whyoleg.cryptography.operations.*
10+
import dev.whyoleg.cryptography.providers.base.*
1011
import dev.whyoleg.cryptography.providers.jdk.*
11-
import dev.whyoleg.cryptography.providers.jdk.internal.*
1212

1313
internal class JdkDigest(
1414
state: JdkCryptographyState,

cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEcdsa.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import dev.whyoleg.cryptography.*
88
import dev.whyoleg.cryptography.algorithms.*
99
import dev.whyoleg.cryptography.bigint.*
1010
import dev.whyoleg.cryptography.operations.*
11+
import dev.whyoleg.cryptography.providers.base.*
1112
import dev.whyoleg.cryptography.providers.jdk.*
1213
import dev.whyoleg.cryptography.providers.jdk.internal.*
1314
import dev.whyoleg.cryptography.providers.jdk.operations.*

0 commit comments

Comments
 (0)