浅谈AES加密

最近在项目上用到了AES加密,虽然是非常简单的加密算法了,但是密码学零基础的话还是要花一点时间理解用法的。主要说一下使用方法,具体的实现就不深究了。

AES加密的实现是将数据看做一个块来实现的,每次对一个块(也就是矩阵)大小的数据进行加密操作,矩阵中的每一个字节都与该回合的加密块(round key)做XOR运算;每个子密钥由密钥生成方案产生。之后回合密钥将会与原矩阵合并。在每次的加密循环中,都会由主密钥产生一把回合密钥(通过Rijndael密钥生成方案产生),这把密钥大小会跟原矩阵一样,以与原矩阵中每个对应的字节作异或加法。

由上述可知,AES每次处理的数据是一块(16字节),当数据不是16字节的倍数时就需要填充,这就是所谓的分组密码(区别于基于比特位的流密码),16字节是分组长度。

AES有几种不同的加密模式,不同的模式需要的参数也是不同的:

  • ECB(Electronic Code Book电子密码本):是一种基础的加密方式,密文被分割成分组长度相等的块(不足补齐),然后单独一个个加密,一个个输出组成密文。

  • CBC(Cipher Block Chaining,加密块链):是一种循环模式,前一个分组的密文和当前分组的明文异或或操作后再加密,是SSL、IPSec的标准。但这样不利于并行计算,且需要初始化向量IV

  • CFB(Cipher FeedBack Mode,加密反馈)/OFB(Output FeedBack,输出反馈):隐藏了明文的模式,分组密码转化为流模式,可以及时加密传送小于分组的数据

几种模式中最常用的便是CBC,是比较可靠的一种方式,我们主要就介绍这一种方式。

代码我们以C语言的OpenSSL库和golang的标准库作为范例。

首先看一下参数

#include <openssl/aes.h>
int AES_set_encrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
//以上两个函数是用来根据用户指定的密码来生成加密所需要的key,也就是加密块
参数 说明
userKey 用户指定的密码。必须是16、24、32字节,对应AES-128、AES-256等加密方式
bits 密码位数。即userKey的长度 * 8,只能是128、192、256位。
key 输出的AES_KEY,即加密块
void AES_cbc_encrypt(const unsigned char *in, unsigned char *out,
                     size_t length, const AES_KEY *key,
                     unsigned char *ivec, const int enc);
参数 说明
in 输入数据。长度任意。
out 输出数据。能够容纳下输入数据,且长度必须是16字节(AES加密块长度)的倍数。
length 输入数据的实际长度。
key 使用AES_set_encrypt/decrypt_key生成的Key。
ivec 可读写的一块内存。长度必须是16字节。
enc 是否是加密操作。AES_ENCRYPT表示加密,AES_DECRYPT表示解密。

实际上输入数据在长度上AES是有要求的,只不过在长度不满足是加密块长度的倍数时,它会自动帮你做填充。输出的数据缓冲区长度就必须满足是加密块长度的倍数。

其中的ivec是AES加密过程中使用的向量,其主要意义是混淆加密,可以让同样的明文产生出不同的加密密文。该向量必须可写、独立,因为他在加密的过程中是会根据当前一轮的加密结果进行变化的。此外加解密需要使用相同的向量,因此将向量放在密文中也是一种加密选择的方式。下面的go代码中我们能看到。

// 这是golang官网文档提供的AES加密实例
key := []byte("example key 1234")
plaintext := []byte("exampleplaintext")
// 此前提到的,明文的长度必须满足是加密块长度的倍数
if len(plaintext)%aes.BlockSize != 0 {
    panic("plaintext is not a multiple of the block size")
}
// 此处根据用户指定的key来生成加密块
block, err := aes.NewCipher(key)
if err != nil {
    panic(err)
}
// IV向量是用来保证加密的随机性的,而不是保证加密的安全性的。
// 我们可以看到,对于密文其申请了明文长度+AES块(即IV长度)的大小,将IV放在了密文的最前一块
ciphertext := make([]byte, aes.BlockSize+len(plaintext))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
    panic(err)
}
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(ciphertext[aes.BlockSize:], plaintext)

fmt.Printf("%x\n", ciphertext)

1447 Words

2019-08-18 08:00 +0800