在数据的传输过程,为了安全,我们需要对其进行加密。加密算法分为双向加密和单向加密。单向加密包括MD5、SHA等摘要算法,它们是不可逆的。双向加密包括对称加密和非对称加密,对称加密包括AES加密、DES加密等。双向加密是可逆的,它使用相同的密钥进行加密和解密。本文主要介绍AES算法以及如何使用golang实现AES加解密。

AES简介

AES是高级加密标准,在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,目前已经被全世界广泛使用,同时AES已经成为对称密钥加密中最流行的算法之一。AES支持三种长度的密钥:128位,192位,256位。

加解密过程为:字节代替、行移位、列混淆、轮密钥加。

相关概念:

  1. 密钥:AES128,AES192,AES256,实际上就是指的AES算法对不同长度密钥的使用

  2. 分组: 把明文拆分成一个个独立的明文块,每一个明文块长度128bit。加密生成一个个独立的密文块,这些密文块拼接在一起。填充模式有:

    1. NoPadding :不做任何填充,但是要求明文必须是16字节的整数倍。
    2. PKCS5Padding: 如果明文块少于16个字节(128bit),在明文块末尾补足相应数量的字符,且每个字节的值等于缺少的字符数。
    3. ISO10126Padding: 如果明文块少于16个字节(128bit),在明文块末尾补足相应数量的字节,最后一个字符值等于缺少的字符数,其他字符填充随机数
  3. 加密模式:

    1. 电码本模式(Electronic Codebook Book (ECB)) 模式是将整个明文分成若干段相同的小段,然后对每一小段进行加密。
    2. 密码分组链接模式(Cipher Block Chaining (CBC)) 模式是先将明文切分成若干小段,然后每一小段与初始块或者上一段的密文段进行异或运算,再与密钥进行加密
    3. 计算器模式(Counter (CTR))不常用,在 CTR 模式中, 有一个自增的算子,这个算子用密钥加密之后输出和明文异或的结果得到密文,相当于一次一密
    4. 密码反馈模式(Cipher FeedBack (CFB))不常用
    5. 输出反馈模式(Output FeedBack (OFB))

golang实现AES加解密

golang使用“crypto/aes”进行AES加解密 。接着我们通过一个使用CBC模式对原文进行加解密的例子来演示如何实现。

package main

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

func main() {
    // 原始数据
    plaintext := []byte("Hello, world!")

    // 密钥,长度必须为 16、24 或 32 字节(128位,192位,256位)
    key := []byte("0123456789abcdef")

    // 使用 AES 创建加密算法对象
    block, err := aes.NewCipher(key)
    if err != nil {
        panic(err)
    }

    // 对数据进行填充,使其长度为块大小的整数倍
    plaintext = PKCS5Padding(plaintext, block.BlockSize())

    // 创建 CBC 模式的加密算法对象
    iv := []byte("0123456789abcdef") // 初始化向量,长度必须为块大小
    mode := cipher.NewCBCEncrypter(block, iv)

    // 加密数据
    ciphertext := make([]byte, len(plaintext))
    mode.CryptBlocks(ciphertext, plaintext)

    // 将密文进行 base64 编码
    encoded := base64.StdEncoding.EncodeToString(ciphertext)
    fmt.Println(encoded)

    // 解码 base64 编码的密文
    decoded, _ := base64.StdEncoding.DecodeString(encoded)

    // 创建 CBC 模式的解密算法对象
    mode = cipher.NewCBCDecrypter(block, iv)

    // 解密数据
    decrypted := make([]byte, len(decoded))
    mode.CryptBlocks(decrypted, decoded)

    // 去除填充数据
    decrypted = PKCS5UnPadding(decrypted)
    fmt.Println(string(decrypted))
}

// 对数据进行填充,使其长度为 blockSize 的整数倍
func PKCS5Padding(data []byte, blockSize int) []byte {
    padding := blockSize - len(data)%blockSize
    padtext := bytes.Repeat([]byte{byte(padding)}, padding)
    return append(data, padtext...)
}

// 去除填充数据
func PKCS5UnPadding(data []byte) []byte {
    length := len(data)
    unpadding := int(data[length-1])
    return data[:(length - unpadding)]
}

输出:

+HzZQh0Do42Kg1PQsdhdcw==
Hello, world!

上例中,分别演示了

  1. 对加密过程:创建加密对象、原文填充、创建CBC模式,加密。
  2. 解密过程: 创建CBC解密模式,解密,去除填充。

为了方便使用,我们对上例的加解密进行封装,详细如下:

package main

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

func main() {
	plaintext := []byte("Hello, world!")
	key := []byte("0123456789abcdef")
	iv := []byte("0123456789abcdef")

	// 加密数据
	encoded, err := AesCBCEncrypt(plaintext, key, iv)
	if err != nil {
		panic(err)
	}
	fmt.Println(encoded)

	// 解密数据
	decoded, err := AesCBCDecrypt(encoded, key, iv)
	if err != nil {
		panic(err)
	}
	fmt.Println(string(decoded))
}

// AesCBCEncrypt 使用 AES CBC 模式加密数据
func AesCBCEncrypt(plaintext, key, iv []byte) (string, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return "", err
	}

	plaintext = PKCS5Padding(plaintext, block.BlockSize())

	mode := cipher.NewCBCEncrypter(block, iv)

	ciphertext := make([]byte, len(plaintext))
	mode.CryptBlocks(ciphertext, plaintext)

	return base64.StdEncoding.EncodeToString(ciphertext), nil
}

// AesCBCDecrypt 使用 AES CBC 模式解密数据
func AesCBCDecrypt(ciphertext string, key, iv []byte) ([]byte, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}

	decoded, err := base64.StdEncoding.DecodeString(ciphertext)
	if err != nil {
		return nil, err
	}

	if len(decoded)%block.BlockSize() != 0 {
		return nil, errors.New("ciphertext is not a multiple of the block size")
	}

	mode := cipher.NewCBCDecrypter(block, iv)

	decrypted := make([]byte, len(decoded))
	mode.CryptBlocks(decrypted, decoded)

	decrypted = PKCS5UnPadding(decrypted)

	return decrypted, nil
}

// PKCS5Padding 对数据进行填充,使其长度为 blockSize 的整数倍
func PKCS5Padding(data []byte, blockSize int) []byte {
	padding := blockSize - len(data)%blockSize
	padtext := bytes.Repeat([]byte{byte(padding)}, padding)
	return append(data, padtext...)
}

// PKCS5UnPadding 去除填充数据
func PKCS5UnPadding(data []byte) []byte {
	length := len(data)
	unpadding := int(data[length-1])
	return data[:(length - unpadding)]
}

总结

本文主要介绍对称加密算法AES,AES使用不同指定长度的密钥,对原文进行分组,可选用更安全的加密模式CBC。在 golang中,可以使用“crypto/aes”快速实现原文填充、加密模式、加密、解密、去除填充等AES加解密过程。

参考