Kotlin 生成私钥和公钥,接收到的公钥转换等操作
import java.security.KeyPairGenerator
import java.security.interfaces.ECPublicKey
import java.security.spec.ECGenParameterSpec
fun createPublicKey() {
if (mPubECKey != null && mPrivateECKey != null) {
return
}
// Initialize the KeyPairGenerator with the NIST P-256 curve
val keyPairGenerator = KeyPairGenerator.getInstance("EC")
val ecSpec = ECGenParameterSpec("secp256r1") // NIST P-256 curve
keyPairGenerator.initialize(ecSpec)
// Generate the key pair
val keyPair: KeyPair = keyPairGenerator.generateKeyPair()
mPrivateECKey = keyPair.private as ECPrivateKey
mPubECKey = keyPair.public as ECPublicKey
// Obtain raw representations
val privateKey = mPrivateECKey ?: throw IllegalStateException("Private key generation failed")
val privateKeyData = privateKey.encoded // PKCS8 format
println("Private Key Data (DER): ${privateKeyData.joinToString { "%02x".format(it) }}")
val publicKey = mPubECKey ?: throw IllegalStateException("Public key generation failed")
val publicKeyData = publicKey.encoded // X.509 format generated 92 bytes publicKey data
println("Public Key Data (DER): ${publicKeyData.joinToString { "%02x".format(it) }}")
}
在Kotlin代码中生成的 publicKeyData 是 92 字节而不是 65 字节,是因为 publicKey.encoded 返回的是经过 X.509 标准封装的公钥数据,而不是纯粹的椭圆曲线公钥。让我们详细分析这个问题,以及如何生成仅包含 65 字节的原始椭圆曲线公钥:
X.509 编码的公钥结构
publicKey.encoded 返回的是 X.509 标准编码格式的公钥数据。这个编码包括以下几个部分:
-
SubjectPublicKeyInfo 结构:
- 标识算法(如
1.2.840.10045.2.1和1.2.840.10045.3.1.7对应EC和secp256r1)。 - 原始椭圆曲线公钥数据。
- 标识算法(如
-
DER 编码:
- X.509 使用 DER(Distinguished Encoding Rules)对结构数据进行编码。
具体来说,X.509 的数据格式如下:
SEQUENCE { SEQUENCE { OBJECT IDENTIFIER (Algorithm: EC Public Key) OBJECT IDENTIFIER (Curve: secp256r1) } BIT STRING (Raw Public Key) } 这使得 publicKey.encoded 的长度增加到 92 字节。
椭圆曲线的原始公钥(65 字节)
椭圆曲线的原始公钥是一个未编码的字节数组,仅包含椭圆曲线点的信息:
- 1 字节的前缀:
0x04,表示未压缩的公钥格式。 - 32 字节的 X 坐标。
- 32 字节的 Y 坐标。
这总共是 65 字节。
如何获取 65 字节的原始公钥?
椭圆曲线的原始公钥是一个未编码的字节数组,仅包含椭圆曲线点的信息:
- 1 字节的前缀:
0x04,表示未压缩的公钥格式。 - 32 字节的 X 坐标。
- 32 字节的 Y 坐标。
这总共是 65 字节。
如何获取 65 字节的原始公钥?
要提取未编码的原始椭圆曲线公钥,可以从 ECPublicKey 对象中直接获取其坐标。
修改代码
import java.security.KeyPairGenerator import java.security.interfaces.ECPublicKey import java.security.spec.ECGenParameterSpec fun createPublicKey() { // Initialize the KeyPairGenerator with the NIST P-256 curve val keyPairGenerator = KeyPairGenerator.getInstance("EC") val ecSpec = ECGenParameterSpec("secp256r1") // NIST P-256 curve keyPairGenerator.initialize(ecSpec) // Generate the key pair val keyPair = keyPairGenerator.generateKeyPair() val mPubECKey = keyPair.public as ECPublicKey // Extract raw EC public key val ecPoint = mPubECKey.w // Get the elliptic curve point (X, Y) val x = ecPoint.affineX.toByteArray() val y = ecPoint.affineY.toByteArray() // Ensure coordinates are 32 bytes (add padding if necessary) val xPadded = ByteArray(32) { 0 } val yPadded = ByteArray(32) { 0 } System.arraycopy(x, maxOf(0, x.size - 32), xPadded, maxOf(0, 32 - x.size), minOf(32, x.size)) System.arraycopy(y, maxOf(0, y.size - 32), yPadded, maxOf(0, 32 - y.size), minOf(32, y.size)) // Combine the prefix and coordinates into 65-byte raw public key val rawPublicKey = byteArrayOf(0x04) + xPadded + yPadded println("Raw Public Key (65 bytes): ${rawPublicKey.joinToString { "%02x".format(it) }}") }其它方法
方式 1:使用 JDK 的 KeyFactory
在 Java 的标准库中,通过 KeyFactory 可以将 ECPublicKey 转换为其底层的原始公钥数据。
示例代码
import java.security.KeyFactory import java.security.KeyPairGenerator import java.security.interfaces.ECPublicKey import java.security.spec.ECGenParameterSpec import java.security.spec.ECPublicKeySpec fun createPublicKey() { // Generate the EC key pair val keyPairGenerator = KeyPairGenerator.getInstance("EC") val ecSpec = ECGenParameterSpec("secp256r1") keyPairGenerator.initialize(ecSpec) val keyPair = keyPairGenerator.generateKeyPair() val mPubECKey = keyPair.public as ECPublicKey // Use KeyFactory to extract the uncompressed public key val keyFactory = KeyFactory.getInstance("EC") val ecPoint = mPubECKey.w val ecSpecParams = mPubECKey.params val pubKeySpec = ECPublicKeySpec(ecPoint, ecSpecParams) // Get the raw 65-byte public key val rawPublicKey = keyFactory.generatePublic(pubKeySpec).encoded println("Raw Public Key (65 bytes): ${rawPublicKey.joinToString { "%02x".format(it) }}") } 解析
- 生成密钥对: 使用标准
KeyPairGenerator和secp256r1曲线生成密钥对。 - 提取点坐标: 通过
ECPublicKey.w提取公钥的椭圆曲线点信息。 - 转换为原始格式: 使用
KeyFactory和ECPublicKeySpec直接生成标准化的公钥。
然而,上述代码输出的 rawPublicKey 可能仍包含一些格式封装。如果需要 精准的 65 字节,仍需手动提取。
方式 2:使用 BouncyCastle 的 API
BouncyCastle 是一个流行的加密库,它提供了更高层次的工具来直接处理椭圆曲线密钥。它可以方便地提取 65 字节的原始公钥。
示例代码
import org.bouncycastle.jce.provider.BouncyCastleProvider import org.bouncycastle.util.encoders.Hex import java.security.KeyPairGenerator import java.security.Security import java.security.spec.ECGenParameterSpec fun createPublicKeyWithBouncyCastle() { // Add BouncyCastle as a security provider Security.addProvider(BouncyCastleProvider()) // Generate the EC key pair val keyPairGenerator = KeyPairGenerator.getInstance("EC", "BC") val ecSpec = ECGenParameterSpec("secp256r1") keyPairGenerator.initialize(ecSpec) val keyPair = keyPairGenerator.generateKeyPair() val mPubECKey = keyPair.public // Extract the raw public key (65 bytes) val rawPublicKey = (mPubECKey as org.bouncycastle.jce.interfaces.ECPublicKey).q.getEncoded(false) println("Raw Public Key (65 bytes): ${Hex.toHexString(rawPublicKey)}") } 解析
- 添加 BouncyCastle: 通过
Security.addProvider注册 BouncyCastle 提供程序。 - 生成密钥对: 使用 BouncyCastle 的
KeyPairGenerator创建椭圆曲线密钥。 - 提取未压缩公钥: 使用
getEncoded(false)提取未压缩(65 字节)的公钥数据,其中false表示不使用压缩格式。
接收到65字节对方公钥后
第一步:把接收到的65字节数据转成公钥格式
// 解析接收到的公钥,保证输出为 ECPublicKey
fun struRcvPublicKey(
message: ByteArray,
headerLength: Int,
hashLength: Int
): ECPublicKey? {
val publicKeyStart = headerLength + 1
val publicKeyLength = message.size - headerLength - hashLength - 1
if (publicKeyStart < 0 || publicKeyLength <= 0 || message.size < publicKeyStart + publicKeyLength) {
return null
}
val publicKeyData = message.sliceArray(publicKeyStart until (publicKeyStart + publicKeyLength))
return when (publicKeyData.size) {
33 -> { // Compressed key
val xBytes = publicKeyData.sliceArray(1 until 33)
val prefix = publicKeyData[0]
val isYOdd = (prefix == 0x03.toByte())
return decompressPublicKey(xBytes, isYOdd)
}
65 -> { // Uncompressed key
val xBytes = publicKeyData.sliceArray(1..32)
val yBytes = publicKeyData.sliceArray(33..64)
return createECPublicKey(xBytes, yBytes)
}
else -> return null
}
}
fun getSecp256r1Spec(): ECParameterSpec {
val keyPairGenerator = KeyPairGenerator.getInstance("EC")
keyPairGenerator.initialize(ECGenParameterSpec("secp256r1"))
val keyPair = keyPairGenerator.generateKeyPair()
val ecPublicKey = keyPair.public as java.security.interfaces.ECPublicKey
return ecPublicKey.params
}
// 解压压缩公钥
fun decompressPublicKey(xBytes: ByteArray, isYOdd: Boolean): ECPublicKey? {
try {
val x = BigInteger(1, xBytes)
val spec = getSecp256r1Spec()
val curve = spec.curve
val p = (curve.field as ECFieldFp).p
// y^2 = x^3 + ax + b (mod p)
val a = curve.a
val b = curve.b
val rhs = x.modPow(BigInteger.valueOf(3), p).add(a.multiply(x)).add(b).mod(p)
val y = rhs.modPow(p.add(BigInteger.ONE).divide(BigInteger.valueOf(4)), p)
val actualY = if (y.testBit(0) == isYOdd) y else p.subtract(y)
return createECPublicKey(x.toByteArray(), actualY.toByteArray())
} catch (e: Exception) {
println("Error decompressing public key: ${e.message}")
return null
}
}
// 根据 x, y 坐标创建 ECPublicKey
fun createECPublicKey(xBytes: ByteArray, yBytes: ByteArray): ECPublicKey? {
try {
val x = BigInteger(1, xBytes)
val y = BigInteger(1, yBytes)
val spec = getSecp256r1Spec()
val ecPoint = ECPoint(x, y)
val keySpec = ECPublicKeySpec(ecPoint, spec)
val keyFactory = KeyFactory.getInstance("EC")
return keyFactory.generatePublic(keySpec) as ECPublicKey
} catch (e: Exception) {
println("Error creating EC public key: ${e.message}")
return null
}
}
第二步:65字节数据转成公钥格式后与自己的私钥计算共享钥
// 获取共享密钥并保存到 ByteArray
fun sharedKeyGetBin(
privateKey: ECPrivateKey,
rcvPublicKey: ECPublicKey,
useCompressed: Boolean
): ByteArray? {
return try {
// 使用 KeyAgreement 执行 ECDH 协商
val keyAgreement = KeyAgreement.getInstance("ECDH")
keyAgreement.init(privateKey)
keyAgreement.doPhase(rcvPublicKey, true)
val sharedSecret = keyAgreement.generateSecret()
// 转换为 ByteArray 并存储到 buffer 中
val buffer = mutableListOf<Byte>()
sharedSecret.getBin(buffer)
buffer.toByteArray()
} catch (e: Exception) {
println("Error calculating shared secret: ${e.message}")
null
}
}
第三步:把计算得到的共享钥转成Byte数据
// 将共享密钥保存到 buffer 中并返回大小
fun ByteArray.getBin(buffer: MutableList<Byte>): Int {
buffer.clear()
buffer.addAll(this.toList())
return this.size
}
第四步:共享钥转成Byte数据后再与自己特定长度的钥匙(不是生成的密钥,是自己商量好的钥匙)进行哈希取值。
var SHA256 = MessageDigest.getInstance("SHA-256")
val sha = SHA256
sha.update(myKey)
sha.update(sharedKey)
val hashTmp = sha.digest()
val tmp16bytes = hashTmp.copyOf(16)
makeKey(tmp16bytes)
fun makeKey(keyData: ByteArray) {
if (keyData.size != KEY_SIZE) {
println("Key length must be 16 bytes for AES-128.")
return
}
this.key = keyData
println("Key set successfully: ${key.toString()}")
}
makekey后的key就是AES的sessionkey,这个sessionkey每次链接后都会根据publickey的改变而改变,这个sessionkey就是后面用来加密和解密用的钥匙。
浙公网安备 33010602011771号