AES(Advanced Encryption Standard)是一種對稱加密演演算法,它是目前廣泛使用的加密演演算法之一。AES演演算法是由美國國家標準與技術研究院(NIST)於2001年釋出的,它取代了原先的DES(Data Encryption Standard)演演算法,成為新的標準。AES是一種對稱加密演演算法,意味著加密和解密使用相同的金鑰。這就要求金鑰的安全性非常重要,因為任何擁有金鑰的人都能進行加密和解密操作。其金鑰長度,包括128位元、192位和256位。不同長度的金鑰提供了不同級別的安全性,通常更長的金鑰長度意味著更高的安全性。
該演演算法支援多種工作模式,其中兩種常見的模式是CBC(Cipher Block Chaining)和ECB(Electronic Codebook)。
在選擇模式時,需要根據具體的應用場景和需求權衡安全性和效能。一般來說,CBC模式是更安全的選擇,而ECB模式可能更容易實現和理解。在實際應用中,還可以考慮其他模式,如CTR(Counter)模式和GCM(Galois/Counter Mode)模式等,這些模式結合了安全性和效能的考慮。
本次案例中所需要使用的標頭檔案資訊如下所示;
#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <iostream>
#include <openssl/err.h>
#include <openssl/aes.h>
#include <openssl/evp.h>
#include <openssl/crypto.h>
#include <openssl/pem.h>
extern "C"
{
#include <openssl/applink.c>
}
#pragma comment(lib,"libssl_static.lib")
#pragma comment(lib,"libcrypto.lib")
Cipher Block Chaining (CBC) 模式是一種對稱加密的分組密碼工作模式。在 CBC 模式中,明文被分成固定大小的塊,並使用加密演演算法逐個處理這些塊。每個塊都與前一個塊的密文進行互斥或運算,然後再進行加密。這個過程導致了一種「連結」效果,因此得名 Cipher Block Chaining。
以下是 CBC 模式的詳細概述:
初始向量 (Initialization Vector, IV):
分組加密:
互斥或運算:
加密:
解密:
模式序列化:
填充:
安全性:
使用場景:
總體而言,CBC 模式提供了一種相對強大的加密方法,但在實現時需要注意使用隨機且不可預測的 IV 以及處理填充的問題。
AES_set_encrypt_key
函數。具體來說,它用於將原始金鑰設定為可以在 AES 加密演演算法中使用的格式。以下是該函數的原型:
int AES_set_encrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
userKey
:指向用於設定金鑰的輸入資料的指標,即原始金鑰。bits
:金鑰長度,以位元為單位。在使用 AES 加密演演算法時,通常為 128、192 或 256。key
:指向 AES_KEY
結構的指標,用於儲存設定後的金鑰資訊。該函數返回值為零表示成功,非零表示失敗。成功呼叫後,key
引數中儲存了經過格式化的金鑰資訊,可以在後續的 AES 加密操作中使用。
AES_cbc_encrypt
是 OpenSSL 庫中用於執行 AES 演演算法中的 Cipher Block Chaining (CBC) 模式的函數。在 CBC 模式中,每個明文塊在加密之前會與前一個密文塊進行互斥或運算,以增加密碼的隨機性。
以下是 AES_cbc_encrypt
函數的原型:
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
:指向輸出資料(密文)的指標。length
:資料的長度,以位元組為單位。key
:指向 AES_KEY
結構的指標,其中包含了加密金鑰。ivec
:Initialization Vector(IV),用於增強密碼的隨機性,也是前一個密文塊。在 CBC 模式中,IV 對於第一個資料塊是必需的,之後的 IV 由前一個密文塊決定。enc
:指定操作是加密(AES_ENCRYPT
)還是解密(AES_DECRYPT
)。AES_set_decrypt_key
函數。該函數用於將加密時使用的金鑰調整為解密時使用的金鑰,以便進行解密操作。
以下是 AES_set_decrypt_key
函數的原型:
int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
userKey
:指向用於設定解密金鑰的輸入金鑰資料的指標。bits
:金鑰長度,以位元為單位。支援的長度包括 128、192 和 256 位元。key
:指向 AES_KEY
結構的指標,該結構將儲存設定後的解密金鑰。實現加解密功能,如下openssl_aes_cbc_encrypt
用於使用CBC模式加密資料,openssl_aes_cbc_decrypt
則相反用於解密資料。
// 初始化金鑰
const unsigned char key[AES_BLOCK_SIZE] = { 0x12,0x55,0x64,0x69,0xf1 };
// 初始化向量
unsigned char iv[AES_BLOCK_SIZE] = { 0 };
// AES CBC 模式加密
// 引數:
// - in: 待加密的資料
// - len: 待加密資料的長度
// - out: 存放加密結果的緩衝區
// 返回值:
// - 返回填充後加密資料的長度,失敗返回-1
int openssl_aes_cbc_encrypt(char* in, size_t len, char* out)
{
AES_KEY aes;
// 填充資料為AES_BLOCK_SIZE的整數倍
char* aesIn;
int blockNum, aesInLen;
// 設定加密金鑰
if (AES_set_encrypt_key(key, 128, &aes) < 0)
{
return -1;
}
// 判斷原始資料長度是否AES_BLOCK_SIZE的整數倍
if ((len % AES_BLOCK_SIZE) != 0)
{
// 不是整數倍則用0填充
blockNum = len / AES_BLOCK_SIZE + 1;
aesInLen = blockNum * AES_BLOCK_SIZE;
aesIn = (char*)calloc(aesInLen, 1);
memcpy(aesIn, in, len);
}
else
{
aesInLen = len;
aesIn = (char*)calloc(aesInLen, 1);
memcpy(aesIn, in, len);
}
// AES CBC 模式加密
AES_cbc_encrypt((unsigned char*)aesIn, (unsigned char*)out, aesInLen, &aes, iv, AES_ENCRYPT);
// 釋放分配的記憶體
free(aesIn);
// 返回填充後加密資料的長度
return aesInLen;
}
// AES CBC 模式解密
// 引數:
// - in: 待解密的資料
// - len: 待解密資料的長度
// - out: 存放解密結果的緩衝區
// 返回值:
// - 成功返回0,失敗返回-1
int openssl_aes_cbc_decrypt(char* in, size_t len, char* out)
{
AES_KEY aes;
// 設定解密金鑰
if (AES_set_decrypt_key(key, 128, &aes) < 0)
{
return -1;
}
// AES CBC 模式解密
AES_cbc_encrypt((unsigned char*)in, (unsigned char*)out, len, &aes, iv, AES_DECRYPT);
// 返回成功
return 0;
}
當需要對資料加密時,首先開啟被加密檔案這裡我們開啟的時csdn.zip
檔案,加密後會寫出為csdn.cbc
檔案;
int main(int argc, char* argv[])
{
// 存放填充位元組數的陣列
char offset[4] = { '0' };
char* src = nullptr, *dst = nullptr;
int inlen, outlen, size;
FILE* srcFile, *dstFile;
// 開啟被加密原始檔
srcFile = fopen("d://comp/csdn.zip", "rb");
// 加密後寫出檔案
dstFile = fopen("d://comp/csdn.cbc", "wb+");
// 獲取檔案大小
fseek(srcFile, 0, SEEK_END);
inlen = ftell(srcFile);
if (inlen < 0)
{
return 0;
}
fseek(srcFile, 0, SEEK_SET);
// -------------------------------------------------------
// 開始加密
src = (char*)calloc(inlen, 1);
size = fread(src, 1, inlen, srcFile);
std::cout << "讀入位元組: " << size << std::endl;
// 輸出變數申請的空間額外增加16位元組
outlen = (inlen / 16 + 1) * 16;
dst = (char*)calloc(outlen, 1);
// 呼叫加密函數
size = openssl_aes_cbc_encrypt(src, inlen, dst);
// 獲取填充的位元組數,記錄到輸出檔案的前4個位元組內
sprintf(offset, "%d", size - inlen);
fwrite(offset, sizeof(char), 4, dstFile);
// -------------------------------------------------------
// 輸出加密後的檔案或者解密後的檔案,檔案大小應與原始檔案一致
size = fwrite(dst, 1, size, dstFile);
std::cout << "輸出檔案大小: " << size << std::endl;
fcloseall();
free(src);
free(dst);
system("pause");
return 0;
}
執行後輸出效果圖如下所示;
解密時同樣需要開啟檔案,將加密檔案csdn.cbc
開啟,並解密輸出成csdnde.zip
檔案;
int main(int argc, char* argv[])
{
// 存放填充位元組數的陣列
char offset[4] = { '0' };
char* src = nullptr, *dst = nullptr;
int inlen, outlen, size;
FILE* srcFile, *dstFile;
// 開啟加密後的檔案
srcFile = fopen("d://comp/csdn.cbc", "rb");
// 解密後寫出的檔案
dstFile = fopen("d://comp/csdnde.zip", "wb+");
// 獲取檔案大小
fseek(srcFile, 0, SEEK_END);
inlen = ftell(srcFile);
if (inlen < 0)
{
return 0;
}
fseek(srcFile, 0, SEEK_SET);
// -------------------------------------------------------
fread(offset, sizeof(char), 4, srcFile);
inlen -= 4;
src = (char*)calloc(inlen, 1);
// 從加密後的檔案中獲取填充的位元組數
size = fread(src, 1, inlen, srcFile);
std::cout << "讀入位元組: " << size << std::endl;
// 得到原始檔案的大小
size = size - atoi(offset);
outlen = (inlen / 16 + 1) * 16;
dst = (char*)calloc(outlen, 1);
// 解密
openssl_aes_cbc_decrypt(src, inlen, dst);
// -------------------------------------------------------
// 輸出加密後的檔案或者解密後的檔案,檔案大小應與原始檔案一致
size = fwrite(dst, 1, size, dstFile);
std::cout << "輸出檔案大小: " << size << std::endl;
fcloseall();
free(src);
free(dst);
system("pause");
return 0;
}
執行後輸出效果圖如下所示;
Electronic Codebook (ECB) 模式是一種對稱加密的分組密碼工作模式。在 ECB 模式中,每個明文塊都被獨立加密,不受其他塊的影響。這意味著相同的明文塊將始終生成相同的密文塊,這可能導致一些安全性問題。
以下是 ECB 模式的詳細概述:
分組加密:
無連結:
模式序列化:
填充:
安全性問題:
使用場景:
總體而言,ECB 模式是一種簡單的分組密碼工作模式,但由於安全性問題,實際應用中更常使用其他工作模式。
AES_ecb_encrypt
是 OpenSSL 庫中用於執行 AES 演演算法的 ECB 模式加密的函數。下面是對該函數的詳細概述:
int AES_ecb_encrypt(const unsigned char *input, unsigned char *output, const AES_KEY *key, const int enc);
引數說明:
input
: 要加密的資料的輸入緩衝區的指標。output
: 加密後的資料的輸出緩衝區的指標。key
: AES 金鑰的結構體指標,其中包含了加密所需的金鑰資訊。enc
: 一個整數值,用於指定是執行加密(AES_ENCRYPT
)還是解密(AES_DECRYPT
)操作。返回值:
功能說明:
AES_ecb_encrypt
函數用於在 ECB 模式下執行 AES 演演算法的加密或解密操作,具體取決於 enc
引數。key
引數提供的金鑰資訊執行加密或解密操作。AES_ecb_encrypt
是 OpenSSL 庫中用於執行 AES 演演算法的 ECB 模式加密或解密的函數。下面是對該函數的詳細概述:
int AES_ecb_encrypt(const unsigned char *input, unsigned char *output, const AES_KEY *key, const int enc);
引數說明:
input
: 要加密或解密的資料塊的輸入緩衝區指標。output
: 加密或解密後的資料塊的輸出緩衝區指標。key
: AES 金鑰的結構體指標,包含了加密或解密所需的金鑰資訊。enc
: 一個整數值,用於指定是執行加密(AES_ENCRYPT
)還是解密(AES_DECRYPT
)操作。返回值:
功能說明:
AES_ecb_encrypt
函數用於在 ECB 模式下執行 AES 演演算法的加密或解密操作,具體取決於 enc
引數。key
引數提供的金鑰資訊執行加密或解密操作。// AES ECB 模式加密
// 引數:
// - in: 待加密的資料
// - len: 待加密資料的長度
// - out: 存放加密結果的緩衝區
// 返回值:
// - 成功返回填充後加密資料的長度,失敗返回-1
int openssl_aes_ecb_enrypt(char* in, size_t len, char* out)
{
int i;
int blockNum;
int aesInLen;
char* aesIn;
AES_KEY aes;
// 設定加密金鑰
if (AES_set_encrypt_key(key, 128, &aes) < 0)
return -1;
// 判斷原始資料長度是否AES_BLOCK_SIZE的整數倍
if ((len % AES_BLOCK_SIZE) != 0)
{
blockNum = len / AES_BLOCK_SIZE + 1;
aesInLen = blockNum * AES_BLOCK_SIZE;
aesIn = (char*)calloc(aesInLen, 1);
memcpy(aesIn, in, len);
}
else
{
blockNum = len / AES_BLOCK_SIZE;
aesInLen = len;
aesIn = (char*)calloc(aesInLen, 1);
memcpy(aesIn, in, len);
}
// 由於ECB每次只處理AES_BLOCK_SIZE大小的資料,所以通過迴圈完成所有資料的加密
for (i = 0; i < blockNum; i++)
{
AES_ecb_encrypt((unsigned char*)aesIn, (unsigned char*)out, &aes, AES_ENCRYPT);
aesIn += AES_BLOCK_SIZE;
out += AES_BLOCK_SIZE;
}
// 釋放記憶體
// free(aesIn);
// 返回填充後加密資料的長度
return aesInLen;
}
// AES ECB 模式解密
// 引數:
// - in: 待解密的資料
// - len: 待解密資料的長度
// - out: 存放解密結果的緩衝區
// 返回值:
// - 成功返回0,失敗返回-1
int openssl_aes_ecb_decrypt(char* in, size_t len, char* out)
{
unsigned int i;
AES_KEY aes;
// 設定解密金鑰
if (AES_set_decrypt_key(key, 128, &aes) < 0)
{
return -1;
}
// 迴圈解密每個資料塊
for (i = 0; i < len / AES_BLOCK_SIZE; i++)
{
AES_ecb_encrypt((unsigned char*)in, (unsigned char*)out, &aes, AES_DECRYPT);
in += AES_BLOCK_SIZE;
out += AES_BLOCK_SIZE;
}
// 返回成功
return 0;
}
當需要對資料加密時,首先開啟被加密檔案這裡我們開啟的時csdn.zip
檔案,加密後會寫出為csdn.ecb
檔案;
int main(int argc, char* argv[])
{
// 存放填充位元組數的陣列
char offset[4] = { '0' };
char* src = nullptr, *dst = nullptr;
int inlen, outlen, size;
FILE* srcFile, *dstFile;
// 開啟被加密原始檔
srcFile = fopen("d://comp/csdn.zip", "rb");
// 加密後寫出檔案
dstFile = fopen("d://comp/csdn.ecb", "wb+");
// 獲取檔案大小
fseek(srcFile, 0, SEEK_END);
inlen = ftell(srcFile);
if (inlen < 0)
{
return 0;
}
fseek(srcFile, 0, SEEK_SET);
// -------------------------------------------------------
// 開始加密
src = (char*)calloc(inlen, 1);
size = fread(src, 1, inlen, srcFile);
std::cout << "讀入位元組: " << size << std::endl;
// 輸出變數申請的空間額外增加16位元組
outlen = (inlen / 16 + 1) * 16;
dst = (char*)calloc(outlen, 1);
// ECB加密
size = openssl_aes_ecb_enrypt(src, inlen, dst);
sprintf(offset, "%d", size - inlen);
fwrite(offset, sizeof(char), 4, dstFile);
// -------------------------------------------------------
// 輸出加密後的檔案或者解密後的檔案,檔案大小應與原始檔案一致
size = fwrite(dst, 1, size, dstFile);
std::cout << "輸出檔案大小: " << size << std::endl;
fcloseall();
free(src);
free(dst);
system("pause");
return 0;
}
執行後輸出效果圖如下所示;
解密時同樣需要開啟檔案,將加密檔案csdn.ecb
開啟,並解密輸出成csdnde.zip
檔案;
int main(int argc, char* argv[])
{
// 存放填充位元組數的陣列
char offset[4] = { '0' };
char* src = nullptr, *dst = nullptr;
int inlen, outlen, size;
FILE* srcFile, *dstFile;
// 開啟加密後的檔案
srcFile = fopen("d://comp/csdn.ecb", "rb");
// 解密後寫出的檔案
dstFile = fopen("d://comp/csdnde.zip", "wb+");
// 獲取檔案大小
fseek(srcFile, 0, SEEK_END);
inlen = ftell(srcFile);
if (inlen < 0)
{
return 0;
}
fseek(srcFile, 0, SEEK_SET);
// -------------------------------------------------------
fread(offset, sizeof(char), 4, srcFile);
inlen -= 4;
src = (char*)calloc(inlen, 1);
// 從加密後的檔案中獲取填充的位元組數
size = fread(src, 1, inlen, srcFile);
std::cout << "讀入位元組: " << size << std::endl;
// 得到原始檔案的大小
size = size - atoi(offset);
outlen = (inlen / 16 + 1) * 16;
dst = (char*)calloc(outlen, 1);
// 解密
openssl_aes_ecb_decrypt(src, inlen, dst);
// -------------------------------------------------------
// 輸出加密後的檔案或者解密後的檔案,檔案大小應與原始檔案一致
size = fwrite(dst, 1, size, dstFile);
std::cout << "輸出檔案大小: " << size << std::endl;
fcloseall();
free(src);
free(dst);
system("pause");
return 0;
}
執行後輸出效果圖如下所示;