入坑指南 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() | |
} |
密钥交换流程
- 服务端、客户端各自生成公私钥后保存在本地,然后通过http/tcp接口交换对方公钥,其中公钥以十六进制形式编码;
- 服务端、客户端各自还原对方公钥;
- 服务端、客户端各自通过自己的私钥和对方公钥生成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 公钥编码
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 { | |
// y² = x³ - 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
需要手动填入。可以看到,客户端和服务端生成的共享密钥是一致的。