分享
  1. 首页
  2. 文章

Kotlin(Java)与Golang的椭圆曲线密钥交换算法

天地一小儒 · · 1876 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

入坑指南 1:kotlin的Byte是有符号,go的是无符号,所以kotlin的ByteArray打印出来有负数,golang没有。因此会造成ByteArray的size有时是33位,有时是32位。(33位是在前面补了一个0,保证数值不会因为符号位产生变化);
入坑指南 2:kotlin和go的encoded publickey算法不同,导致相互无法转换正确。
入坑指南 3:kotlin的标准secp256r1曲线和go的曲线参数不一样。
入坑指南 4: kotlin和go的密钥交换算法原理相同,实现大有千秋,这里使用java实现go的密钥交换算法。鉴于笔者kotlin/java语言现学现卖,可能已经有实现好的算法库,奈何我即不会找kotlin的底层源代码,又没有找到相对应go的算法库,只好自己实现,能用就行,我还奢求什么呢?

背景

go写的服务端后台,android是客户端之一,需要用到密钥交换(ecdh)算法生成aes密钥加密数据。公私钥生成算法,ECC-P256,也即secp256r1.

go 公私钥生成算法

func GenerateECP256Keypair() (privBytes []byte, pubBytes []byte, err error) {
 priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 if err != nil {
 return nil, nil, fmt.Errorf("Failed to generate ecdsa key using curve p256, error: %v", err)
 }
 privBytes, err = x509.MarshalECPrivateKey(priv)
 if err != nil {
 return nil, nil, fmt.Errorf("Failed to marshal EC private key, error: %v", err)
 }
 pubBytes = elliptic.Marshal(elliptic.P256(), priv.X, priv.Y)
 return
}

坑2注意 pubBytes 的生成方式:pubBytes = elliptic.Marshal(elliptic.P256(), priv.X, priv.Y),google大半天,android的官方手册也翻烂,确认kotlin是没有相对应的方法实现的,无奈只能手撸。

kotlin 公私钥生成算法

fun generateKeyPair(): KeyPair {
 val kpg = KeyPairGenerator.getInstance("EC")
 kpg.initialize(256)
 return kpg.generateKeyPair()
}

密钥交换流程

  1. 服务端、客户端各自生成公私钥后保存在本地,然后通过http/tcp接口交换对方公钥,其中公钥以十六进制形式编码;
  2. 服务端、客户端各自还原对方公钥;
  3. 服务端、客户端各自通过自己的私钥和对方公钥生成aes密钥。

公钥编码

go 公钥编码

func Test_GenerateECP256Keypair(t *testing.T) {
 privBytes, pubBytes, err := GenerateECP256Keypair()
 assert.NoError(t, err)
 fmt.Println("priv:", hex.EncodeToString(privBytes))
 fmt.Println("pub:", hex.EncodeToString(pubBytes))
}

kotlin 公钥编码

@Test
fun formatPublicKey() {
 var clientPubKey = generateKeyPair().public as ECPublicKey
 var ecPubHex = toPublicHex(clientPubKey)
 println("pub: $ecPubHex")
}
fun toPublicHex(publicKey: ECPublicKey): String {
 val pubBytes = ECC.marshal(publicKey.params.curve, publicKey.w)
 return HexUtil.uBytesToHex(pubBytes)
}
val U_BYTE_ARRAY_SIZE = 33
fun marshal(curve: EllipticCurve, g: ECPoint): UByteArray {
 val byteLen = (curve.field.fieldSize + 7) shr 3
 val ret = UByteArray(1 + 2 * byteLen)
 // uncompressed point
 ret[0] = 4.toUByte()
 // copy xBytes into ret
 var xBytes = g.affineX.toByteArray().toUByteArray()
 if (xBytes.size == U_BYTE_ARRAY_SIZE) {
 xBytes = xBytes.copyOfRange(1, U_BYTE_ARRAY_SIZE)
 }
 xBytes.copyInto(ret, 1 + byteLen - xBytes.size)
 // copy yBytes into ret
 var yBytes = g.affineY.toByteArray().toUByteArray()
 if (yBytes.size == U_BYTE_ARRAY_SIZE) {
 yBytes = yBytes.copyOfRange(1, U_BYTE_ARRAY_SIZE)
 }
 yBytes.copyInto(ret, 1 + 2 * byteLen - yBytes.size)
 return ret
}
fun uBytesToHex(data: UByteArray): String {
 return data.toHex()
}
private fun UByteArray.toHex(): String {
 val result = StringBuffer()
 forEach {
 val octet = it.toInt()
 val firstIndex = (octet and 0xF0).ushr(4)
 val secondIndex = octet and 0x0F
 result.append(HEX_CHARS[firstIndex])
 result.append(HEX_CHARS[secondIndex])
 }
 return result.toString()
}

坑1 由于java的byte是有符号的,而go的是无符号的,因此,所有涉及到byte转换的全部采用ubyte处理,否则会出现数据不一致的问题。注意kotlin的十六进制转换二进制都是用的 UByteArray
坑2 官方推荐的publickey编码方式是keypair.public.encoded,然鹅此方式是采用X509格式编码的,具体实现我找不到源码(O_o),也无从判断到底在go中应该是怎样。而go的X509的语法糖并没有类似的方法,如图。因此只好对着go的源码实现了一版kotlin的,go-x509语法糖如图所示:

go-x509.png

go的公钥Marshal源码:
// $GOROOT/src/crypto/elliptic/elliptic.go
func Marshal(curve Curve, x, y *big.Int) []byte {
 byteLen := (curve.Params().BitSize + 7) >> 3
 ret := make([]byte, 1+2*byteLen)
 ret[0] = 4 // uncompressed point
 fmt.Println("ret:", ret)
 xBytes := x.Bytes()
 copy(ret[1+byteLen-len(xBytes):], xBytes)
 yBytes := y.Bytes()
 copy(ret[1+2*byteLen-len(yBytes):], yBytes)
 return ret
}

公钥还原

go公钥还原

func Test_Android_ECDH(t *testing.T) {
 androidPubKey := "045D8A26B69C8929E1B22FFBA03DD3F1FA59A1BD6AA22B5A43A14F8BAA769939055BDE35936605A897B5CF295029FC3F02F4AC22D173FC08795B1258F0AC4B9B25"
 pubBytes, err := hex.DecodeString(androidPubKey)
 if err != nil {
 t.Fatal(err)
 }
 x, y := elliptic.Unmarshal(elliptic.P256(), pubBytes)
 pubkey := &ecdsa.PublicKey{Curve: elliptic.P256(), X: x, Y: y}
}
// $GOROOT/src/crypto/elliptic/elliptic.go
func Unmarshal(curve Curve, data []byte) (x, y *big.Int) {
 byteLen := (curve.Params().BitSize + 7) >> 3
 if len(data) != 1+2*byteLen {
 return
 }
 if data[0] != 4 { // uncompressed form
 return
 }
 p := curve.Params().P
 x = new(big.Int).SetBytes(data[1 : 1+byteLen])
 y = new(big.Int).SetBytes(data[1+byteLen:])
 if x.Cmp(p) >= 0 || y.Cmp(p) >= 0 {
 return nil, nil
 }
 if !curve.IsOnCurve(x, y) {
 return nil, nil
 }
 return
}

kotlin 公钥还原

fun fromPublicHex(pubHex: String): ECPublicKey {
 HexUtil.hexToUBytes(pubHex).run {
 ECC.unmarshal(getParams().curve, this)
 }.run {
 ECPublicKeySpec(this, getParams())
 }.run {
 KeyFactory.getInstance("EC").generatePublic(this)
 }.run {
 return this as ECPublicKey
 }
}
fun hexToUBytes(encoded: String): UByteArray {
 if (encoded.length % 2 !== 0)
 throw IllegalArgumentException("Input string must contain an even number of characters")
 val result = UByteArray(encoded.length / 2)
 val enc = encoded.toCharArray()
 var i = 0
 while (i < enc.size) {
 val curr = StringBuilder(2)
 curr.append(enc[i]).append(enc[i + 1])
 result[i / 2] = Integer.parseInt(curr.toString(), 16).toUByte()
 i += 2
 }
 return result
}
val ERROR_EC_POINT = ECPoint(BigInteger("0"), BigInteger("0"))
// unmarshal converts a point, serialized by Marshal, into an x, y pair.
// It is an error if the point is not in uncompressed form or is not on the curve.
// On error, ECPoint = ERROR_EC_POINT.
fun unmarshal(curve: EllipticCurve, data: UByteArray): ECPoint {
 val byteLen = (curve.field.fieldSize + 7) shr 3
 if (data.size != 1 + 2 * byteLen) {
 return ERROR_EC_POINT
 }
 // uncompressed form
 if (data[0].toInt() != 4) {
 return ERROR_EC_POINT
 }
 // get x from data
 var xBytes = UByteArray(1 + byteLen)
 data.copyInto(xBytes, 1, 1, 1 + byteLen)
 val x = BigInteger(xBytes.toByteArray())
 // get y from data
 var yBytes = UByteArray(1 + byteLen)
 data.copyInto(yBytes, 1, 1 + byteLen, data.size)
 val y = BigInteger(yBytes.toByteArray())
 // check x and y
 val p = getGoLangP(curve)
 if (x >= p || y >= p) {
 return ERROR_EC_POINT
 }
 if (!isOnCurve(curve, x, y)) {
 return ERROR_EC_POINT
 }
 return ECPoint(x, y)
}

注意,在UByteArray转换为BigInteger上时,一定一定要在前面多出一位来取消java的符号位限制,否则整数可能会变成负数。
坑3源码打印出来,go的曲线结构为:

go-curve.png

其中:各个参数为定值:
go-params.png

而kotlin的曲线结构为:
kotlin-curve.png

其中:各个参数为定值:
kotlin-params.png

对比上面4个图可以看到,go中多一个参数N,且go中的P正好是kotlin的a+3,而go中的B则完全对应kotlin中的b。另,Go中的BitSize则对应kotlin中的filedSize,都是256。

因此,在实现用kotlin实现go的unmarshal方法时,必须要做一个变换:

private fun getGoLangP(curve: EllipticCurve): BigInteger {
 return curve.a.add(BigInteger("3"))
}
fun isOnCurve(curve: EllipticCurve, x: BigInteger, y: BigInteger): Boolean {
 // y2 = x3 - 3x + b
 var y2 = y.multiply(y)
 val curveP = getGoLangP(curve)
 y2 = y2.mod(curveP)
 var x3 = x.multiply(x)
 x3 = x3.multiply(x)
 var threeX = x.shl(1)
 threeX = threeX.add(x)
 x3 = x3.subtract(threeX)
 x3 = x3.add(curve.b)
 x3 = x3.mod(curveP)
 return x3.compareTo(y2) == 0
}

密钥交换

go密钥交换

// ECDH is Elliptic Curve Diffie-Hellman as defined in ANSI X9.63 and as described in RFC 3278: "Use of Elliptic
// Curve Cryptography (ECC) Algorithms in Cryptographic Message Syntax (CMS)."
//
// see more detail: https://www.ietf.org/rfc/rfc3278.txt
type ECDH struct{}
// GenerateSharedSecret creates the shared secret and returns it as a sha256 hashed object.
func (ecdh *ECDH) GenerateSharedSecret(priv crypto.PrivateKey, pub crypto.PublicKey) ([]byte, error) {
 privateKey, ok := priv.(*ecdsa.PrivateKey)
 if !ok {
 return nil, errors.New("priv only support ecdsa.PrivateKey point type")
 }
 publicKey, ok := pub.(*ecdsa.PublicKey)
 if !ok {
 return nil, errors.New("pub only support ecdsa.PublicKey point type")
 }
 x, _ := publicKey.Curve.ScalarMult(publicKey.X, publicKey.Y, privateKey.D.Bytes())
 sharedKey := sha256.Sum256(x.Bytes())
 return sharedKey[:], nil
}

注意:在go中,共享密钥的生成是通过publicKey.Curve.ScalarMult(publicKey.X, publicKey.Y, privateKey.D.Bytes())来计算而得的,kotlin官方推荐是这样:

fun generateSharedSecret(privateKey: PrivateKey, publicKey: PublicKey): SecretKey {
 val keyAgreement = KeyAgreement.getInstance("ECDH", org.bouncycastle.jce.provider.BouncyCastleProvider())
 keyAgreement.init(privateKey)
 keyAgreement.doPhase(publicKey, true)
 return keyAgreement.generateSecret("AES")
}

由于源码未可知,和go的区别在哪也不敢轻下断言,故而只能再次手撸kotlin版go的密钥交换。

ScalarMult

func (curve *CurveParams) ScalarMult(Bx, By *big.Int, k []byte) (*big.Int, *big.Int) {
 Bz := new(big.Int).SetInt64(1)
 x, y, z := new(big.Int), new(big.Int), new(big.Int)
 for _, byte := range k {
 for bitNum := 0; bitNum < 8; bitNum++ {
 x, y, z = curve.doubleJacobian(x, y, z)
 if byte&0x80 == 0x80 {
 x, y, z = curve.addJacobian(Bx, By, Bz, x, y, z)
 }
 byte <<= 1
 }
 }
 return curve.affineFromJacobian(x, y, z)
}
// doubleJacobian takes a point in Jacobian coordinates, (x, y, z), and
// returns its double, also in Jacobian form.
func (curve *CurveParams) doubleJacobian(x, y, z *big.Int) (*big.Int, *big.Int, *big.Int) {
 // See https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#doubling-dbl-2001-b
 delta := new(big.Int).Mul(z, z)
 delta.Mod(delta, curve.P)
 gamma := new(big.Int).Mul(y, y)
 gamma.Mod(gamma, curve.P)
 alpha := new(big.Int).Sub(x, delta)
 if alpha.Sign() == -1 {
 alpha.Add(alpha, curve.P)
 }
 alpha2 := new(big.Int).Add(x, delta)
 alpha.Mul(alpha, alpha2)
 alpha2.Set(alpha)
 alpha.Lsh(alpha, 1)
 alpha.Add(alpha, alpha2)
 beta := alpha2.Mul(x, gamma)
 x3 := new(big.Int).Mul(alpha, alpha)
 beta8 := new(big.Int).Lsh(beta, 3)
 beta8.Mod(beta8, curve.P)
 x3.Sub(x3, beta8)
 if x3.Sign() == -1 {
 x3.Add(x3, curve.P)
 }
 x3.Mod(x3, curve.P)
 z3 := new(big.Int).Add(y, z)
 z3.Mul(z3, z3)
 z3.Sub(z3, gamma)
 if z3.Sign() == -1 {
 z3.Add(z3, curve.P)
 }
 z3.Sub(z3, delta)
 if z3.Sign() == -1 {
 z3.Add(z3, curve.P)
 }
 z3.Mod(z3, curve.P)
 beta.Lsh(beta, 2)
 beta.Sub(beta, x3)
 if beta.Sign() == -1 {
 beta.Add(beta, curve.P)
 }
 y3 := alpha.Mul(alpha, beta)
 gamma.Mul(gamma, gamma)
 gamma.Lsh(gamma, 3)
 gamma.Mod(gamma, curve.P)
 y3.Sub(y3, gamma)
 if y3.Sign() == -1 {
 y3.Add(y3, curve.P)
 }
 y3.Mod(y3, curve.P)
 return x3, y3, z3
}
// addJacobian takes two points in Jacobian coordinates, (x1, y1, z1) and
// (x2, y2, z2) and returns their sum, also in Jacobian form.
func (curve *CurveParams) addJacobian(x1, y1, z1, x2, y2, z2 *big.Int) (*big.Int, *big.Int, *big.Int) {
 // See https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#addition-add-2007-bl
 x3, y3, z3 := new(big.Int), new(big.Int), new(big.Int)
 if z1.Sign() == 0 {
 x3.Set(x2)
 y3.Set(y2)
 z3.Set(z2)
 return x3, y3, z3
 }
 if z2.Sign() == 0 {
 x3.Set(x1)
 y3.Set(y1)
 z3.Set(z1)
 return x3, y3, z3
 }
 z1z1 := new(big.Int).Mul(z1, z1)
 z1z1.Mod(z1z1, curve.P)
 z2z2 := new(big.Int).Mul(z2, z2)
 z2z2.Mod(z2z2, curve.P)
 u1 := new(big.Int).Mul(x1, z2z2)
 u1.Mod(u1, curve.P)
 u2 := new(big.Int).Mul(x2, z1z1)
 u2.Mod(u2, curve.P)
 h := new(big.Int).Sub(u2, u1)
 xEqual := h.Sign() == 0
 if h.Sign() == -1 {
 h.Add(h, curve.P)
 }
 i := new(big.Int).Lsh(h, 1)
 i.Mul(i, i)
 j := new(big.Int).Mul(h, i)
 s1 := new(big.Int).Mul(y1, z2)
 s1.Mul(s1, z2z2)
 s1.Mod(s1, curve.P)
 s2 := new(big.Int).Mul(y2, z1)
 s2.Mul(s2, z1z1)
 s2.Mod(s2, curve.P)
 r := new(big.Int).Sub(s2, s1)
 if r.Sign() == -1 {
 r.Add(r, curve.P)
 }
 yEqual := r.Sign() == 0
 if xEqual && yEqual {
 return curve.doubleJacobian(x1, y1, z1)
 }
 r.Lsh(r, 1)
 v := new(big.Int).Mul(u1, i)
 x3.Set(r)
 x3.Mul(x3, x3)
 x3.Sub(x3, j)
 x3.Sub(x3, v)
 x3.Sub(x3, v)
 x3.Mod(x3, curve.P)
 y3.Set(r)
 v.Sub(v, x3)
 y3.Mul(y3, v)
 s1.Mul(s1, j)
 s1.Lsh(s1, 1)
 y3.Sub(y3, s1)
 y3.Mod(y3, curve.P)
 z3.Add(z1, z2)
 z3.Mul(z3, z3)
 z3.Sub(z3, z1z1)
 z3.Sub(z3, z2z2)
 z3.Mul(z3, h)
 z3.Mod(z3, curve.P)
 return x3, y3, z3
}
// affineFromJacobian reverses the Jacobian transform. See the comment at the
// top of the file. If the point is ∞ it returns 0, 0.
func (curve *CurveParams) affineFromJacobian(x, y, z *big.Int) (xOut, yOut *big.Int) {
 if z.Sign() == 0 {
 return new(big.Int), new(big.Int)
 }
 zinv := new(big.Int).ModInverse(z, curve.P)
 zinvsq := new(big.Int).Mul(zinv, zinv)
 xOut = new(big.Int).Mul(x, zinvsq)
 xOut.Mod(xOut, curve.P)
 zinvsq.Mul(zinvsq, zinv)
 yOut = new(big.Int).Mul(y, zinvsq)
 yOut.Mod(yOut, curve.P)
 return
}

对应的kotlin实现为:

Kotlin密钥交换

fun generateSharedSecret(privateKey: ECPrivateKey, publicKey: ECPublicKey): ByteArray {
 val (x, _) = scalarMultiply(
 privateKey.params.curve,
 publicKey.w.affineX,
 publicKey.w.affineY,
 privateKey.s.toByteArray().toUByteArray()
 )
 val data = x.toByteArray()
 if (data.size == U_BYTE_ARRAY_SIZE) {
 data.copyOfRange(1, U_BYTE_ARRAY_SIZE)
 }
 val digest = MessageDigest.getInstance("SHA-256")
 return digest.digest(data)
}

scalarMultiply

fun scalarMultiply(
 curve: EllipticCurve,
 Bx: BigInteger,
 By: BigInteger,
 s: UByteArray
): Pair<BigInteger, BigInteger> {
 var k = s
 if (k.size == U_BYTE_ARRAY_SIZE) {
 k = k.copyOfRange(1, U_BYTE_ARRAY_SIZE)
 }
 val Bz = BigInteger.ONE
 var xyz = Triple(BigInteger.ZERO, BigInteger.ZERO, BigInteger.ZERO)
 for (byte in k) {
 var b = byte
 for (bitNum in 0..7) {
 xyz = curve.doubleJacobian(xyz)
 if (b and 0x80.toUByte() == 0x80.toUByte()) {
 xyz = curve.addJacobian(Bx, By, Bz, xyz)
 }
 b = (b.toInt().shl(1)).toUByte()
 }
 }
 return curve.affineFromJacobian(xyz)
}
// doubleJacobian takes a point in Jacobian coordinates, (x, y, z), and
// returns its double, also in Jacobian form.
fun EllipticCurve.doubleJacobian(xyz: Triple<BigInteger, BigInteger, BigInteger>): Triple<BigInteger, BigInteger, BigInteger> {
 val (x, y, z) = xyz
 val p = getGoLangP(this)
 // See https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#doubling-dbl-2001-b
 var delta = z.multiply(z)
 delta = delta.mod(p)
 var gamma = y.multiply(y)
 gamma = gamma.mod(p)
 var alpha = x.subtract(delta)
 if (alpha.signum() == -1) {
 alpha = alpha.add(p)
 }
 var alpha2 = x.add(delta)
 alpha = alpha.multiply(alpha2)
 alpha2 = alpha.add(BigInteger.ZERO)
 alpha = alpha.shiftLeft(1)
 alpha = alpha.add(alpha2)
 var beta = x.multiply(gamma)
 var x3 = alpha.multiply(alpha)
 var beta8 = beta.shiftLeft(3)
 beta8 = beta8.mod(p)
 x3 = x3.subtract(beta8)
 if (x3.signum() == -1) {
 x3 = x3.add(p)
 }
 x3 = x3.mod(p)
 var z3 = y.add(z)
 z3 = z3.multiply(z3)
 z3 = z3.subtract(gamma)
 if (z3.signum() == -1) {
 z3 = z3.add(p)
 }
 z3 = z3.subtract(delta)
 if (z3.signum() == -1) {
 z3 = z3.add(p)
 }
 z3 = z3.mod(p)
 beta = beta.shiftLeft(2)
 beta = beta.subtract(x3)
 if (beta.signum() == -1) {
 beta = beta.add(p)
 }
 var y3 = alpha.multiply(beta)
 gamma = gamma.multiply(gamma)
 gamma = gamma.shiftLeft(3)
 gamma = gamma.mod(p)
 y3 = y3.subtract(gamma)
 if (y3.signum() == -1) {
 y3 = y3.add(p)
 }
 y3 = y3.mod(p)
 return Triple(x3, y3, z3)
}
// addJacobian takes two points in Jacobian coordinates, (x1, y1, z1) and
// (x2, y2, z2) and returns their sum, also in Jacobian form.
fun EllipticCurve.addJacobian(
 x1: BigInteger,
 y1: BigInteger,
 z1: BigInteger,
 xyz: Triple<BigInteger, BigInteger, BigInteger>
): Triple<BigInteger, BigInteger, BigInteger> {
 val (x2, y2, z2) = xyz
 val p = getGoLangP(this)
 // See https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#addition-add-2007-bl
 var (x3, y3, z3) = Triple(BigInteger.ZERO, BigInteger.ZERO, BigInteger.ZERO)
 if (z1.signum() == 0) {
 x3 = x2.add(BigInteger.ZERO)
 y3 = y2.add(BigInteger.ZERO)
 z3 = z3.add(BigInteger.ZERO)
 return Triple(x3, y3, z3)
 }
 if (z2.signum() == 0) {
 x3 = x1.add(BigInteger.ZERO)
 y3 = y1.add(BigInteger.ZERO)
 z3 = z1.add(BigInteger.ZERO)
 return Triple(x3, y3, z3)
 }
 var z1z1 = z1.multiply(z1)
 z1z1 = z1z1.mod(p)
 var z2z2 = z2.multiply(z2)
 z2z2 = z2z2.mod(p)
 var u1 = x1.multiply(z2z2)
 u1 = u1.mod(p)
 var u2 = x2.multiply(z1z1)
 u2 = u2.mod(p)
 var h = u2.subtract(u1)
 var xEqual = h.signum() == 0
 if (h.signum() == -1) {
 h = h.add(p)
 }
 var i = h.shiftLeft(1)
 i = i.multiply(i)
 var j = h.multiply(i)
 var s1 = y1.multiply(z2)
 s1 = s1.multiply(z2z2)
 s1 = s1.mod(p)
 var s2 = y2.multiply(z1)
 s2 = s2.multiply(z1z1)
 s2 = s2.mod(p)
 var r = s2.subtract(s1)
 if (r.signum() == -1) {
 r = r.add(p)
 }
 var yEqual = r.signum() == 0
 if (xEqual && yEqual) {
 return this.doubleJacobian(Triple(x1, y1, z1))
 }
 r = r.shiftLeft(1)
 var v = u1.multiply(i)
 x3 = r.add(BigInteger.ZERO)
 x3 = x3.multiply(x3)
 x3 = x3.subtract(j)
 x3 = x3.subtract(v)
 x3 = x3.subtract(v)
 x3 = x3.mod(p)
 y3 = r.add(BigInteger.ZERO)
 v = v.subtract(x3)
 y3 = y3.multiply(v)
 s1 = s1.multiply(j)
 s1 = s1.shiftLeft(1)
 y3 = y3.subtract(s1)
 y3 = y3.mod(p)
 z3 = z1.add(z2)
 z3 = z3.multiply(z3)
 z3 = z3.subtract(z1z1)
 z3 = z3.subtract(z2z2)
 z3 = z3.multiply(h)
 z3 = z3.mod(p)
 return Triple(x3, y3, z3)
}
// affineFromJacobian reverses the Jacobian transform. See the comment at the
// top of the file. If the point is ∞ it returns 0, 0.
fun EllipticCurve.affineFromJacobian(
 xyz: Triple<BigInteger, BigInteger, BigInteger>
): Pair<BigInteger, BigInteger> {
 val (x, y, z) = xyz
 val p = getGoLangP(this)
 if (z.signum() == 0) {
 return Pair(BigInteger.ZERO, BigInteger.ZERO)
 }
 var zinv = z.modInverse(p)
 var zinvsq = zinv.multiply(zinv)
 var xOut = x.multiply(zinvsq)
 xOut = xOut.mod(p)
 zinvsq = zinvsq.multiply(zinv)
 var yOut = y.multiply(zinvsq)
 yOut = yOut.mod(p)
 return Pair(xOut, yOut)
}

密钥测试

go生成密钥的密钥对为:

var (
 privHexForTests = "307702010104207843249525ae7f43e623f5bb2b28bb8b22420e8b07d14212c12ce367e980f568a00a06082a8648ce3d030107a14403420004deb43a5bb4c34cf8db53311d4d9f95d2356b8c011349ecb04fc00b73c303bc9dc0675f4ca45a562f589b993a94129482eb9b03f259ce8982e525927c3f70fdbe"
 pubHexForTests = "04deb43a5bb4c34cf8db53311d4d9f95d2356b8c011349ecb04fc00b73c303bc9dc0675f4ca45a562f589b993a94129482eb9b03f259ce8982e525927c3f70fdbe"
)

kotlin采用随机生成,不做固定(待改进,用生成好的固定测试密钥对)

val keypair = generateKeyPair()
println("pubHex:${toPublicHex(keypair.public as ECPublicKey)}")

生成android客户端公私钥对后,服务端生成共享密钥需要用到客户端的公钥,因此打印出来放入服务端。

kotlin单元测试

fun generateSharedSecret_isCorrect() {
 val keypair = generateKeyPair()
 println("pubHex:${toPublicHex(keypair.public as ECPublicKey)}")
 val serverPubHex = "04deb43a5bb4c34cf8db53311d4d9f95d2356b8c011349ecb04fc00b73c303bc9dc0675f4ca45a562f589b993a94129482eb9b03f259ce8982e525927c3f70fdbe"
 val ecPubKey = ECCP256.fromPublicHex(serverPubHex)
 var aesKey = ECDH.generateSharedSecret(keypair.private as ECPrivateKey, ecPubKey)
 val aesHex = HexUtil.bytesToHex(aesKey)
 println("aesHex:$aesHex")
}

结果为:

pubHex:04c4fe11531633ca616d2334377396095fca56dc47ac48f2b55b7f7a97c0e7ce529a779d9e099d21be8647db63da946a0b54b8b07a02795ec2074046b9e30749e3
aesHex:12c4e2f52bfd953c75a32503f37e89bba00a0b941544ed632ca5c1837d0a3340

注意:由于kotlin的密钥是随机生成,所以上述结果必然不会相同,但只要aesHex和服务端相同即可

go单元测试

func Test_Android_ECDH(t *testing.T) {
 androidPubKey := "04ac0625a2554c9075dc463e1976ccba1f2b837d8276383556e0fc07c5d673e329cf2e6f291bfe0be8256ba28fa3828427b7b2dae3aee3dcb3249cdb94f2c38684"
 pubBytes, err := hex.DecodeString(androidPubKey)
 if err != nil {
 t.Fatal(err)
 }
 x, y := elliptic.Unmarshal(elliptic.P256(), pubBytes)
 pubkey := &ecdsa.PublicKey{Curve: elliptic.P256(), X: x, Y: y}
 priv, err := FromPrivHex(privHexForTests)
 if err != nil {
 t.Fatal(err)
 }
 ecdh := &ECDH{}
 secretkey, err := ecdh.GenerateSharedSecret(priv, pubkey)
 assert.NoError(t, err)
 fmt.Println("secretkey:", hex.EncodeToString(secretkey))
}

结果为:

secretkey: 12c4e2f52bfd953c75a32503f37e89bba00a0b941544ed632ca5c1837d0a3340

注意,由于kotlin的密钥是随机生成,所以 androidPubKey 需要手动填入。可以看到,客户端和服务端生成的共享密钥是一致的。


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:天地一小儒

查看原文:Kotlin(Java)与Golang的椭圆曲线密钥交换算法

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
1876 次点击
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏