密码学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)
}

执行结果如图所示:

猜你喜欢

转载自blog.csdn.net/u013792921/article/details/85057646