解析 非对称加密之RSA (go语言实现)

坚持原创,如有错误请指正

非对称加密是区块链常用的一类加密方式,比如说有下面几种:

RSA

secp256k1 (椭圆曲线)

ElGamal等等

接下来我会尝试用明白易懂的语言讲解

讲解RSA之前,很多同学可能跟比特币的签名方式搞混,所以我就先简单的说下比特币转账签名.比特币采用UTXO模型(例子,我有5btc,我要给你转1btc. 实际操作是5个btc全转出,1btc给你,剩下4个btc再转给我自己,每次交易等式都要配平),扯远了.

每次的转账记录可以简单地看为

{
"付款地址":A
"收款地址":B
"转账数量":1btc
}  ==>计算其hash为H

接下来我要把该交易发送到其他节点,其他节点接收到广播信息,会首先进行验证,如果验证通过则会再次转发到其他节点中, 最终交易池会选取该交易,等待矿工打包入区块中(等待时间由网络拥堵状况和手续费等因素决定).

转账第一步,对这笔交易进行消息摘要,也就是计算其hash,唯一的交易交易记录有唯一的hash值,碰撞的概率接近于零.假设hash为H.
第二步,对这个hash进行签名,其实这里就是一个非对称加密了.
使用我的账户的私钥对 H 进行加密,得到一段密文.其他节点使用我的公钥对其解密,得到一段代码H2 . 如果H2与通过转账记录计算得到的H相等,验证通过.
签名过程即为用私钥对交易摘要的加密过程,验证过程为用公钥解密的过程(实际上规则要更复杂一些,这里只是方便大家理解).

接下来初步的讲下RSA
RSA也分为公钥和私钥
密文=明文(待加密) ^E(E次方) 再取N的余数 ==>加密
明文(待加密)=密文 ^D(D次方) 再取N的余数 ==>解密
与比特币签名不同的是,这里是公钥加密,私钥解密(加密快,解密慢)
比特币签名(加密)慢,验证(解密)快.

D 21616157824859089947467865763905877208766194802261304393547211764767872553922733033101317695599363434608856645166753
N 26861170560633109174956418630434195575102580750015151802056322369493708310600722979313108414200817132792257978619899
E 65537

将公钥私钥进行打印,发现
公钥格式{N E}
私钥格式{{N E} D [..........]}

可以发现,公钥长度较短, 私钥长度较长,私钥中包含了 公钥

接下来计算创建私钥花费的时间,直观的让你明白RSA的效率
func main() {
    var timeSum time.Duration
    i := 0
    bits := 2304//创造自定义位数的key
    for ; i < 60; {
        now1 := time.Now()
        
        test.GenerateRsaKey(bits)//调用test包下的方法
        now2 := time.Now()
        timeSub := now2.Sub(now1)
        timeSum += timeSub
        i++
        fmt.Println("第", i, "次任务,耗时毫秒数", timeSub.Nanoseconds()/1000000)
    }
    fmt.Println("平均毫秒数为", int64(timeSum.Nanoseconds())/int64(1000000)/int64(i), ",运行次数为", i, ",位数为", bits)
}
func GenerateRsaKey(bits int) {

   privateKey, _ := rsa.GenerateKey(rand.Reader, bits)
   fmt.Println("D",privateKey.D) 
   fmt.Println("N",privateKey.N)
   fmt.Println("E",privateKey.E)
}

不同长度密钥(单位bit),创建私钥任务耗时如下
平均毫秒数为 1699 ,运行次数为 60 ,位数为 2304
平均毫秒数为 885 ,运行次数为 60 ,位数为 2048
平均毫秒数为 581 ,运行次数为 60 ,位数为 1792
平均毫秒数为 79 ,运行次数为 60 ,位数为 1024
平均毫秒数为 35 ,运行次数为 60 ,位数为 768
平均毫秒数为 11 ,运行次数为 60 ,位数为 512
平均毫秒数为 5 ,运行次数为 60 ,位数为 384

随着位数的提高,耗时指数性增长.

抗暴力破解能力对比图,本图截取自<应用密码学>.可以看出相同密钥长度,非对称加密比不上对称加密,而非对称加密又是个极耗资源的算法.


解析 非对称加密之RSA (go语言实现)_第1张图片
image.png

生成公钥私钥

func GenerateRsaKey(bits int) {

    privateKey, _ := rsa.GenerateKey(rand.Reader, bits)
    
    //保存私钥
    //使用X509规范,对公钥私钥进行格式化
    x509PrivateKey := x509.MarshalPKCS1PrivateKey(privateKey)

    //pem格式编码 将所有内容替换为0-9 a-z A-Z, 基于base64解码 末尾=用于补齐,末尾=个数为0-2个.

    block := pem.Block{
        Type:  "pem私钥",
        Bytes: x509PrivateKey,
    }
    fmt.Println(block)
    privateKeyFile, _ := os.Create("private.pem")
    pem.Encode(privateKeyFile, &block)
    privateKeyFile.Close()
    //保存公钥
    x509publicKey := x509.MarshalPKCS1PublicKey(&privateKey.PublicKey)

    publicBlock := pem.Block{
        Type:  "公钥",
        Bytes: x509publicKey,
    }
    publicKeyFile, _ := os.Create("public.pem")
    pem.Encode(publicKeyFile, &publicBlock)
    publicKeyFile.Close()

}

接下来进行加密解密操作
代码千篇一律

func main() {
    
    var timeSum time.Duration
    i := 0
    bits := 2304//设置密钥位数
    for ; i < 100; {
        now1 := time.Now()
        ////创造多长的rsa
        //test.GenerateRsaKey(bits)
        //明文
         text :=[]byte("我就是明文啦,要对我进行加密")
        rsa_encrypt := test.Rsa_Encrypt(text, "public.pem")
        fmt.Println("密文为", string(rsa_encrypt))
        rsa_decrypt := test.Rsa_Decrypt(rsa_encrypt, "private.pem")
        fmt.Println("原文为", string(rsa_decrypt))
        now2 := time.Now()
        timeSub := now2.Sub(now1)
        timeSum += timeSub
        i++
        fmt.Println("第", i, "次任务,耗时毫秒数", timeSub.Nanoseconds()/1000000)
    }
    fmt.Println("平均毫秒数为", int64(timeSum.Nanoseconds())/int64(1000000)/int64(i), ",运行次数为", i, ",位数为", bits)
    fmt.Println("总耗时为", int64(timeSum.Nanoseconds())/int64(1000000), "毫秒")
}

被调用部分代码

func Rsa_Encrypt(plainText []byte,path string) []byte{
    //1.读取文件
    file, _ := os.Open(path)
    // 获取文件信息
    fileInfo, _ := file.Stat()

    //2.读取文件的内容 pem
    data := make([]byte,fileInfo.Size())
    file.Read(data)
    //3.pem解码 --- x509编码的内容
    block, _ := pem.Decode(data)
    //4.x509解码(反序列化)
    publicKey, _ := x509.ParsePKCS1PublicKey(block.Bytes)

    cipherText, _ := rsa.EncryptPKCS1v15(rand.Reader, publicKey, plainText)
    return cipherText
}
// 解密操作
func Rsa_Decrypt(cipherText []byte,path string) []byte{
    //1.读取文件
    file, _ := os.Open(path)
    //2.读取文件的内容
    fileInfo, _ := file.Stat()
    data := make([]byte,fileInfo.Size())
    file.Read(data)

    //3.pem 解码
    block, _ := pem.Decode(data)
    //4.x509解码
    privateKey, _ := x509.ParsePKCS1PrivateKey(block.Bytes)
    //5. 解密操作
    plainText, _ := rsa.DecryptPKCS1v15(rand.Reader, privateKey, cipherText)

    return plainText
}

平均毫秒数为 15 ,运行次数为 100 ,位数为 2304,总耗时为1506毫秒
平均毫秒数为 1 ,运行次数为 100 ,位数为 768,总耗时为 155 毫秒

由此可见,随着位数的增加,加密解密操作耗时指数型增长,感兴趣的可以与对称加密进行对比,对相同明文进行加密解密,效率有极大不同. 对称加密快很多.

接下来进行一个实验
设置密钥位数 bits=768
明文为 sdfdsfsdggs1231241412`efwe23efqfeqafqawegeetwt23452346ty24wgesewrwqfsaegvsaedaesd3ear

运行程序,输出结果
密文为 !���yɄm���"cj0!nt��g2cY���YJw@R���֏C'��&�D*'R0c�NU��^��W�i�����,g�Ā���-��z]����"
��\N��

将明文结尾再添加X
明文为sdfdsfsdggs1231241412`efwe23efqfeqafqawegeetwt23452346ty24wgesewrwqfsaegvsaedaesd3earX
运行程序
发现密文没有输出,原因是明文长度超出范围了.
现在对bits进行操作,将768改为1024
运行程序,这次有结果输出了.
密文为 "L������ׇ��d��'��U�JP���MT��k��� aʅܚJ��\�������\w��ئ�"':��qp;��.}_�����]�KF������ ��?(����F,�*��紛+�GA�x�͘ʩ�6�N��� �=���

为什么呢?
文章最上面说过
密文=明文(待加密) ^E(E次方) 再取N的余数
也就是说N限制了明文的最大长度. 如果明文过长,就会发生碰撞(不同的明文产生了相同的密文,这里可以好好理解下),而N与密钥长度有关.

1.可加密明文的最大长度与密钥长度有关,密钥越长,可加密明文的最大长度越长.
2.rsa可加密的数据仍然很小,传输大数据不太可能.
假设用rsa加密一部电影,需要将电影进行拆分,分开加密解密,按照非对称加密的效率,几十年可能都搞不定.
引申:加密大文件,要用对称加密先加密,再将对称加密的密钥进行非对称加密.这样就能兼顾效率和安全.

你可能感兴趣的:(解析 非对称加密之RSA (go语言实现))