1. 程式人生 > >密碼學07--數字簽名之go中的橢圓曲線數字簽名

密碼學07--數字簽名之go中的橢圓曲線數字簽名

目錄

1.ECC

1.1 簡介

1.2 GO語言中的ECC說明

1.3 Go語言中的ECC相關

1.4 Go語言中的ECC數字簽名流程

1.4.1 ECDSA金鑰對生成

1.4.2 ECDSA金鑰對本地化

1.4.3 ECDSA私鑰數字簽名

1.4.4 ECDSA公鑰簽名校驗

1.5 Go語言中的ECC數字簽名模板


1.ECC

1.1 簡介

Elliptic curve cryptography,橢圓曲線密碼學,即ECC。是一種建立公開金鑰加密的演算法,基於橢圓曲線數學。橢圓曲線在密碼學中的使用是在1985年由Neal Koblitz和Victor Miller分別獨立提出的。詳情連結

https://www.cnblogs.com/Kalafinaian/p/7392505.html

如果你能夠堅持看完上面的部落格,並且能夠看懂裡面的內容,我十分佩服,因為我實在是沒看下去。不過或多或少了解了一些基本的概念比如橢圓曲線函式並不是說真的就是一個函式來生成橢圓上的兩個點這麼簡單,函式的幾何形狀也並不真的是一個橢圓。(真的就看懂了這麼點東西..)所以有機會還是希望多多閱讀一下大神的部落格。

1.2 GO語言中的ECC說明

在上一篇部落格中我提到了一個來自大神們的嘲諷,那就是:

  • go語言只提供了RSA演算法的非對稱加密介面,而沒有提供ECC的非對稱加密介面!
  • 然而go語言卻同時提供了RSA演算法的數字簽名介面,和ECC的數字簽名介面。

這就意味著我們雖然不能使用ECC生成的公私鑰對我們的資訊進行非對稱加密操作,但是我們卻可以使用ECC生成的公私鑰進行數字簽名操作。直白一點說就是:RSA演算法可以使用生成的金鑰對來對資訊加密,也能進行數字簽名;而ECC則只能使用生成的金鑰對進行數字簽名。

1.3 Go語言中的ECC相關

(1)go中用於宣告橢圓曲線模型的包 crypto/elliptic包,並提供生成4種素域的橢圓曲線函式(素域:執行不同ECC函式的到的不同橢圓曲線)。當然如果你對ECC有相當的研究並懂得它的執行原理,也可以通過go語言中提供的詳細ECC方法來定製屬於自己標準的ECC函式╮(╯▽╰)╭ 我暫時先放棄了。對於go語言自帶的ECC函式來說,數字越大對應的ECC的公私鑰的長度就越長,對應的加密等級就越高,當然也就越安全,那麼對應的執行效率也就會相對降低。

(2)go中用於橢圓曲線數字簽名的包 crypto/ecdsa包

1.4 Go語言中的ECC數字簽名流程

1.4.1 ECDSA金鑰對生成

與RSA不同的是,RSA演算法生成的金鑰對都是*rsa型別的,而ECC演算法生成的金鑰對都是*ecdsa型別的。所以當生成*ecdsa金鑰對的時候需要呼叫的自然不能是crypto/rsa包中的函式,而應當是crypto/ecdsa包中的GenerateKey函式。

//GenerateKey函式生成一對ecdsa型別的公鑰/私鑰。
func GenerateKey(c elliptic.Curve, rand io.Reader) (priv *PrivateKey, err error)

相信你已經注意到本函式的第一個引數是一個elliptic.Curve型別的物件,這是什麼?這就是ECC橢圓曲線!那麼我們會如何生成ECC橢圓曲線嗎?嗯,我不會,所以我們可以直接通過呼叫crypto/elliptic包中的自帶ECC函式來生成一個ECC橢圓曲線。

//返回一個實現了P-224的曲線。
func P224() Curve
//返回一個實現了P-256的曲線。
func P256() Curve
//返回一個實現了P-384的曲線。
func P384() Curve
//返回一個實現了P-512的曲線。
func P521() Curve

1.4.2 ECDSA金鑰對本地化

(1)私鑰本地化

a.將GenerateKey方法生成的私鑰進行x509序列化,生成一個ASN.1標準的DER二進位制編碼

//MarshalECPrivateKey將ecdsa私鑰序列化為ASN.1 DER編碼。
func MarshalECPrivateKey(key *ecdsa.PrivateKey) ([]byte, error)

b.構建pem.Block資料塊,將私鑰DER編碼賦值到pem.Block資料塊的Bytes屬性中

type Block struct {
    Type    string            // 得自前言的型別(如"RSA PRIVATE KEY")
    Headers map[string]string // 可選的頭項
    Bytes   []byte            // 內容解碼後的資料,一般是DER編碼的ASN.1結構
}

c.使用encoding/pem包中的Encode方法對pem.Block資料塊進行base64編碼後本地化

func Encode(out io.Writer, b *Block) error

(2)公鑰本地化

a.從私鑰中取出公鑰

//PrivateKey代表一個ECDSA私鑰。
type PrivateKey struct {
    PublicKey
    D   *big.Int
}

b.將得到的公鑰進行x509序列化,生成一個ASN.1標準的DER二進位制編碼(記得引數要取地址,因為在函式內部進行了一次型別斷言以便決定究竟用到的指標是*rsa.PrivateKey型別還是*ecdsa.PrivateKey型別)

//MarshalPKIXPublicKey將公鑰序列化為PKIX格式DER編碼。
func MarshalPKIXPublicKey(pub interface{}) ([]byte, error)

c.構建pem.Block資料塊,將公鑰DER編碼賦值到pem.Block資料塊的Bytes屬性中

d.使用encoding/pem包中的Encode方法對pem.Block資料塊進行base64編碼後本地化

1.4.3 ECDSA私鑰數字簽名

(1)讀取私鑰檔案內容到緩衝區,此時緩衝區檔案是base64編碼序列

(2)使用crypto/pem包中的Decode方法反序列化,得到存有私鑰DER編碼內容的pem.Block資料塊

(3)使用crypto/x509包中的方法對pem.Block資料塊中的Bytes屬性對應的ASN.1標準的DER字串解析,獲得ecdsa私鑰

//ParseECPrivateKey解析ASN.1 DER編碼的ecdsa私鑰。
func ParseECPrivateKey(der []byte) (key *ecdsa.PrivateKey, err error)

(4)選擇hash演算法,生成原像對應的雜湊值(New+Write+Sum三連擊)

//返回一個新的使用某Hash校驗演算法的hash.Hash介面。
func New() hash.Hash
//Writer介面用於包裝基本的寫入方法。
type Writer interface {
    Write(p []byte) (n int, err error)
}
// 返回新增b到當前的hash值後的新切片,不會改變底層的hash狀態
Sum(b []byte) []byte

(5)使用crypto/ecdsa包中的sign方法,將雜湊值與ecdsa私鑰完成數字簽名

/*
引數1:rand.Reader密碼學隨機數生成器
引數2:privatekey讀取到的私鑰
引數3:hashValue原像的雜湊值
返回值:橢圓曲線上的兩個點代表的資料的記憶體地址
*/
func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err error)

但是一般並不直接儲存這兩個地址,而是將這兩個地址中的值取出另行儲存。這是因為在正常情況下發送端和接收端應當是兩臺不同的裝置。而不同裝置中相同的記憶體地址所代表的值是幾乎不可能一樣的,當然幾千億分之一甚至幾千兆億分之一的巧合我們不考慮所以我們需要藉助math/big包中的轉換方法,將一對*big.Inde的大數轉換成[]byte型別傳輸。

//本方法實現了encoding.TextMarshaler介面。
func (z *Int) MarshalText() (text []byte, err error)

1.4.4 ECDSA公鑰簽名校驗

(1)讀取公鑰檔案內容到緩衝區,此時緩衝區檔案是base64編碼序列

(2)使用crypto/pem包中的Decode方法反序列化,得到存有公鑰DER編碼內容的pem.Block資料塊

(3)使用crypto/x509包中的方法對pem.Block資料塊中的Bytes屬性對應的ASN.1標準的DER字串解析,記得斷言獲得ecdsa公鑰

//MarshalPKIXPublicKey將公鑰序列化為PKIX格式DER編碼。
func MarshalPKIXPublicKey(pub interface{}) ([]byte, error)

(4)選擇hash演算法,生成原像對應的雜湊值(New+Write+Sum三連擊)

(5)使用crypto/ecdsa包中的verify方法,將雜湊值、ecdsa公鑰和接收到的ECC數字簽名信息完成簽名校驗

/*
引數1: publicKey讀取到的公鑰
引數2: hashValue生成的雜湊值
引數3: 儲存ECC簽名中橢圓曲線上的某一點r的資訊的變數的記憶體地址
引數3: 儲存ECC簽名中橢圓曲線上的某一點s的資訊的變數的記憶體地址

返回值:校驗結果,成功是true,失敗是false
*/
func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool

顯然1.4.3中已經說明我們不會在生成ECC簽名信息後直接儲存數字簽名信息r和s,而是會將r和s轉換為[]byte儲存。那麼在進行簽名校驗的時候我們就必須通過獲得的[]byte型別資料中反響轉換為*big.Int型別的資料,才能傳入verify方法中進行簽名校驗。因為生成ECC簽名信息時我們使用的是math/big包中的轉換方法將一對*big.Inde的大數轉換成[]byte型別傳輸,所以在進行校驗的時候仍然使用math/big包中的轉換方法將[]byte型別反響轉換回*big.Int型別的資料。

//本方法實現了encoding.TextUnmarshaler介面。
func (r *Rat) UnmarshalText(text []byte) error

1.5 Go語言中的ECC數字簽名模板

func generateECDSAKey(){
        //生成
	frankPrivateKey,err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
	if err!=nil{
		panic(err)
	}
        //私鑰本地化
	privateDerBytes,err := x509.MarshalECPrivateKey(frankPrivateKey)
	if err!=nil{
		panic(err)
	}
	privatePemBlock := pem.Block{Type:"FRANK ECDSA PRIVATEKEY", Bytes:privateDerBytes}
	privateFile,err := os.Create("ECDSAPrivateKey.pem")
	if err!=nil{
		panic(err)
	}
	pem.Encode(privateFile, &privatePemBlock)
	privateFile.Close()
        //公鑰本地化
	frankPublicKey := frankPrivateKey.PublicKey
	publickDerBytes,err := x509.MarshalPKIXPublicKey(&frankPublicKey)
	if err!=nil{
		panic(err)
	}
	publickPemBlock := pem.Block{Type:"FRANK ECDSA PUBLICKEY", Bytes:publickDerBytes}
	publicFile,err := os.Create("ECDSAPublicKey.pem")
	if err!=nil{
		panic(err)
	}
	pem.Encode(publicFile, &publickPemBlock)
	publicFile.Close()
}
//ECDSA私鑰數字簽名
func ECDSAUsePrivateKeySign (plainText []byte, privateKeyFileName string)(rText,sText []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解碼
	block,_ := pem.Decode(buffer)
        //3.x509 ASN.1 DER解碼
	privateKey,err := x509.ParseECPrivateKey(block.Bytes)
	if err!=nil{
		panic(err)
	}
        //4.雜湊值
	hash256 := sha256.New()
	hash256.Write(plainText)
	hashValue := hash256.Sum(nil)
        //5.簽名
	r,s,err := ecdsa.Sign(rand.Reader, privateKey, hashValue)
	rText,_ = r.MarshalText()
	sText,_ = s.MarshalText()
	return
}
//ECDSA公鑰簽名校驗
func ECDSAUsePublicKeyVerify(plainText,rText,sText []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解碼
	block,_ := pem.Decode(buffer)
        //3.x509 ASN.1 DER解碼
	publicInterface,err := x509.ParsePKIXPublicKey(block.Bytes)
	if err!=nil{
		panic(err)
	}
	publicKey := publicInterface.(*ecdsa.PublicKey)
        //4.雜湊值
	hash256 := sha256.New()
	hash256.Write(plainText)
	hashValue := hash256.Sum(nil)
        //5.簽名校驗
	var r,s big.Int
	r.UnmarshalText(rText)
	s.UnmarshalText(sText)
	boolflag := ecdsa.Verify(publicKey,hashValue,&r,&s)
	if boolflag{
		flag = "驗證成功"
	}else{
		flag = "驗證失敗"
	}
	return
}
func main(){
        //明文
	plainText := []byte("helloworld今天天氣好晴朗處處好風光")
        //公私鑰對
	generateECDSAKey()

        //私鑰簽名
	rText,sText := ECDSAUsePrivateKeySign(plainText, "ECDSAPrivateKey.pem")
	fmt.Printf("rText:%s\nsText:%s\n",rText,sText)
        //公鑰校驗
	result := ECDSAUsePublicKeyVerify(plainText,rText,sText,"ECDSAPublicKey.pem")
	fmt.Printf("%s",result)
}

執行結果如圖所示: