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加密解密