理解椭圆曲线加密算法

椭圆曲线加密算法

椭圆曲线加密算法,即:Elliptic Curve Cryptography,简称ECC,是基于椭圆曲线数学理论实现的一种非对称加密算法。相比RSA,ECC优势是可以使用更短的密钥,来实现与RSA相当或更高的安全。据研究,160位ECC加密安全性相当于1024位RSA加密,210位ECC加密安全性相当于2048位RSA加密。

一般椭圆曲线方程式表示为:(其中a,b,c,d为系数)
> y2=ax3+ bx2+cx+d
典型的椭圆曲线如:y2=x3−4x2+16


椭圆曲线

先摆一个栗子:

小米只会加法,不会乘法,一天小王问小米。

小王: 你知道几个5相加等于15?
小米: 3个。
小王:再考考你,几个5相加等于55?
小米:算了半天,终于算出来了,11对吧。
小王: 我就不行考不到你,那390625呢?
小米:.............

小米很难算到的那个数,就是公钥密码算法中的私钥(一个公钥密码算法安全的必要条件(非充分)是“由公钥不能反推出私钥”),公钥密码算法最根本的原理是利用信息的不对称性:即掌握私钥的人在整个通信过程中掌握最多的信息。
椭圆曲线加密算法是一个基于加法阶数难求问题的密码方案。 对于椭圆曲线来讲,椭圆曲线的基点就是例子里面的5,而私钥就是基点的加法阶数(例子里面的11),公钥是基点(5)进行对应阶数的加法(11次)得到的结果(55)。

简单描述就是:G * k = K (G,K公开,k保密)

  • k就是传说中的密钥
  • K就是传说中的公钥
  • G一般在算法里写死了,也叫做曲线参数(例子中的G点)

上述例子相对简单,椭圆曲线加密算法里的加法建立在 “有限域上的二元三次曲线上的点”上 ,组成一个“有限加法循环群”。具体的说,这个加法的几何定义如下图,两个点的加法结果是指这两点的连线和曲线的交点关于x轴的镜像。

  • 加法 :过曲线上的两点A、B画一条直线,找到直线与椭圆曲线的交点,交点关于x轴对称位置的点,定义为A+B,即为加法。如下图所示:A + B = C。

  • 如果A=B时,即两点重合的情况,将椭圆曲线在A点的切线,与椭圆曲线的交点,交点关于x轴对称位置的点,定义为A + A,即2A。如图:


    A+B
  • 将A关于x轴对称位置的点定义为-A,即椭圆曲线的正负取反运算,如图:


    2A

如果我们从某一点出发(所谓的单位元,比如正整数域的1,代表一个空间里的最基本单元),不停做自增操作(所谓群操作,比如++),枚举出整个空间的集合元素。如图:


因此给定椭圆曲线的某一点G,从G出发,不停做切线,找对称点,依次得到-2G,2G,-4G,4G,-8G,8G... 。即:当给定G点时,已知x,求xG点并不困难。反之,已知xG点,求x则非常困难。即Q = NG,N就是我们的私钥,Q就是我们的公钥。

私钥/公钥

现在我们知道了公钥(Q)和私钥(N)的生成的原理,我们在看看椭圆曲线数字签名算法(ECDSA)的过程,椭圆曲线数字签名算法(ECDSA)是使用椭圆曲线密码(ECC)对数字签名算法(DSA)的模拟。ECDSA于1999年成为ANSI标准,并于2000年成为IEEE和NIST标准。

私钥主要用于签名,解密;公钥主要用于验签,加密,可以通过私钥可以计算出公钥,反之则不行。
公钥加密:公钥加密的内容可以用私钥来解密——只有私钥持有者才能解密。
私钥签名:私钥签名的内容可以用公钥验证。公钥能验证的签名均可视为私钥持有人所签署。

通常需要六个参数来描叙一个特定的椭圆曲线:T = (p, a, b, G, n, h).
p: 代表有限域Fp的那个质数 a,b:椭圆方程的参数 G: 椭圆曲线上的一个基点G = (xG, yG) n:G在Fp中规定的序号,一个质数。 h:余因数(cofactor),控制选取点的密度。h = #E(Fp) / n。

这里以secp256k1曲线(比特币签名所使用的曲线)为例介绍一下公私钥对的产生的过成。
secp256k1的参数为:

  • p = FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F = 2^256 − 2^32 − 2^9 − 2^8 − 2^7 − 2^6 − 2^4 − 1
  • a = 0
  • b = 7
  • G =04 79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798 483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8
  • n = FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141
  • h = 01
// randFieldElement returns a random element of the field underlying the given
// curve using the procedure given in [NSA] A.2.1.
func randFieldElement(c elliptic.Curve, rand io.Reader) (k *big.Int, err error) {
    params := c.Params()
    b := make([]byte, params.BitSize/8+8)
    _, err = io.ReadFull(rand, b)
    if err != nil {
        return
    }
    // k: 固定字节长度一个随机数
    k = new(big.Int).SetBytes(b)
    // 使k满足N
    n := new(big.Int).Sub(params.N, one)
    k.Mod(k, n)
    k.Add(k, one)
    return
}

// GenerateKey generates a public and private key pair.
func GenerateKey(c elliptic.Curve, rand io.Reader) (*PrivateKey, error) {
    k, err := randFieldElement(c, rand)
    if err != nil {
        return nil, err
    }

    priv := new(PrivateKey)
    priv.PublicKey.Curve = c
    priv.D = k
    // 公钥
    priv.PublicKey.X, priv.PublicKey.Y = c.ScalarBaseMult(k.Bytes())
    return priv, nil
}

func GenerateKey() (*ecdsa.PrivateKey, error) {
    // secp256k1曲线
    return ecdsa.GenerateKey(S256(), rand.Reader)
}

本质上ECDSA的私钥就是一个随机数满足在曲线G的n阶里及k∈(0,n),根据Q=kG可以计算出公钥,生成的私钥一般为32字节大小,公钥通常为64个字节大小。如:

私钥: 0xa8d264b13e6c7949fc31c0c7555fe10849d0f3f05af0a1ffeb8239f68b2fe7e1
公钥: 0xc22d7010aabab9ff4ee9d9468ade4fbbf8801bd90c89fd060070bc82c387cedf7730a17c9e6a2c6ef2496d54436ee957a121633bf9e9939392a8386d6682aed7

ECDSA签名算法的输入是数据的哈希值,而不是数据的本身,我们假设用户的密钥对:(d, Q);(d为私钥,Q为公钥) 待签名的信息:M; e = Hash(M);签名:Signature(e) = ( r, s)。

签名过程:

  • 1、根据ECC算法随机生成一个临时密钥对(k, R), R=(xR, yR)。(曲线上R点x,y的大整数)
  • 2、令 r = xR mod n,如果r = 0,则返回步骤1。
  • 3、计算 H = Hash(M)
  • 4、按照数据类型转换规则,将H转化为一个big endian的整数e
  • 5、s = k^-1 (e + r*d) mod n,若s = 0, 则返回步骤1 (k^-1为k对n的逆元,r为R点的x标量)
  • 6、输出的S =(r,s)即为签名。

签名接口:

func Sign(msg []byte, seckey []byte) ([]byte, error) {
    if len(msg) != 32 {
        return nil, ErrInvalidMsgLen
    }
    if len(seckey) != 32 {
        return nil, ErrInvalidKey
    }
    seckeydata := (*C.uchar)(unsafe.Pointer(&seckey[0]))
    if C.secp256k1_ec_seckey_verify(context, seckeydata) != 1 {
        return nil, ErrInvalidKey
    }

    var (
        msgdata   = (*C.uchar)(unsafe.Pointer(&msg[0]))
        noncefunc = C.secp256k1_nonce_function_rfc6979
        sigstruct C.secp256k1_ecdsa_recoverable_signature
    )
    if C.secp256k1_ecdsa_sign_recoverable(context, &sigstruct, msgdata, seckeydata, noncefunc, nil) == 0 {
        return nil, ErrSignFailed
    }

    var (
        sig     = make([]byte, 65)
        sigdata = (*C.uchar)(unsafe.Pointer(&sig[0]))
        recid   C.int
    )
    C.secp256k1_ecdsa_recoverable_signature_serialize_compact(context, sigdata, &recid, &sigstruct)
    sig[64] = byte(recid) // add back recid to get 65 bytes sig
    return sig, nil
}

验证过程:

  • 1、计算 u1 = es^-1 mod n, u2 = rs^-1 mod n
  • 2、计算 R = (xR, yR) = u1G + u2Q, 如果R = 零点,则验证该签名无效
  • 3、令 v = xR mod n
  • 4、若 v == r,则签名有效,若 v ≠ r, 则签名无效。

验证接口:

func VerifySignature(pubkey, msg, signature []byte) bool {
    if len(msg) != 32 || len(signature) != 64 || len(pubkey) == 0 {
        return false
    }
    sigdata := (*C.uchar)(unsafe.Pointer(&signature[0]))
    msgdata := (*C.uchar)(unsafe.Pointer(&msg[0]))
    keydata := (*C.uchar)(unsafe.Pointer(&pubkey[0]))
    return C.secp256k1_ext_ecdsa_verify(context, sigdata, msgdata, keydata, C.size_t(len(pubkey))) != 0
}

一个例子:

func Test_09(t *testing.T) {
    priv, _ := crypto.GenerateKey()
    pk := crypto.FromECDSAPub(&priv.PublicKey)
    fmt.Println("priv:", hex.EncodeToString(crypto.FromECDSA(priv)))
    fmt.Println("pubkey:", hex.EncodeToString(pk))
    h := crypto.Keccak256([]byte{1, 2, 3, 4, 5})
    s, err := crypto.Sign(h[:], priv)
    if err != nil {
        fmt.Println("sign err:", err)
    } else {
        fmt.Println("sign_data:", hex.EncodeToString(s))
        fmt.Println(crypto.VerifySignature(pk, h[:], s[:64]))
    }
}

你可能感兴趣的:(理解椭圆曲线加密算法)