理解橢圓曲線加密算法

橢圓曲線加密算法

橢圓曲線加密算法,即:Elliptic Curve Cryptography,簡(jiǎn)稱ECC,是基于橢圓曲線數(shù)學(xué)理論實(shí)現(xiàn)的一種非對(duì)稱加密算法。相比RSA,ECC優(yōu)勢(shì)是可以使用更短的密鑰,來實(shí)現(xiàn)與RSA相當(dāng)或更高的安全。據(jù)研究,160位ECC加密安全性相當(dāng)于1024位RSA加密,210位ECC加密安全性相當(dāng)于2048位RSA加密。

一般橢圓曲線方程式表示為:(其中a,b,c,d為系數(shù))
> y2=ax3+ bx2+cx+d
典型的橢圓曲線如:y2=x3?4x2+16


橢圓曲線

先擺一個(gè)栗子:

小米只會(huì)加法,不會(huì)乘法,一天小王問小米。

小王: 你知道幾個(gè)5相加等于15?
小米: 3個(gè)。
小王:再考考你,幾個(gè)5相加等于55?
小米:算了半天,終于算出來了,11對(duì)吧。
小王: 我就不行考不到你,那390625呢?
小米:.............

小米很難算到的那個(gè)數(shù),就是公鑰密碼算法中的私鑰(一個(gè)公鑰密碼算法安全的必要條件(非充分)是“由公鑰不能反推出私鑰”),公鑰密碼算法最根本的原理是利用信息的不對(duì)稱性:即掌握私鑰的人在整個(gè)通信過程中掌握最多的信息。
橢圓曲線加密算法是一個(gè)基于加法階數(shù)難求問題的密碼方案。 對(duì)于橢圓曲線來講,橢圓曲線的基點(diǎn)就是例子里面的5,而私鑰就是基點(diǎn)的加法階數(shù)(例子里面的11),公鑰是基點(diǎn)(5)進(jìn)行對(duì)應(yīng)階數(shù)的加法(11次)得到的結(jié)果(55)。

簡(jiǎn)單描述就是:G * k = K (G,K公開,k保密)

  • k就是傳說中的密鑰
  • K就是傳說中的公鑰
  • G一般在算法里寫死了,也叫做曲線參數(shù)(例子中的G點(diǎn))

上述例子相對(duì)簡(jiǎn)單,橢圓曲線加密算法里的加法建立在 “有限域上的二元三次曲線上的點(diǎn)”上 ,組成一個(gè)“有限加法循環(huán)群”。具體的說,這個(gè)加法的幾何定義如下圖,兩個(gè)點(diǎn)的加法結(jié)果是指這兩點(diǎn)的連線和曲線的交點(diǎn)關(guān)于x軸的鏡像。

  • 加法 :過曲線上的兩點(diǎn)A、B畫一條直線,找到直線與橢圓曲線的交點(diǎn),交點(diǎn)關(guān)于x軸對(duì)稱位置的點(diǎn),定義為A+B,即為加法。如下圖所示:A + B = C。

  • 如果A=B時(shí),即兩點(diǎn)重合的情況,將橢圓曲線在A點(diǎn)的切線,與橢圓曲線的交點(diǎn),交點(diǎn)關(guān)于x軸對(duì)稱位置的點(diǎn),定義為A + A,即2A。如圖:


    A+B
  • 將A關(guān)于x軸對(duì)稱位置的點(diǎn)定義為-A,即橢圓曲線的正負(fù)取反運(yùn)算,如圖:


    2A

如果我們從某一點(diǎn)出發(fā)(所謂的單位元,比如正整數(shù)域的1,代表一個(gè)空間里的最基本單元),不停做自增操作(所謂群操作,比如++),枚舉出整個(gè)空間的集合元素。如圖:


因此給定橢圓曲線的某一點(diǎn)G,從G出發(fā),不停做切線,找對(duì)稱點(diǎn),依次得到-2G,2G,-4G,4G,-8G,8G... 。即:當(dāng)給定G點(diǎn)時(shí),已知x,求xG點(diǎn)并不困難。反之,已知xG點(diǎn),求x則非常困難。即Q = NG,N就是我們的私鑰,Q就是我們的公鑰。

私鑰/公鑰

現(xiàn)在我們知道了公鑰(Q)和私鑰(N)的生成的原理,我們?cè)诳纯礄E圓曲線數(shù)字簽名算法(ECDSA)的過程,橢圓曲線數(shù)字簽名算法(ECDSA)是使用橢圓曲線密碼(ECC)對(duì)數(shù)字簽名算法(DSA)的模擬。ECDSA于1999年成為ANSI標(biāo)準(zhǔn),并于2000年成為IEEE和NIST標(biāo)準(zhǔn)。

私鑰主要用于簽名,解密;公鑰主要用于驗(yàn)簽,加密,可以通過私鑰可以計(jì)算出公鑰,反之則不行。
公鑰加密:公鑰加密的內(nèi)容可以用私鑰來解密——只有私鑰持有者才能解密。
私鑰簽名:私鑰簽名的內(nèi)容可以用公鑰驗(yàn)證。公鑰能驗(yàn)證的簽名均可視為私鑰持有人所簽署。

通常需要六個(gè)參數(shù)來描敘一個(gè)特定的橢圓曲線:T = (p, a, b, G, n, h).
p: 代表有限域Fp的那個(gè)質(zhì)數(shù) a,b:橢圓方程的參數(shù) G: 橢圓曲線上的一個(gè)基點(diǎn)G = (xG, yG) n:G在Fp中規(guī)定的序號(hào),一個(gè)質(zhì)數(shù)。 h:余因數(shù)(cofactor),控制選取點(diǎn)的密度。h = #E(Fp) / n。

這里以secp256k1曲線(比特幣簽名所使用的曲線)為例介紹一下公私鑰對(duì)的產(chǎn)生的過成。
secp256k1的參數(shù)為:

  • 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: 固定字節(jié)長(zhǎng)度一個(gè)隨機(jī)數(shù)
    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)
}

本質(zhì)上ECDSA的私鑰就是一個(gè)隨機(jī)數(shù)滿足在曲線G的n階里及k∈(0,n),根據(jù)Q=kG可以計(jì)算出公鑰,生成的私鑰一般為32字節(jié)大小,公鑰通常為64個(gè)字節(jié)大小。如:

私鑰: 0xa8d264b13e6c7949fc31c0c7555fe10849d0f3f05af0a1ffeb8239f68b2fe7e1
公鑰: 0xc22d7010aabab9ff4ee9d9468ade4fbbf8801bd90c89fd060070bc82c387cedf7730a17c9e6a2c6ef2496d54436ee957a121633bf9e9939392a8386d6682aed7

ECDSA簽名算法的輸入是數(shù)據(jù)的哈希值,而不是數(shù)據(jù)的本身,我們假設(shè)用戶的密鑰對(duì):(d, Q);(d為私鑰,Q為公鑰) 待簽名的信息:M; e = Hash(M);簽名:Signature(e) = ( r, s)。

簽名過程:

  • 1、根據(jù)ECC算法隨機(jī)生成一個(gè)臨時(shí)密鑰對(duì)(k, R), R=(xR, yR)。(曲線上R點(diǎn)x,y的大整數(shù))
  • 2、令 r = xR mod n,如果r = 0,則返回步驟1。
  • 3、計(jì)算 H = Hash(M)
  • 4、按照數(shù)據(jù)類型轉(zhuǎn)換規(guī)則,將H轉(zhuǎn)化為一個(gè)big endian的整數(shù)e
  • 5、s = k^-1 (e + r*d) mod n,若s = 0, 則返回步驟1 (k^-1為k對(duì)n的逆元,r為R點(diǎn)的x標(biāo)量)
  • 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
}

驗(yàn)證過程:

  • 1、計(jì)算 u1 = es^-1 mod n, u2 = rs^-1 mod n
  • 2、計(jì)算 R = (xR, yR) = u1G + u2Q, 如果R = 零點(diǎn),則驗(yàn)證該簽名無效
  • 3、令 v = xR mod n
  • 4、若 v == r,則簽名有效,若 v ≠ r, 則簽名無效。

驗(yàn)證接口:

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
}

一個(gè)例子:

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]))
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。