Golang AES/CBC/PKCS7Padding

标签: none

Golang没有像PHP那样提供一个现成的aes加密函数,不过标准库里有crypto,利用里面的aes等可以自己封装个加密函数,不过需要理解下整个加解密的过程和原理

基本概念

  • 密码学中,块密码的工作模式(英语:mode of operation)允许使用同一个块密码密钥对多于一块的数据进行加密,并保证其安全性。
  • 块密码自身只能加密长度等于密码块长度的单块数据,若要加密变长数据,则数据必须先被划分为一些单独的密码块。通常而言,最后一块数据也需要使用合适填充方式将数据扩展到符合密码块大小的长度。
  • 一种工作模式描述了加密每一数据块的过程,并常常使用基于一个通常称为初始化向量的附加输入值以进行随机化,以保证安全。
    常见的模式有ECB,CBC,OFB,CFB,CTR和XTS等
  • 加密模式仅仅保证 机密性 ,对于保证 完整性
    或未篡改,需要采用分离的消息验证码,例如CBC-MAC。密码学社群认识到了对专用的保证完整性的方法的需求,NIST因此提出了HMAC,CMAC和GMAC。
  • 在发现将认证模式与加密模式联合起来的难度之后,密码学社区开始研究结合了加密和认证的单一模式,这种模式被称为认证加密模式(AE,Authenticated
    Encryption),或称为authenc。AE模式的例子包括CCM,GCM[11],CWC,EAX,IAPM和OCB。

初始化向量(IV)

初始化向量(IV,Initialization Vector)是许多工作模式中用于随机化加密的一块数据,因此可以由相同的明文,相同的密钥产生不同的密文,而无需重新产生密钥,避免了通常相当复杂的这一过程。

初始化向量与密钥相比有不同的安全性需求,因此IV通常无须保密,然而在大多数情况中,不应当在使用同一密钥的情况下两次使用同一个IV。对于CBC和CFB,重用IV会导致泄露明文首个块的某些信息,亦包括两个不同消息中相同的前缀。对于OFB和CTR而言,重用IV会导致完全失去安全性。另外,在CBC模式中,IV在加密时必须是无法预测的;特别的,在许多实现中使用的产生IV的方法,例如SSL2.0使用的,即采用上一个消息的最后一块密文作为下一个消息的IV,是不安全的。

这里使用的是AES加密中的CBC模式,块加密需要划分成整数长度相等个消息块不断加密(串行),分组长度是固定128位,但密钥的长度可以使用128位,192位或者256位(这里指的是bit),即密钥16,24,32长度对应AES-128, AES-192, AES-256。

package encrypt

import (
   "bytes"
   "crypto/aes"
   "io"
   "crypto/rand"
   "crypto/cipher"
   "encoding/base64"
)

/*CBC加密 按照golang标准库的例子代码
不过里面没有填充的部分,所以补上
*/

//使用PKCS7进行填充,IOS也是7
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
   padding := blockSize - len(ciphertext) % blockSize
   padtext := bytes.Repeat([]byte{byte(padding)}, padding)
   return append(ciphertext, padtext...)
}

func PKCS7UnPadding(origData []byte) []byte {
   length := len(origData)
   unpadding := int(origData[length-1])
   return origData[:(length - unpadding)]
}

//aes加密,填充秘钥key的16位,24,32分别对应AES-128, AES-192, or AES-256.
func AesCBCEncrypt(rawData,key []byte) ([]byte, error) {
   block, err := aes.NewCipher(key)
   if err != nil {
       panic(err)
   }

   //填充原文
   blockSize := block.BlockSize()
   rawData = PKCS7Padding(rawData, blockSize)
   //初始向量IV必须是唯一,但不需要保密
   cipherText := make([]byte,blockSize+len(rawData))
   //block大小 16
   iv := cipherText[:blockSize]
   if _, err := io.ReadFull(rand.Reader,iv); err != nil {
       panic(err)
   }

   //block大小和初始向量大小一定要一致
   mode := cipher.NewCBCEncrypter(block,iv)
   mode.CryptBlocks(cipherText[blockSize:],rawData)

   return cipherText, nil
}

func AesCBCDncrypt(encryptData, key []byte) ([]byte,error) {
   block, err := aes.NewCipher(key)
   if err != nil {
       panic(err)
   }

   blockSize := block.BlockSize()

   if len(encryptData) < blockSize {
       panic("ciphertext too short")
   }
   iv := encryptData[:blockSize]
   encryptData = encryptData[blockSize:]

   // CBC mode always works in whole blocks.
   if len(encryptData)%blockSize != 0 {
       panic("ciphertext is not a multiple of the block size")
   }

   mode := cipher.NewCBCDecrypter(block, iv)

   // CryptBlocks can work in-place if the two arguments are the same.
   mode.CryptBlocks(encryptData, encryptData)
   //解填充
   encryptData = PKCS7UnPadding(encryptData)
   return encryptData,nil
}


func Encrypt(rawData,key []byte) (string,error) {
   data, err:= AesCBCEncrypt(rawData,key)
   if err != nil {
       return "",err
   }
   return base64.StdEncoding.EncodeToString(data),nil
}

func Dncrypt(rawData string,key []byte) (string,error) {
   data,err := base64.StdEncoding.DecodeString(rawData)
   if err != nil {
       return "",err
   }
   dnData,err := AesCBCDncrypt(data,key)
   if err != nil {
       return "",err
   }
   return string(dnData),nil
}

填充

  • 部分模式(ECB和CBC)需要最后一块在加密前进行填充
  • CFB,OFB和CTR模式不需要对长度不为密码块大小整数倍的消息进行特别的处理。因为这些模式是通过对块密码的输出与平文进行异或工作的。最后一个平文块(可能是不完整的)与密钥流块的前几个字节异或后,产生了与该平文块大小相同的密文块。流密码的这个特性使得它们可以应用在需要密文和平文数据长度严格相等的场合,也可以应用在以流形式传输数据而不便于进行填充的场合。

其他填充模式PKCS5Padding ZeroPadding

func PKCS5Padding(ciphertext []byte, blockSize int) []byte {
        padding := blockSize - len(ciphertext)%blockSize //需要padding的数目
        //只要少于256就能放到一个byte中,默认的blockSize=16(即采用16*8=128, AES-128长的密钥)
        //最少填充1个byte,如果原文刚好是blocksize的整数倍,则再填充一个blocksize
        padtext := bytes.Repeat([]byte{byte(padding)}, padding)  //生成填充的文本
        return append(ciphertext, padtext...)
}

func PKCS5UnPadding(origData []byte) []byte {
        length := len(origData)
        unpadding := int(origData[length-1])
        return origData[:(length - unpadding)]
}

func PKCS5Trimming(encrypt []byte) []byte {
    padding := encrypt[len(encrypt)-1]
    return encrypt[:len(encrypt)-int(padding)]
}

func ZeroPadding(ciphertext []byte, blockSize int) []byte {
        padding := blockSize - len(ciphertext)%blockSize
        padtext := bytes.Repeat([]byte{0}, padding)//用0去填充
        return append(ciphertext, padtext...)
}

func ZeroUnPadding(origData []byte) []byte {
        return bytes.TrimFunc(origData,
                func(r rune) bool {
                        return r == rune(0)
                })
}

cipher.go

package config

import (
    "bytes"
    "crypto/aes"
    "crypto/cipher"
    "encoding/hex"
    "fmt"
    "strings"
)

var ivspec = []byte("0000000000000000")

const Key = "iKwb6Kt5pnqcVZcd"

func PKCS5Padding(ciphertext []byte, blockSize int) []byte {
    padding := blockSize - len(ciphertext)%blockSize
    padtext := bytes.Repeat([]byte{byte(padding)}, padding)
    return append(ciphertext, padtext...)
}

func PKCS5Trimming(encrypt []byte) []byte {
    padding := encrypt[len(encrypt)-1]
    return encrypt[:len(encrypt)-int(padding)]
}
func AESEncodeStr(src, key string) string {
    block, err := aes.NewCipher([]byte(key))
    if err != nil {
        fmt.Println("key error1", err)
    }
    if src == "" {
        fmt.Println("plain content empty")
    }
    ecb := cipher.NewCBCEncrypter(block, ivspec)
    content := []byte(src)
    content = PKCS5Padding(content, block.BlockSize())
    crypted := make([]byte, len(content))
    ecb.CryptBlocks(crypted, content)
    return hex.EncodeToString(crypted)
}

func AESDecodeStr(crypt, key string) string {
    crypted, err := hex.DecodeString(strings.ToLower(crypt))
    if err != nil || len(crypted) == 0 {
        fmt.Println("plain content empty")
    }
    block, err := aes.NewCipher([]byte(key))
    if err != nil {
        fmt.Println("key error1", err)
    }
    ecb := cipher.NewCBCDecrypter(block, ivspec)
    decrypted := make([]byte, len(crypted))
    ecb.CryptBlocks(decrypted, crypted)

    return string(PKCS5Trimming(decrypted))
}

golang 中AES加密详解
Golang-AES加密(CBC模式,PKCS7填充)


扫描二维码,在手机上阅读!

已有 2 条评论

  1. chfs chfs

    PKCS7Padding 和 PKCS5Padding 是一样的???

    1. 不一样PKCS5Padding block size 固定为 8字节
      这里解释更详细
      https://stackoverflow.com/questions/20770072/aes-cbc-pkcs5padding-vs-aes-cbc-pkcs7padding-with-256-key-size-performance-java

添加新评论