jamiechoo

 

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 标准编码格式的公钥数据。这个编码包括以下几个部分:

  1. SubjectPublicKeyInfo 结构

    • 标识算法(如 1.2.840.10045.2.11.2.840.10045.3.1.7 对应 ECsecp256r1)。
    • 原始椭圆曲线公钥数据。
  2. 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) }}")
}

解析

  1. 生成密钥对: 使用标准 KeyPairGeneratorsecp256r1 曲线生成密钥对。
  2. 提取点坐标: 通过 ECPublicKey.w 提取公钥的椭圆曲线点信息。
  3. 转换为原始格式: 使用 KeyFactoryECPublicKeySpec 直接生成标准化的公钥。

然而,上述代码输出的 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)}")
}

解析

  1. 添加 BouncyCastle: 通过 Security.addProvider 注册 BouncyCastle 提供程序。
  2. 生成密钥对: 使用 BouncyCastle 的 KeyPairGenerator 创建椭圆曲线密钥。
  3. 提取未压缩公钥: 使用 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就是后面用来加密和解密用的钥匙。

 

posted on 2024-11-26 20:42  jamiechoo  阅读(91)  评论(0)    收藏  举报

导航