Golang加密系列之RSA

  1. Golang加密系列之AESgit

  2. 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)。

相關文章
相關標籤/搜索