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)。