Golang加密系列之RSA

前端之家收集整理的这篇文章主要介绍了Golang加密系列之RSA前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
  1. Golang加密系列之AES

  2. Golang加密系列之RSA


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

代码的结构如下图

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


定义一个对外开放的接口

  1. packagersa
  2.  
  3. import"crypto"
  4.  
  5. typeCipherinterface{
  6. Encrypt(plaintext[]byte)([]byte,error)
  7. Decrypt(ciphertext[]byte)([]byte,error)
  8. Sign(src[]byte,hashcrypto.Hash)([]byte,error)
  9. Verify(src[]byte,sign[]byte,hashcrypto.Hash)error
  10. }

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 是相等的。

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

  1. packagersa
  2.  
  3. funcpkcs1Padding(src[]byte,keySizeint)[][]byte{
  4.  
  5. srcSize:=len(src)
  6.  
  7. blockSize:=keySize-11
  8.  
  9. varv[][]byte
  10.  
  11. ifsrcSize<=blockSize{
  12. v=append(v,src)
  13. }else{
  14. groups:=len(src)/blockSize
  15. fori:=0;i<groups;i++{
  16. block:=src[:blockSize]
  17.  
  18. v=append(v,block)
  19. src=src[blockSize:]
  20.  
  21. iflen(src)<blockSize{
  22. v=append(v,src)
  23. }
  24. }
  25. }
  26. returnv
  27. }
  28.  
  29. funcunPadding(src[]byte,keySizeint)[][]byte{
  30.  
  31. srcSize:=len(src)
  32.  
  33. blockSize:=keySize
  34.  
  35. varv[][]byte
  36.  
  37. ifsrcSize==blockSize{
  38. v=append(v,block)
  39. src=src[blockSize:]
  40. }
  41. }
  42. returnv
  43. }


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

  1. packagersa
  2.  
  3. import(
  4. "bytes"
  5. "crypto"
  6. "crypto/rand"
  7. "crypto/rsa"
  8. )
  9.  
  10. typepkcsClientstruct{
  11. privateKey*rsa.PrivateKey
  12. publicKey*rsa.PublicKey
  13. }
  14.  
  15. func(this*pkcsClient)Encrypt(plaintext[]byte)([]byte,error){
  16.  
  17. blocks:=pkcs1Padding(plaintext,this.publicKey.N.BitLen()/8)
  18.  
  19. buffer:=bytes.Buffer{}
  20. for_,block:=rangeblocks{
  21. ciphertextPart,err:=rsa.EncryptPKCS1v15(rand.Reader,this.publicKey,block)
  22. iferr!=nil{
  23. returnnil,err
  24. }
  25. buffer.Write(ciphertextPart)
  26. }
  27.  
  28. returnbuffer.Bytes(),nil
  29. }
  30.  
  31. func(this*pkcsClient)Decrypt(ciphertext[]byte)([]byte,error){
  32.  
  33. ciphertextBlocks:=unPadding(ciphertext,this.privateKey.N.BitLen()/8)
  34.  
  35. buffer:=bytes.Buffer{}
  36. for_,ciphertextBlock:=rangeciphertextBlocks{
  37. plaintextBlock,err:=rsa.DecryptPKCS1v15(rand.Reader,this.privateKey,ciphertextBlock)
  38. iferr!=nil{
  39. returnnil,err
  40. }
  41. buffer.Write(plaintextBlock)
  42. }
  43.  
  44. returnbuffer.Bytes(),nil
  45. }
  46.  
  47. func(this*pkcsClient)Sign(src[]byte,error){
  48. h:=hash.New()
  49. h.Write(src)
  50. hashed:=h.Sum(nil)
  51. returnrsa.SignPKCS1v15(rand.Reader,hash,hashed)
  52. }
  53.  
  54. func(this*pkcsClient)Verify(src[]byte,hashcrypto.Hash)error{
  55. h:=hash.New()
  56. h.Write(src)
  57. hashed:=h.Sum(nil)
  58. returnrsa.VerifyPKCS1v15(this.publicKey,hashed,sign)
  59. }

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

  1. packageprivatekey
  2.  
  3. typeTypeint64
  4.  
  5. const(
  6. PKCS1Type=iota
  7. PKCS8
  8. )

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

  1. packagersa
  2.  
  3. import(
  4. "crypto/rsa"
  5. "crypto/x509"
  6. "encoding/pem"
  7. "errors"
  8. "github.com/89hmdys/toast/rsa/privatekey"
  9. )
  10.  
  11. //默认客户端,pkcs8私钥格式,pem编码
  12. funcNewDefault(privateKey,publicKeystring)(Cipher,error){
  13. blockPri,_:=pem.Decode([]byte(privateKey))
  14. ifblockPri==nil{
  15. returnnil,errors.New("privatekeyerror")
  16. }
  17.  
  18. blockPub,_:=pem.Decode([]byte(publicKey))
  19. ifblockPub==nil{
  20. returnnil,errors.New("publickeyerror")
  21. }
  22.  
  23. returnNew(blockPri.Bytes,blockPub.Bytes,privatekey.PKCS8)
  24. }
  25.  
  26. funcNew(privateKey,publicKey[]byte,privateKeyTypeprivatekey.Type)(Cipher,error){
  27.  
  28. priKey,err:=genPriKey(privateKey,privateKeyType)
  29. iferr!=nil{
  30. returnnil,err
  31. }
  32. pubKey,err:=genPubKey(publicKey)
  33. iferr!=nil{
  34. returnnil,err
  35. }
  36. return&pkcsClient{privateKey:priKey,publicKey:pubKey},nil
  37. }
  38.  
  39. funcgenPubKey(publicKey[]byte)(*rsa.PublicKey,error){
  40. pub,err:=x509.ParsePKIXPublicKey(publicKey)
  41. iferr!=nil{
  42. returnnil,err
  43. }
  44. returnpub.(*rsa.PublicKey),nil
  45. }
  46.  
  47. funcgenPriKey(privateKey[]byte,privateKeyTypeprivatekey.Type)(*rsa.PrivateKey,error){
  48. varpriKey*rsa.PrivateKey
  49. varerrerror
  50. switchprivateKeyType{
  51. caseprivatekey.PKCS1:
  52. {
  53. priKey,err=x509.ParsePKCS1PrivateKey([]byte(privateKey))
  54. iferr!=nil{
  55. returnnil,err
  56. }
  57. }
  58. caseprivatekey.PKCS8:
  59. {
  60. prkI,err:=x509.ParsePKCS8PrivateKey([]byte(privateKey))
  61. iferr!=nil{
  62. returnnil,err
  63. }
  64. priKey=prkI.(*rsa.PrivateKey)
  65. }
  66. default:
  67. {
  68. returnnil,errors.New("unsupportprivatekeytype")
  69. }
  70. }
  71. returnpriKey,nil
  72. }

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

  1. packagersa_test
  2.  
  3. import(
  4. "crypto"
  5. "encoding/base64"
  6. "encoding/hex"
  7. "fmt"
  8. "testing"
  9. "toast/rsa"
  10. )
  11.  
  12. varcipherrsa.Cipher
  13.  
  14. funcinit(){
  15. client,err:=rsa.NewDefault(`-----BEGINPRIVATEKEY-----
  16. 私钥信息
  17. -----ENDPRIVATEKEY-----`,`-----BEGINPUBLICKEY-----
  18. 公钥信息
  19. -----ENDPUBLICKEY-----`)
  20.  
  21. iferr!=nil{
  22. fmt.Println(err)
  23. }
  24.  
  25. cipher=client
  26. }
  27.  
  28. funcTest_DefaultClient(t*testing.T){
  29.  
  30. cp,err:=cipher.Encrypt([]byte("测试加密解密"))
  31. iferr!=nil{
  32. t.Error(err)
  33. }
  34. cpStr:=base64.URLEncoding.EncodeToString(cp)
  35.  
  36. fmt.Println(cpStr)
  37.  
  38. ppBy,err:=base64.URLEncoding.DecodeString(cpStr)
  39. iferr!=nil{
  40. t.Error(err)
  41. }
  42. pp,err:=cipher.Decrypt(ppBy)
  43.  
  44. fmt.Println(string(pp))
  45. }
  46.  
  47. funcTest_Sign_DefaultClient(t*testing.T){
  48.  
  49. src:="测试签名验签"
  50.  
  51. signBytes,err:=cipher.Sign([]byte(src),crypto.SHA256)
  52. iferr!=nil{
  53. t.Error(err)
  54. }
  55. sign:=hex.EncodeToString(signBytes)
  56. fmt.Println(sign)
  57.  
  58. signB,err:=hex.DecodeString(sign)
  59.  
  60. errV:=cipher.Verify([]byte(src),signB,crypto.SHA256)
  61. iferrV!=nil{
  62. t.Error(errV)
  63. }
  64. fmt.Println("verifysuccess")
  65. }

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

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


2月14日补充,

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

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

猜你在找的Go相关文章