RSA加解密及簽名算法的技術原理及其Go語言實現

  對稱加密中,加密和解密使用相同的密鑰,因此必須向解密者配送密鑰,即密鑰配送問題。而非對稱加密中,由於加密和解密分別使用公鑰和私鑰,而公鑰是公開的,因此可以規避密鑰配送問題。非對稱加密算法,也稱公鑰加密算法。
 
  1977年,Ron Rivest、Adi Shamir、Leonard Adleman三人在美國公佈了一種公鑰加密算法,即RSA公鑰加密算法。RSA是目前最有影響力和最常用的公鑰加密算法,可以說是公鑰加密算法的事實標準。
 

RSA加密原理

 
  使用M和C分別表示明文和密文,則RSA加密、解密過程如下:
 
RSA加解密及簽名算法的技術原理及其Go語言實現
 
  其中e、n的組合(e, n)即爲公鑰,d、n的組合(d, n)即爲私鑰。當然e、d、n並非任意取值,需要符合一定條件,如下即爲e、d、n的求解過程。
 

生成密鑰對

 
  e、d、n的求解過程,也即生成密鑰對的過程。涉及如下步驟:
  1、取兩個大質數(也稱素數)p、q,n = pq。
  2、取正整數e、d,使得ed mod (p-1)(q-1) = 1,也即:ed ≡ 1 mod (p-1)(q-1)。
  e和d是模(p-1)(q-1)的乘法逆元,僅當e與(p-1)(q-1)互質時,存在d。
 
  舉例驗證:
  1、取p、q分別爲13、17,n = pq = 221。
  2、而(p-1)(q-1) = 12x16 = 192,取e、d分別爲13、133,有13x133 mod 192 = 1
  取明文M = 60,公鑰加密、私鑰解密,加密和解密過程分別如下:
 
RSA加解密及簽名算法的技術原理及其Go語言實現
 

RSA加密原理證明過程

 
RSA加解密及簽名算法的技術原理及其Go語言實現
 

手動求解密鑰對中的d

 
  ed mod (p-1)(q-1) = 1,已知e和(p-1)(q-1)求d,即求e對模(p-1)(q-1)的乘法逆元。
  如上面例子中,p、q爲13、17,(p-1)(q-1)=192,取e=13,求13d mod 192 = 1中的d。
 
  13d ≡ 1 (mod 192),在右側添加192的倍數,使計算結果可以被13整除。
  13d ≡ 1 + 192x9 ≡ 13x133 (mod 192),因此d = 133
 
  其他計算方法有:費馬小定律、擴展歐幾里得算法、歐拉定理。
 

RSA安全性

 
  由於公鑰公開,即e、n公開。
  因此破解RSA私鑰,即爲已知e、n情況下求d。
  因ed mod (p-1)(q-1) = 1,且n=pq,因此該問題演變爲:對n質因數分解求p、q。
 
  目前已被證明,已知e、n求d和對n質因數分解求p、q兩者是等價的。實際中n長度爲2048位以上,而當n>200位時分解n是非常困難的,因此RSA算法目前仍被認爲是安全實用的。
 

RSA計時***和防範

 
  RSA解密的本質是模冪運算,即:
 
RSA加解密及簽名算法的技術原理及其Go語言實現
 
  其中C爲密文,(d,n)爲私鑰,均爲超過1024位的大數運算,直接計算並不可行,因此最經典的算法爲蒙哥馬利算法。而這種計算是比較是耗時的,因此***者可以觀察不同的輸入對應的解密時間,通過分析推斷私鑰,稱爲計時***。而防範RSA計時***的辦法,即在解密時加入隨機因素,使得***者無法準確獲取解密時間。
 
  具體實現步驟如下:
 
RSA加解密及簽名算法的技術原理及其Go語言實現
 

go標準庫中的RSA加解密實現

 
  go標準庫中解密即實現了對計時***的防範,代碼如下:
 

//加密
//m爲明文
//(pub.E, pub.N)爲公鑰
//c爲密文
func encrypt(c *big.Int, pub *PublicKey, m *big.Int) *big.Int {
    e := big.NewInt(int64(pub.E))
    c.Exp(m, e, pub.N)
    return c
}

//解密
//傳入random支持防範計時***
func decrypt(random io.Reader, priv *PrivateKey, c *big.Int) (m *big.Int, err error) {
    if c.Cmp(priv.N) > 0 {
        err = ErrDecryption
        return
    }
    if priv.N.Sign() == 0 {
        return nil, ErrDecryption
    }

    var ir *big.Int
    if random != nil {
        var r *big.Int

        for {
            //步驟1產生0至n-1之間隨機數r
            r, err = rand.Int(random, priv.N)
            if err != nil {
                return
            }
            if r.Cmp(bigZero) == 0 {
                r = bigOne
            }
            var ok bool
            //r的模n的乘法逆元ir,步驟4中使用
            ir, ok = modInverse(r, priv.N)
            if ok {
                break
            }
        }
        bigE := big.NewInt(int64(priv.E))
        //計算步驟2中C'
        rpowe := new(big.Int).Exp(r, bigE, priv.N) // N != 0
        cCopy := new(big.Int).Set(c)
        cCopy.Mul(cCopy, rpowe)
        cCopy.Mod(cCopy, priv.N)
        c = cCopy
    }

    if priv.Precomputed.Dp == nil {
        //步驟3,使用C'計算對應的M'
        m = new(big.Int).Exp(c, priv.D, priv.N)
    } else {
        //略
    }

    if ir != nil {
        //步驟4計算實際的M
        m.Mul(m, ir)
        m.Mod(m, priv.N)
    }

    return
}
//代碼位置src/crypto/rsa/rsa.go

RSA簽名和驗籤的原理

 
  非對稱加密算法,除支持加密外,還可以實現簽名。原理如下:
 
  簽名:
  1、提取消息摘要,使用發送方私鑰對消息摘要加密,生成消息簽名。
  2、將消息簽名和消息一起,使用接收方公鑰加密,獲得密文。
 
  驗籤:
  1、使用接收方私鑰對密文解密,獲得消息和消息簽名。
  2、使用發送方公鑰解密消息簽名,獲得消息摘要。
  3、使用相同辦法重新提取消息摘要,與上一步中消息摘要對比,如相同則驗籤成功。
 
  附示意圖如下:
 
RSA加解密及簽名算法的技術原理及其Go語言實現
 

go標準庫中的RSA簽名實現

 

//簽名
func SignPKCS1v15(rand io.Reader, priv *PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, error) {
    //哈希提取消息摘要
    hashLen, prefix, err := pkcs1v15HashInfo(hash, len(hashed))
    if err != nil {
        return nil, err
    }

    tLen := len(prefix) + hashLen
    k := (priv.N.BitLen() + 7) / 8
    if k < tLen+11 {
        return nil, ErrMessageTooLong
    }

    // EM = 0x00 || 0x01 || PS || 0x00 || T
    em := make([]byte, k)
    em[1] = 1
    for i := 2; i < k-tLen-1; i++ {
        em[i] = 0xff
    }
    //整合消息摘要和消息體
    copy(em[k-tLen:k-hashLen], prefix)
    copy(em[k-hashLen:k], hashed)

    m := new(big.Int).SetBytes(em)
    //使用發送方私鑰加密消息摘要和消息體,即爲簽名
    c, err := decryptAndCheck(rand, priv, m)
    if err != nil {
        return nil, err
    }

    copyWithLeftPad(em, c.Bytes())
    return em, nil
}

//驗證簽名
func VerifyPKCS1v15(pub *PublicKey, hash crypto.Hash, hashed []byte, sig []byte) error {
    //哈希提取消息摘要
    hashLen, prefix, err := pkcs1v15HashInfo(hash, len(hashed))
    if err != nil {
        return err
    }

    tLen := len(prefix) + hashLen
    k := (pub.N.BitLen() + 7) / 8
    if k < tLen+11 {
        return ErrVerification
    }

    c := new(big.Int).SetBytes(sig)
    //使用發送方公鑰解密,提取消息體和消息簽名
    m := encrypt(new(big.Int), pub, c)
    em := leftPad(m.Bytes(), k)
    // EM = 0x00 || 0x01 || PS || 0x00 || T

    //對比發送方和接收方消息體、以及消息簽名
    ok := subtle.ConstantTimeByteEq(em[0], 0)
    ok &= subtle.ConstantTimeByteEq(em[1], 1)
    ok &= subtle.ConstantTimeCompare(em[k-hashLen:k], hashed)
    ok &= subtle.ConstantTimeCompare(em[k-tLen:k-hashLen], prefix)
    ok &= subtle.ConstantTimeByteEq(em[k-tLen-1], 0)

    for i := 2; i < k-tLen-1; i++ {
        ok &= subtle.ConstantTimeByteEq(em[i], 0xff)
    }

    if ok != 1 {
        return ErrVerification
    }

    return nil
}
//代碼位置src/crypto/rsa/pkcs1v15.go

 

後記

 
  RSA算法中使用了大量數論知識,有關數論知識還有待學習。待續。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章