美文网首页Go进阶系列
8 Go 密码学(五)非对称加密之椭圆曲线算法

8 Go 密码学(五)非对称加密之椭圆曲线算法

作者: GoFuncChan | 来源:发表于2019-07-09 16:35 被阅读0次

    一、椭圆曲线概述

    椭圆曲线密码学(Elliptic curve cryptography),简称ECC,其和RAS类似属于公开秘钥的加密算法体系。ECC被公认为在给定密钥长度下最安全的加密算法。近年来由于比特币以太币等区块链应用ECC加密算法,业界也普遍看好ECC。

    我们说过现代密码学是基于数学难题构建的,如RSA基于大数分解,ECC则基于椭圆曲线的椭圆曲线上的离散对数问题。

    椭圆曲线是代数几何中一类重要的曲线,至今已有 100 多年的研究历史。而应用于密码学中的椭圆曲线是基于有限域上的,通过引入无穷远点,将椭圆曲线上的所有点和无穷远点组成一个集合,并在该集合上定义一个运算,从而该集合和运算构成了群。在有限域上的椭圆曲线群有两种,分别基于GF(p)以及GF(2m),它们各自有不同的群元素和群运算,然而对于群上的 ECDLP 问题,都认为是一个指数级的困难问题。基于这个困难问题,构建了ECC算法,包括==公钥加密、私钥解密、数字签名、签名验证、DH交换==等。

    二、Go中使用ECDSA数字签名及签名验证

    ECDSA 算法是目前使用最为广泛的标准算法,它是以 ECDLP 困难问题为基础,采用ELGamal体制构建的一个签名算法,它包含一个签名算法和一个验证算法。ECDSA算法如下:
    
    首先选择好系统参数,如有限域类型和表示方法,曲线参数a,b,以及一个曲线上的基点G以及G的阶n,要求n必须为一个大素数(相关的系统参数的选择可见文献[23])。
    
    参数确定以后,ECDSA 算法分为如下 3 个模块分别执行不同的功能,即密钥产生模块、数字签名模块以及签名验证模块。另外介绍一个基于ECC的Diffie-Hellman交换型算法以及公钥加密算法。
    
    密钥产生:
    1.在区间[1,n−1]上随机产生一个整数d(当然d不能太小)。
    2.计算标量乘法Q=dG。
    3.公开Q为公钥,保留d为私钥。
    
    数字签名:
    1.使用安全散列函数H对需要签名的消息M进行杂凑计算e=H(M)。
    2.随机生成一个区间[1,n−1]上的本地秘密随机数k,并计算kG=(x1,y1)。
    3.计算r=x1 mod n。
    4.计算s=k−1(e+dr)mod n。
    5.则数据r||s即为ECDSA算法下对消息M的签名(其中||表示两个比特串的串接)。
    
    签名验证:
    1.使用与签名一样的散列算法H计算e=H(M)。
    2.计算c=s−1 mod n。
    3.计算u1=ec mod n,u2=rc mod n。
    4.计算(x1,y1)=u1G+uQ2。
    5.计算v=x1mod n。若v=r,则为一个合法签名,否则验证不通过。
    
    DH交换:
    1.A随机生成一个区间[1,n−1]上的本地秘密随机数k,计算并发送kG到B。
    2.B随机生成一个区间[1,n−1]上的本地秘密随机数l,计算并发送lG到A。
    3.最后A计算k(lG)同时B计算l(kG)作为双方的共享密钥。
    
    公钥加密算法:
    1.公钥加密:随机生成一个区间[1,n-1]上的本地秘密随机数k,并计算,对需要加密的消息M。计算的密文C=kG||kQ+M(其中+为XOR运算)。
    2.私钥解密:M=(kQ+M)−d(kG)。
    
    1.生成密钥对
    import (
        "crypto/ecdsa"
        "crypto/elliptic"
        "time"
    
        "crypto/x509"
        "encoding/pem"
        "errors"
        mathRand "math/rand"
        "os"
        "strings"
    )
    
    const (
        PRIVATEFILE = "src/cryptography/myECDSA/privateKey.pem"
        PUBLICFILE  = "src/cryptography/myECDSA/publicKey.pem"
    )
    
    //生成指定math/rand字节长度的随机字符串
    func GetRandomString(length int) string {
        str := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ~!@#$%^&*()_+?=-"
        bytes := []byte(str)
        result := []byte{}
    
        r := mathRand.New(mathRand.NewSource(time.Now().UnixNano()))
        for i := 0; i < length; i++ {
            result = append(result, bytes[r.Intn(len(bytes))])
        }
        return string(result)
    }
    
    //生成ECC算法的公钥和私钥文件
    //根据随机字符串生成,randKey至少36位
    func GenerateKey(randKey string) error {
    
        var err error
        var privateKey *ecdsa.PrivateKey
        var publicKey ecdsa.PublicKey
        var curve elliptic.Curve
    
        //一、生成私钥文件
    
        //根据随机字符串长度设置curve曲线
        length := len(randKey)
        //elliptic包实现了几条覆盖素数有限域的标准椭圆曲线,Curve代表一个短格式的Weierstrass椭圆曲线,其中a=-3
        if length < 224/8 {
            err = errors.New("私钥长度太短,至少为36位!")
            return err
        }
    
        if length >= 521/8+8 {
            //长度大于73字节,返回一个实现了P-512的曲线
            curve = elliptic.P521()
        } else if length >= 384/8+8 {
            //长度大于56字节,返回一个实现了P-384的曲线
            curve = elliptic.P384()
        } else if length >= 256/8+8 {
            //长度大于40字节,返回一个实现了P-256的曲线
            curve = elliptic.P256()
        } else if length >= 224/8+8 {
            //长度大于36字节,返回一个实现了P-224的曲线
            curve = elliptic.P224()
        }
    
        //GenerateKey方法生成私钥
        privateKey, err = ecdsa.GenerateKey(curve, strings.NewReader(randKey))
        if err != nil {
            return err
        }
        //通过x509标准将得到的ecc私钥序列化为ASN.1的DER编码字符串
        privateBytes, err := x509.MarshalECPrivateKey(privateKey)
        if err != nil {
            return err
        }
        //将私钥字符串设置到pem格式块中
        privateBlock := pem.Block{
            Type:  "ecc private key",
            Bytes: privateBytes,
        }
    
        //通过pem将设置好的数据进行编码,并写入磁盘文件
        privateFile, err := os.Create(PRIVATEFILE)
        if err != nil {
            return err
        }
        defer privateFile.Close()
        err = pem.Encode(privateFile, &privateBlock)
        if err != nil {
            return err
        }
    
        //二、生成公钥文件
        //从得到的私钥对象中将公钥信息取出
        publicKey = privateKey.PublicKey
    
        //通过x509标准将得到的ecc公钥序列化为ASN.1的DER编码字符串
        publicBytes, err := x509.MarshalPKIXPublicKey(&publicKey)
        if err != nil {
            return err
        }
        //将公钥字符串设置到pem格式块中
        publicBlock := pem.Block{
            Type:  "ecc public key",
            Bytes: publicBytes,
        }
    
        //通过pem将设置好的数据进行编码,并写入磁盘文件
        publicFile, err := os.Create(PUBLICFILE)
        if err != nil {
            return err
        }
        err = pem.Encode(publicFile, &publicBlock)
        if err != nil {
            return err
        }
    
        return nil
    }
    
    2.ECDSA 签名及校验
    import (
        "bytes"
        "compress/gzip"
        "crypto/ecdsa"
        "encoding/hex"
        "errors"
        "math/big"
        "strings"
    )
    
    //使用ECC算法加密签名,返回签名数据
    func CryptSignByEcc(input, priKeyFile, randSign string) (output string, err error) {
        //获取私钥
        privateKey, err := GetPrivateKeyByPemFile(priKeyFile)
        if err != nil {
            return "", err
        }
    
        //ecc私钥和随机签字符串数据得到哈希
        r, s, err := ecdsa.Sign(strings.NewReader(randSign), privateKey, []byte(input))
        if err != nil {
            return "", err
        }
    
        rt, err := r.MarshalText()
        if err != nil {
            return "", err
        }
    
        st, err := s.MarshalText()
        if err != nil {
            return "", err
        }
    
        //拼接两个椭圆曲线参数哈希
        var b bytes.Buffer
        writer := gzip.NewWriter(&b)
        defer writer.Close()
    
        _, err = writer.Write([]byte(string(rt) + "+" + string(st)))
        if err != nil {
            return "", err
        }
        writer.Flush()
    
        return hex.EncodeToString(b.Bytes()), nil
    }
    
    //使用ECC算法,对密文和明文进行匹配校验
    func VerifyCryptEcc(srcStr, cryptStr string) (bool, error) {
    
        decodeBytes, err := hex.DecodeString(cryptStr)
        if err != nil {
            return false, err
        }
    
        //解密签名信息,返回椭圆曲线参数:两个大整数
        rint, sint, err := UnSignCryptEcc(decodeBytes)
    
        //获取公钥验证数据
        publicKey, err := GetPublicKeyByPemFile(PUBLICFILE)
        if err != nil {
            return false, err
        }
        //使用公钥、原文、以及签名信息解密后的两个椭圆曲线的大整数参数进行校验
        verify := ecdsa.Verify(publicKey, []byte(srcStr), &rint, &sint)
    
        return verify, nil
    }
    
    //使用ECC算法解密,返回加密前的椭圆曲线大整数
    func UnSignCryptEcc(cryptBytes []byte) (rint, sint big.Int, err error) {
        reader, err := gzip.NewReader(bytes.NewBuffer(cryptBytes))
        if err != nil {
            err = errors.New("decode error," + err.Error())
        }
        defer reader.Close()
    
        buf := make([]byte, 1024)
        count, err := reader.Read(buf)
        if err != nil {
            err = errors.New("decode read error," + err.Error())
        }
    
        rs := strings.Split(string(buf[:count]), "+")
        if len(rs) != 2 {
            err = errors.New("decode fail")
            return
        }
        err = rint.UnmarshalText([]byte(rs[0]))
        if err != nil {
            err = errors.New("decrypt rint fail, " + err.Error())
            return
        }
        err = sint.UnmarshalText([]byte(rs[1]))
        if err != nil {
            err = errors.New("decrypt sint fail, " + err.Error())
            return
        }
        return
    }
    
    
    获取私钥文件里的数据:
    
    func GetPrivateKeyByPemFile(priKeyFile string) (*ecdsa.PrivateKey, error) {
        //将私钥文件中的私钥读出,得到使用pem编码的字符串
        file, err := os.Open(priKeyFile)
        if err != nil {
            return nil, err
        }
        defer file.Close()
    
        fileInfo, err := file.Stat()
        if err != nil {
            return nil, err
        }
        size := fileInfo.Size()
        buffer := make([]byte, size)
        _, err = file.Read(buffer)
        if err != nil {
            return nil, err
        }
        //将得到的字符串解码
        block, _ := pem.Decode(buffer)
    
        //使用x509将编码之后的私钥解析出来
        privateKey, err := x509.ParseECPrivateKey(block.Bytes)
        if err != nil {
            return nil, err
        }
    
        return privateKey, nil
    }
    
    
    获取公钥文件里的数据:
    func GetPublicKeyByPemFile(pubKeyFile string) (*ecdsa.PublicKey, error) {
        var err error
        //从公钥文件获取钥匙字符串
        file, err := os.Open(pubKeyFile)
        if err != nil {
            return nil, err
        }
        defer file.Close()
    
        fileInfo, err := file.Stat()
        if err != nil {
            return nil, err
        }
    
        buffer := make([]byte, fileInfo.Size())
        _, err = file.Read(buffer)
        if err != nil {
            return nil, err
        }
        //将得到的字符串解码
        block, _ := pem.Decode(buffer)
    
        //使用x509将编码之后的公钥解析出来
        pubInner, err := x509.ParsePKIXPublicKey(block.Bytes)
        if err != nil {
            return nil, err
        }
    
        publicKey := pubInner.(*ecdsa.PublicKey)
    
        return publicKey, nil
    }
    
    
    数字签名及演示
    func TestECDSA() {
        //生成随机钥字符串长度40字节,用于生产公私钥证书
        randKey := myECDSA.GetRandomString(40)
        //生成随机签名字符串40字节,用于加密数据
        randSign := myECDSA.GetRandomString(40)
    
        //使用随机钥字符串生成公私钥文件
        e := myECDSA.GenerateKey(randKey)
        if e != nil {
            fmt.Println(e)
        }
    
        //签名附加信息
        srcInfo := "GO 密码学 —— ECDSA 椭圆曲线实现数字签名"
        fmt.Println("原文:", srcInfo)
    
        //ECC签名加密
        signByEcc, e := myECDSA.CryptSignByEcc(srcInfo, myECDSA.PRIVATEFILE, randSign)
        if e != nil {
            fmt.Println(e)
        }
        fmt.Println("ECDSA私钥加密签名为:", signByEcc)
    
        //ECC签名算法校验
        verifyCryptEcc, e := myECDSA.VerifyCryptEcc(srcInfo, signByEcc)
        if e != nil {
            fmt.Println(e)
        }
        fmt.Println("ECDSA公钥解密后验签校验结果:", verifyCryptEcc)
    
    }
    
    //OUTPUT:
    原文: GO 密码学 —— ECDSA 椭圆曲线实现数字签名
    ECDSA私钥加密签名为: 1f8b08000000000000ff14cbb10d43510c02c081d2609e31b0ff62d16f4fbac1bd3a8666cb598f9f595e208fa950d9e4721457071f85ae9a06d1e37b600dc0f885fa0c37aea7678ac6ce75c4d4e5c386ad446a0fd2adced767ccf70628ac81f60f0000ffff
    ECDSA公钥解密后验签校验结果: true
    
    

    三、GO 使用ECIES 加解密算法

    go标准包的ECDSA仅支持数字签名和验签,对数据传输的加解密还未提供,不过以太坊基于crypto/ecdsa实现了ECIES加解密算法,github.com/ethereum/go-ethereum ,感兴趣的可阅读器源码使用,该包声明未经过审核,以下演示仅供个人学习使用:

    1.生成ECIES密钥对
    package myECIES
    
    import (
        "crypto/ecdsa"
        "crypto/elliptic"
        "github.com/ethereum/go-ethereum/crypto/ecies"
        "strings"
        "time"
    
        "crypto/x509"
        "encoding/pem"
        "errors"
        mathRand "math/rand"
        "os"
    )
    
    const (
        PRIVATEFILE = "src/cryptography/myECIES/privateKey.pem"
        PUBLICFILE  = "src/cryptography/myECIES/publicKey.pem"
    )
    
    //生成指定math/rand字节长度的随机字符串
    func GetRandomString(length int) string {
        str := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ~!@#$%^&*()_+?=-"
        bytes := []byte(str)
        result := []byte{}
    
        r := mathRand.New(mathRand.NewSource(time.Now().UnixNano()))
        for i := 0; i < length; i++ {
            result = append(result, bytes[r.Intn(len(bytes))])
        }
        return string(result)
    }
    
    //生成ECC算法的公钥和私钥文件
    //根据随机字符串生成,randKey至少36位
    func GenerateKey(randKey string) error {
    
        var err error
        var privateKey *ecdsa.PrivateKey
        var publicKey ecdsa.PublicKey
        var curve elliptic.Curve
    
        //一、生成私钥文件
    
        //根据随机字符串长度设置curve曲线
        length := len(randKey)
        //elliptic包实现了几条覆盖素数有限域的标准椭圆曲线,Curve代表一个短格式的Weierstrass椭圆曲线,其中a=-3
        if length < 224/8 {
            err = errors.New("私钥长度太短,至少为36位!")
            return err
        }
    
        if length >= 521/8+8 {
            //长度大于73字节,返回一个实现了P-512的曲线
            curve = elliptic.P521()
        } else if length >= 384/8+8 {
            //长度大于56字节,返回一个实现了P-384的曲线
            curve = elliptic.P384()
        } else if length >= 256/8+8 {
            //长度大于40字节,返回一个实现了P-256的曲线
            curve = elliptic.P256()
        } else if length >= 224/8+8 {
            //长度大于36字节,返回一个实现了P-224的曲线
            curve = elliptic.P224()
        }
    
        //GenerateKey方法生成私钥
        privateKey, err = ecdsa.GenerateKey(curve, strings.NewReader(randKey))
        if err != nil {
            return err
        }
        //通过x509标准将得到的ecc私钥序列化为ASN.1的DER编码字符串
        privateBytes, err := x509.MarshalECPrivateKey(privateKey)
        if err != nil {
            return err
        }
        //将私钥字符串设置到pem格式块中
        privateBlock := pem.Block{
            Type:  "ecc private key",
            Bytes: privateBytes,
        }
    
        //通过pem将设置好的数据进行编码,并写入磁盘文件
        privateFile, err := os.Create(PRIVATEFILE)
        if err != nil {
            return err
        }
        defer privateFile.Close()
        err = pem.Encode(privateFile, &privateBlock)
        if err != nil {
            return err
        }
    
        //二、生成公钥文件
        //从得到的私钥对象中将公钥信息取出
        publicKey = privateKey.PublicKey
    
        //通过x509标准将得到的ecc公钥序列化为ASN.1的DER编码字符串
        publicBytes, err := x509.MarshalPKIXPublicKey(&publicKey)
        if err != nil {
            return err
        }
        //将公钥字符串设置到pem格式块中
        publicBlock := pem.Block{
            Type:  "ecc public key",
            Bytes: publicBytes,
        }
    
        //通过pem将设置好的数据进行编码,并写入磁盘文件
        publicFile, err := os.Create(PUBLICFILE)
        if err != nil {
            return err
        }
        err = pem.Encode(publicFile, &publicBlock)
        if err != nil {
            return err
        }
    
        return nil
    }
    
    //获取私钥文件里的私钥内容函数
    func GetPrivateKeyByPemFile(priKeyFile string) (*ecies.PrivateKey, error) {
        //将私钥文件中的私钥读出,得到使用pem编码的字符串
        file, err := os.Open(priKeyFile)
        if err != nil {
            return nil, err
        }
        defer file.Close()
    
        fileInfo, err := file.Stat()
        if err != nil {
            return nil, err
        }
        size := fileInfo.Size()
        buffer := make([]byte, size)
        _, err = file.Read(buffer)
        if err != nil {
            return nil, err
        }
        //将得到的字符串解码
        block, _ := pem.Decode(buffer)
    
        //使用x509将编码之后的私钥解析出来
        privateKey, err := x509.ParseECPrivateKey(block.Bytes)
        if err != nil {
            return nil, err
        }
    
        //读取文件的ecdsa私钥转化成ecies私钥
        privateKeyForEcies := ecies.ImportECDSA(privateKey)
    
        return privateKeyForEcies, nil
    }
    
    //获取公钥文件里的公钥内容函数
    func GetPublicKeyByPemFile(pubKeyFile string) (*ecies.PublicKey, error) {
        var err error
        //从公钥文件获取钥匙字符串
        file, err := os.Open(pubKeyFile)
        if err != nil {
            return nil, err
        }
        defer file.Close()
    
        fileInfo, err := file.Stat()
        if err != nil {
            return nil, err
        }
    
        buffer := make([]byte, fileInfo.Size())
        _, err = file.Read(buffer)
        if err != nil {
            return nil, err
        }
        //将得到的字符串解码
        block, _ := pem.Decode(buffer)
    
        //使用x509将编码之后的公钥解析出来
        pubInner, err := x509.ParsePKIXPublicKey(block.Bytes)
        if err != nil {
            return nil, err
        }
    
        publicKey := pubInner.(*ecdsa.PublicKey)
    
        publicKeyForEcies := ecies.ImportECDSAPublic(publicKey)
    
        return publicKeyForEcies, nil
    }
    
    
    2.数据加解密函数的实现:
    import (
        "crypto/rand"
        "encoding/hex"
        "fmt"
        "github.com/ethereum/go-ethereum/crypto/ecies"
    )
    
    //ECIES 公钥数据加密
    func EnCryptByEcies(srcData, publicFile string) (cryptData string, err error) {
        //获取公钥数据
        publicKey, err := GetPublicKeyByPemFile(publicFile)
        if err != nil {
            return "", err
        }
    
        //公钥加密数据
        encryptBytes, err := ecies.Encrypt(rand.Reader, publicKey, []byte(srcData), nil, nil)
        if err != nil {
            return "", err
        }
    
        cryptData = hex.EncodeToString(encryptBytes)
    
        return
    }
    
    //ECIES 私钥数据解密
    func DeCryptByEcies(cryptData, privateFile string) (srcData string, err error) {
        //获取私钥信息
        privateKey, err := GetPrivateKeyByPemFile(privateFile)
        if err != nil {
            return "", err
        }
    
        //私钥解密数据
        cryptBytes, err := hex.DecodeString(cryptData)
        srcByte, err := privateKey.Decrypt(cryptBytes, nil, nil)
        if err != nil {
            fmt.Println("解密错误:", err)
            return "", err
        }
        srcData = string(srcByte)
    
        return
    }
    
    
    3.加解密演示:
    //测试ECC椭圆曲线实现数据加解密
    func TestECIES() {
        //获取随机字符串
        randomKey := myECIES.GetRandomString(40)
    
        //生成私钥和公钥
        e := myECIES.GenerateKey(randomKey)
        if e != nil {
            fmt.Println(e)
        }
    
        //加密前源信息
        srcInfo := "GO 密码学 —— ECIES 椭圆曲线实现数据加解密"
        fmt.Println("原文:", srcInfo)
    
        //加密信息
        cryptData, e := myECIES.EnCryptByEcies(srcInfo, myECIES.PUBLICFILE)
        if e != nil {
            fmt.Println(e)
        }
        fmt.Println("ECIES加密后为:", cryptData)
    
        //解密信息
        srcData, e := myECIES.DeCryptByEcies(cryptData, myECIES.PRIVATEFILE)
        if e != nil {
            fmt.Println(e)
        }
        fmt.Println("ECIES解密后为:", srcData)
    
    }
    
    //OUTPUT:
    原文: GO 密码学 —— ECIES 椭圆曲线实现数据加解密
    ECIES加密后为: 0494ba1ad5e9d4606e8360432723727a10fac1206f64063414dd038df359ccb663725a3cd4a17a07330e1ec52f6a40a7ee278ea7491f9c0beace8ef283152555bd86da3f622408503266e1dcadb0efd1d371cedbd874f0b08b3f9a3dbd47da4cf1917e4c0d20d913dbe8851db38895de46377bfd929b4432a07c8d99da89127da5a8a212b400aed12bdb172a8b219d847106bad4f2ea21ac4d758447bc70f798b5d4272088e435ab3804337d
    ECIES解密后为: GO 密码学 —— ECIES 椭圆曲线实现数据加解密
    
    

    至此Go 密码学实践告一段落,其具体实战应该后期会在实战专题展开。

    相关文章

      网友评论

        本文标题:8 Go 密码学(五)非对称加密之椭圆曲线算法

        本文链接:https://www.haomeiwen.com/subject/rfqxkctx.html