Golang AES/CBC/PKCS7Padding 实现

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

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

package encrypt

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

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

// 使用 PKCS7 进行填充, iOS 也是 7
// 只要少于 256 就能放到一个 byte 中, 默认的 blockSize=16 (即采用 16*8=128, AES-128 长的密钥)
// 最少填充 1 个 byte, 如果明文刚好是 blocksize 的整数倍, 则再填充一个 blocksize
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
	// 计算需要 padding 的数目, 并生成填充的文本
	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
}

其他填充模式 PKCS5Padding

package encrypt

import (
	"bytes"
)

func PKCS5Padding(ciphertext []byte) []byte {
	// PKCS5Padding 中 blockSize 固定为 8
	blockSize := 8
	// 计算需要 padding 的数目, 并生成填充的文本
	padding := blockSize - len(ciphertext)%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)]
}

其他填充模式 ZeroPadding

package encrypt

import (
	"bytes"
)

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

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

其他实例 cipher.go

这个例子中的错误的 PKCS5Padding 函数名, 实际在做 PKCS7Padding 的工作。blockSize 作为参数传入函数, 并非固定为 8

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填充)

最后更新于 2019-07-10
使用 Hugo 构建
主题 StackJimmy 设计