对称加密算法AES简介及在OpenSSL中使用举例

高级加密标准(AES, Advanced Encryption Standard)由美国国家标准和技术协会(NIST)于2000年公布,它是一种对称加密算法,用来替代DES。AES也称为Rijndael算法,是由两个比利时密码学家Vincent Rijmen和Joah Daemen开发的,他们的姓氏作为这个加密的名字。严格来说,AES和Rijndael加密算法并不完全一样。

关于DES的介绍可以参考:https://blog.csdn.net/fengbingchun/article/details/42273257

AES算法是对称分组加密算法,可以加密也可以解密信息,加密将数据转换成无法理解的形式,称为密文;解密是将密文转换为数据的原始形式,也即明文,其分块长度固定为128比特。AES算法密钥长度有三个版本,分别是128比特、192比特和256比特三种。不同的密钥长度会对子密钥的个数和加密轮数产生影响,其中,使用128比特的加密轮次为10轮,192比特密钥对应12轮,256比特密钥有14轮,所有这三种AES方案中除了最后一轮处理外,其它轮处理都是一样的。对于加密,每轮包含四个步骤:(1).字节替换(ByteSub);(2).行位移(ShiftRow);(3).列混合(MixColumn);(4).轮密钥加(AddRoundKey)。对于解密,每轮包含四个步骤:(1).逆字节替换(InvByteSub);(2).逆行移位(InvShiftRow);(3).逆列混合(InvMixColumn);(4).论密钥加的逆变换(InvAddRoundKey)。AES算法的加解密过程如下图所示:

AES算法中的输入和输出都是128bit的序列,这些序列都看作为分组块,其包含的位数称为分组块长度。AES算法中的基本处理单元是一个字节。对于一个输入、输出或加密密钥阵列,将序列以8bit为单位划分,来生成字节阵列。

AES算法加密模式:操作模式是指用分组密码加密任意长度明文的方法,AES有5种基本操作模式,分别是电子密码本(ECB)、密码分组链接模式(CBC)、密码反馈模式(CFB)、输出反馈模式(OFB)以及计数器模式(CTR),其中前四种模式也被DES加密算法所使用。其它衍生的算法操作模式,都是建立在这五种模式基础上的。实际中加密的明文长度很少是分组长度的倍数,如果要使用某些模式(如CBC)进行分组加密,需要在加密前对明文进行填充,填充的方案有多种。

(1). ECB:这种模式直接对明文分组进行加密,它将加密的数据分成若干组,每组的大小跟加密密钥长度相同,然后每组都用相同的密钥进行加密,具有简单易行、可并行化及误差不传递的优点。但这种模式无法实现保密性。

(2). CBC:是SSL/IPSec的标准,广泛应用于对话中的加密传输,它克服了ECB模式安全性弱的缺点。在这种模式下,每个明文分组在加密前需要和一个初始向量(IV)做异或操作,然后做加密操作,得到密文分组。IV具有和密码分组同样的长度,它的选取必须随机。在会话中,IV通常是以明文的形式和密文一起传送的。在该模式下,密码不能以并行的方式进行调用。使用CBC模式进行解密,只需要把密文传给解密机,并把解密机的输出和链接值进行异或即可。

(3). CTR:被设计用来解决CBC模式中遇到的无法并行化的问题。在这种模式中,可以像流密码一样对明文进行加密。在CTR模式中,其加密和解密的过程是一样的。

(4). OFB:是使用分组密码来产生伪随机流,然后和明文进行异或操作。OFB模式的解密操作和加密操作一样。

(5). CFB:同OFB模式一样,CFB也是产生一个密钥流,然后和明文进行异或,不同点在其产生伪随机流/密钥流的方式不同。

以上整理的内容主要摘自:

1. 《AES算法及其DSP实现》,哈尔滨工业大学,硕论,2008

2. 《AES算法的FPGA实现与分析》,天津大学,硕论,2012

3. 《针对AES加密算法的缓存攻击研究》,中国科学院大学,硕论,2016

以下为测试代码(test_openssl_aes):

#include "funset.hpp"
#include <string.h>
#include <string>
#include <vector>
#include <memory>
#include <algorithm>
#include <openssl/des.h>
#include <openssl/rc4.h>
#include <openssl/md5.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/aes.h>
#include <b64/b64.h>

//////////////////////////// AES ///////////////////////////////
namespace {

const unsigned char aes_key[] = {
    0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
    0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff,
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
};

} // namespace

int test_openssl_aes()
{
	const char* cleartext = "中国北京12345$abcde%ABCDE!!!!";
	fprintf(stdout, "cleartext length: %d, contents: %s\n", strlen(cleartext), cleartext);

	const int key_bits = sizeof(aes_key) / sizeof(aes_key[0]) * 8;

	// encrypt
	AES_KEY enc_key;
	int ret = AES_set_encrypt_key(aes_key, key_bits, &enc_key); 
	if (ret != 0) return ret;

	char* cleartext_encode = b64_encode(reinterpret_cast<const unsigned char*>(cleartext), strlen(cleartext));
	std::shared_ptr<char> ptr1;
	ptr1.reset(cleartext_encode, [](char* p) { free(p); });
	int encoded_length = (strlen(ptr1.get()) + AES_BLOCK_SIZE - 1) / AES_BLOCK_SIZE * AES_BLOCK_SIZE;
	std::unique_ptr<unsigned char[]> cleartext_encode2(new unsigned char[encoded_length]);
	memset(cleartext_encode2.get(), 0, encoded_length);
	memcpy(cleartext_encode2.get(), ptr1.get(), strlen(ptr1.get()));

	std::unique_ptr<unsigned char[]> cleartext_encrypt(new unsigned char[encoded_length]);
	memset(cleartext_encrypt.get(), 0, encoded_length);
	int count = 1;
	while (encoded_length + AES_BLOCK_SIZE - 1 >= AES_BLOCK_SIZE * count) {
		const unsigned char* p1 = cleartext_encode2.get() + AES_BLOCK_SIZE * (count - 1);
		unsigned char* p2 = cleartext_encrypt.get() + AES_BLOCK_SIZE * (count - 1);
		AES_encrypt(p1, p2, &enc_key);
		++count;
	}

	fprintf(stdout, "cleartext encrypt: ");
	std::for_each(cleartext_encrypt.get(), cleartext_encrypt.get() + encoded_length, [](unsigned char v) { fprintf(stdout, "%02X", v); });
	fprintf(stdout, "\n");

	// decrypt
	AES_KEY dec_key;
	ret = AES_set_decrypt_key(aes_key, key_bits, &dec_key);
	if (ret != 0) return ret;

	std::unique_ptr<unsigned char[]> ciphertext_decrypt(new unsigned char[encoded_length]);
	memset(ciphertext_decrypt.get(), 0, encoded_length);
	count = 1;
	while (encoded_length + AES_BLOCK_SIZE - 1 >= AES_BLOCK_SIZE * count) {
		const unsigned char* p1 = cleartext_encrypt.get() + AES_BLOCK_SIZE * (count - 1);
		unsigned char* p2 = ciphertext_decrypt.get() + AES_BLOCK_SIZE * (count - 1);
		AES_decrypt(p1, p2, &dec_key);
		++count;
	}

	fprintf(stdout, "ciphertext decrypt: ");
	std::for_each(ciphertext_decrypt.get(), ciphertext_decrypt.get() + encoded_length, [](unsigned char v) { fprintf(stdout, "%02X", v); });
	fprintf(stdout, "\n");

	unsigned char* decrypt_decode = b64_decode(reinterpret_cast<const char*>(ciphertext_decrypt.get()), encoded_length);
	std::shared_ptr<unsigned char> ptr2;
	ptr2.reset(decrypt_decode, [](unsigned char* p) { free(p); });
	fprintf(stdout, "decrypt result: %s\n", ptr2.get());

	if (strcmp(cleartext, reinterpret_cast<char*>(ptr2.get())) != 0) {
		fprintf(stderr, "aes decrypt fail\n");
		return -1;
	}

	return 0;
}

执行结果如下:

GitHubhttps://github.com//fengbingchun/OpenSSL_Test

发布了718 篇原创文章 · 获赞 1131 · 访问量 609万+

猜你喜欢

转载自blog.csdn.net/fengbingchun/article/details/100139524