Golang加密系列之RSA
github
Golang加密系列的最後一篇,嗯,RSA涉及的概念太多,弄了很久才搞清楚。。。算法
代碼的結構以下圖app
PS:StarUML這玩意在Mac上全部連到Interface的線都變成直線了...我很惆悵...函數
定義一個對外開放的接口
測試
package rsa import "crypto" type Cipher interface { Encrypt(plaintext []byte) ([]byte, error) Decrypt(ciphertext []byte) ([]byte, error) Sign(src []byte, hash crypto.Hash) ([]byte, error) Verify(src []byte, sign []byte, hash crypto.Hash) error }
RSA也是一個塊加密算法,老是在一個固定大小的塊(block)上進行操做。但跟AES等不一樣的是,RSA 的block size是跟key length 以及所使用的填充模式有關的。填充方式有如下幾種。this
一、RSA_PKCS1_PADDING 填充模式,最經常使用的模式。編碼
輸入block長度 : block size比RSA 鑰模長(modulus) 短至少11個字節, 也就是RSA_size(rsa) – 11加密
輸出結果長度 : 和modulus同樣長spa
例如,對於1024bit的密鑰,blockSize = 1024/8 – 11 = 117 字節
二、RSA_PKCS1_OAEP_PADDING
輸入block長度:RSA_size(rsa) – 41
輸出結果長度 : 和modulus同樣長
三、RSA_NO_PADDING
不填充
解密的時候,若是密文長度過長,也須要切分紅多個塊進行解密,block size 和 key length 是相等的。
這裏僅支持了第一種填充方式。
package rsa func pkcs1Padding(src []byte, keySize int) [][]byte { srcSize := len(src) blockSize := keySize - 11 var v [][]byte if srcSize <= blockSize { v = append(v, src) } else { groups := len(src) / blockSize for i := 0; i < groups; i++ { block := src[:blockSize] v = append(v, block) src = src[blockSize:] if len(src) < blockSize { v = append(v, src) } } } return v } func unPadding(src []byte, keySize int) [][]byte { srcSize := len(src) blockSize := keySize var v [][]byte if srcSize == blockSize { v = append(v, src) } else { groups := len(src) / blockSize for i := 0; i < groups; i++ { block := src[:blockSize] v = append(v, block) src = src[blockSize:] } } return v }
定義私有的pkcsClient ,實現Cipher接口, PKCS格式的私鑰都使用這個client
package rsa import ( "bytes" "crypto" "crypto/rand" "crypto/rsa" ) type pkcsClient struct { privateKey *rsa.PrivateKey publicKey *rsa.PublicKey } func (this *pkcsClient) Encrypt(plaintext []byte) ([]byte, error) { blocks := pkcs1Padding(plaintext, this.publicKey.N.BitLen()/8) buffer := bytes.Buffer{} for _, block := range blocks { ciphertextPart, err := rsa.EncryptPKCS1v15(rand.Reader, this.publicKey, block) if err != nil { return nil, err } buffer.Write(ciphertextPart) } return buffer.Bytes(), nil } func (this *pkcsClient) Decrypt(ciphertext []byte) ([]byte, error) { ciphertextBlocks := unPadding(ciphertext, this.privateKey.N.BitLen()/8) buffer := bytes.Buffer{} for _, ciphertextBlock := range ciphertextBlocks { plaintextBlock, err := rsa.DecryptPKCS1v15(rand.Reader, this.privateKey, ciphertextBlock) if err != nil { return nil, err } buffer.Write(plaintextBlock) } return buffer.Bytes(), nil } func (this *pkcsClient) Sign(src []byte, hash crypto.Hash) ([]byte, error) { h := hash.New() h.Write(src) hashed := h.Sum(nil) return rsa.SignPKCS1v15(rand.Reader, this.privateKey, hash, hashed) } func (this *pkcsClient) Verify(src []byte, sign []byte, hash crypto.Hash) error { h := hash.New() h.Write(src) hashed := h.Sum(nil) return rsa.VerifyPKCS1v15(this.publicKey, hash, hashed, sign) }
將私鑰類型定義成枚舉類型
package privatekey type Type int64 const ( PKCS1 Type = iota PKCS8 )
定義一個New/NewDefault函數,用於建立client
package rsa import ( "crypto/rsa" "crypto/x509" "encoding/pem" "errors" "github.com/89hmdys/toast/rsa/privatekey" ) //默認客戶端,pkcs8私鑰格式,pem編碼 func NewDefault(privateKey, publicKey string) (Cipher, error) { blockPri, _ := pem.Decode([]byte(privateKey)) if blockPri == nil { return nil, errors.New("private key error") } blockPub, _ := pem.Decode([]byte(publicKey)) if blockPub == nil { return nil, errors.New("public key error") } return New(blockPri.Bytes, blockPub.Bytes, privatekey.PKCS8) } func New(privateKey, publicKey []byte, privateKeyType privatekey.Type) (Cipher, error) { priKey, err := genPriKey(privateKey, privateKeyType) if err != nil { return nil, err } pubKey, err := genPubKey(publicKey) if err != nil { return nil, err } return &pkcsClient{privateKey: priKey, publicKey: pubKey}, nil } func genPubKey(publicKey []byte) (*rsa.PublicKey, error) { pub, err := x509.ParsePKIXPublicKey(publicKey) if err != nil { return nil, err } return pub.(*rsa.PublicKey), nil } func genPriKey(privateKey []byte, privateKeyType privatekey.Type) (*rsa.PrivateKey, error) { var priKey *rsa.PrivateKey var err error switch privateKeyType { case privatekey.PKCS1: { priKey, err = x509.ParsePKCS1PrivateKey([]byte(privateKey)) if err != nil { return nil, err } } case privatekey.PKCS8: { prkI, err := x509.ParsePKCS8PrivateKey([]byte(privateKey)) if err != nil { return nil, err } priKey = prkI.(*rsa.PrivateKey) } default: { return nil, errors.New("unsupport private key type") } } return priKey, nil }
最後,看看如何使用上面的代碼來進行加密/解密,簽名驗籤
package rsa_test import ( "crypto" "encoding/base64" "encoding/hex" "fmt" "testing" "toast/rsa" ) var cipher rsa.Cipher func init() { client, err := rsa.NewDefault(`-----BEGIN PRIVATE KEY----- 私鑰信息 -----END PRIVATE KEY-----`, `-----BEGIN PUBLIC KEY----- 公鑰信息 -----END PUBLIC KEY-----`) if err != nil { fmt.Println(err) } cipher = client } func Test_DefaultClient(t *testing.T) { cp, err := cipher.Encrypt([]byte("測試加密解密")) if err != nil { t.Error(err) } cpStr := base64.URLEncoding.EncodeToString(cp) fmt.Println(cpStr) ppBy, err := base64.URLEncoding.DecodeString(cpStr) if err != nil { t.Error(err) } pp, err := cipher.Decrypt(ppBy) fmt.Println(string(pp)) } func Test_Sign_DefaultClient(t *testing.T) { src := "測試簽名驗籤" signBytes, err := cipher.Sign([]byte(src), crypto.SHA256) if err != nil { t.Error(err) } sign := hex.EncodeToString(signBytes) fmt.Println(sign) signB, err := hex.DecodeString(sign) errV := cipher.Verify([]byte(src), signB, crypto.SHA256) if errV != nil { t.Error(errV) } fmt.Println("verify success") }
關於RSA相關的一些概念,參見個人另外一篇博客.pem引起的血案
這裏還有一個已經編寫好的AES/RSA加解密的包,能夠直接引用,github地址:https://github.com/89hmdys/toast
2月14日補充,
近期公司對接了一個保險平臺,對數據進行RSA加密的時候發現當待加密數據長度超過了祕鑰長度時,會報錯(好比生成了1024bit/128byte的祕鑰,那麼待加密的數據長度只能是<=128byte,而輸出的數據長度是和祕鑰長度相等的)
找了些資料,發現rsa加密若是數據過長的時候,須要對數據進行分片,根據所選擇的填充方式的不一樣,每片的數據長度也不同。例如選擇PKCS1填充方式的時候,數據長度=祕鑰長度-MIN(11)。