Golang加密系列之RSA

  1. Golang加密系列之AES

  2. Golang加密系列之RSA


Golang加密系列的最后一篇,嗯,RSA涉及的概念太多,弄了好久才搞清楚。。。

代码的结构如下图

PS:StarUML这玩意在Mac上所有连到Interface的线都变成直线了...我很惆怅...


定义一个对外开放的接口

packagersa

import"crypto"

typeCipherinterface{
Encrypt(plaintext[]byte)([]byte,error)
Decrypt(ciphertext[]byte)([]byte,error)
Sign(src[]byte,hashcrypto.Hash)([]byte,error)
Verify(src[]byte,sign[]byte,hashcrypto.Hash)error
}

RSA也是一个块加密算法,总是在一个固定大小的块(block)上进行操作。但跟AES等不同的是,RSA 的block size是跟key length 以及所使用的填充模式有关的。填充方式有以下几种。

1、RSA_PKCS1_PADDING 填充模式,最常用的模式。

输入block长度 :block size比RSA 钥模长(modulus) 短至少11个字节,也就是RSA_size(rsa) – 11

输出结果长度: 和modulus一样长

例如,对于1024bit的密钥,blockSize = 1024/8 – 11 = 117 字节

2、RSA_PKCS1_OAEP_PADDING

输入block长度:RSA_size(rsa) – 41

输出结果长度 : 和modulus一样长

3、RSA_NO_PADDING

不填充

解密的时候,如果密文长度过长,也需要切分成多个块进行解密,block size 和 key length 是相等的。

这里仅支持了第一种填充方式。

packagersa

funcpkcs1Padding(src[]byte,keySizeint)[][]byte{

srcSize:=len(src)

blockSize:=keySize-11

varv[][]byte

ifsrcSize<=blockSize{
v=append(v,src)
}else{
groups:=len(src)/blockSize
fori:=0;i<groups;i++{
block:=src[:blockSize]

v=append(v,block)
src=src[blockSize:]

iflen(src)<blockSize{
v=append(v,src)
}
}
}
returnv
}

funcunPadding(src[]byte,keySizeint)[][]byte{

srcSize:=len(src)

blockSize:=keySize

varv[][]byte

ifsrcSize==blockSize{
v=append(v,block)
src=src[blockSize:]
}
}
returnv
}


定义私有的pkcsClient ,实现Cipher接口, PKCS格式的私钥都使用这个client

packagersa

import(
"bytes"
"crypto"
"crypto/rand"
"crypto/rsa"
)

typepkcsClientstruct{
privateKey*rsa.PrivateKey
publicKey*rsa.PublicKey
}

func(this*pkcsClient)Encrypt(plaintext[]byte)([]byte,error){

blocks:=pkcs1Padding(plaintext,this.publicKey.N.BitLen()/8)

buffer:=bytes.Buffer{}
for_,block:=rangeblocks{
ciphertextPart,err:=rsa.EncryptPKCS1v15(rand.Reader,this.publicKey,block)
iferr!=nil{
returnnil,err
}
buffer.Write(ciphertextPart)
}

returnbuffer.Bytes(),nil
}

func(this*pkcsClient)Decrypt(ciphertext[]byte)([]byte,error){

ciphertextBlocks:=unPadding(ciphertext,this.privateKey.N.BitLen()/8)

buffer:=bytes.Buffer{}
for_,ciphertextBlock:=rangeciphertextBlocks{
plaintextBlock,err:=rsa.DecryptPKCS1v15(rand.Reader,this.privateKey,ciphertextBlock)
iferr!=nil{
returnnil,err
}
buffer.Write(plaintextBlock)
}

returnbuffer.Bytes(),nil
}

func(this*pkcsClient)Sign(src[]byte,error){
h:=hash.New()
h.Write(src)
hashed:=h.Sum(nil)
returnrsa.SignPKCS1v15(rand.Reader,hash,hashed)
}

func(this*pkcsClient)Verify(src[]byte,hashcrypto.Hash)error{
h:=hash.New()
h.Write(src)
hashed:=h.Sum(nil)
returnrsa.VerifyPKCS1v15(this.publicKey,hashed,sign)
}

将私钥类型定义成枚举类型

packageprivatekey

typeTypeint64

const(
PKCS1Type=iota
PKCS8
)

定义一个New/NewDefault函数,用于创建client

packagersa

import(
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"github.com/89hmdys/toast/rsa/privatekey"
)

//默认客户端,pkcs8私钥格式,pem编码
funcNewDefault(privateKey,publicKeystring)(Cipher,error){
blockPri,_:=pem.Decode([]byte(privateKey))
ifblockPri==nil{
returnnil,errors.New("privatekeyerror")
}

blockPub,_:=pem.Decode([]byte(publicKey))
ifblockPub==nil{
returnnil,errors.New("publickeyerror")
}

returnNew(blockPri.Bytes,blockPub.Bytes,privatekey.PKCS8)
}

funcNew(privateKey,publicKey[]byte,privateKeyTypeprivatekey.Type)(Cipher,error){

priKey,err:=genPriKey(privateKey,privateKeyType)
iferr!=nil{
returnnil,err
}
pubKey,err:=genPubKey(publicKey)
iferr!=nil{
returnnil,err
}
return&pkcsClient{privateKey:priKey,publicKey:pubKey},nil
}

funcgenPubKey(publicKey[]byte)(*rsa.PublicKey,error){
pub,err:=x509.ParsePKIXPublicKey(publicKey)
iferr!=nil{
returnnil,err
}
returnpub.(*rsa.PublicKey),nil
}

funcgenPriKey(privateKey[]byte,privateKeyTypeprivatekey.Type)(*rsa.PrivateKey,error){
varpriKey*rsa.PrivateKey
varerrerror
switchprivateKeyType{
caseprivatekey.PKCS1:
{
priKey,err=x509.ParsePKCS1PrivateKey([]byte(privateKey))
iferr!=nil{
returnnil,err
}
}
caseprivatekey.PKCS8:
{
prkI,err:=x509.ParsePKCS8PrivateKey([]byte(privateKey))
iferr!=nil{
returnnil,err
}
priKey=prkI.(*rsa.PrivateKey)
}
default:
{
returnnil,errors.New("unsupportprivatekeytype")
}
}
returnpriKey,nil
}

最后,看看如何使用上面的代码来进行加密/解密,签名验签

packagersa_test

import(
"crypto"
"encoding/base64"
"encoding/hex"
"fmt"
"testing"
"toast/rsa"
)

varcipherrsa.Cipher

funcinit(){
client,err:=rsa.NewDefault(`-----BEGINPRIVATEKEY-----
私钥信息
-----ENDPRIVATEKEY-----`,`-----BEGINPUBLICKEY-----
公钥信息
-----ENDPUBLICKEY-----`)

iferr!=nil{
fmt.Println(err)
}

cipher=client
}

funcTest_DefaultClient(t*testing.T){

cp,err:=cipher.Encrypt([]byte("测试加密解密"))
iferr!=nil{
t.Error(err)
}
cpStr:=base64.URLEncoding.EncodeToString(cp)

fmt.Println(cpStr)

ppBy,err:=base64.URLEncoding.DecodeString(cpStr)
iferr!=nil{
t.Error(err)
}
pp,err:=cipher.Decrypt(ppBy)

fmt.Println(string(pp))
}

funcTest_Sign_DefaultClient(t*testing.T){

src:="测试签名验签"

signBytes,err:=cipher.Sign([]byte(src),crypto.SHA256)
iferr!=nil{
t.Error(err)
}
sign:=hex.EncodeToString(signBytes)
fmt.Println(sign)

signB,err:=hex.DecodeString(sign)

errV:=cipher.Verify([]byte(src),signB,crypto.SHA256)
iferrV!=nil{
t.Error(errV)
}
fmt.Println("verifysuccess")
}

关于RSA相关的一些概念,参见我的另一篇博客.pem引发的血案

这里还有一个已经编写好的AES/RSA加解密的包,可以直接引用,github地址:https://github.com/89hmdys/toast


2月14日补充,

近期公司对接了一个保险平台,对数据进行RSA加密的时候发现当待加密数据长度超过了秘钥长度时,会报错(比如生成了1024bit/128byte的秘钥,那么待加密的数据长度只能是<=128byte,而输出的数据长度是和秘钥长度相等的)

找了些资料,发现rsa加密如果数据过长的时候,需要对数据进行分片,根据所选择的填充方式的不同,每片的数据长度也不一样。例如选择PKCS1填充方式的时候,数据长度=秘钥长度-MIN(11)。

相关文章

类型转换 1、int转string 2、string转int 3、string转float ...
package main import s &quot;strings&quot; import...
类使用:实现一个people中有一个sayhi的方法调用功能,代码如...
html代码: beego代码:
1、读取文件信息: 2、读取文件夹下的所有文件: 3、写入文件...
配置环境:Windows7+推荐IDE:LiteIDEGO下载地址:http:...