ecc算法的代码实现
什么是ecc算法
Elliptic curve cryptography,椭圆曲线密码学,即ECC。是一种建立公开密钥加密的算法,基于椭圆曲线数学。椭圆曲线在密码学中的使用是在1985年由Neal Koblitz和Victor Miller分别独立提出的。详情链接https://www.cnblogs.com/Kalafinaian/p/7392505.html。
如果你能够坚持看完上面的博客,并且能够看懂里面的内容,我十分佩服,因为我实在是没看下去。不过或多或少了解了一些基本的概念比如椭圆曲线函数并不是说真的就是一个函数来生成椭圆上的两个点这么简单,函数的几何形状也并不真的是一个椭圆。(真的就看懂了这么点东西..)所以有机会还是希望多多阅读一下大神的博客。
有关与go语言的ecc包
在go语言中crypto/elliptic包是声明椭圆曲线模型的包
注意:对于go语言自带的ECC函数来说,数字越大对应的ECC的公私钥的长度就越长,对应的加密等级就越高,当然也就越安全,那么对应的执行效率也就会相对降低。
crypto/ecdsa包则是go中用于椭圆曲线数字签名的包。
具体实现流程
一、生成公私钥
生成密钥-->将生成的私钥进行x509序列化为ASN.1标准的DER二进制编码--> 构建pem.Block数据块-->pem编码【公钥同理】
二、ecdsa私钥生成数字签名
读取本地私钥pem文件-->pem解析pem数据块-->x509解析der字符串,获得私钥-->计算hash-->ecdsa签名
三、ecdsa签名验证
读取本地公钥pem文件-->pem解析pem数据块-->x509解析der字符串,获得公钥-->计算hash-->ecdsa签名验证
代码实现
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"crypto/x509"
"encoding/pem"
"fmt"
"math/big"
"os"
)
// 初始化创建ecc密钥
func generateECDSAKey() {
// 生成ecc算法的密钥
privateKey, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
if err != nil {
panic(err)
}
// 将私钥本地化,使用x509进行序列化
privateDerBytes, err := x509.MarshalECPrivateKey(privateKey)
if err != nil {
panic(err)
}
// 再将它转换成pem的格式编码
privatePemBlock := pem.Block{
Type: "Ecc PrivateKey", // 简介使用算法类型rsa/ecc
Bytes: privateDerBytes,
}
// 在本地创建pem文件
privateFile, err := os.Create("ECDSAPrivateKey.pem")
if err != nil {
panic(err)
}
defer privateFile.Close()
// 进行pem编码
err = pem.Encode(privateFile, &privatePemBlock)
if err != nil {
panic(err)
}
// 公钥同理
publicKey := privateKey.PublicKey
publicDerBytes, err := x509.MarshalPKIXPublicKey(&publicKey)
if err != nil {
panic(err)
}
publicPemBlock := pem.Block{
Type: "Ecc PublicKey",
Bytes: publicDerBytes,
}
publicFile, err := os.Create("ECDSAPublicKey.pem")
if err != nil {
panic(err)
}
defer publicFile.Close()
pem.Encode(publicFile, &publicPemBlock)
}
// 私钥签名
func privateKeySignature(data []byte, privateKeyPemFileName string) (rText, sText []byte) {
// 读取私钥
privateFile, err := os.Open(privateKeyPemFileName)
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)
// pem解码
block, _ := pem.Decode(buffer)
// x509 DER解码
privateKey, err := x509.ParseECPrivateKey(block.Bytes)
if err != nil {
panic(err)
}
// 进行签名
r, s, err := ecdsa.Sign(rand.Reader, privateKey, getHash(data))
// 将big的数据转换成[]byte
rText, _ = r.MarshalText()
sText, _ = s.MarshalText()
return
}
// 获取哈希
func getHash(data []byte) []byte {
hash256 := sha256.New()
hash256.Write(data)
return hash256.Sum(nil)
}
// 公钥验证
func publicKeyVerify(data, rText, sText []byte, publicKeyPemFileName string) bool {
// 读取公钥
publicFile, err := os.Open(publicKeyPemFileName)
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)
block, _ := pem.Decode(buffer)
publicKeyType, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
panic(err)
}
// 获取到publicKey的类型,要进行断言判断rsa/ecc
publicKey := publicKeyType.(*ecdsa.PublicKey)
// 对[]byte签名数据转换成big数据
var r, s big.Int
r.UnmarshalJSON(rText)
s.UnmarshalJSON(sText)
if !ecdsa.Verify(publicKey, getHash(data), &r, &s) {
return false
} else {
return true
}
}
func main() {
data := []byte("我向某用户转账10元")
generateECDSAKey()
rText, sText := privateKeySignature(data, "ECDSAPrivateKey.pem")
fmt.Println(publicKeyVerify(data, rText, sText, "ECDSAPublicKey.pem"))
}
使用以太坊的ecc加密算法实现
以太坊的crypto模块
该模块分为两个部分一个是实现sha3,一个是实现secp256k1(这也是比特币中使用的签名算法). 需要说明的是secp256k1有两种实现方式,一种是依赖libsecp256k1,需要cgo,另外一种是依赖github.com/btcsuite/btcd,这是一个使用go语言实现的比特币的客户端.
sha3模块
这个模块实际上可以认为就是一个功能计算sha3-256,用法也很简单,就是调用crypto中的Keccak256,输出是一个32字节的hash结果
hash := crypto.Keccak256Hash([]byte("hello"))
//hash值:4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45
secp256k1模块
这个模块比较复杂,如果要细度源码,需要对密码学有比较深入的理解,但是使用起来其实比较简单.
主要就是签名,验证,以及公钥与以太坊地址转换
1.签名
secp256k1的私钥地址长度是32字节256位,公钥地址长度是65字节,而以太坊的地址长度是20字节.
2.验证
验证签名是否正确,需要公钥,hash(对message进行hash的结果),以及签名. 这里面真正校验的是第三步,也就是公钥是否和我的相同,而不像普通工RSA签名验证一样.当然我们可以封装成和RSA签名验证一样形式的func VerifySignature(pubKey,msg,sig []byte) error
3.公钥与地址的转换
以太坊中并没有直接拿公钥当做账户地址,而是进行了一个简单的转换,具体来说就是hash(公钥)的后20位,这里的hash算法是sha3-256,可以用一行代码来表示
crypto.Keccak256(pubKey)[12:]
以太坊ecc的简单模式实现
package main
import (
"bytes"
"crypto/ecdsa"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"fmt"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/ecies"
"strconv"
)
// 获取私钥
func getKey() (*ecdsa.PrivateKey, error) {
privateKey, err := ecdsa.GenerateKey(crypto.S256(), rand.Reader)
if err != nil {
return privateKey, err
}
return privateKey, nil
}
// 加密
func ECCEncrypt(publicKey ecies.PublicKey, data []byte) ([]byte, error) {
ct, err := ecies.Encrypt(rand.Reader, &publicKey, data, nil, nil)
return ct, err
}
// 解密
func ECCDecrypt(privateKey ecies.PrivateKey, ct []byte) ([]byte, error) {
m, err := privateKey.Decrypt(ct, nil, nil)
return m, err
}
// 获取哈希
func getHash2(data []byte, nonce int) string {
hashBytes := sha256.Sum256([]byte(string(data) + strconv.Itoa(nonce)))
return hex.EncodeToString(hashBytes[:])
}
// 获取挖矿等级需求来进行判断hash是否满足
func getMineDiff(diff int) (str string) {
for i := 0; i < diff; i++ {
str = str + "0"
}
return
}
// 开始挖矿[链的挖矿难度]
func calculationHash(diff int, data []byte) string {
strDiff := getMineDiff(diff)
var nonce int
for {
if getHash2(data, nonce)[:diff] == strDiff {
return getHash2(data, nonce)
}
nonce++
}
}
// 签名
func signature(hash []byte, privateKey *ecdsa.PrivateKey) ([]byte, error) {
signature, err := crypto.Sign(hash, privateKey)
if err != nil {
return signature, err
}
return signature, nil
}
// 验证
func validate(recoveredPub, recoveredPubBytes []byte) bool {
if !bytes.Equal(recoveredPubBytes, recoveredPub) {
return false
}
return true
}
func main() {
privateKeyECDSA, _ := getKey()
// 将ecdsa的私钥转换成以太坊的私钥
privateKey := ecies.ImportECDSA(privateKeyECDSA)
publicKey := privateKey.PublicKey
// 公钥加密
data := []byte("我向某用户转账10元")
hash := calculationHash(4, data)
fmt.Println("哈希散列为", hash)
encryptData, err := ECCEncrypt(publicKey, []byte(hash))
if err != nil {
panic(err)
}
fmt.Println("公钥加密后为", hex.EncodeToString(encryptData))
// 私钥解密
decryptData, err := ECCDecrypt(*privateKey, encryptData)
if err != nil {
panic(err)
}
fmt.Println("私钥解密后为", string(decryptData))
// 进行签名
signData, _ := signature(crypto.Keccak256([]byte(hash)), privateKey.ExportECDSA())
fmt.Println("签名为", hex.EncodeToString(signData))
// 验证
recoveredPub, _ := crypto.Ecrecover(crypto.Keccak256([]byte(hash)), signData)
recoveredPubBytes := crypto.FromECDSAPub(&privateKeyECDSA.PublicKey)
fmt.Println(validate(recoveredPub, recoveredPubBytes))
}
ecc密钥对与[]byte类型转换
借鉴参考:
https://blog.csdn.net/u013792921/article/details/85057646#1.ECC 密码学07--数字签名之go中的椭圆曲线数字签名
https://www.cnblogs.com/baizx/p/6936258.html 以太坊系列之三: 以太坊的crypto模块--以太坊源码学习
https://blog.csdn.net/reigns_/article/details/83069118 golang实现ecc加密解密