为了账号安全,请及时绑定邮箱和手机立即绑定

GO语言ECDSA验证有效签名无效

GO语言ECDSA验证有效签名无效

Go
慕盖茨4494581 2023-02-14 16:40:05
我在我的 C++ 程序中编写了 ECDSA 函数。当我测试我的签名时,它在我的 C++ ECDSA 验证函数中工作正常,但不是每个测试都通过了 GO 语言。因此,我尝试将我的公钥(从我的 C++ 中)作为缓冲区的十六进制字符串(33 字节)导出到 GO 语言。然后,我使用ellipitc.UnmarshalCompressed在 GO 程序上检索我的公钥。后来,我发现使用标量乘法从密钥生成公钥时,Unmarshal 公钥具有不同的 Y 坐标。我把代码放在下面。import (    "crypto/ecdsa"    "crypto/elliptic"    "crypto/sha256"    "encoding/hex"    "fmt"    "log"    "math/big"    "os")    func main() {    /* UnmarshalCompressed public key from C++ buffer (hex string) */    publicKeyBufferFromCplusplus, err := hex.DecodeString("02d36b0e521ca9a28cd6f2ddc56dc0973215702f6f67ed0670b9bc9a98c28d473b")    if err != nil {        fmt.Println("Unable to convert hex to byte. ", err)    }    pk := new(ecdsa.PublicKey)    pk.Curve = elliptic.P256()    pk.X, pk.Y = elliptic.UnmarshalCompressed(elliptic.P256(), publicKeyBufferFromCplusplus[:])    /* Generate the key pair in GO, using the private key (as decimal) from C++ */    expect_sk := new(ecdsa.PrivateKey)    expect_sk.D, _ = new(big.Int).SetString("50228957095953179898827503463423289296009712707225507368245266147079499081684", 10)    expect_sk.PublicKey.Curve = elliptic.P256()    expect_sk.PublicKey.X, expect_sk.PublicKey.Y = expect_sk.PublicKey.Curve.ScalarBaseMult(expect_sk.D.Bytes())    expect_pk := expect_sk.PublicKey    /* compare the two public keys, the X coordinate is the same, but Y is different */    fmt.Printf("pk_x:\t\t%d\n", pk.X)    fmt.Printf("expect pk_x:\t%d\n\n", expect_pk.X)    fmt.Printf("pk_y:\t\t%d\n", pk.Y)    fmt.Printf("expect pk_y:\t%d\n", expect_pk.Y)}这是终端的结果pk_x:           95627162525183504786576659676808415919520991299985517290103803735976207796027expect pk_x:    95627162525183504786576659676808415919520991299985517290103803735976207796027pk_y:           106312815215663533204607583749797836088594130128596587441436180287153537381066expect pk_y:    9479273994692715558089863199609737441492013286693726754097451021713560472885请注意,不同之处在于 Y 坐标,而 X 坐标是相同的。
查看完整描述

1 回答

?
翻过高山走不出你

TA贡献1875条经验 获得超3个赞

我发现我的 C++ 椭圆曲线库在将公钥(点)压缩(编组)为SEC 1,版本 2.0,第 2.3.3 节形式时有一个错误。有些点第一个字节错误(从奇数到偶数,或从偶数到奇数)。

1*G     GO buffer: 0x036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296     C++ buffer: 0x026b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296

2*G     GO buffer: 0x037cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978     C++ buffer: 0x037cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978

3*G     GO buffer: 0x025ecbe4d1a6330a44c8f7ef951d4bf165e6c6b721efada985fb41661bc6e7fd6c     C++ buffer: 0x035ecbe4d1a6330a44c8f7ef951d4bf165e6c6b721efada985fb41661bc6e7fd6c

4*G     GO buffer: 0x02e2534a3532d08fbba02dde659ee62bd0031fe2db785596ef509302446b030852     C++ buffer: 0x02e2534a3532d08fbba02dde659ee62bd0031fe2db785596ef509302446b030852

5*G     GO buffer: 0x0251590b7a515140d2d784c85608668fdfef8c82fd1f5be52421554a0dc3d033ed     C++ buffer: 0x0251590b7a515140d2d784c85608668fdfef8c82fd1f5be52421554a0dc3d033ed

6*G     GO buffer: 0x02b01a172a76a4602c92d3242cb897dde3024c740debb215b4c6b0aae93c2291a9     C++ buffer: 0x03b01a172a76a4602c92d3242cb897dde3024c740debb215b4c6b0aae93c2291a9

7*G     GO buffer: 0x028e533b6fa0bf7b4625bb30667c01fb607ef9f8b8a80fef5b300628703187b2a3     C++ buffer: 0x038e533b6fa0bf7b4625bb30667c01fb607ef9f8b8a80fef5b300628703187b2a3

8*G     GO buffer: 0x0262d9779dbee9b0534042742d3ab54cadc1d238980fce97dbb4dd9dc1db6fb393     C++ buffer: 0x0362d9779dbee9b0534042742d3ab54cadc1d238980fce97dbb4dd9dc1db6fb393

9*G     GO buffer: 0x02ea68d7b6fedf0b71878938d51d71f8729e0acb8c2c6df8b3d79e8a4b90949ee0     C++ buffer: 0x02ea68d7b6fedf0b71878938d51d71f8729e0acb8c2c6df8b3d79e8a4b90949ee0

因此,一些公钥(点)被导出为而{x,-y} 不是{x,y}

原来的

椭圆曲线 (EC) 公钥遵循高效密码术标准 (SEC),SEC 1:椭圆曲线密码术部分中的 2.3.3 椭圆曲线点到八位字节字符串转换。

如果 y 坐标为偶数,则压缩形式(作为字节)的公钥的第一个字节为0x02 。否则,如果 y 坐标为奇数,则为 0x03


在数学上,只给定一个 x 坐标

y^2 = x^3 + ax + b (mod p)

上面的等式有两个解,(x, y)(x, -y)因为 y^2 = (-y)^2

此外,-y 等于 (p - y),因为它是整数模素数p的字段。

由于p 是素数且 p!=2,因此p 是奇数。然后,如果y是奇数,则-y(或(py))是偶数,反之亦然。


ECDSA 验证算法不需要公钥的 y 坐标,因为它只需要它的 x 坐标。因此,当我检查签名元组{r,s}时,我将使用相同 x 坐标但不同 y 坐标(从奇数翻转到偶数,反之亦然)的公钥对其进行检查。我真的不知道为什么 GO 语言 ECDSA 包只允许两个 y 坐标值之一。

下面是我的代码示例。假设我有一个压缩的公钥作为缓冲区publicKeyBuffer[:]

然后我可以解组它以获得 x 坐标和 y 坐标

  pk := new(ecdsa.PublicKey)

    pk.Curve = elliptic.P256()    

    pk.X, pk.Y = elliptic.UnmarshalCompressed(elliptic.P256(), publicKeyBuffer[:])

我们可以检查签名元组 {r,s} 是否有效


valid := ecdsa.Verify(pk, hash[:], r, s)

如果签名无效,即valid == false尝试将值更改pk.Y为对应的值。您可以通过两种简单的方式做到这一点。


第一种方法,您将公钥缓冲区的第一个字节从 更改为0x02或0x03反之亦然。您可以通过XOR使用(仅 1)执行第一个字节来实现此目的0x01。


    valid = ecdsa.Verify(pk, hash[:], r, s)


    /* The 1st way */

    if !valid {

        publicKeyBuffer[0] ^= 1 // change the y-coordinate by switching 0x02 to 0x03 (or vice versa) of public key buffer

        pk.X, pk.Y = elliptic.UnmarshalCompressed(elliptic.P256(), publicKeyBuffer[:])

        valid = ecdsa.Verify(pk, hash[:], r, s)

    }

    fmt.Println("signature verified:", valid) 

在第二种方式中,我们将 y 坐标更改为其 (py) 值,即,将其更改为 (-y)


    valid = ecdsa.Verify(pk, hash[:], r, s)


    /* The 2nd way */

    if !valid {

        pk.Y = new(big.Int).Sub(pk.Curve.Params().P, pk.Y) // replace y-coordinate pk.Y with (-y), Note. (-y) is equal (p - y)

        valid = ecdsa.Verify(pk, hash[:], r, s)

    }

    fmt.Println("signature verified:", valid) 

如果签名仍然无效,那么这与公钥 Unmarshal 无关。


PS 我不知道为什么 GO ECDSA 语言不允许公钥的其他 y 坐标值


查看完整回答
反对 回复 2023-02-14
  • 1 回答
  • 0 关注
  • 303 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号