-
Notifications
You must be signed in to change notification settings - Fork 26
Add support for encoding/decoding compressed EC keys #57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
internal fun ECParameterSpec.decodePoint(bytes: ByteArray): ECPoint { | ||
val fieldSize = curveOrderSize() | ||
return when (bytes[0].toInt()) { | ||
0x02, // compressed evenY | ||
0x03, // compressed oddY | ||
-> { | ||
check(bytes.size == fieldSize + 1) { "Wrong compressed key size ${bytes.size}" } | ||
val p = (curve.field as ECFieldFp).p | ||
val a = curve.a | ||
val b = curve.b | ||
val x = BigInteger(1, bytes.copyOfRange(1, bytes.size)) | ||
var y = x.multiply(x).add(a).multiply(x).add(b).mod(p).modSqrt(p) | ||
if (y.testBit(0) != (bytes[0].toInt() == 0x03)) { | ||
y = p.subtract(y) | ||
} | ||
ECPoint(x, y) | ||
} | ||
0x04, // uncompressed | ||
-> { | ||
check(bytes.size == fieldSize * 2 + 1) { "Wrong uncompressed key size ${bytes.size}" } | ||
val x = bytes.copyOfRange(1, fieldSize + 1) | ||
val y = bytes.copyOfRange(fieldSize + 1, fieldSize + 1 + fieldSize) | ||
ECPoint(BigInteger(1, x), BigInteger(1, y)) | ||
} | ||
else -> error("Unsupported key type ${bytes[0].toInt()}") | ||
} | ||
} | ||
|
||
internal fun BigInteger.modSqrt(p: BigInteger): BigInteger { | ||
check(p.testBit(0) && p.testBit(1)) { "Unsupported curve modulus" } // p ≡ 3 (mod 4) | ||
return modPow(p.add(BigInteger.ONE).shiftRight(2), p) // Tonelli-Shanks | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BC is added as a provider without some API access so it requires manually calculating y
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementation looks legit, thanks!
The only thing I would like to add is some link to the reference based on what the implementation is written.
For example link to a https://www.secg.org/sec1-v2.pdf or anything else?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR!
Looks nice!
Here is the first round of review. I still need to check JDK implementation, as it contains more hand-written logic :)
else -> null | ||
format.name == "JWK" && !provider.isWebCrypto | ||
-> "JWK key format" | ||
format == EC.PublicKey.Format.RAW.Compressed && provider.isApple |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you please mention this in limitations of apple provider?
cryptography-providers/webcrypto/src/commonMain/kotlin/algorithms/WebCryptoEc.kt
Show resolved
Hide resolved
val ecKey = checkError(EVP_PKEY_get1_EC_KEY(key)) | ||
val ecGroup = checkError(EC_KEY_get0_group(ecKey)) | ||
val ecPoint = checkError(EC_KEY_get0_public_key(ecKey)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those functions are deprecated in openssl:
- https://docs.openssl.org/3.1/man3/EC_KEY_new/#synopsis
- https://docs.openssl.org/3.1/man3/EVP_PKEY_set1_RSA/#synopsis
Could you please replace them?
Looks like we need to find a way to provide OPENSSL_API_COMPAT
to cinterop to hide those declarations to avoid usage them in future
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI: I was able to enforce OPENSSL_NO_DEPRECATED
in cinterop in eeea305 and now those declarations are not available.
This commit will be soon merged into main
, so if you willing to proceed with changing those you can temporary cherry-pick it from whyoleg/dev
branch
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the late reply. I will check when free.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No problem, take your time.
FYI: openssl changes mentioned are already in main
, so you can just rebase.
FYI2: At the current moment, I plan to do a release in mid-June, so it would be really nice to include those changes in it, along with your other contribution of RIPEMD!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR one more and sorry for long review.
Finally I was able to validate the JDK implementation and left some small remarks.
Let's resolve them, migrate OpenSSL implementation to use non-deprecated APIs and we are good to go!
EC.PublicKey.Format.RAW -> encodeToRaw(false) | ||
EC.PublicKey.Format.RAW.Compressed -> encodeToRaw(true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice to use named parameters here:
EC.PublicKey.Format.RAW -> encodeToRaw(false) | |
EC.PublicKey.Format.RAW.Compressed -> encodeToRaw(true) | |
EC.PublicKey.Format.RAW -> encodeToRaw(compressed = false) | |
EC.PublicKey.Format.RAW.Compressed -> encodeToRaw(compressed = true) |
|
||
if (compressed) { | ||
val output = ByteArray(fieldSize + 1) | ||
output[0] = if (!BigInteger(1, y).testBit(0)) 0x02 else 0x03 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor: maybe it’s possible to use key.w.affineY
here? no need to re-create BigInteger
from bytes, as we already have original value? :)
val x = bytes.copyOfRange(1, fieldSize + 1) | ||
val y = bytes.copyOfRange(fieldSize + 1, fieldSize + 1 + fieldSize) | ||
val point = ECPoint(BigInteger(1, x), BigInteger(1, y)) | ||
// use RawEncodedKeySpec |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this comment is a bit missleading, as there is no such class. Do you mind just dropping it?
internal fun ECParameterSpec.decodePoint(bytes: ByteArray): ECPoint { | ||
val fieldSize = curveOrderSize() | ||
return when (bytes[0].toInt()) { | ||
0x02, // compressed evenY | ||
0x03, // compressed oddY | ||
-> { | ||
check(bytes.size == fieldSize + 1) { "Wrong compressed key size ${bytes.size}" } | ||
val p = (curve.field as ECFieldFp).p | ||
val a = curve.a | ||
val b = curve.b | ||
val x = BigInteger(1, bytes.copyOfRange(1, bytes.size)) | ||
var y = x.multiply(x).add(a).multiply(x).add(b).mod(p).modSqrt(p) | ||
if (y.testBit(0) != (bytes[0].toInt() == 0x03)) { | ||
y = p.subtract(y) | ||
} | ||
ECPoint(x, y) | ||
} | ||
0x04, // uncompressed | ||
-> { | ||
check(bytes.size == fieldSize * 2 + 1) { "Wrong uncompressed key size ${bytes.size}" } | ||
val x = bytes.copyOfRange(1, fieldSize + 1) | ||
val y = bytes.copyOfRange(fieldSize + 1, fieldSize + 1 + fieldSize) | ||
ECPoint(BigInteger(1, x), BigInteger(1, y)) | ||
} | ||
else -> error("Unsupported key type ${bytes[0].toInt()}") | ||
} | ||
} | ||
|
||
internal fun BigInteger.modSqrt(p: BigInteger): BigInteger { | ||
check(p.testBit(0) && p.testBit(1)) { "Unsupported curve modulus" } // p ≡ 3 (mod 4) | ||
return modPow(p.add(BigInteger.ONE).shiftRight(2), p) // Tonelli-Shanks | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementation looks legit, thanks!
The only thing I would like to add is some link to the reference based on what the implementation is written.
For example link to a https://www.secg.org/sec1-v2.pdf or anything else?
This PR add support for
Compressed
andUncompressed
(default) format underRAW
EC Public Key.