Skip to content

Commit 41d8176

Browse files
committed
[JDK] Implement API to get public key from private key for RSA and EC
1 parent 01d2383 commit 41d8176

File tree

16 files changed

+398
-440
lines changed

16 files changed

+398
-440
lines changed

cryptography-providers/base/api/cryptography-provider-base.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ public abstract class dev/whyoleg/cryptography/providers/base/algorithms/BaseHkd
6161
public final class dev/whyoleg/cryptography/providers/base/algorithms/EcKt {
6262
public static final fun convertEcPrivateKeyFromPkcs8ToSec1 ([B)[B
6363
public static final fun convertEcPrivateKeyFromSec1ToPkcs8 ([B)[B
64+
public static final fun getEcPublicKeyFromPrivateKeyPkcs8 ([B)[B
6465
}
6566

6667
public final class dev/whyoleg/cryptography/providers/base/materials/KeysKt {

cryptography-providers/base/api/cryptography-provider-base.klib.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ final val dev.whyoleg.cryptography.providers.base/EmptyByteArray // dev.whyoleg.
140140
final fun (kotlin/ByteArray).dev.whyoleg.cryptography.providers.base/ensureSizeExactly(kotlin/Int): kotlin/ByteArray // dev.whyoleg.cryptography.providers.base/ensureSizeExactly|[email protected](kotlin.Int){}[0]
141141
final fun dev.whyoleg.cryptography.providers.base.algorithms/convertEcPrivateKeyFromPkcs8ToSec1(kotlin/ByteArray): kotlin/ByteArray // dev.whyoleg.cryptography.providers.base.algorithms/convertEcPrivateKeyFromPkcs8ToSec1|convertEcPrivateKeyFromPkcs8ToSec1(kotlin.ByteArray){}[0]
142142
final fun dev.whyoleg.cryptography.providers.base.algorithms/convertEcPrivateKeyFromSec1ToPkcs8(kotlin/ByteArray): kotlin/ByteArray // dev.whyoleg.cryptography.providers.base.algorithms/convertEcPrivateKeyFromSec1ToPkcs8|convertEcPrivateKeyFromSec1ToPkcs8(kotlin.ByteArray){}[0]
143+
final fun dev.whyoleg.cryptography.providers.base.algorithms/getEcPublicKeyFromPrivateKeyPkcs8(kotlin/ByteArray): kotlin/ByteArray? // dev.whyoleg.cryptography.providers.base.algorithms/getEcPublicKeyFromPrivateKeyPkcs8|getEcPublicKeyFromPrivateKeyPkcs8(kotlin.ByteArray){}[0]
143144
final fun dev.whyoleg.cryptography.providers.base.materials/unwrapPem(dev.whyoleg.cryptography.serialization.pem/PemLabel, kotlin/ByteArray): kotlin/ByteArray // dev.whyoleg.cryptography.providers.base.materials/unwrapPem|unwrapPem(dev.whyoleg.cryptography.serialization.pem.PemLabel;kotlin.ByteArray){}[0]
144145
final fun dev.whyoleg.cryptography.providers.base.materials/unwrapPrivateKeyInfo(dev.whyoleg.cryptography.serialization.asn1/ObjectIdentifier, kotlin/ByteArray): kotlin/ByteArray // dev.whyoleg.cryptography.providers.base.materials/unwrapPrivateKeyInfo|unwrapPrivateKeyInfo(dev.whyoleg.cryptography.serialization.asn1.ObjectIdentifier;kotlin.ByteArray){}[0]
145146
final fun dev.whyoleg.cryptography.providers.base.materials/unwrapSubjectPublicKeyInfo(dev.whyoleg.cryptography.serialization.asn1/ObjectIdentifier, kotlin/ByteArray): kotlin/ByteArray // dev.whyoleg.cryptography.providers.base.materials/unwrapSubjectPublicKeyInfo|unwrapSubjectPublicKeyInfo(dev.whyoleg.cryptography.serialization.asn1.ObjectIdentifier;kotlin.ByteArray){}[0]

cryptography-providers/base/src/commonMain/kotlin/algorithms/Ec.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,14 @@ public fun convertEcPrivateKeyFromSec1ToPkcs8(input: ByteArray): ByteArray {
4343
)
4444
return Der.encodeToByteArray(PrivateKeyInfo.serializer(), privateKeyInfo)
4545
}
46+
47+
@CryptographyProviderApi
48+
public fun getEcPublicKeyFromPrivateKeyPkcs8(input: ByteArray): ByteArray? {
49+
val privateKeyInfo = Der.decodeFromByteArray(PrivateKeyInfo.serializer(), input)
50+
val privateKeyAlgorithm = privateKeyInfo.privateKeyAlgorithm
51+
check(privateKeyAlgorithm is EcKeyAlgorithmIdentifier) {
52+
"Expected algorithm '${ObjectIdentifier.EC}', received: '${privateKeyAlgorithm.algorithm}'"
53+
}
54+
val ecPrivateKey = Der.decodeFromByteArray(EcPrivateKey.serializer(), privateKeyInfo.privateKey)
55+
return ecPrivateKey.publicKey?.byteArray
56+
}

cryptography-providers/jdk/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ kotlin {
2727
jvmMain.dependencies {
2828
api(projects.cryptographyCore)
2929
implementation(projects.cryptographyProviderBase)
30+
31+
// should be used carefully, for specific cases
32+
compileOnly(libs.bouncycastle)
3033
}
3134
}
3235
}

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

Lines changed: 55 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,17 @@ import java.math.*
1616
import java.security.interfaces.*
1717
import java.security.spec.*
1818

19-
internal sealed class JdkEc<PublicK : EC.PublicKey, PrivateK : EC.PrivateKey, KP : EC.KeyPair<PublicK, PrivateK>>(
19+
internal sealed class JdkEc<PublicK : EC.PublicKey, PrivateK : EC.PrivateKey<PublicK>, KP : EC.KeyPair<PublicK, PrivateK>>(
2020
protected val state: JdkCryptographyState,
2121
) : EC<PublicK, PrivateK, KP> {
22+
protected abstract val wrapPublicKey: (JPublicKey) -> PublicK
23+
protected abstract val wrapPrivateKey: (JPrivateKey, PublicK?) -> PrivateK
24+
protected abstract val wrapKeyPair: (PublicK, PrivateK) -> KP
25+
2226
private fun algorithmParameters(spec: AlgorithmParameterSpec): JAlgorithmParameters {
2327
return state.algorithmParameters("EC").also { it.init(spec) }
2428
}
2529

26-
protected abstract fun JPublicKey.convert(): PublicK
27-
protected abstract fun JPrivateKey.convert(): PrivateK
28-
protected abstract fun JKeyPair.convert(): KP
29-
3030
final override fun publicKeyDecoder(curve: EC.Curve): KeyDecoder<EC.PublicKey.Format, PublicK> {
3131
return EcPublicKeyDecoder(algorithmParameters(ECGenParameterSpec(curve.jdkName)).curveName())
3232
}
@@ -54,19 +54,33 @@ internal sealed class JdkEc<PublicK : EC.PublicKey, PrivateK : EC.PrivateKey, KP
5454
initialize(keyGenParameters, state.secureRandom)
5555
}
5656

57-
override fun JKeyPair.convert(): KP = with(this@JdkEc) { convert() }
57+
override fun JKeyPair.convert(): KP {
58+
val publicKey = wrapPublicKey(public)
59+
val privateKey = wrapPrivateKey(private, publicKey)
60+
return wrapKeyPair(publicKey, privateKey)
61+
}
5862
}
5963

6064
private inner class EcPublicKeyDecoder(
6165
private val curveName: String,
6266
) : JdkPublicKeyDecoder<EC.PublicKey.Format, PublicK>(state, "EC") {
67+
68+
fun fromPrivateKey(privateKey: ECPrivateKey): PublicK = decode(
69+
BouncyCastleBridge.derivePublicKey(privateKey, curveName) ?: error(
70+
"Getting public key from private key for EC is not supported in JDK without BouncyCastle APIs"
71+
)
72+
)
73+
74+
fun fromEncodedPrivateKey(bytes: ByteArray): PublicK? =
75+
getEcPublicKeyFromPrivateKeyPkcs8(bytes)?.let(::decodeFromRaw)
76+
6377
override fun JPublicKey.convert(): PublicK {
6478
check(this is ECPublicKey)
6579

6680
val keyCurve = algorithmParameters(params).curveName()
6781
check(curveName == keyCurve) { "Key curve $keyCurve is not equal to expected curve $curveName" }
6882

69-
return with(this@JdkEc) { convert() }
83+
return wrapPublicKey(this)
7084
}
7185

7286
override fun decodeFromByteArrayBlocking(format: EC.PublicKey.Format, bytes: ByteArray): PublicK = when (format) {
@@ -77,29 +91,19 @@ internal sealed class JdkEc<PublicK : EC.PublicKey, PrivateK : EC.PrivateKey, KP
7791
EC.PublicKey.Format.PEM -> decodeFromDer(unwrapPem(PemLabel.PublicKey, bytes))
7892
}
7993

80-
private fun decodeFromRaw(bytes: ByteArray): PublicK = run {
94+
private fun decodeFromRaw(bytes: ByteArray): PublicK {
8195
check(bytes.isNotEmpty()) { "Encoded key is empty!" }
8296
val parameters = algorithmParameters(ECGenParameterSpec(curveName)).getParameterSpec(ECParameterSpec::class.java)
8397
val point = parameters.decodePoint(bytes)
8498

85-
keyFactory.use {
86-
it.generatePublic(ECPublicKeySpec(point, parameters))
87-
}.convert()
99+
return decode(ECPublicKeySpec(point, parameters))
88100
}
89-
90101
}
91102

92103
private inner class EcPrivateKeyDecoder(
93104
private val curveName: String,
94105
) : JdkPrivateKeyDecoder<EC.PrivateKey.Format, PrivateK>(state, "EC") {
95-
override fun JPrivateKey.convert(): PrivateK {
96-
check(this is ECPrivateKey)
97-
98-
val keyCurve = algorithmParameters(params).curveName()
99-
check(curveName == keyCurve) { "Key curve $keyCurve is not equal to expected curve $curveName" }
100-
101-
return with(this@JdkEc) { convert() }
102-
}
106+
override fun JPrivateKey.convert(): PrivateK = create(this, null)
103107

104108
override fun decodeFromByteArrayBlocking(format: EC.PrivateKey.Format, bytes: ByteArray): PrivateK = when (format) {
105109
EC.PrivateKey.Format.JWK -> error("$format is not supported")
@@ -113,10 +117,27 @@ internal sealed class JdkEc<PublicK : EC.PublicKey, PrivateK : EC.PrivateKey, KP
113117
EC.PrivateKey.Format.DER.SEC1 -> decodeFromDer(convertEcPrivateKeyFromSec1ToPkcs8(bytes))
114118
EC.PrivateKey.Format.PEM.SEC1 -> decodeFromDer(convertEcPrivateKeyFromSec1ToPkcs8(unwrapPem(PemLabel.EcPrivateKey, bytes)))
115119
}
120+
121+
override fun decodeFromDer(input: ByteArray): PrivateK {
122+
val privateKey = decodeFromDerRaw(input)
123+
return create(
124+
privateKey = privateKey,
125+
publicKey = EcPublicKeyDecoder(curveName).fromEncodedPrivateKey(input)
126+
)
127+
}
128+
129+
private fun create(privateKey: JPrivateKey, publicKey: PublicK?): PrivateK {
130+
check(privateKey is ECPrivateKey)
131+
132+
val keyCurve = algorithmParameters(privateKey.params).curveName()
133+
check(curveName == keyCurve) { "Key curve $keyCurve is not equal to expected curve $curveName" }
134+
135+
return wrapPrivateKey(privateKey, publicKey)
136+
}
116137
}
117138

118139
protected abstract class BaseEcPublicKey(
119-
private val key: JPublicKey,
140+
val key: JPublicKey,
120141
) : EC.PublicKey, JdkEncodableKey<EC.PublicKey.Format>(key) {
121142
final override fun encodeToByteArrayBlocking(format: EC.PublicKey.Format): ByteArray = when (format) {
122143
EC.PublicKey.Format.JWK -> error("$format is not supported")
@@ -150,9 +171,19 @@ internal sealed class JdkEc<PublicK : EC.PublicKey, PrivateK : EC.PrivateKey, KP
150171

151172
}
152173

153-
protected abstract class BaseEcPrivateKey(
154-
private val key: JPrivateKey,
155-
) : EC.PrivateKey, JdkEncodableKey<EC.PrivateKey.Format>(key) {
174+
protected abstract inner class BaseEcPrivateKey(
175+
val key: JPrivateKey,
176+
private var publicKey: PublicK?,
177+
) : EC.PrivateKey<PublicK>, JdkEncodableKey<EC.PrivateKey.Format>(key) {
178+
override fun getPublicKeyBlocking(): PublicK {
179+
if (publicKey == null) {
180+
key as ECPrivateKey
181+
val curveName = algorithmParameters(key.params).curveName()
182+
publicKey = EcPublicKeyDecoder(curveName).fromPrivateKey(key)
183+
}
184+
return publicKey!!
185+
}
186+
156187
final override fun encodeToByteArrayBlocking(format: EC.PrivateKey.Format): ByteArray = when (format) {
157188
EC.PrivateKey.Format.JWK -> error("$format is not supported")
158189
EC.PrivateKey.Format.DER -> encodeToDer()

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

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2024 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright (c) 2024-2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

55
package dev.whyoleg.cryptography.providers.jdk.algorithms
@@ -10,18 +10,17 @@ import dev.whyoleg.cryptography.providers.jdk.*
1010
import dev.whyoleg.cryptography.providers.jdk.operations.*
1111

1212
internal class JdkEcdh(state: JdkCryptographyState) : JdkEc<ECDH.PublicKey, ECDH.PrivateKey, ECDH.KeyPair>(state), ECDH {
13-
override fun JPublicKey.convert(): ECDH.PublicKey = EcdhPublicKey(state, this)
14-
override fun JPrivateKey.convert(): ECDH.PrivateKey = EcdhPrivateKey(state, this)
15-
override fun JKeyPair.convert(): ECDH.KeyPair = EcdhKeyPair(public.convert(), private.convert())
13+
override val wrapPublicKey: (JPublicKey) -> ECDH.PublicKey = ::EcdhPublicKey
14+
override val wrapPrivateKey: (JPrivateKey, ECDH.PublicKey?) -> ECDH.PrivateKey = ::EcdhPrivateKey
15+
override val wrapKeyPair: (ECDH.PublicKey, ECDH.PrivateKey) -> ECDH.KeyPair = ::EcdhKeyPair
1616

1717
private class EcdhKeyPair(
1818
override val publicKey: ECDH.PublicKey,
1919
override val privateKey: ECDH.PrivateKey,
2020
) : ECDH.KeyPair
2121

22-
private class EcdhPublicKey(
23-
private val state: JdkCryptographyState,
24-
val key: JPublicKey,
22+
private inner class EcdhPublicKey(
23+
key: JPublicKey,
2524
) : ECDH.PublicKey, BaseEcPublicKey(key), SharedSecretGenerator<ECDH.PrivateKey> {
2625
private val keyAgreement = state.keyAgreement("ECDH")
2726
override fun sharedSecretGenerator(): SharedSecretGenerator<ECDH.PrivateKey> = this
@@ -32,10 +31,10 @@ internal class JdkEcdh(state: JdkCryptographyState) : JdkEc<ECDH.PublicKey, ECDH
3231
}
3332
}
3433

35-
private class EcdhPrivateKey(
36-
private val state: JdkCryptographyState,
37-
val key: JPrivateKey,
38-
) : ECDH.PrivateKey, BaseEcPrivateKey(key), SharedSecretGenerator<ECDH.PublicKey> {
34+
private inner class EcdhPrivateKey(
35+
key: JPrivateKey,
36+
publicKey: ECDH.PublicKey?,
37+
) : ECDH.PrivateKey, BaseEcPrivateKey(key, publicKey), SharedSecretGenerator<ECDH.PublicKey> {
3938
private val keyAgreement = state.keyAgreement("ECDH")
4039
override fun sharedSecretGenerator(): SharedSecretGenerator<ECDH.PublicKey> = this
4140
override fun generateSharedSecretToByteArrayBlocking(other: ECDH.PublicKey): ByteArray {

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

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2023-2024 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright (c) 2023-2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

55
package dev.whyoleg.cryptography.providers.jdk.algorithms
@@ -17,18 +17,17 @@ import dev.whyoleg.cryptography.serialization.asn1.modules.*
1717
import java.security.interfaces.*
1818

1919
internal class JdkEcdsa(state: JdkCryptographyState) : JdkEc<ECDSA.PublicKey, ECDSA.PrivateKey, ECDSA.KeyPair>(state), ECDSA {
20-
override fun JPublicKey.convert(): ECDSA.PublicKey = EcdsaPublicKey(state, this)
21-
override fun JPrivateKey.convert(): ECDSA.PrivateKey = EcdsaPrivateKey(state, this)
22-
override fun JKeyPair.convert(): ECDSA.KeyPair = EcdsaKeyPair(public.convert(), private.convert())
20+
override val wrapPublicKey: (JPublicKey) -> ECDSA.PublicKey = ::EcdsaPublicKey
21+
override val wrapPrivateKey: (JPrivateKey, ECDSA.PublicKey?) -> ECDSA.PrivateKey = ::EcdsaPrivateKey
22+
override val wrapKeyPair: (ECDSA.PublicKey, ECDSA.PrivateKey) -> ECDSA.KeyPair = ::EcdsaKeyPair
2323

2424
private class EcdsaKeyPair(
2525
override val publicKey: ECDSA.PublicKey,
2626
override val privateKey: ECDSA.PrivateKey,
2727
) : ECDSA.KeyPair
2828

29-
private class EcdsaPublicKey(
30-
private val state: JdkCryptographyState,
31-
private val key: JPublicKey,
29+
private inner class EcdsaPublicKey(
30+
key: JPublicKey,
3231
) : ECDSA.PublicKey, BaseEcPublicKey(key) {
3332
override fun signatureVerifier(digest: CryptographyAlgorithmId<Digest>, format: ECDSA.SignatureFormat): SignatureVerifier {
3433
val verifier = JdkSignatureVerifier(state, key, digest.hashAlgorithmName() + "withECDSA", null)
@@ -39,10 +38,10 @@ internal class JdkEcdsa(state: JdkCryptographyState) : JdkEc<ECDSA.PublicKey, EC
3938
}
4039
}
4140

42-
private class EcdsaPrivateKey(
43-
private val state: JdkCryptographyState,
44-
private val key: JPrivateKey,
45-
) : ECDSA.PrivateKey, BaseEcPrivateKey(key) {
41+
private inner class EcdsaPrivateKey(
42+
key: JPrivateKey,
43+
publicKey: ECDSA.PublicKey?,
44+
) : ECDSA.PrivateKey, BaseEcPrivateKey(key, publicKey) {
4645
override fun signatureGenerator(digest: CryptographyAlgorithmId<Digest>, format: ECDSA.SignatureFormat): SignatureGenerator {
4746
val generator = JdkSignatureGenerator(state, key, digest.hashAlgorithmName() + "withECDSA", null)
4847
return when (format) {

0 commit comments

Comments
 (0)