密码学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哈希值长度。

猜你喜欢

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