數字簽名、證書,RSA加密、解密

一、相關知識掃盲篇

數字簽名、數字證書

A與B的交互,通過一方的公私密鑰,實現數據的加密,解密,驗證數據簽名,以保證其數據是對的。

但是存在比如B處存放的A的公鑰被別人替換爲如C的公鑰可能性,這樣C去發送信息給B,B會誤以爲是A發的,達到欺騙的效果
由此出現了,中心證書的CA(Certificate authority)機構,做中間人,把公鑰持有人的信息,再用機構的私鑰對A的信息進行加密。

B拿到數據,先用證書持有機構的公鑰進行解密,證明是A的數據,然後再用A的公鑰進行驗籤

引申:既然道格可以替換鮑勃的公鑰,爲什麼不能故技重施,僞造CA的公鑰,然後用自己的私鑰僞造成CA的數字證書,從而達到欺騙蘇珊的目的呢?

解釋:CA的公鑰是公開的,是可查的,他們的公鑰在自己網站上提供下載,因此無法僞造

總結:公私鑰用於加密、驗證之間的數據,證書(中心媒介)用於證明其拿到的是正確的交互方的公鑰。

防止證書僞造 之 證書鏈-Digital Certificates

文檔地址,點擊跳轉

SHA256withRSA 先用RAS加密,在執行SHA256的哈希運算,主要用在簽名驗證操作

二、公私鑰格式、協議規範

主要涉及的go包:

  • x509 x509包解析X.509(密碼學裏公鑰證書的格式標準)編碼的證書和密鑰
  • pem包,pem包實現了PEM數據編碼(源自保密增強郵件協議 Privacy Enbanced Mail)。目前PEM編碼主要用於TLS密鑰和證書
  • crypto/rsa rsa包實現了PKCS#1規定的RSA加密算法

三、golang RSA加密、解密具體實現

RSA加密、解密

  • RSA加密數據
  • 再配合 SHA256哈希 獲得不可修改的簽名signature

RSA公鑰加密

// 公鑰加密-分段

func RsaEncryptBlock(src, publicKeyByte []byte) (bytesEncrypt []byte, err error) {
    // 解碼公鑰文件,返回block結構
    // The encoded form is:
    //    -----BEGIN Type-----
    //    Headers
    //    base64-encoded Bytes
    //    -----END Type-----
	block, _ := pem.Decode(publicKeyByte)
	if block == nil {
		return nil, errors.New("Decode PublicKey Fail")
	}
	
	// 解析 PKIX 格式的公鑰,返回 *rsa.PublicKey, *dsa.PublicKey, *ecdsa.PublicKey 等格式的struct
	publicKey, err := x509.ParsePKIXPublicKey(block.Bytes)
	if err != nil {
		return nil, err
	}
	
	//根據自己具體使用的格式做推測
	pub, keySize :=  publicKey.(*rsa.PublicKey), publicKey.(*rsa.PublicKey).Size()
	srcSize := len(src)
	
    fmt.Println("密鑰長度:", keySize, "\t明文長度:\t", srcSize)
	//單次加密的長度需要減掉padding的長度,PKCS1爲11
	

    // 按需加密
	offSet, once := 0, keySize-11
	buffer := bytes.Buffer{}
	for offSet < srcSize {
		endIndex := offSet + once
		if endIndex > srcSize {
			endIndex = srcSize
		}
		// 加密一部分
		bytesOnce, err := rsa.EncryptPKCS1v15(myrand.Reader, pub, src[offSet:endIndex])
		if err != nil {
			return nil, err
		}
		buffer.Write(bytesOnce)
		offSet = endIndex
	}
	bytesEncrypt = buffer.Bytes()
	return
}

RSA私鑰解密

**
私鑰解密-分段
 */
func RsaDecryptBlock(src, privateKeyByte []byte) (bytesDecrypt  []byte, err error) {
	block, _ := pem.Decode(privateKeyByte)
	if block == nil {
		return nil, errors.New("Decode PrivateKey Fail")
	}
	
	// 注意格式 PKCS8 / PKCS1
	privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
	if err != nil {
		return nil, err
	}
	
	private := privateKey.(*rsa.PrivateKey)
	keySize, srcSize := private.Size(), len(src)
	fmt.Println("密鑰長度:", keySize, "\t密文長度:\t", srcSize)
	
	var offSet = 0
	var buffer = bytes.Buffer{}
	for offSet < srcSize {
		endIndex := offSet + keySize
		if endIndex > srcSize {
			endIndex = srcSize
		}
		// 解密
		bytesOnce, err := rsa.DecryptPKCS1v15(myrand.Reader, private, src[offSet:endIndex])
		if err != nil {
			return nil, err
		}
		buffer.Write(bytesOnce)
		offSet = endIndex
	}
	bytesDecrypt = buffer.Bytes()
	return
}

hmac_sha256加密

func HmacSha256(message string, secret string) string {
	key := []byte(secret)
	h := hmac.New(sha256.New, key)
	h.Write([]byte(message))
	return base64.StdEncoding.EncodeToString(h.Sum(nil))  // 轉base64str
}

私鑰加密,生成簽名SHA256withRSA

func RSASign(origData string, privateKeyBytes []byte) (sign string,err error) {
    /*
    // 可以通過讀取私鑰文件,獲取私鑰字節數據
    privateKeyBytes, err := ioutil.ReadFile(filename)
    if err != nil {
        return nil, err
    }
    */
    
    // 解碼私鑰
	blocks,_  := pem.Decode(privateKeyBytes)
    if block == nil {
        return nil, errors.New("decode privatekey fail")
    }
	// 解析私鑰對象
	privateKey, _ := x509.ParsePKCS8PrivateKey(blocks.Bytes)
	
	// 選擇hash算法,對數據進行hash
	h := sha256.New()
	h.Write([]byte(origData))
	digest := h.Sum(nil)
	
	// 生成簽名
	s, _ := rsa.SignPKCS1v15(nil, privateKey.(*rsa.PrivateKey), crypto.SHA256, digest)
	// 獲取base64str
	sign = base64.StdEncoding.EncodeToString(s)
	return sign, nil
}

RSA公鑰驗證簽名

func RSAVerify(data []byte, base64Sig, filename string) error {
    // 對base64編碼的簽名內容進行解碼
    sign_bytes, err := base64.StdEncoding.DecodeString(base64Sig)
    if err != nil {
        return err
    }
    // 選擇hash算法,對需要簽名的數據進行hash運算
    myhash := crypto.SHA256
    hashInstance := myhash.New()
    hashInstance.Write(data)
    hashed := hashInstance.Sum(nil)
    
    // 讀取公鑰文件,解析出公鑰對象
    // 1、讀取公鑰文件,獲取公鑰字節
    publicKeyBytes, err := ioutil.ReadFile(filename)
    if err != nil {
        return err
    }
    // 2、解碼公鑰字節,生成加密對象
    block, _ := pem.Decode(publicKeyBytes)
    if block == nil {
        return errors.New("decode publicKey fail")
    }
    // 3、解析DER編碼的公鑰,生成公鑰接口
    publicKeyInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
    if err != nil {
        return err
    }
    // 4、公鑰接口轉型成公鑰對象(按需轉換判斷類型)
    publicKey := publicKeyInterface.(*rsa.PublicKey)

    
    // RSA驗證數字簽名(參數是公鑰對象、哈希類型、簽名文件的哈希串、已知的簽名數據)
    return rsa.VerifyPKCS1v15(publicKey, myhash, hashed, sign_bytes)
}

這裏以PayPal webhook數據驗籤(SHA256withRSA)舉例:

import (
	"crypto"
	"crypto/rsa"
	"crypto/x509"
	"encoding/base64"
	"encoding/pem"
	"errors"
	"github.com/astaxie/beego/logs"
	"hash/crc32"
	"io/ioutil"
	"net/http"
	"strconv"
)


type PayPalWebHook struct {
	TransmissionID   string // Paypal-Transmission-Id
	TransmissionTime string // Paypal-Transmission-Time (請求頭header裏邊)
	TransmissionSig  string // Paypal-Transmission-Sig 傳遞來的簽名
	WebHookID        string // webhook_id 注意與  webhook_event_id做區分
	CertUrl          string // Paypal-Cert-Url
	EventData        []byte // payload data
}

func (this *PayPalWebHook) VerifySignature() (bool, error) {

	//獲取證書 
	// 這裏使用下載證書的方式,如果是本地使用io操作讀取即可
	resp, err := http.Get(this.CertUrl)
	if err != nil {
		logs.Error("[Get Cert Error]: ", err)
		return false, err
	}
	defer resp.Body.Close()
	cert_bytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return false, err
	}


	//解析pub_key,從證書裏邊解析出公鑰
	// 若已有公鑰,則忽略此從證書解出公鑰的操作
	pub_key, err := this.getPublicKey(cert_bytes)
	if err != nil {
		return false, err
	}

	//拼接簽名所需字符串。
	expect_str := this.getExpectedStr()

	//執行校驗
	sig_bytes, err := base64.StdEncoding.DecodeString(this.TransmissionSig)
	if err != nil {
		logs.Error("[base64 decode error]: ", err)
		return false, err
	}
	hash_handler := crypto.SHA256
	hash_instance := hash_handler.New()
	hash_instance.Write([]byte(expect_str))
	hashed := hash_instance.Sum(nil)

	err = rsa.VerifyPKCS1v15(pub_key, hash_handler, hashed, sig_bytes)
	if err != nil {
		logs.Error("[Verify Signature Error]: ", err)
		return false, err
	}
	return true, nil
}



//獲取簽名拼接字符串(按需設置)
func (this *PayPalWebHook) getExpectedStr() string {
	// 這裏取數據的crc32冗餘校驗的值,和相關參數做拼接的hash前的數據字符串。按需改爲需要進行簽名操作的數據
	//crc冗餘校驗
	event_crc32 := crc32.ChecksumIEEE(this.EventData)
	//PayPal簽名要求格式
	expect_string := this.TransmissionID + "|" + this.TransmissionTime + "|" + this.WebHookID + "|" + strconv.FormatUint(uint64(event_crc32), 10)
	logs.Debug("[Expect string]: ", expect_string)
	return expect_string
}

// 從證書裏提取公鑰
func (this *PayPalWebHook) getPublicKey(cert_bytes []byte) (*rsa.PublicKey, error) {
	//解碼證書pem,驗證格式
	cert_der_block, _ := pem.Decode(cert_bytes)
	if cert_der_block == nil {
		logs.Error(cert_der_block)
		return nil, errors.New("decode_cert_error")
	}
	//解析證書
	cert, err := x509.ParseCertificate(cert_der_block.Bytes)
	if err != nil {
		logs.Error("[ParseCertificate Error]: , err")
		return nil, err
	}
	//序列化公鑰
	pub_key_der, err := x509.MarshalPKIXPublicKey(cert.PublicKey)
	if err != nil {
		logs.Error("[MarshalPKIXPublicKey Error]: ", err)
		return nil, err
	}
	//解析公鑰
	pub_key, err := x509.ParsePKIXPublicKey(pub_key_der)
	if err != nil {
		logs.Error("[ParsePKIXPublicKey Error]: ", err)
		return nil, err
	}
	//這裏因爲知道其加密方式,直接推斷的rsa,這裏根據具體情況修改實現
	return pub_key.(*rsa.PublicKey), nil
}

golang解析數字證書操作

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