RSA非對稱加密算法實現:Golang


  RSA是1977年由羅納德·李維斯特(Ron Rivest)、阿迪·薩莫爾(Adi Shamir)和倫納德·阿德曼(Leonard Adleman)一起提出的。當時他們三人都在麻省理工學院工作。RSA就是他們三人姓氏開頭字母拼在一起組成的。

  RSA解決了對稱加密的一個不足,比如AES算法加密和解密時使用的是同一個秘鑰,因此這個秘鑰不能公開,因此對於需要公開秘鑰的場合,我們需要在加密和解密過程中使用不同的秘鑰,加密使用的公鑰可以公開,解密使用的私鑰要保密,這就是非對稱加密的好處。 

  常用的開發語言來實現RSA加密:

  RSA非對稱加密算法實現:Java

  RSA非對稱加密算法實現:C#

  RSA非對稱加密算法實現:Golang

  RSA非對稱加密算法實現:Python

 

  公鑰與私鑰

  公鑰與私鑰是成對的,一般的,我們認為的是公鑰加密、私鑰解密、私鑰簽名、公鑰驗證,有人說成私鑰加密,公鑰解密時不對的。

  公鑰與私鑰的生成有多種方式,可以通過程序生成(下文具體實現),可以通過openssl工具:  

    # 生成一個私鑰,推薦使用1024位的秘鑰,秘鑰以pem格式保存到-out參數指定的文件中,采用PKCS1格式
    openssl genrsa -out rsa.pem 1024 
    # 生成與私鑰對應的公鑰,生成的是Subject Public Key,一般配合PKCS8格式私鑰使用
    openssl rsa -in rsa.pem -pubout -out rsa.pub  

  RSA生成公鑰與私鑰一般有兩種格式:PKCS1和PKCS8,上面的命令生成的秘鑰是PKCS1格式的,而公鑰是Subject Public Key,一般配合PKCS8格式私鑰使用,所以就可能會涉及到PKCS1和PKCS8之間的轉換:

    # PKCS1格式私鑰轉換為PKCS8格式私鑰,私鑰直接輸出到-out參數指定的文件中
    openssl pkcs8 -topk8 -inform PEM -in rsa.pem -outform pem -nocrypt -out rsa_pkcs8.pem
    # PKCS8格式私鑰轉換為PKCS1格式私鑰,私鑰直接輸出到-out參數指定的文件中
    openssl rsa -in rsa_pkcs8.pem -out rsa_pkcs1.pem

    # PKCS1格式公鑰轉換為PKCS8格式公鑰,轉換后的內容直接輸出
    openssl rsa -pubin -in rsa.pub -RSAPublicKey_out
    # PKCS8格式公鑰轉換為PKCS1格式公鑰,轉換后的內容直接輸出
    openssl rsa -RSAPublicKey_in -pubout -in rsa.pub

  現實中,我們往往從pem、crt、pfx文件獲取公私和私鑰,crt、pfx的制作可以參考:簡單的制作ssl證書,並在nginx和IIS中使用,或者使用現成的:https://pan.baidu.com/s/1MJ5YmuZiLBnf-DfNR_6D7A (提取碼:c6tj),密碼都是:123456

   

  Golang實現

  為了方便讀取pem、crt、pfx文件中的公私和私鑰,這里我使用了第三方的包:golang.org/x/crypto/pkcs12,可以使用go get安裝:go get -u golang.org/x/crypto/pkcs12

  安裝之后,封裝一個工具包rsautil.go:  

  
package rsautil

import (
    "crypto"
    "crypto/rand"
    "crypto/rsa"
    "crypto/x509"
    "crypto/x509/pkix"
    "encoding/asn1"
    "encoding/pem"
    "fmt"
    "golang.org/x/crypto/pkcs12"
    "io"
    "io/ioutil"
    "os"
    "strconv"
)

//從Pem文件中讀取秘鑰
func ReadFromPem(pemFile string) ([]byte, error) {
    buffer, err := ioutil.ReadFile(pemFile)
    if err != nil {
        return nil, err
    }

    block, _ := pem.Decode(buffer)
    return block.Bytes, nil
}

//從pfx文件中讀取公私密鑰(需要安裝golang.org/x/crypto/pkcs12)
func ReadFromPfx(pfxFile, password string, usePKCS8 bool) ([]byte, []byte) {
    buffer, err := ioutil.ReadFile(pfxFile)
    if err != nil {
        panic(err)
    }

    privateKeyInterface, certificate, err := pkcs12.Decode(buffer, password)
    if err != nil {
        panic(err)
    }

    privateKey := privateKeyInterface.(*rsa.PrivateKey)
    publicKey := certificate.PublicKey.(*rsa.PublicKey)

    var (
        privateKeyBuffer []byte
        publicKeyBuffer  []byte
    )
    if usePKCS8 {
        privateKeyBuffer, err = x509.MarshalPKCS8PrivateKey(privateKey)
        if err != nil {
            panic(err)
        }
        publicKeyBuffer, err = x509.MarshalPKIXPublicKey(publicKey)
        if err != nil {
            panic(err)
        }
    } else {
        privateKeyBuffer = x509.MarshalPKCS1PrivateKey(privateKey)
        publicKeyBuffer = x509.MarshalPKCS1PublicKey(publicKey)
    }
    return publicKeyBuffer, privateKeyBuffer
}

//從crt中讀取公鑰
func ReadPublicKeyFromCrt(crtFile string, usePKCS8 bool) ([]byte, error) {
    buffer, err := ioutil.ReadFile(crtFile)
    if err != nil {
        return nil, err
    }
    certDERBlock, _ := pem.Decode(buffer)
    certificate, err := x509.ParseCertificate(certDERBlock.Bytes)
    if err != nil {
        return nil, err
    }
    publicKey := certificate.PublicKey.(*rsa.PublicKey)

    var publicKeyBuffer []byte
    if usePKCS8 {
        publicKeyBuffer, err = x509.MarshalPKIXPublicKey(publicKey)
    } else {
        publicKeyBuffer = x509.MarshalPKCS1PublicKey(publicKey)
    }
    if err != nil {
        return nil, err
    }
    return publicKeyBuffer, nil
}

//將秘鑰寫入Pem文件
func WriteToPem(isPrivateKey bool, buffer []byte, pemFile string) error {
    var _type string
    if isPrivateKey {
        _type = "RSA PRIVATE KEY"
    } else {
        _type = "RSA PUBLIC KEY"
    }

    block := &pem.Block{
        Type:  _type, //這個字符串隨便寫
        Bytes: buffer,
    }

    file, err := os.Create(pemFile)
    if err != nil {
        return err
    }
    return pem.Encode(file, block)
}

//Pkcs1轉換為Pkcs8
func Pkcs1ToPkcs8(isPrivateKey bool, buffer []byte) []byte {
    var (
        oid  = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1}
        info interface{}
    )
    if isPrivateKey {
        val := struct {
            Version    int
            Algo       []asn1.ObjectIdentifier
            PrivateKey []byte
        }{}
        val.Version = 0
        val.Algo = []asn1.ObjectIdentifier{oid}
        val.PrivateKey = buffer
        info = val
    } else {
        val := struct {
            Algo      pkix.AlgorithmIdentifier
            BitString asn1.BitString
        }{}
        val.Algo.Algorithm = oid
        val.Algo.Parameters = asn1.NullRawValue
        val.BitString.Bytes = buffer
        val.BitString.BitLength = 8 * len(buffer)
        info = val
    }

    b, err := asn1.Marshal(info)
    if err != nil {
        panic(err)
    }
    return b
}

//Pkcs8轉換為Pkcs1
func Pkcs8ToPkcs1(isPrivateKey bool, buffer []byte) []byte {
    if isPrivateKey {
        val := struct {
            Version    int
            Algo       pkix.AlgorithmIdentifier
            PrivateKey []byte
        }{}
        _, err := asn1.Unmarshal(buffer, &val)
        if err != nil {
            panic(err)
        }
        return val.PrivateKey
    } else {
        val := struct {
            Algo      pkix.AlgorithmIdentifier
            BitString asn1.BitString
        }{}

        _, err := asn1.Unmarshal(buffer, &val)
        if err != nil {
            panic(err)
        }
        return val.BitString.Bytes
    }
}

//生成公私鑰
//usePKCS8:是否使用pkcs8
func GenerateRsaKey(usePKCS8 bool) ([]byte, []byte) {
    //生成私鑰
    privateKey, err := rsa.GenerateKey(rand.Reader, 1024) //1024位
    if err != nil {
        panic(err)
    }
    //公鑰
    publicKey := privateKey.PublicKey

    var (
        privateKeyBuffer []byte
        publicKeyBuffer  []byte
    )

    if usePKCS8 {
        privateKeyBuffer, err = x509.MarshalPKCS8PrivateKey(privateKey)
        if err != nil {
            panic(err)
        }
        publicKeyBuffer, err = x509.MarshalPKIXPublicKey(&publicKey)
        if err != nil {
            panic(err)
        }
    } else {
        privateKeyBuffer = x509.MarshalPKCS1PrivateKey(privateKey)
        publicKeyBuffer = x509.MarshalPKCS1PublicKey(&publicKey)
    }

    return publicKeyBuffer, privateKeyBuffer
}

func parsePkcsKey(buffer []byte, isPrivateKey, usePKCS8 bool) (interface{}, error) {
    var (
        err          error
        keyInterface interface{}
    )

    if isPrivateKey {
        if usePKCS8 {
            keyInterface, err = x509.ParsePKCS8PrivateKey(buffer)
        } else {
            keyInterface, err = x509.ParsePKCS1PrivateKey(buffer)
        }
    } else {
        if usePKCS8 {
            keyInterface, err = x509.ParsePKIXPublicKey(buffer)
        } else {
            keyInterface, err = x509.ParsePKCS1PublicKey(buffer)
        }
    }
    if err != nil {
        return nil, err
    }
    return keyInterface, nil
}

//RSA加密
func RsaEncrypt(value string, publicKey []byte, usePKCS8 bool) (string, error) {
    keyInterface, err := parsePkcsKey(publicKey, false, usePKCS8)
    if err != nil {
        return "", err
    }
    rsaPublicKey := keyInterface.(*rsa.PublicKey)
    buffer, err := rsa.EncryptPKCS1v15(rand.Reader, rsaPublicKey, []byte(value))
    if err != nil {
        return "", err
    }

    //以hex格式數值輸出
    encryptText := fmt.Sprintf("%x", buffer)
    return encryptText, nil
}

//RSA解密
func RsaDecrypt(value string, privateKey []byte, usePKCS8 bool) (string, error) {
    //將hex格式數據轉換為byte切片
    valueBytes := []byte(value)
    var buffer = make([]byte, len(valueBytes)/2)
    for i := 0; i < len(buffer); i++ {
        b, err := strconv.ParseInt(value[i*2:i*2+2], 16, 10)
        if err != nil {
            return "", err
        }
        buffer[i] = byte(b)
    }

    keyInterface, err := parsePkcsKey(privateKey, true, usePKCS8)
    if err != nil {
        return "", err
    }
    key := keyInterface.(*rsa.PrivateKey)
    buffer, err = rsa.DecryptPKCS1v15(rand.Reader, key, buffer)
    return string(buffer), nil
}

//RSA簽名
func Sign(value string, privateKey []byte, hash crypto.Hash, usePKCS8 bool) (string, error) {
    keyInterface, err := parsePkcsKey(privateKey, true, usePKCS8)
    if err != nil {
        return "", err
    }
    key := keyInterface.(*rsa.PrivateKey)

    var _hash = hash.New()
    if _, err := io.WriteString(_hash, value); err != nil {
        return "", err
    }

    hashed := _hash.Sum(nil)
    result, err := rsa.SignPKCS1v15(rand.Reader, key, hash, hashed)
    if err != nil {
        return "", err
    }

    //以hex格式數值輸出
    encryptText := fmt.Sprintf("%x", result)
    return encryptText, nil
}

//RSA驗證簽名
func Verify(value string, publicKey []byte, signature string, hash crypto.Hash, usePKCS8 bool) error {
    //將hex格式數據轉換為byte切片
    valueBytes := []byte(signature)
    var buffer = make([]byte, len(valueBytes)/2)
    for i := 0; i < len(buffer); i++ {
        b, err := strconv.ParseInt(signature[i*2:i*2+2], 16, 10)
        if err != nil {
            return err
        }
        buffer[i] = byte(b)
    }

    keyInterface, err := parsePkcsKey(publicKey, false, usePKCS8)
    if err != nil {
        return err
    }

    key := keyInterface.(*rsa.PublicKey)

    var _hash = hash.New()
    if _, err := io.WriteString(_hash, value); err != nil {
        return err
    }

    hashed := _hash.Sum(nil)
    return rsa.VerifyPKCS1v15(key, hash, hashed, buffer)
}
rsautil

  可以使用生成RSA的公私秘鑰:

    //生成Rsa
    publicKey, privateKey := rsautil.GenerateRsaKey(usePKCS8)

  生成秘鑰后,需要保存,一般保存到pem文件中:  

    //保存到Pem文件,filePath是文件目錄
    rsautil.WriteToPem(false, publicKey, filepath.Join(filePath, "rsa.pub"))
    rsautil.WriteToPem(true, privateKey, filepath.Join(filePath, "rsa.pem"))

  從pem文件中讀取:  

    //從Pem文件讀取秘鑰,filePath是文件目錄
    publicKey, _ := rsautil.ReadFromPem(filepath.Join(filePath, "rsa.pub"))
    privateKey, _ := rsautil.ReadFromPem(filepath.Join(filePath, "rsa.pem"))

  還可以從crt證書中讀取公鑰,而crt文件不包含私鑰,因此需要單獨獲取私鑰:

    //從crt文件中讀取公鑰,filePath是文件目錄
    publicKey, _ := rsautil.ReadPublicKeyFromCrt(filepath.Join(filePath, "demo.crt"), usePKCS8)
    privateKey, _ := rsautil.ReadFromPem(filepath.Join(filePath, "demo.key"))

  pfx文件中包含了公鑰和私鑰,可以很方便就讀取到:

    //從pfx文件中讀取秘鑰,filePath是文件目錄
    publicKey, privateKey := rsautil.ReadFromPfx(filepath.Join(filePath, "demo.pfx"), "123456", usePKCS8)

  有時候我們還可能需要進行秘鑰的轉換:  

    //Pkcs8格式公鑰轉換為Pkcs1格式公鑰
    publicKey = rsautil.Pkcs8ToPkcs1(false, publicKey)
    // Pkcs8格式私鑰轉換為Pkcs1格式私鑰
    privateKey = rsautil.Pkcs8ToPkcs1(true, privateKey)
    // Pkcs1格式公鑰轉換為Pkcs8格式公鑰
    publicKey = rsautil.Pkcs1ToPkcs8(false, publicKey)
    // Pkcs1格式私鑰轉換為Pkcs8格式私鑰
    privateKey = rsautil.Pkcs1ToPkcs8(true, privateKey)

  有了公鑰和私鑰,接下就就能實現加密、解密、簽名、驗證簽名等操作了:  

    encryptText, _ := rsautil.RsaEncrypt(text, publicKey, usePKCS8)
    fmt.Printf("【%s】經過【RSA】加密后:%s\n", text, encryptText)

    decryptText, _ := rsautil.RsaDecrypt(encryptText, privateKey, usePKCS8)
    fmt.Printf("【%s】經過【RSA】解密后:%s\n", encryptText, decryptText)

    signature, _ := rsautil.Sign(text, privateKey, crypto.MD5, usePKCS8)
    fmt.Printf("【%s】經過【RSA】簽名后:%s\n", text, signature)

    result := rsautil.Verify(text, publicKey, signature, crypto.MD5, usePKCS8) == nil
    fmt.Printf("【%s】的簽名【%s】經過【RSA】驗證后結果是:"+strconv.FormatBool(result), text, signature)

  完整的demo代碼:

package main

import (
    "crypto"
    "demo/rsautil"
    "fmt"
    "os"
    "path/filepath"
    "strconv"
)

func main() {
    text := "上山打老虎"
    usePKCS8 := true // usePKCS8=true表示是否成PKCS8格式的公私秘鑰,否則乘車PKCS1格式的公私秘鑰
    path, _ := os.Executable()
    filePath := filepath.Dir(path)
    fmt.Printf("文件路徑:%s\n", filePath) // 存放pem,crt,pfx等文件的目錄

    //生成Rsa
    publicKey, privateKey := rsautil.GenerateRsaKey(usePKCS8)
    //從Pem文件讀取秘鑰,filePath是文件目錄
    //publicKey, _ := rsautil.ReadFromPem(filepath.Join(filePath, "rsa.pub"))
    //privateKey, _ := rsautil.ReadFromPem(filepath.Join(filePath, "rsa.pem"))
    //從pfx文件中讀取秘鑰,filePath是文件目錄
    //publicKey, privateKey := rsautil.ReadFromPfx(filepath.Join(filePath, "demo.pfx"), "123456", usePKCS8)
    //從crt文件中讀取公鑰,filePath是文件目錄
    //publicKey, _ := rsautil.ReadPublicKeyFromCrt(filepath.Join(filePath, "demo.crt"), usePKCS8)
    //privateKey, _ := rsautil.ReadFromPem(filepath.Join(filePath, "demo.key"))

    //保存到Pem文件,filePath是文件目錄
    rsautil.WriteToPem(false, publicKey, filepath.Join(filePath, "rsa.pub"))
    rsautil.WriteToPem(true, privateKey, filepath.Join(filePath, "rsa.pem"))

    //Pkcs8格式公鑰轉換為Pkcs1格式公鑰
    publicKey = rsautil.Pkcs8ToPkcs1(false, publicKey)
    // Pkcs8格式私鑰轉換為Pkcs1格式私鑰
    privateKey = rsautil.Pkcs8ToPkcs1(true, privateKey)
    // Pkcs1格式公鑰轉換為Pkcs8格式公鑰
    publicKey = rsautil.Pkcs1ToPkcs8(false, publicKey)
    // Pkcs1格式私鑰轉換為Pkcs8格式私鑰
    privateKey = rsautil.Pkcs1ToPkcs8(true, privateKey)

    encryptText, _ := rsautil.RsaEncrypt(text, publicKey, usePKCS8)
    fmt.Printf("【%s】經過【RSA】加密后:%s\n", text, encryptText)

    decryptText, _ := rsautil.RsaDecrypt(encryptText, privateKey, usePKCS8)
    fmt.Printf("【%s】經過【RSA】解密后:%s\n", encryptText, decryptText)

    signature, _ := rsautil.Sign(text, privateKey, crypto.MD5, usePKCS8)
    fmt.Printf("【%s】經過【RSA】簽名后:%s\n", text, signature)

    result := rsautil.Verify(text, publicKey, signature, crypto.MD5, usePKCS8) == nil
    fmt.Printf("【%s】的簽名【%s】經過【RSA】驗證后結果是:"+strconv.FormatBool(result), text, signature)
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM