OpenSSL 使用AES對文件加解密

AES(Advanced Encryption Standard)是一種對稱加密算法,它是目前廣泛使用的加密算法之一。AES算法是由美國國家標準與技術研究院(NIST)於2001年發佈的,它取代了原先的DES(Data Encryption Standard)算法,成爲新的標準。AES是一種對稱加密算法,意味着加密和解密使用相同的密鑰。這就要求密鑰的安全性非常重要,因爲任何擁有密鑰的人都能進行加密和解密操作。其密鑰長度,包括128位、192位和256位。不同長度的密鑰提供了不同級別的安全性,通常更長的密鑰長度意味着更高的安全性。

該算法支持多種工作模式,其中兩種常見的模式是CBC(Cipher Block Chaining)和ECB(Electronic Codebook)。

  1. CBC 模式(Cipher Block Chaining):
    • 工作原理:
      • CBC模式對每個明文塊進行加密前,先與前一個密文塊進行異或操作。首個塊使用一個初始化向量(IV)與明文異或。這種鏈式反饋機制使得每個密文塊的加密都依賴於前一個塊的密文,從而增加了安全性。
    • 特點:
      • 帶有初始化向量,對同樣的明文塊加密得到的密文塊會隨着其前面的明文塊的不同而不同。
      • 適用於加密長度超過一個塊的數據。
    • 優點和缺點:
      • 優點:提供更高的安全性,適用於加密大塊的數據。
      • 缺點:由於加密是依賴於前一個塊的密文,所以無法進行並行加密處理。
  2. ECB 模式(Electronic Codebook):
    • 工作原理:
      • ECB模式將明文分割成塊,每個塊獨立加密,然後再組合成密文。相同的明文塊將始終加密爲相同的密文塊。
    • 特點:
      • 不需要初始化向量,同樣的明文會得到同樣的密文。
      • 適用於加密獨立的數據塊,但對於相同的塊,ECB模式下的輸出相同。
    • 優點和缺點:
      • 優點:簡單,易於實現。
      • 缺點:相同的明文塊生成相同的密文塊,可能導致安全性問題。不適用於加密大塊的數據。

在選擇模式時,需要根據具體的應用場景和需求權衡安全性和性能。一般來說,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")

使用CBC模式加解密

Cipher Block Chaining (CBC) 模式是一種對稱加密的分組密碼工作模式。在 CBC 模式中,明文被分成固定大小的塊,並使用加密算法逐個處理這些塊。每個塊都與前一個塊的密文進行異或運算,然後再進行加密。這個過程導致了一種“鏈接”效果,因此得名 Cipher Block Chaining。

以下是 CBC 模式的詳細概述:

初始向量 (Initialization Vector, IV)

  • 在 CBC 模式中,每個消息的第一個塊使用一個初始向量 (IV)。IV 是一個固定長度的隨機數,它在每次加密不同消息時都應該是唯一的。IV 的作用是在每個塊的加密中引入隨機性,以防止相同的明文塊生成相同的密文塊。

分組加密

  • 消息被分成固定大小的塊(通常爲 64 比特或 128 比特),然後每個塊都被分組加密。最常用的塊加密算法是 AES。

異或運算

  • 在每個塊加密之前,明文塊與前一個密文塊進行異或運算。這就是“鏈接”發生的地方。第一個塊與 IV 異或。

加密

  • 異或運算後的結果被送入塊加密算法進行加密。得到的密文塊成爲下一個塊的 IV。

解密

  • 在解密時,密文塊被送入塊解密算法進行解密。解密後的結果與前一個密文塊進行異或運算,得到明文塊。

模式串行化

  • CBC 模式是串行的,因爲每個塊的加密都依賴於前一個塊的密文。這也意味着無法並行處理整個消息。

填充

  • 如果明文的長度不是塊大小的整數倍,需要進行填充。常見的填充方案有 PKCS#7 填充。

安全性

  • 當使用 CBC 模式時,密文塊的順序對安全性至關重要。如果消息的兩個塊對調,解密後會得到不同的明文。因此,必須保證密文塊的順序不被篡改。

使用場景

  • CBC 模式常用於保護傳輸層安全協議(如 TLS)中,以提供加密和數據完整性。

總體而言,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;
}

運行後輸出效果圖如下所示;

使用ECB模式加解密

Electronic Codebook (ECB) 模式是一種對稱加密的分組密碼工作模式。在 ECB 模式中,每個明文塊都被獨立加密,不受其他塊的影響。這意味着相同的明文塊將始終生成相同的密文塊,這可能導致一些安全性問題。

以下是 ECB 模式的詳細概述:

分組加密

  • 消息被分成固定大小的塊(通常爲 64 比特或 128 比特),然後每個塊都被獨立加密。最常用的塊加密算法是 AES。

無鏈接

  • 在 ECB 模式中,每個塊的加密是獨立的,不會受到前一個或後一個塊的影響。這意味着相同的明文塊將生成相同的密文塊。

模式串行化

  • ECB 模式允許對整個消息進行並行處理,因爲每個塊都是獨立加密的。這是與 CBC 模式相比的一個優勢,因爲它允許更高效的實現。

填充

  • 如果明文的長度不是塊大小的整數倍,需要進行填充。常見的填充方案有 PKCS#7 填充。

安全性問題

  • 主要的安全性問題在於相同的明文塊生成相同的密文塊,這可能導致一些攻擊。例如,如果兩個塊的內容相同,那麼它們的密文也將相同。

使用場景

  • 由於安全性問題,ECB 模式並不適合所有場景。一般來說,ECB 模式主要用於對稱加密算法的基本理解和學術研究,而在實際應用中更常使用其他工作模式,如 CBC 或 GCM。

總體而言,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)操作。

返回值:

  • 返回 0 表示成功,其他值表示錯誤。

功能說明:

  • AES_ecb_encrypt 函數用於在 ECB 模式下執行 AES 算法的加密或解密操作,具體取決於 enc 參數。
  • 在 ECB 模式下,該函數將輸入的數據塊獨立地加密(或解密),每個塊的輸出結果不受前後塊的影響。
  • 函數通過 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)操作。

返回值:

  • 返回 0 表示成功,其他值表示錯誤。

功能說明:

  • AES_ecb_encrypt 函數用於在 ECB 模式下執行 AES 算法的加密或解密操作,具體取決於 enc 參數。
  • 在 ECB 模式下,該函數將輸入的數據塊獨立地加密(或解密),每個塊的輸出結果不受前後塊的影響。
  • 函數通過 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;
}

運行後輸出效果圖如下所示;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章