Skip to content

Commit b141d11

Browse files
committed
WIP: chacha20
1 parent 36969fa commit b141d11

File tree

10 files changed

+476
-59
lines changed

10 files changed

+476
-59
lines changed

build-logic/src/main/kotlin/ckbuild/tests/GenerateProviderTestsTask.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ abstract class GenerateProviderTestsTask : DefaultTask() {
106106
"AesEcbCompatibilityTest",
107107
"AesGcmTest",
108108
"AesGcmCompatibilityTest",
109+
"ChaCha20Poly1305Test",
110+
"ChaCha20Poly1305CompatibilityTest",
109111

110112
"HmacTest",
111113
"HmacCompatibilityTest",

cryptography-core/api/cryptography-core.api

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,29 @@ public abstract interface class dev/whyoleg/cryptography/algorithms/AES$OFB$Key
199199
public abstract fun cipher ()Ldev/whyoleg/cryptography/operations/IvCipher;
200200
}
201201

202+
public abstract interface class dev/whyoleg/cryptography/algorithms/ChaCha20Poly1305 : dev/whyoleg/cryptography/CryptographyAlgorithm {
203+
public static final field Companion Ldev/whyoleg/cryptography/algorithms/ChaCha20Poly1305$Companion;
204+
public fun getId ()Ldev/whyoleg/cryptography/CryptographyAlgorithmId;
205+
public abstract fun keyDecoder ()Ldev/whyoleg/cryptography/materials/key/KeyDecoder;
206+
public abstract fun keyGenerator ()Ldev/whyoleg/cryptography/materials/key/KeyGenerator;
207+
}
208+
209+
public final class dev/whyoleg/cryptography/algorithms/ChaCha20Poly1305$Companion : dev/whyoleg/cryptography/CryptographyAlgorithmId {
210+
}
211+
212+
public abstract interface class dev/whyoleg/cryptography/algorithms/ChaCha20Poly1305$Key : dev/whyoleg/cryptography/materials/key/EncodableKey {
213+
public abstract fun cipher ()Ldev/whyoleg/cryptography/operations/IvAuthenticatedCipher;
214+
}
215+
216+
public final class dev/whyoleg/cryptography/algorithms/ChaCha20Poly1305$Key$Format : java/lang/Enum, dev/whyoleg/cryptography/materials/key/KeyFormat {
217+
public static final field JWK Ldev/whyoleg/cryptography/algorithms/ChaCha20Poly1305$Key$Format;
218+
public static final field RAW Ldev/whyoleg/cryptography/algorithms/ChaCha20Poly1305$Key$Format;
219+
public static fun getEntries ()Lkotlin/enums/EnumEntries;
220+
public synthetic fun getName ()Ljava/lang/String;
221+
public static fun valueOf (Ljava/lang/String;)Ldev/whyoleg/cryptography/algorithms/ChaCha20Poly1305$Key$Format;
222+
public static fun values ()[Ldev/whyoleg/cryptography/algorithms/ChaCha20Poly1305$Key$Format;
223+
}
224+
202225
public abstract interface class dev/whyoleg/cryptography/algorithms/Digest : dev/whyoleg/cryptography/CryptographyAlgorithm {
203226
public abstract fun getId ()Ldev/whyoleg/cryptography/CryptographyAlgorithmId;
204227
public abstract fun hasher ()Ldev/whyoleg/cryptography/operations/Hasher;

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,31 @@ abstract interface <#A: dev.whyoleg.cryptography.materials.key/KeyFormat> dev.wh
461461
open suspend fun encodeToByteString(#A): kotlinx.io.bytestring/ByteString // dev.whyoleg.cryptography.materials.key/EncodableKey.encodeToByteString|encodeToByteString(1:0){}[0]
462462
}
463463

464+
abstract interface dev.whyoleg.cryptography.algorithms/ChaCha20Poly1305 : dev.whyoleg.cryptography/CryptographyAlgorithm { // dev.whyoleg.cryptography.algorithms/ChaCha20Poly1305|null[0]
465+
open val id // dev.whyoleg.cryptography.algorithms/ChaCha20Poly1305.id|{}id[0]
466+
open fun <get-id>(): dev.whyoleg.cryptography/CryptographyAlgorithmId<dev.whyoleg.cryptography.algorithms/ChaCha20Poly1305> // dev.whyoleg.cryptography.algorithms/ChaCha20Poly1305.id.<get-id>|<get-id>(){}[0]
467+
468+
abstract fun keyDecoder(): dev.whyoleg.cryptography.materials.key/KeyDecoder<dev.whyoleg.cryptography.algorithms/ChaCha20Poly1305.Key.Format, dev.whyoleg.cryptography.algorithms/ChaCha20Poly1305.Key> // dev.whyoleg.cryptography.algorithms/ChaCha20Poly1305.keyDecoder|keyDecoder(){}[0]
469+
abstract fun keyGenerator(): dev.whyoleg.cryptography.materials.key/KeyGenerator<dev.whyoleg.cryptography.algorithms/ChaCha20Poly1305.Key> // dev.whyoleg.cryptography.algorithms/ChaCha20Poly1305.keyGenerator|keyGenerator(){}[0]
470+
471+
abstract interface Key : dev.whyoleg.cryptography.materials.key/EncodableKey<dev.whyoleg.cryptography.algorithms/ChaCha20Poly1305.Key.Format> { // dev.whyoleg.cryptography.algorithms/ChaCha20Poly1305.Key|null[0]
472+
abstract fun cipher(): dev.whyoleg.cryptography.operations/IvAuthenticatedCipher // dev.whyoleg.cryptography.algorithms/ChaCha20Poly1305.Key.cipher|cipher(){}[0]
473+
474+
final enum class Format : dev.whyoleg.cryptography.materials.key/KeyFormat, kotlin/Enum<dev.whyoleg.cryptography.algorithms/ChaCha20Poly1305.Key.Format> { // dev.whyoleg.cryptography.algorithms/ChaCha20Poly1305.Key.Format|null[0]
475+
enum entry JWK // dev.whyoleg.cryptography.algorithms/ChaCha20Poly1305.Key.Format.JWK|null[0]
476+
enum entry RAW // dev.whyoleg.cryptography.algorithms/ChaCha20Poly1305.Key.Format.RAW|null[0]
477+
478+
final val entries // dev.whyoleg.cryptography.algorithms/ChaCha20Poly1305.Key.Format.entries|#static{}entries[0]
479+
final fun <get-entries>(): kotlin.enums/EnumEntries<dev.whyoleg.cryptography.algorithms/ChaCha20Poly1305.Key.Format> // dev.whyoleg.cryptography.algorithms/ChaCha20Poly1305.Key.Format.entries.<get-entries>|<get-entries>#static(){}[0]
480+
481+
final fun valueOf(kotlin/String): dev.whyoleg.cryptography.algorithms/ChaCha20Poly1305.Key.Format // dev.whyoleg.cryptography.algorithms/ChaCha20Poly1305.Key.Format.valueOf|valueOf#static(kotlin.String){}[0]
482+
final fun values(): kotlin/Array<dev.whyoleg.cryptography.algorithms/ChaCha20Poly1305.Key.Format> // dev.whyoleg.cryptography.algorithms/ChaCha20Poly1305.Key.Format.values|values#static(){}[0]
483+
}
484+
}
485+
486+
final object Companion : dev.whyoleg.cryptography/CryptographyAlgorithmId<dev.whyoleg.cryptography.algorithms/ChaCha20Poly1305> // dev.whyoleg.cryptography.algorithms/ChaCha20Poly1305.Companion|null[0]
487+
}
488+
464489
abstract interface dev.whyoleg.cryptography.algorithms/Digest : dev.whyoleg.cryptography/CryptographyAlgorithm { // dev.whyoleg.cryptography.algorithms/Digest|null[0]
465490
abstract val id // dev.whyoleg.cryptography.algorithms/Digest.id|{}id[0]
466491
abstract fun <get-id>(): dev.whyoleg.cryptography/CryptographyAlgorithmId<dev.whyoleg.cryptography.algorithms/Digest> // dev.whyoleg.cryptography.algorithms/Digest.id.<get-id>|<get-id>(){}[0]
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package dev.whyoleg.cryptography.algorithms
6+
7+
import dev.whyoleg.cryptography.*
8+
import dev.whyoleg.cryptography.materials.key.*
9+
import dev.whyoleg.cryptography.operations.*
10+
11+
@SubclassOptInRequired(CryptographyProviderApi::class)
12+
public interface ChaCha20Poly1305 : CryptographyAlgorithm {
13+
override val id: CryptographyAlgorithmId<ChaCha20Poly1305> get() = Companion
14+
15+
public companion object : CryptographyAlgorithmId<ChaCha20Poly1305>("ChaCha20-Poly1305")
16+
17+
public fun keyDecoder(): KeyDecoder<Key.Format, Key>
18+
public fun keyGenerator(): KeyGenerator<Key>
19+
20+
@SubclassOptInRequired(CryptographyProviderApi::class)
21+
public interface Key : EncodableKey<Key.Format> {
22+
public fun cipher(): IvAuthenticatedCipher
23+
24+
public enum class Format : KeyFormat { RAW, JWK }
25+
}
26+
}

cryptography-providers/jdk/src/jvmMain/kotlin/JdkCryptographyProvider.kt

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -109,35 +109,36 @@ internal class JdkCryptographyProvider(provider: Provider?) : CryptographyProvid
109109
@Suppress("UNCHECKED_CAST")
110110
override fun <A : CryptographyAlgorithm> getOrNull(identifier: CryptographyAlgorithmId<A>): A? = cache.getOrPut(identifier) {
111111
when (identifier) {
112-
MD4 -> JdkDigest(state, "MD4", MD4)
113-
MD5 -> JdkDigest(state, "MD5", MD5)
114-
SHA1 -> JdkDigest(state, "SHA-1", SHA1)
115-
SHA224 -> JdkDigest(state, "SHA-224", SHA224)
116-
SHA256 -> JdkDigest(state, "SHA-256", SHA256)
117-
SHA384 -> JdkDigest(state, "SHA-384", SHA384)
118-
SHA512 -> JdkDigest(state, "SHA-512", SHA512)
119-
SHA3_224 -> JdkDigest(state, "SHA3-224", SHA3_224)
120-
SHA3_256 -> JdkDigest(state, "SHA3-256", SHA3_256)
121-
SHA3_384 -> JdkDigest(state, "SHA3-384", SHA3_384)
122-
SHA3_512 -> JdkDigest(state, "SHA3-512", SHA3_512)
123-
RIPEMD160 -> JdkDigest(state, "RIPEMD160", RIPEMD160)
124-
HMAC -> JdkHmac(state)
125-
AES.CBC -> JdkAesCbc(state)
126-
AES.CMAC -> JdkAesCmac(state)
127-
AES.CTR -> JdkAesCtr(state)
128-
AES.CFB -> JdkAesCfb(state)
129-
AES.OFB -> JdkAesOfb(state)
130-
AES.ECB -> JdkAesEcb(state)
131-
AES.GCM -> JdkAesGcm(state)
132-
RSA.OAEP -> JdkRsaOaep(state)
133-
RSA.PSS -> JdkRsaPss(state)
134-
RSA.PKCS1 -> JdkRsaPkcs1(state)
135-
RSA.RAW -> JdkRsaRaw(state)
136-
ECDSA -> JdkEcdsa(state)
137-
ECDH -> JdkEcdh(state)
138-
PBKDF2 -> JdkPbkdf2(state)
139-
HKDF -> JdkHkdf(state, this)
140-
else -> null
112+
MD4 -> JdkDigest(state, "MD4", MD4)
113+
MD5 -> JdkDigest(state, "MD5", MD5)
114+
SHA1 -> JdkDigest(state, "SHA-1", SHA1)
115+
SHA224 -> JdkDigest(state, "SHA-224", SHA224)
116+
SHA256 -> JdkDigest(state, "SHA-256", SHA256)
117+
SHA384 -> JdkDigest(state, "SHA-384", SHA384)
118+
SHA512 -> JdkDigest(state, "SHA-512", SHA512)
119+
SHA3_224 -> JdkDigest(state, "SHA3-224", SHA3_224)
120+
SHA3_256 -> JdkDigest(state, "SHA3-256", SHA3_256)
121+
SHA3_384 -> JdkDigest(state, "SHA3-384", SHA3_384)
122+
SHA3_512 -> JdkDigest(state, "SHA3-512", SHA3_512)
123+
RIPEMD160 -> JdkDigest(state, "RIPEMD160", RIPEMD160)
124+
HMAC -> JdkHmac(state)
125+
AES.CBC -> JdkAesCbc(state)
126+
AES.CMAC -> JdkAesCmac(state)
127+
AES.CTR -> JdkAesCtr(state)
128+
AES.CFB -> JdkAesCfb(state)
129+
AES.OFB -> JdkAesOfb(state)
130+
AES.ECB -> JdkAesEcb(state)
131+
AES.GCM -> JdkAesGcm(state)
132+
ChaCha20Poly1305 -> JdkChaCha20Poly1305(state)
133+
RSA.OAEP -> JdkRsaOaep(state)
134+
RSA.PSS -> JdkRsaPss(state)
135+
RSA.PKCS1 -> JdkRsaPkcs1(state)
136+
RSA.RAW -> JdkRsaRaw(state)
137+
ECDSA -> JdkEcdsa(state)
138+
ECDH -> JdkEcdh(state)
139+
PBKDF2 -> JdkPbkdf2(state)
140+
HKDF -> JdkHkdf(state, this)
141+
else -> null
141142
}
142143
} as A?
143144
}

cryptography-providers/jdk/src/jvmMain/kotlin/JdkCryptographyState.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ internal class JdkCryptographyState(private val provider: JProvider?) {
4646
}
4747
}
4848

49-
fun cipher(algorithm: String): Pooled<JCipher> =
50-
ciphers.get(algorithm, JCipher::getInstance, JCipher::getInstance)
49+
fun cipher(algorithm: String, cached: Boolean = true): Pooled<JCipher> =
50+
ciphers.get(algorithm, JCipher::getInstance, JCipher::getInstance, cached)
5151

5252
fun messageDigest(algorithm: String): Pooled<JMessageDigest> =
5353
messageDigests.get(algorithm, JMessageDigest::getInstance, JMessageDigest::getInstance)
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright (c) 2025 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.*
8+
import dev.whyoleg.cryptography.algorithms.*
9+
import dev.whyoleg.cryptography.materials.key.*
10+
import dev.whyoleg.cryptography.operations.*
11+
import dev.whyoleg.cryptography.providers.base.algorithms.*
12+
import dev.whyoleg.cryptography.providers.base.operations.*
13+
import dev.whyoleg.cryptography.providers.jdk.*
14+
import dev.whyoleg.cryptography.providers.jdk.materials.*
15+
import dev.whyoleg.cryptography.providers.jdk.operations.*
16+
import javax.crypto.spec.*
17+
18+
internal class JdkChaCha20Poly1305(
19+
private val state: JdkCryptographyState,
20+
) : ChaCha20Poly1305 {
21+
private val keyWrapper: (JSecretKey) -> ChaCha20Poly1305.Key = { key -> JdkChaCha20Poly1305Key(state, key) }
22+
private val keyDecoder = JdkSecretKeyDecoder<ChaCha20Poly1305.Key.Format, _>("ChaCha20", keyWrapper)
23+
24+
override fun keyDecoder(): KeyDecoder<ChaCha20Poly1305.Key.Format, ChaCha20Poly1305.Key> = keyDecoder
25+
override fun keyGenerator(): KeyGenerator<ChaCha20Poly1305.Key> =
26+
JdkSecretKeyGenerator(state, "ChaCha20", keyWrapper) {
27+
init(state.secureRandom)
28+
}
29+
}
30+
31+
private class JdkChaCha20Poly1305Key(
32+
private val state: JdkCryptographyState,
33+
private val key: JSecretKey,
34+
) : ChaCha20Poly1305.Key, JdkEncodableKey<ChaCha20Poly1305.Key.Format>(key) {
35+
override fun cipher(): IvAuthenticatedCipher = JdkChaCha20Poly1305Cipher(state, key)
36+
37+
override fun encodeToByteArrayBlocking(format: ChaCha20Poly1305.Key.Format): ByteArray = when (format) {
38+
ChaCha20Poly1305.Key.Format.JWK -> error("$format is not supported")
39+
ChaCha20Poly1305.Key.Format.RAW -> encodeToRaw()
40+
}
41+
}
42+
43+
private const val chachaNonceSize: Int = 12
44+
45+
private class JdkChaCha20Poly1305Cipher(
46+
private val state: JdkCryptographyState,
47+
private val key: JSecretKey,
48+
) : BaseIvAuthenticatedCipher {
49+
// TODO: !!! we can't cache it, because the check about reusing IV is too strict, as on JDK 11 and 17 it will check on decryption too - TBD?
50+
private val cipher = state.cipher("ChaCha20-Poly1305", cached = false)
51+
52+
override fun createEncryptFunction(associatedData: ByteArray?): CipherFunction {
53+
val iv = ByteArray(chachaNonceSize).also(state.secureRandom::nextBytes)
54+
return BaseImplicitIvEncryptFunction(iv, createEncryptFunctionWithIv(iv, associatedData))
55+
}
56+
57+
override fun createDecryptFunction(associatedData: ByteArray?): CipherFunction {
58+
return BaseImplicitIvDecryptFunction(chachaNonceSize) { iv: ByteArray, startIndex: Int ->
59+
createDecryptFunctionWithIv(iv, startIndex, chachaNonceSize, associatedData)
60+
}
61+
}
62+
63+
override fun createEncryptFunctionWithIv(iv: ByteArray, associatedData: ByteArray?): CipherFunction {
64+
return JdkCipherFunction(cipher.borrowResource {
65+
init(JCipher.ENCRYPT_MODE, key, IvParameterSpec(iv), state.secureRandom)
66+
associatedData?.let(this::updateAAD)
67+
})
68+
}
69+
70+
private fun createDecryptFunctionWithIv(
71+
iv: ByteArray,
72+
startIndex: Int,
73+
ivSize: Int,
74+
associatedData: ByteArray?,
75+
): CipherFunction {
76+
return JdkCipherFunction(cipher.borrowResource {
77+
init(JCipher.DECRYPT_MODE, key, IvParameterSpec(iv, startIndex, ivSize), state.secureRandom)
78+
associatedData?.let(this::updateAAD)
79+
})
80+
}
81+
82+
override fun createDecryptFunctionWithIv(iv: ByteArray, associatedData: ByteArray?): CipherFunction {
83+
return createDecryptFunctionWithIv(iv, 0, iv.size, associatedData)
84+
}
85+
}

0 commit comments

Comments
 (0)