文章目錄
一、相關知識掃盲篇
數字簽名、數字證書
A與B的交互,通過一方的公私密鑰,實現數據的加密,解密,驗證數據簽名,以保證其數據是對的。
但是存在比如B處存放的A的公鑰被別人替換爲如C的公鑰可能性,這樣C去發送信息給B,B會誤以爲是A發的,達到欺騙的效果
由此出現了,中心證書的CA(Certificate authority)機構,做中間人,把公鑰持有人的信息,再用機構的私鑰對A的信息進行加密。
B拿到數據,先用證書持有機構的公鑰進行解密,證明是A的數據,然後再用A的公鑰進行驗籤
引申:既然道格可以替換鮑勃的公鑰,爲什麼不能故技重施,僞造CA的公鑰,然後用自己的私鑰僞造成CA的數字證書,從而達到欺騙蘇珊的目的呢?
解釋:CA的公鑰是公開的,是可查的,他們的公鑰在自己網站上提供下載,因此無法僞造
總結:公私鑰用於加密、驗證之間的數據,證書(中心媒介)用於證明其拿到的是正確的交互方的公鑰。
防止證書僞造 之 證書鏈-Digital Certificates
SHA256withRSA
先用RAS加密,在執行SHA256的哈希運算,主要用在簽名驗證操作
二、公私鑰格式、協議規範
- 公私鑰格式、標準、規範,golang生成公私鑰,見(https://blog.csdn.net/BuquTianya/article/details/82958194)
- openssl數字證書常見格式與協議介紹
主要涉及的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
}