1. 程式人生 > >密碼學06--數字簽名之go中的RSA數字簽名

密碼學06--數字簽名之go中的RSA數字簽名

目錄

1.數字簽名

1.1 概念

1.2 原理

1.3 實現

2.go語言實現RSA數字簽名

2.1 數字簽名【簽名-核驗】流程

2.1.1 使用rsa包生成金鑰對

2.1.2 使用私鑰對資訊進行數字簽名

2.1.3 使用公鑰對數字簽名進行校驗

2.2 數字簽名【簽名-核驗】模板


1.數字簽名

1.1 概念

數字簽名,就是隻有資訊的傳送者才能產生的別人無法偽造的一段數字串,這段數字串同時也是對資訊的傳送者傳送資訊真實性的一個有效證明。--摘自百度百科。其實本質上是為了解決訊息驗證的無法引入第三方公正缺陷問題而創造的一種方式,它用到的技術非常簡單:

資訊傳送者利用非對稱加密私鑰來對資料雜湊值進行加密,然後將公鑰公佈,這樣一來所有獲得公鑰的人都能夠證明資訊傳送者真的傳送了這條資料。這樣不論是資訊傳送者對原始資料作出了修改,還是不承認這條資料傳送,持有公鑰的人們都能夠輕而易舉的識別出資訊傳送者的毀約操作;而同時不論資訊遭到怎樣的竊取篡奪,因為持有公鑰的人們總是能夠輕而易舉識別出篡改後的資訊雜湊值與原資訊的雜湊值不同,進而保護了資訊傳送者資訊資料的安全。可以說數字簽名的出現直接解決了訊息驗證當中無法引入第三方公正的缺陷問題。

1.2 原理

由於資料傳送方使用的是私鑰加密,所以所有持有對應公鑰的人都能夠對這個資料進行解密進而進行簽名校驗。但是由於使用公鑰解密出來的資料仍然是加密資料,必須使用對應的特殊key來解密才能真正得到明文內容,而這個特殊key是隻有Frank和Alex擁有的。所以其他人儘管能夠下載到Frank的公鑰進行簽名簽證的第三方證明人,能夠看到簽名中的原始資料密文,但是仍舊無法真正看到密文背後真正的內容。

此時,既然每個人都能持有公鑰,那麼就意味著Frank私自對原始資料作出任何修改都會導致數字簽名變更。此時每一位持有公鑰的使用者對數字簽名進行再次驗證就會失敗,所以Amy此時充當了Alex的證明人。從另一方面來說如果Alex毀約不承認Frank曾經對其傳送過資料或者資料曾經被篡改,那麼Amy此時依然可以充當Frank的證明人,因為Frank的確傳送了資料,所以Amy的再次驗證是能夠成功的。這就是數字簽名第三方公正的原理。

1.3 實現

經過剛才的原理分析,我們能夠顯然發現go語言中如果想要實現數字簽名就必須實現非對稱加密的金鑰對的分發功能。非對稱加密方式常見有兩種:一種就是大名鼎鼎的RSA演算法(我之前的文章中提到過),而另一種則是橢圓曲線(雖然很少聽到但非常犀利,第二代人民幣就是用這種方式加密的)。比較讓人頭大的地方在於:

  1. go語言只提供了RSA演算法的非對稱加密介面,而沒有提供橢圓曲線的非對稱加密介面!
  2. 然而go語言卻同時提供了RSA演算法的數字簽名介面,和橢圓曲線的數字簽名介面。

這就意味著我們儘管在進行非對稱加密操作的時候不能使用go語言一睹橢圓曲線的風采,但是可以直接呼叫現成的介面來進行橢圓曲線的數字簽名功能。這不得不說是一次來自大佬的深深的鄙視(估計是怕公開給我們我們也不會...),所以本篇只討論RSA數字簽名。

2.go語言實現RSA數字簽名

2.1 數字簽名【簽名-核驗】流程

2.1.1 使用rsa包生成金鑰對

  1. 使用rsa包內的GenerateKey函式來生成金鑰對,其中公鑰資料儲存在私鑰資料中
  2. 使用x509包內的Marshal方法,將金鑰資訊序列化為ASN.1標準的DER編碼字串(公鑰私鑰的Marshal方法不同)
  3. 使用pem包內的資料模型構建pem.Block資料塊
  4. 使用pem包內的Encode方法將pem.Block資料塊寫入本地檔案

2.1.2 使用私鑰對資訊進行數字簽名

  1. 使用os.Open開啟私鑰檔案後,使用Read讀取檔案內容存入緩衝區
  2. 使用pem包中的Decode方法解碼,將緩衝區中的檔案流轉換成一個存有ASN.1標準的DER編碼字串資訊的pem.Block塊
  3. 使用x509包中的Parse方法,將pem.Block包中的block.Bytes欄位中的ASN.1標準的DER編碼字串解析,得到私鑰
  4. 使用指定的hash單向雜湊函式計算出明文資料的雜湊值(建立雜湊物件sha256.new()、然後write和sum那個玩意兒)
  5. 使用RSA包中提供的SignPKCS1v15方法,對私鑰明文資料的雜湊值完成簽名
// SignPKCS1v15 calculates the signature of hashed using
// RSASSA-PKCS1-V1_5-SIGN from RSA PKCS#1 v1.5.  Note that hashed must
// be the result of hashing the input message using the given hash
// function. If hash is zero, hashed is signed directly. This isn't
// advisable except for interoperability.
//
// If rand is not nil then RSA blinding will be used to avoid timing
// side-channel attacks.
//
// This function is deterministic. Thus, if the set of possible
// messages is small, an attacker may be able to build a map from
// messages to signatures and identify the signed messages. As ever,
// signatures provide authenticity, not confidentiality.
//
//
//引數1:rand.Reader加密隨機數計數器
//引數2:從私鑰檔案中讀取到的私鑰
//引數3:選定生成雜湊值的hash演算法。(語法要求:crypto.md5或者crypto.sha1或者crypto.sha256等)
//引數4:明文資料根據選定hash演算法生成的雜湊值
//返回值:數字簽名內容

func SignPKCS1v15(rand io.Reader, priv *PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, error) {
	hashLen, prefix, err := pkcs1v15HashInfo(hash, len(hashed))
	if err != nil {
		return nil, err
	}

	tLen := len(prefix) + hashLen
	k := priv.Size()
	if k < tLen+11 {
		return nil, ErrMessageTooLong
	}

	// EM = 0x00 || 0x01 || PS || 0x00 || T
	em := make([]byte, k)
	em[1] = 1
	for i := 2; i < k-tLen-1; i++ {
		em[i] = 0xff
	}
	copy(em[k-tLen:k-hashLen], prefix)
	copy(em[k-hashLen:k], hashed)

	m := new(big.Int).SetBytes(em)
	c, err := decryptAndCheck(rand, priv, m)
	if err != nil {
		return nil, err
	}

	copyWithLeftPad(em, c.Bytes())
	return em, nil
}

2.1.3 使用公鑰對數字簽名進行校驗

  1. 使用os.Open開啟下載的公鑰檔案後,使用Read讀取檔案內容存入緩衝區
  2. 使用pem包中的Decode方法解碼,將緩衝區中的檔案流轉換成一個存有ASN.1標準的DER編碼字串資訊的pem.Block塊
  3. 使用x509包中的Parse方法,將pem.Block包中的block.Bytes欄位中的ASN.1標準的DER編碼字串解析,得到公鑰(記得斷言)
  4. 使用與簽名時一樣的hash單向雜湊函式計算出接收到的明文資料的雜湊值
  5. 使用RSA包中提供的VerifyPKCS1v15方法,將下載的公鑰接收到的明文資料的重新計算的雜湊值一併生成新的本地數字簽名,然後與接收到的數字簽名校驗,得到結果
// VerifyPKCS1v15 verifies an RSA PKCS#1 v1.5 signature.
// hashed is the result of hashing the input message using the given hash
// function and sig is the signature. A valid signature is indicated by
// returning a nil error. If hash is zero then hashed is used directly. This
// isn't advisable except for interoperability.
//
//
//引數1:從公鑰檔案中讀取到的公鑰
//引數2:選定生成雜湊值的hash演算法。(語法要求:crypto.md5或者crypto.sha1或者crypto.sha256等)
//引數3:根據接收的明文生成的本地雜湊值
//引數4:接收到傳送者發來數字簽名
//返回值:數字簽名的校驗結果
//      返回nil代表校驗成功
//      返回不是nil代表校驗失敗

func VerifyPKCS1v15(pub *PublicKey, hash crypto.Hash, hashed []byte, sig []byte) error {
	hashLen, prefix, err := pkcs1v15HashInfo(hash, len(hashed))
	if err != nil {
		return err
	}

	tLen := len(prefix) + hashLen
	k := pub.Size()
	if k < tLen+11 {
		return ErrVerification
	}

	c := new(big.Int).SetBytes(sig)
	m := encrypt(new(big.Int), pub, c)
	em := leftPad(m.Bytes(), k)
	// EM = 0x00 || 0x01 || PS || 0x00 || T

	ok := subtle.ConstantTimeByteEq(em[0], 0)
	ok &= subtle.ConstantTimeByteEq(em[1], 1)
	ok &= subtle.ConstantTimeCompare(em[k-hashLen:k], hashed)
	ok &= subtle.ConstantTimeCompare(em[k-tLen:k-hashLen], prefix)
	ok &= subtle.ConstantTimeByteEq(em[k-tLen-1], 0)

	for i := 2; i < k-tLen-1; i++ {
		ok &= subtle.ConstantTimeByteEq(em[i], 0xff)
	}

	if ok != 1 {
		return ErrVerification
	}

	return nil
}

2.2 數字簽名【簽名-核驗】模板

//建立函式生成RSA金鑰對,而後本地化儲存
func GenerateRSAkeyForSign(keySize int){
    //私鑰
    privateKey,err := rsa.GenerateKey(rand.Reader, keySize)
    if err!=nil{
    	panic(err)
    }
    derPrivateCode := x509.MarshalPKCS1PrivateKey(privateKey)
    peoPrivateBlock := pem.Block{Type:"FRANK RSA PRIVATE KEY",Bytes:derPrivateCode}
    filePrivate,err := os.Create("frankPrivate.pem")
    err = pem.Encode(filePrivate,&peoPrivateBlock)
    if err!=nil{
    	panic(err)
    }
    filePrivate.Close()
    //公鑰
    publickKey := privateKey.PublicKey
    derPublicCode,pubErr := x509.MarshalPKIXPublicKey(&publickKey)
    if pubErr!=nil{
    	panic(pubErr)
    }
    peoPublicBlock := pem.Block{Type:"FRANK RSA PUBLIC KEY",Bytes:derPublicCode}
    filePublic,err := os.Create("frankPublic.pem")
    err = pem.Encode(filePublic,&peoPublicBlock)
    if err!=nil{
    	panic(err)
    }
    filePublic.Close()
}
//使用RSA私鑰簽名函式
func RSAUsePrivateKeySign(plainText []byte, privateKeyFileName string) []byte{
    //1.將檔案當中的加密私鑰讀出存入緩衝區
    privateFile,err := os.Open(privateKeyFileName)
    if err!=nil{
    	panic(err)
    }
    defer privateFile.Close()
    fileInfo,err := privateFile.Stat()
    if err!=nil{
    	panic(err)
    }
    buffer := make([]byte, fileInfo.Size())
    privateFile.Read(buffer)
    //2.pem解碼,將資料流轉換成一個存有DER字串的pem.Block塊
    block,_ := pem.Decode(buffer)
    //3.使用x509方法,對DER字串解析,獲取到私鑰
    privateKey,err := x509.ParsePKCS1PrivateKey(block.Bytes)
    if err!=nil{
    	panic(err)
    }
    //4.生成明文訊息對應的雜湊值
    hash256 := sha256.New()
    hash256.Write(plainText)
    hashValue := hash256.Sum(nil)
    //5.使用[私鑰]和[雜湊值]完成簽名(RSA包內的方法)
    codeSign,err := rsa.SignPKCS1v15(rand.Reader,privateKey,crypto.SHA256,hashValue)
    if err!=nil{
    	panic(err)
    }
    return codeSign
}
//使用RSA公鑰校驗函式
func RSAUsePublicKeyVerify(plainText,codeSign []byte, publicKeyFileName string)(flag string){
    //1.將檔案當中的加密公鑰讀出存入緩衝區
    publicFile,err := os.Open(publicKeyFileName)
    if err!=nil{
    	panic(err)
    }
    defer publicFile.Close()
    fileInfo,err := publicFile.Stat()
    if err!=nil{
    	panic(err)
    }
    buffer := make([]byte, fileInfo.Size())
    publicFile.Read(buffer)
    //2.pem解碼,將資料流轉換成一個存有DER字串的pem.Block塊
    block,_ := pem.Decode(buffer)
    //3.使用x509方法,對DER字串解析,獲取到公鑰(記得斷言)
    publicInterface,err := x509.ParsePKIXPublicKey(block.Bytes)
    if err!=nil{
    	panic(err)
    }
    publicKey := publicInterface.(*rsa.PublicKey)
    //4.生成明文訊息對應的雜湊值
    hash256 := sha256.New()
    hash256.Write(plainText)
    hashValue := hash256.Sum(nil)
    //5.使用[公鑰]和[雜湊值]完成簽名校驗(RSA包內的方法)
    err = rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, hashValue, codeSign)
    if err!=nil{
    	flag = "驗證失敗"
    }else{
    	flag = "驗證成功"
    }
    return
}
func main(){
    plainText := []byte("helloworld今天天氣好晴朗處處好風光")
    //數字簽名
    codeSign := RSAUsePrivateKeySign(plainText, "frankPrivate.pem")
    //只是為了能看到所有內容而已,所以base64編碼一下輸出,不然都是亂碼
    //實際核驗操作還是是用codeSign
    result := hex.EncodeToString(codeSign);
    fmt.Printf("%s\n", result);
    //簽名驗證
    flag := RSAUsePublicKeyVerify(plainText, codeSign, "frankPublic.pem")
    fmt.Printf("%s\n",flag)
}

結果很輕鬆看到base64序列化之後的數字簽名,長度剛好是我們選擇的SHA256演算法的hash雜湊值長度。