---
title : 05_RC4加密-使用BCrypt
---
# BCrypt函式庫
by WL, Liu & BG, Tasi
Date : 2021-10-8
---
## 壹、基本函式
| 作用 | 函式 | 功能 |
|:----------:|:---------------------------:|:----------------------:|
| 開始與準備 | BCryptOpenAlgorithmProvider | 載入並初始化演算法題供者 |
| 開始與準備 | BCryptGenerateSymmetricKey | 產生對稱式金鑰 |
| 加密、解密 | BCryptEncrypt | 加密 |
| 加密、解密 | BCryptDecrypt | 解密 |
| 結束關閉 | BCryptDestroyKey | 銷毀金鑰 |
| 結束關閉 | BCryptCloseAlgorithmProvider | 關閉演算法提供者 |
## 貳、函數介紹
### 01. 開啟演算法提供者-BCryptOpenAlgorithmProvider
* 在這裡我們是選用微軟提供的演算法提供者,也可指定其他特定提供者的演算法。
* 在MSDN說明文件上看到BCryptOpenAlgorithmProvider的宣告使用四個參數:
* **phAlgorithm** => BCryptOpenAlgorithmProvider()呼叫成功時,會將資料寫入phAlgorithm,並指向接收 CNG 提供程序句柄的BCRYPT_ALG_HANDLE變量的指針。使用完畢後,通過BCryptCloseAlgorithmProvider()函數來釋放它。
* **pszAlgId** => 向以空字符結尾的 Unicode 字串的指針,該字符串用來指定所求的加密算法。以下列舉其中三個:
| Unicode 字串 | 演算法 |
|:--------------------------:|:-------------------:|
| BCRYPT_RC4_ALGORITHML"RC4" | RC4演算法 |
| BCRYPT_RC4_ALGORITHML"RSA" | RSA公開金鑰演算法 |
| BCRYPT_RC4_ALGORITHML"AES" | AES對稱式加密演算法 |
* **pszImplementation** => 指向以空字符結尾的 Unicode 字串的指針,該字符串標識要加載的特定提供程序。可以為NULL。如果此參數為NULL,則將加載指定算法的默認提供程序。以下為目前提供者:
| Unicode 字串 | 提供者 |
| ------------------------------------- |:---------------------------------------------------------------------- |
| L"Microsoft Primitive Provider" | Identifies the basic Microsoft CNG provider |
| L"Microsoft Platform Crypto Provider" | Identifies the TPM key storage provider that is provided by Microsoft. |
* **dwFlags** => 旗標,是用來修改函數的,這可以是零或以下值中的一個或多個的組合:
| 旗標 | 意義 |
| ---- | ---- |
| BCRYPT_ALG_HANDLE_HMAC_FLAG | 提供程序將執行 Hash-Based Message Authentication Code (HMAC)演算法,此標誌僅由散列算法提供程序使用。 |
| BCRYPT_PROV_DISPATCH | 指定此標誌時,在釋放所有相關對象之前不得關閉返回的句柄。 |
| BCRYPT_HASH_REUSABLE_FLAG | 創建一個可重用的散列對象。該對象可以在調用BCryptFinishHash後立即用於新的散列操作。 |
* BCrypt所有的函數,回傳值都是NTSTATUS這個資料型別,而NTSTATUS定義在ntdef.h中,以下是回傳結果:
| 回傳值 | 描述 |
| ------ | ---- |
| STATUS_SUCCESS | 成功 |
| STATUS_NOT_FOUND | 找不到指定算法 ID 的提供者。 |
| STATUS_INVALID_PARAMETER | 參數無效 |
| STATUS_NO_MEMORY | 記憶題配值出現問題 |
可先判定回傳值是否為STATUS_SUCCESS,若不是,在近一步了解錯誤原因。以上回傳值定義在ntstatus.h引入檔。
### 02. 產生對稱式金鑰-BCryptGenerateSymmetricKey
* BCryptGenerateSymmetricKey總共有七個參數,在某些加密方法並未完全用到,填入0或NULL即可。以下分別介紹七個參數:
* **hAlgorithm** => 指向BCryptOpenAlgorithmProvider()函數創建的算法提供程序的結構。創建提供程序時指定的算法必須支持對稱密鑰加密。
* **phKey** => 一個指向接收密鑰BCRYPT_KEY_HANDLE的指針。先準備BCRYPT_KEY_HANDLE以用來將其地址傳給BCryptGenerateSymmetricKey(),呼叫成功後會取得金鑰的HANDLE。當不再需要時,必須通過BCryptDestroyKey()函數來釋放它。
* **pbKeyObject** => 指向接收密鑰對象的緩衝區的指針。可以通過調用BCryptGetProperty()函數獲取BCRYPT_OBJECT_LENGTH屬性來獲取此緩衝區所需的空間大小。
* **cbKeyObject** => pbKeyObject緩衝區的大小(資料型態為byte)。必須是BCryptGetProperty()取得BCRYPT_OBJECT_LENGTH的值。
* **pbSecret** => 指向緩衝區的指針,緩衝區從中創建密鑰對象的密鑰。cbSecret參數包含該緩衝區的大小(資料型態為byte)。這通常是密碼或其他一些可複製數據的散,資料型態為byte。若傳入的數據超過了目標key大小,數據超出的部分會忽略。
* **cbSecret** => 密碼的長度,也就是pbSecret的長度(資料型態為byte)。
* **dwFlags** => 目前保留未使用,填入0即可。
* 這函數回傳結果如下:
| 回傳值 | 描述 |
| ------ | ---- |
| STATUS_SUCCESS | 成功 |
| STATUS_BUFFER_TOO_SMALL | cbKeyObject的值太小 |
| STATUS_INVALID_HANDLE | 提供者之HANDLE無效 |
| STATUS_INVALID_PARAMETER | 參數無效 |
### 03. 加密與解密-BCryptEncrypt & BCryptDecrypt
* 類似於BCryptGenerateSymmetricKey,若沒用到直接給0或NULL即可。以下為十個BCryptEncrypt的參數:
* **hKey** => 用於加密數據的密鑰,從密鑰創建函數之一獲得的,例如: BCryptGenerateSymmetricKey()取得的對稱式金鑰、BCryptGenerateKeyPair()產生的非對稱式金鑰、BCryptImportKey()匯入(import)取得的金鑰。
* **pbInput** => 被加密的明文的地址,資料型態為byte。
* **cbInput** => 被加密的明文pbInput的大小,以byte為單位。
* **pPaddingInfo** => 指向包含填充信息的結構的指針,關於Padding的資訊,此參數僅用於非對稱密鑰和經過身份驗證的加密模式。如果使用經過身份驗證的加密模式,則此參數必須指向BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO結構,如果使用非對稱密鑰,則此參數指向的結構類型由dwFlags參數的值決定。反之,參數必須設置為NULL。
* **pbIV** => 要在加密期間使用的初始化向量的緩衝區的地址,可以通過調用BCryptGetProperty()參數pszProperty為BCRYPT_BLOCK_LENGTH屬性來獲取所需的大小。
* **cbIV** => 初始向量的大小,以byte為單位,也就是pbIV緩衝區的大小,必須是BCryptGetProperty()參數pszProperty為BCRYPT_BLOCK_LENGTH取得的值。
* **pbOutput** => 接收此函數產生的密文的緩衝區地址,cbOutput參數包含該緩衝區的大小。如果此參數為NULL,則BCryptEncrypt函數計算pbInput參數中傳遞的數據的密文所需的大小。在這種情況下,pcbResult參數指向的位置包含此大小,並且函數返回STATUS_SUCCESS。
* **cbOutput** => pbOutput緩衝區的大小,以byte為單位。
* **pcbResult** => 指向ULONG變量的指針,該變量接收存入到pbOutput緩衝區的byte數。如果pbOutput為NULL,則接收密文所需的大小(以byte為單位)。
* **dwFlags** => 一組修改此函數行為的標誌。允許的標誌集取決於hKey參數指定的密鑰類型。如果密鑰是對稱密鑰,則這可以是零或以下值。
| 值 | 意義 |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| BCRYPT_BLOCK_PADDING | 允許加密算法將數據填充到下一個塊大小。如果未指定此標誌,則cbInput參數中指定的明文大小必須是算法塊大小的倍數。可以通過調用BCryptGetProperty()函數獲取密鑰的BCRYPT_BLOCK_LENGTH屬性來獲取塊大小。這將為算法提供塊的大小。 |
若密鑰是非對稱密鑰,則可以是以下值之一。
| 值 | 意義 |
| -------- | -------- |
| BCRYPT_PAD_NONE | 不用任何填充,pPaddingInfo不使用此參數,cbInput參數中指定的明文大小必須是算法塊大小的倍數。 |
| BCRYPT_PAD_OAEP | 使用最佳非對稱加密填充 (OAEP) 方案,所述pPaddingInfo參數是一個指向BCRYPT_OAEP_PADDING_INFO結構。 |
| BCRYPT_PAD_PKCS1 | 數據將填充一個隨機數以四捨五入大小,pPaddingInfo不使用此參數。 |
* 以下為可能出現的返回結果與其意義:
| 返回值 | 描述 |
| -------- | -------- |
| STATUS_SUCCESS | 成功 |
| STATUS_BUFFER_TOO_SMALL | cbOutput參數指定的大小不足以容納密文 |
| STATUS_INVALID_BUFFER_SIZE | 所述cbInput參數不是區塊大小的整數倍 |
| STATUS_INVALID_HANDLE | hKey有問題 |
| STATUS_INVALID_PARAMETER | 參數出現錯誤 |
| STATUS_NOT_SUPPORTED | 這演算法不支持加密 |
* BCryptEncrypt與BCryptDecrypt參數幾乎一模一樣,使用上也一樣,僅效果不同,一作為加密一作為解密,在此不多做贅述。
### 04. 銷毀金鑰-BCryptDestroyKey
* 加密或解密結束時,一定要銷毀金鑰,否則可從記憶體中取出解密金鑰,從而將檔案成功破解。
* 僅有一個參數**hkey**,即輸入要刪除之金鑰即可。
* 回傳結果如下:
| 回傳值 | 意義 |
| ------ | ---- |
| STATUS_SUCCESS | 成功 |
| STATUS_INVALID_HANDLE | hkey有問題 |
### 05. 關閉演算者提供者-BCryptCloseAlgorithmProvider
* 這是使用BCrypt的最後一步,用於關閉提供者,以下輸入之參數:
* hAlgorithm => 關閉算法提供者,算法提供者由BCryptCloseAlgorithmProvider()產生。
* dwFlags => 一組修改此函數行為的標誌,目前沒有為此函數定義標誌。
* 回傳結果如下:
| 回傳值 | 意義 |
| -------- | -------- |
| STATUS_SUCCESS | 成功 |
| STATUS_INVALID_HANDLE | hAlgorithm參數中的HANDLE有問題 |
## 參、RC4加密實作(使用BCrypt)
```clike=
#include <stdio.h>
#include <string.h>
#include <Windows.h>
#include <bcrypt.h>
#include <tchar.h>
#pragma comment(lib, "bcrypt")
#ifndef NT_SUCCESS
#define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0)
#endif
#ifdef STATUS_UNSUCCESSFUL
#define STATUS_UNSUCCESSFUL 0xC0000001
#endif
#define CHECHERR(n, m){if(!NT_SUCCESS(n)){printf("Error: %s\n, m"); exit(1);}}
int rc4_sbox_1(unsigned char sbox[256]) {
for (int i = 0; i < 256; i++) {
sbox[i] = i;
}
return 0;
}
int rc4_sbox_2(unsigned char sbox[256], const unsigned char* password) {
int password_len = strlen((char*)password);
for (int i = 0, j = 0; i < 256; i++) {
j = (j + sbox[i] + password[i % password_len]) % 256;
int t = sbox[i];
sbox[i] = sbox[j];
sbox[j] = t;
}
return 0;
}
int rc4_encrypt(unsigned char sbox[256], unsigned char* data, int size, unsigned char* output) {
for (int i = 0, j = 0, k, p = 0; p < size; p++) {
i = (i + 1) % 256;
j = (j + sbox[i]) % 256;
int t = sbox[i];
sbox[i] = sbox[j];
sbox[i] = sbox[j];
sbox[j] = t;
k = (sbox[i] + sbox[j]) % 256;
output[p] = data[p] ^ sbox[k];
}
return 0;
}
ULONG hexdump(PUCHAR data, ULONG size) {
ULONG nResult = 0;
for (ULONG i = 0; i < size; i += 16) {
nResult += printf("%08X |", i);
for (ULONG j = 0; j < 16; j++) {
if (i + j < size) {
nResult = printf(" %02X", data[i + j]);
}
else {
nResult += printf(" ");
}
if ((i + 1) % 8 == 0) {
nResult += printf(" ");
}
}
nResult += printf("|");
for (ULONG j = 0; j < 16; j++) {
if (i + j < size) {
UCHAR k = data[i + j];
UCHAR c = k < 32 || k>127 ? '.' : k;
nResult += printf("%c", c);
}
else {
nResult += printf(" ");
}
}
nResult += printf("\n");
}
return nResult;
}
int main(void) {
unsigned char sbox[256];
unsigned char abText[1024] = "this is a test";
ULONG len = strlen((char*)abText) + 1;
unsigned char abCipher[1024];
unsigned char* pnPlain;
ULONG cbPlain;
printf("Text: \n");
hexdump(abText, len);
printf("RC4 Encrypted:\n");
rc4_sbox_1(sbox);
rc4_sbox_2(sbox, (unsigned char*)"password");
rc4_encrypt(sbox, abText, len, abCipher);
hexdump(abCipher, len);
printf("decrypt by BCrypt: \n");
rc4_sbox_1(sbox);
rc4_sbox_2(sbox, (unsigned char*)"password");
rc4_encrypt(sbox, abCipher, len, abText);
hexdump(abText, len);
NTSTATUS status;
BCRYPT_HANDLE hProv;
BCRYPT_KEY_HANDLE hkey;
status = BCryptOpenAlgorithmProvider(
&hProv,
BCRYPT_RC4_ALGORITHM,
NULL, 0);
}
```
* 19~24行 S-Box初始化第一階段(將S-Box裡所有值初始化,將長度256的字元陣列填上0x00~0xFF)
* 26~34行 S-Box初始化第二階段(初始化和密碼做運算,期間每次會有兩個位置裡的值交換,增加混亂程度)
* 37~48行 正式加密(準備好S-Box後才可加密,再加密過程中,S-Box的內容不停地在改變,現今破解方法是利用統計來猜測的)(加密的部分是透過特定計算,取出S-Box中某位置的值做XOR運算,同時還會將S-Box裡兩個位置的值交換,加密過程仍在改變S-Box的內容,增加破解難度)
* 82~91行 為整個加密的流程
* 92~96行 為解密的流程
* 97~107行 為加密與解密完後摧毀金鑰與關閉演算法提供者