文章目录

  • 1.简介
  • 区块长度
  • 密钥长度
  • 加密模式
  • 2.加解密实现
  • 3.dablelv/cyan
  • 参考文献


1.简介

利用 Go 提供的 AES 加解密与 Base64 编解码包,我们可以轻松实现 AES 加解密。

实现之前,首先了解一下 AES 的基本知识。

AES(Advanced Encryption Standard)高级加密标准,是流行的对称加密算法,由美国国家标准与技术研究院(NIST)于 2001 年发布,用于取代 DES。Rijndael 算法 是 AES 标准的一个实现,一般说 AES 指的就是 Rijndael 算法。

区块长度

AES 是一种对称分组加密算法,区块长度固定为 128bits(16 字节)。

密钥长度

AES 秘钥的长度只能是16、24 或 32 字节,分别对应三种加密模式 AES-128、AES-192 和 AES-256,三者的区别是加密轮数不同。

AES

分组长度(字节)

密钥长度(字节)

加密轮数

AES-128

16

16

10

AES-192

16

24

12

AES-256

16

32

14

加密模式

AES 支持 5 种加密模式。

AES(Advanced Encryption Standard)本身是一个块密码算法,它只能加密固定大小的数据块。然而,在实际应用中,我们通常需要加密更大的数据流或消息,并且需要解决一些特定的安全性问题,如数据传输中的完整性、随机性和错误传播问题。这就是为什么需要加密模式的原因。

不同的加密模式采用不同的策略来处理这些问题。以下是 AES 支持的常见加密模式。

  1. ECB(Electronic Codebook)模式: 最简单的模式,将明文分成固定大小的块,然后分别加密。这种模式的问题是相同的明文块会加密成相同的密文块,因此缺乏随机性和安全性。
  2. CBC(Cipher Block Chaining)模式: 每个明文块与前一个密文块进行异或操作,然后加密。这增加了随机性,并且在错误传播方面具有良好的性质,但需要一个初始化向量(IV)。
  3. CFB(Cipher Feedback)模式: 通过将前一个密文块作为输入与明文块进行异或操作,产生密文块。这种模式对于错误传播具有良好的性质,但也需要一个初始化向量。
  4. OFB(Output Feedback)模式: 类似于 CFB,但是通过生成一个密钥流来与明文进行异或操作。也对错误传播具有良好的性质。
  5. CTR(Counter)模式: 使用计数器来生成一个密钥流,然后与明文进行异或操作。在并行计算环境中效率较高。

2.加解密实现

下面以 CBC 模式为例,实现 AES 加解密。

package main

import (
    "fmt"
    "crypto/cipher"
    "crypto/aes"
    "bytes"
    "encoding/base64"
)

// PKCS7Padding fills plaintext as an integral multiple of the block length
func PKCS7Padding(p []byte, blockSize int) []byte {
	pad := blockSize - len(p)%blockSize
	padtext := bytes.Repeat([]byte{byte(pad)}, pad)
	return append(p, padtext...)
}

// PKCS7UnPadding removes padding data from the tail of plaintext
func PKCS7UnPadding(p []byte) []byte {
	length := len(p)
	paddLen := int(p[length-1])
	return p[:(length - paddLen)]
}

// AESCBCEncrypt encrypts data with AES algorithm in CBC mode
// Note that key length must be 16, 24 or 32 bytes to select AES-128, AES-192, or AES-256
// Note that AES block size is 16 bytes
func AESCBCEncrypt(p, key []byte) ([]byte, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}

	p = PKCS7Padding(p, block.BlockSize())
	ciphertext := make([]byte, len(p))
	blockMode := cipher.NewCBCEncrypter(block, key[:block.BlockSize()])
	blockMode.CryptBlocks(ciphertext, p)
	return ciphertext, nil
}

// AESCBCDecrypt decrypts cipher text with AES algorithm in CBC mode
// Note that key length must be 16, 24 or 32 bytes to select AES-128, AES-192, or AES-256
// Note that AES block size is 16 bytes
func AESCBCDecrypt(c, key []byte) ([]byte, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}

	plaintext := make([]byte, len(c))
	blockMode := cipher.NewCBCDecrypter(block, key[:block.BlockSize()])
	blockMode.CryptBlocks(plaintext, c)
	return PKCS7UnPadding(plaintext)
}

// Base64AESCBCEncrypt encrypts data with AES algorithm in CBC mode and encoded by base64
func Base64AESCBCEncrypt(p, key []byte) (string, error) {
	c, err := AESCBCEncrypt(p, key)
	if err != nil {
		return "", err
	}
	return base64.StdEncoding.EncodeToString(c), nil
}

// Base64AESCBCDecrypt decrypts cipher text encoded by base64 with AES algorithm in CBC mode
func Base64AESCBCDecrypt(c string, key []byte) ([]byte, error) {
	oriCipher, err := base64.StdEncoding.DecodeString(c)
	if err != nil {
		return nil, err
	}
	p, err := AESCBCDecrypt(oriCipher, key)
	if err != nil {
		return nil, err
	}
	return p, nil
}

使用示例如下:

func main() {
   	p := []byte("plaintext")
	key := []byte("12345678abcdefgh")
	
	ciphertext, _ := Base64AESCBCEncrypt(p, key)
	fmt.Println(ciphertext)

	plaintext, _ := Base64AESCBCDecrypt(ciphertext, key)
	fmt.Println(string(plaintext))
}

运行输出:

A67NhD3RBiNaMgG6HTm8LQ==
plaintext

3.dablelv/cyan

以上代码已放置实用函数库 dablelv/cyan,可直 import 直接使用,欢迎大家 star 和 pr。

import (
	"github.com/dablelv/cyan/crypto"
)

p := []byte("plaintext")
key := []byte("12345678abcdefgh")

ciphertext, _ := crypto.Base64AESCBCEncrypt(p, key)			// A67NhD3RBiNaMgG6HTm8LQ==
plaintext, _ := crypto.Base64AESCBCDecrypt(ciphertext, key)	// plaintext

参考文献

crypto/aes - Go PackagesAES Encryption and Decryption Online Tool