Kotlin(Java)與Golang的橢圓曲線密鑰交換算法

入坑指南 1:kotlin的Byte是有符號(hào)吏恭,go的是無符號(hào),所以kotlin的ByteArray打印出來有負(fù)數(shù),golang沒有。因此會(huì)造成ByteArray的size有時(shí)是33位颅停,有時(shí)是32位。(33位是在前面補(bǔ)了一個(gè)0掠拳,保證數(shù)值不會(huì)因?yàn)榉?hào)位產(chǎn)生變化)癞揉;
入坑指南 2:kotlin和go的encoded publickey算法不同,導(dǎo)致相互無法轉(zhuǎn)換正確碳想。
入坑指南 3:kotlin的標(biāo)準(zhǔn)secp256r1曲線和go的曲線參數(shù)不一樣。
入坑指南 4: kotlin和go的密鑰交換算法原理相同毁靶,實(shí)現(xiàn)大有千秋胧奔,這里使用java實(shí)現(xiàn)go的密鑰交換算法。鑒于筆者kotlin/java語言現(xiàn)學(xué)現(xiàn)賣预吆,可能已經(jīng)有實(shí)現(xiàn)好的算法庫龙填,奈何我即不會(huì)找kotlin的底層源代碼,又沒有找到相對(duì)應(yīng)go的算法庫拐叉,只好自己實(shí)現(xiàn)岩遗,能用就行,我還奢求什么呢凤瘦?

背景

go寫的服務(wù)端后臺(tái)宿礁,android是客戶端之一,需要用到密鑰交換(ecdh)算法生成aes密鑰加密數(shù)據(jù)蔬芥。公私鑰生成算法梆靖,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的官方手冊(cè)也翻爛,確認(rèn)kotlin是沒有相對(duì)應(yīng)的方法實(shí)現(xiàn)的乎婿,無奈只能手?jǐn)]测僵。

kotlin 公私鑰生成算法

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

密鑰交換流程

  1. 服務(wù)端、客戶端各自生成公私鑰后保存在本地谢翎,然后通過http/tcp接口交換對(duì)方公鑰捍靠,其中公鑰以十六進(jìn)制形式編碼;
  2. 服務(wù)端森逮、客戶端各自還原對(duì)方公鑰剂公;
  3. 服務(wù)端、客戶端各自通過自己的私鑰和對(duì)方公鑰生成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是有符號(hào)的纲辽,而go的是無符號(hào)的颜武,因此,所有涉及到byte轉(zhuǎn)換的全部采用ubyte處理拖吼,否則會(huì)出現(xiàn)數(shù)據(jù)不一致的問題鳞上。注意kotlin的十六進(jìn)制轉(zhuǎn)換二進(jìn)制都是用的 UByteArray
坑2 官方推薦的publickey編碼方式是keypair.public.encoded吊档,然鵝此方式是采用X509格式編碼的篙议,具體實(shí)現(xiàn)我找不到源碼(O_o),也無從判斷到底在go中應(yīng)該是怎樣怠硼。而go的X509的語法糖并沒有類似的方法鬼贱,如圖。因此只好對(duì)著go的源碼實(shí)現(xiàn)了一版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轉(zhuǎn)換為BigInteger上時(shí),一定一定要在前面多出一位來取消java的符號(hào)位限制葡秒,否則整數(shù)可能會(huì)變成負(fù)數(shù)姻乓。
坑3源碼打印出來,go的曲線結(jié)構(gòu)為:

go-curve.png

其中:各個(gè)參數(shù)為定值:
go-params.png

而kotlin的曲線結(jié)構(gòu)為:
kotlin-curve.png

其中:各個(gè)參數(shù)為定值:
kotlin-params.png

對(duì)比上面4個(gè)圖可以看到眯牧,go中多一個(gè)參數(shù)N蹋岩,且go中的P正好是kotlin的a+3,而go中的B則完全對(duì)應(yīng)kotlin中的b学少。另剪个,Go中的BitSize則對(duì)應(yīng)kotlin中的filedSize,都是256版确。

因此禁偎,在實(shí)現(xiàn)用kotlin實(shí)現(xiàn)go的unmarshal方法時(shí),必須要做一個(gè)變換:

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())來計(jì)算而得的如暖,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的區(qū)別在哪也不敢輕下斷言忌堂,故而只能再次手?jǐn)]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
}

對(duì)應(yīng)的kotlin實(shí)現(xiàn)為:

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)
}

密鑰測(cè)試

go生成密鑰的密鑰對(duì)為:

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

kotlin采用隨機(jī)生成,不做固定(待改進(jìn)士修,用生成好的固定測(cè)試密鑰對(duì))

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

生成android客戶端公私鑰對(duì)后枷遂,服務(wù)端生成共享密鑰需要用到客戶端的公鑰,因此打印出來放入服務(wù)端棋嘲。

kotlin單元測(cè)試

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")
}

結(jié)果為:

pubHex:04c4fe11531633ca616d2334377396095fca56dc47ac48f2b55b7f7a97c0e7ce529a779d9e099d21be8647db63da946a0b54b8b07a02795ec2074046b9e30749e3
aesHex:12c4e2f52bfd953c75a32503f37e89bba00a0b941544ed632ca5c1837d0a3340

注意:由于kotlin的密鑰是隨機(jī)生成酒唉,所以上述結(jié)果必然不會(huì)相同,但只要aesHex和服務(wù)端相同即可

go單元測(cè)試

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))
}

結(jié)果為:

secretkey: 12c4e2f52bfd953c75a32503f37e89bba00a0b941544ed632ca5c1837d0a3340

注意沸移,由于kotlin的密鑰是隨機(jī)生成痪伦,所以 androidPubKey 需要手動(dòng)填入侄榴。可以看到网沾,客戶端和服務(wù)端生成的共享密鑰是一致的癞蚕。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市辉哥,隨后出現(xiàn)的幾起案子桦山,更是在濱河造成了極大的恐慌,老刑警劉巖醋旦,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件恒水,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡饲齐,警方通過查閱死者的電腦和手機(jī)钉凌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來箩张,“玉大人甩骏,你說我怎么就攤上這事窗市∠瓤叮” “怎么了?”我有些...
    開封第一講書人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵咨察,是天一觀的道長(zhǎng)论熙。 經(jīng)常有香客問我,道長(zhǎng)摄狱,這世上最難降的妖魔是什么脓诡? 我笑而不...
    開封第一講書人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮媒役,結(jié)果婚禮上祝谚,老公的妹妹穿的比我還像新娘。我一直安慰自己酣衷,他們只是感情好交惯,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著穿仪,像睡著了一般席爽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上啊片,一...
    開封第一講書人閱讀 51,598評(píng)論 1 305
  • 那天只锻,我揣著相機(jī)與錄音,去河邊找鬼紫谷。 笑死齐饮,一個(gè)胖子當(dāng)著我的面吹牛捐寥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播沈矿,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼上真,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了羹膳?” 一聲冷哼從身側(cè)響起睡互,我...
    開封第一講書人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎陵像,沒想到半個(gè)月后就珠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡醒颖,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年妻怎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泞歉。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡逼侦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出腰耙,到底是詐尸還是另有隱情榛丢,我是刑警寧澤,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布挺庞,位于F島的核電站晰赞,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏选侨。R本人自食惡果不足惜掖鱼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望援制。 院中可真熱鬧戏挡,春花似錦、人聲如沸晨仑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽寻歧。三九已至掌栅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間码泛,已是汗流浹背猾封。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留噪珊,地道東北人晌缘。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓齐莲,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親磷箕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子选酗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355