CNG加密学习

这里可以查看微软的官方文档https://learn.microsoft.com/zh-cn/windows/win32/seccng/cng-cryptographic-primitive-functions

1.概述

1.微软新一代的加密API

2.CNG 支持采用内核模式加密:

在内核和用户模式下使用相同的 API,以完全支持加密功能

3.CNG支持扩展到所有所需算法的B套件:

AES (所有密钥大小)

SHA-2 系列 (SHA-256、SHA-384 和 SHA-512) 哈希算法

ECDH 和椭圆曲线 DSA (ECDSA) NIST 标准黄金曲线 P-256

P-384 和 P-521

Microsoft 算法 Windows提供程序不支持二进制曲线、Koblitz 曲线、自定义黄金曲线和椭圆曲线 Menezes-Qu-Vanstone (ECMQV)

2.关键特性

1.分为BCrypto系列和NCrypto系列

1.内容

BCrypto系列是加密算法的核心:

哈希、随机数、对称加密、非对称加密、(对称和非对称)数字签名认证,密钥交换协议等算法支持

NCrypto系列仅包含与非对称有关的部分:

非对称加密,(非对称)数据签名认证,密钥交换协议等

2.内存机制

BCrypto系列所有算法和密钥都是在内存中完成的

NCrypto系列则会保存和优先读取保存在系统中的密钥(私钥)

3.存储位置

密钥类型目录
用户专用%APPDATA%\Microsoft\Crypto\Keys
本地系统专用%ALLUSERSPROFILE%\Application Data\Microsoft\Crypto\SystemKeys
本地服务专用%WINDIR%\ServiceProfiles\LocalService
网络服务专用%WINDIR%\ServiceProfiles\NetworkService
共享专用%ALLUSERSPROFILE%\Application Data\Microsoft\Crypto\Keys

3.实现AES CBC模式

1.CBC 模式是需要使用 IV (初始化管理)

2.相比于 ECB 模式:

CBC 模式对每个明文包都要与前一个密文包进行异或

3.IV作用:

两个明文包的值,经过`多字节异或,对应的两个密文包的值也不一定是正确的

IV的使用就是为了解决第一个明文包跟谁或异的问题

4.初始化保护(初始化向量):

当加密第一个明文包时,由于不存在“前一个密文包”,因此需要事先准备一个长度为一个包的比特序列来代替“前一个密文包”,通常缩写为 IV

一般来说,每次加密时都会随机产生一个不同的比特序列来作为初始化向量

IV 的长度与数据包的大小是一致的(AES 的标准数据包为 128 Bits,即为 16 字节)

5.AES有三个输入和一个输出:

1.输入1:IV

IV不得大于16字节,如果用户输入大于则截断,小于则填充0。

2.输入2:Key

用于加密的密钥。标准AES为16字节。如果用户输入大于16字节则中断,小于则填充0。

3.输入3:明文

4.输出1:密文

4.完整代码

// Sample program for AES-CBC encryption using CNG


#include <windows.h>
#include <stdio.h>
#pragma comment(lib, "bcrypt.lib")


#define NT_SUCCESS(Status)         (((NTSTATUS)(Status)) >= 0)

#define STATUS_UNSUCCESSFUL         ((NTSTATUS)0xC0000001L)


#define DATA_TO_ENCRYPT "Test Data"


const BYTE rgbPlaintext[] =
{
   0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
   0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
};

static const BYTE rgbIV[] =
{
   0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
   0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x18
};

static const BYTE rgbAES128Key[] =
{
   0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
   0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
};

void PrintBytes(
   IN BYTE* pbPrintData,
   IN DWORD    cbDataLen)
{
   DWORD dwCount = 0;

   for (dwCount = 0; dwCount < cbDataLen;dwCount++)
  {
       printf("0x%02x, ", pbPrintData[dwCount]);

       if (0 == (dwCount + 1) % 10) putchar('\n');
  }

}

void __cdecl wmain(
   int                      argc,
   __in_ecount(argc) LPWSTR* wargv)
{

   BCRYPT_ALG_HANDLE       hAesAlg = NULL;
   BCRYPT_KEY_HANDLE       hKey = NULL;
   NTSTATUS                status = STATUS_UNSUCCESSFUL;
   DWORD                   cbCipherText = 0,
       cbPlainText = 0,
       cbData = 0,
       cbKeyObject = 0,
       cbBlockLen = 0,
       cbBlob = 0;
   PBYTE                   pbCipherText = NULL,
       pbPlainText = NULL,
       pbKeyObject = NULL,
       pbIV = NULL,
       pbBlob = NULL;

   UNREFERENCED_PARAMETER(argc);
   UNREFERENCED_PARAMETER(wargv);


   // Open an algorithm handle.
   if (!NT_SUCCESS(status = BCryptOpenAlgorithmProvider(
       &hAesAlg,
       BCRYPT_AES_ALGORITHM,
       NULL,
       0)))
  {
       wprintf(L"**** Error 0x%x returned by BCryptOpenAlgorithmProvider\n", status);
       goto Cleanup;
  }

   // Calculate the size of the buffer to hold the KeyObject.
   if (!NT_SUCCESS(status = BCryptGetProperty(
       hAesAlg,
       BCRYPT_OBJECT_LENGTH,
      (PBYTE)&cbKeyObject,
       sizeof(DWORD),
       &cbData,
       0)))
  {
       wprintf(L"**** Error 0x%x returned by BCryptGetProperty\n", status);
       goto Cleanup;
  }

   // Allocate the key object on the heap.
   pbKeyObject = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbKeyObject);
   if (NULL == pbKeyObject)
  {
       wprintf(L"**** memory allocation failed\n");
       goto Cleanup;
  }

   // Calculate the block length for the IV.
   if (!NT_SUCCESS(status = BCryptGetProperty(
       hAesAlg,
       BCRYPT_BLOCK_LENGTH,
      (PBYTE)&cbBlockLen,
       sizeof(DWORD),
       &cbData,
       0)))
  {
       wprintf(L"**** Error 0x%x returned by BCryptGetProperty\n", status);
       goto Cleanup;
       
  }
   //printf("%d\n", cbBlockLen);

   // Determine whether the cbBlockLen is not longer than the IV length.
   if (cbBlockLen > sizeof(rgbIV))
  {
       wprintf(L"**** block length is longer than the provided IV length\n");
       goto Cleanup;
  }

   // Allocate a buffer for the IV. The buffer is consumed during the
   // encrypt/decrypt process.
   pbIV = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbBlockLen);
   if (NULL == pbIV)
  {
       wprintf(L"**** memory allocation failed\n");
       goto Cleanup;
  }

   memcpy(pbIV, rgbIV, cbBlockLen);

   if (!NT_SUCCESS(status = BCryptSetProperty(
       hAesAlg,
       BCRYPT_CHAINING_MODE,
      (PBYTE)BCRYPT_CHAIN_MODE_CBC,
       sizeof(BCRYPT_CHAIN_MODE_CBC),
       0)))
  {
       wprintf(L"**** Error 0x%x returned by BCryptSetProperty\n", status);
       goto Cleanup;
  }



   // Generate the key from supplied input key bytes.
   if (!NT_SUCCESS(status = BCryptGenerateSymmetricKey(
       hAesAlg,
       &hKey,
       pbKeyObject,
       cbKeyObject,
      (PBYTE)rgbAES128Key,
       sizeof(rgbAES128Key),
       0)))
  {
       wprintf(L"**** Error 0x%x returned by BCryptGenerateSymmetricKey\n", status);
       goto Cleanup;
  }


   // Save another copy of the key for later.
   if (!NT_SUCCESS(status = BCryptExportKey(
       hKey,
       NULL,
       BCRYPT_OPAQUE_KEY_BLOB,
       NULL,
       0,
       &cbBlob,
       0)))
  {
       wprintf(L"**** Error 0x%x returned by BCryptExportKey\n", status);
       goto Cleanup;
  }

   
   // Allocate the buffer to hold the BLOB.
   pbBlob = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbBlob);
   if (NULL == pbBlob)
  {
       wprintf(L"**** memory allocation failed\n");
       goto Cleanup;
  }

   if (!NT_SUCCESS(status = BCryptExportKey(
       hKey,
       NULL,
       BCRYPT_OPAQUE_KEY_BLOB,
       pbBlob,
       cbBlob,
       &cbBlob,
       0)))
  {
       
       
       wprintf(L"**** Error 0x%x returned by BCryptExportKey\n", status);
       goto Cleanup;
  }
   //PrintBytes(pbBlob,cbBlob);



   cbPlainText = sizeof(rgbPlaintext);
   pbPlainText = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbPlainText);
   if (NULL == pbPlainText)
  {
       wprintf(L"**** memory allocation failed\n");
       goto Cleanup;
  }

   memcpy(pbPlainText, rgbPlaintext, sizeof(rgbPlaintext));

   //
   // Get the output buffer size.
   //
   if (!NT_SUCCESS(status = BCryptEncrypt(
       hKey,
       pbPlainText,
       cbPlainText,
       NULL,
       pbIV,
       cbBlockLen,
       NULL,
       0,
       &cbCipherText,
       BCRYPT_BLOCK_PADDING)))
  {
       wprintf(L"**** Error 0x%x returned by BCryptEncrypt\n", status);
       goto Cleanup;
  }

   pbCipherText = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbCipherText);
   if (NULL == pbCipherText)
  {
       wprintf(L"**** memory allocation failed\n");
       goto Cleanup;
  }

   // Use the key to encrypt the plaintext buffer.
   // For block sized messages, block padding will add an extra block.
   if (!NT_SUCCESS(status = BCryptEncrypt(
       hKey,
       pbPlainText,
       cbPlainText,
       NULL,
       pbIV,
       cbBlockLen,
       pbCipherText,
       cbCipherText,
       &cbData,
       BCRYPT_BLOCK_PADDING)))
  {
       wprintf(L"**** Error 0x%x returned by BCryptEncrypt\n", status);
       goto Cleanup;
  }

   // Destroy the key and reimport from saved BLOB.
   if (!NT_SUCCESS(status = BCryptDestroyKey(hKey)))
  {
       wprintf(L"**** Error 0x%x returned by BCryptDestroyKey\n", status);
       goto Cleanup;
  }
   hKey = 0;

   if (pbPlainText)
  {
       HeapFree(GetProcessHeap(), 0, pbPlainText);
  }

   pbPlainText = NULL;

   // We can reuse the key object.
   memset(pbKeyObject, 0, cbKeyObject);


   // Reinitialize the IV because encryption would have modified it.
   memcpy(pbIV, rgbIV, cbBlockLen);


   if (!NT_SUCCESS(status = BCryptImportKey(
       hAesAlg,
       NULL,
       BCRYPT_OPAQUE_KEY_BLOB,
       &hKey,
       pbKeyObject,
       cbKeyObject,
       pbBlob,
       cbBlob,
       0)))
  {
       wprintf(L"**** Error 0x%x returned by BCryptGenerateSymmetricKey\n", status);
       goto Cleanup;
  }


   //
   // Get the output buffer size.
   //
   if (!NT_SUCCESS(status = BCryptDecrypt(
       hKey,
       pbCipherText,
       cbCipherText,
       NULL,
       pbIV,
       cbBlockLen,
       NULL,
       0,
       &cbPlainText,
       BCRYPT_BLOCK_PADDING)))
  {
       wprintf(L"**** Error 0x%x returned by BCryptDecrypt\n", status);
       goto Cleanup;
  }

   pbPlainText = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbPlainText);
   if (NULL == pbPlainText)
  {
       wprintf(L"**** memory allocation failed\n");
       goto Cleanup;
  }

   if (!NT_SUCCESS(status = BCryptDecrypt(
       hKey,
       pbCipherText,
       cbCipherText,
       NULL,
       pbIV,
       cbBlockLen,
       pbPlainText,
       cbPlainText,
       &cbPlainText,
       BCRYPT_BLOCK_PADDING)))
  {
       wprintf(L"**** Error 0x%x returned by BCryptDecrypt\n", status);
       goto Cleanup;
  }


   if (0 != memcmp(pbPlainText, (PBYTE)rgbPlaintext, sizeof(rgbPlaintext)))
  {
       wprintf(L"Expected decrypted text comparison failed.\n");
       goto Cleanup;
  }

   wprintf(L"Success!\n");


Cleanup:

   if (hAesAlg)
  {
       BCryptCloseAlgorithmProvider(hAesAlg, 0);
  }

   if (hKey)
  {
       BCryptDestroyKey(hKey);
  }

   if (pbCipherText)
  {
       HeapFree(GetProcessHeap(), 0, pbCipherText);
  }

   if (pbPlainText)
  {
       HeapFree(GetProcessHeap(), 0, pbPlainText);
  }

   if (pbKeyObject)
  {
       HeapFree(GetProcessHeap(), 0, pbKeyObject);
  }

   if (pbIV)
  {
       HeapFree(GetProcessHeap(), 0, pbIV);
  }
}

4.代码分析

1.打开一个 AES 算法句柄

BCRYPT_ALG_HANDLE       hAesAlg = NULL;
BCryptOpenAlgorithmProvider(
       &hAesAlg,
       BCRYPT_AES_ALGORITHM,
       NULL,
       0)

2.指定CBC模式

BCryptSetProperty(
       hAesAlg,
       BCRYPT_CHAINING_MODE,
      (PBYTE)BCRYPT_CHAIN_MODE_CBC,
       sizeof(BCRYPT_CHAIN_MODE_CBC),
       0)

3.通过key获取hkey

所谓生成Key,就是使用用户输入的按键,经过处理返回一个hkey句柄

句柄的设计思路是提醒用户在使用完之后销毁

BCryptGenerateSymmetricKey(
       hAesAlg,
       &hKey,
       pbKeyObject,
       cbKeyObject,
      (PBYTE)rgbAES128Key,
       sizeof(rgbAES128Key),
       0)

4.通过BCryptGenerateSymmetricKey API 是从用户输入的密钥返回 hKey

NTSTATUS BCryptGenerateSymmetricKey(
 BCRYPT_ALG_HANDLE hAlgorithm,
 BCRYPT_KEY_HANDLE *phKey,
 PUCHAR            pbKeyObject,
 ULONG             cbKeyObject,
 PUCHAR            pbSecret,
 ULONG             cbSecret,
 ULONG             dwFlags
);

BCryptGenerateSymmetricKey 函数是Windows平台的加密服务提供者CNG(Cryptography API: Next Generation)中的一个API函数,用于生成对称密钥。下面是参数的含义:

  1. BCRYPT_ALG_HANDLE hAlgorithm: 加密算法的句柄。这个句柄是通过先前调用BCryptOpenAlgorithmProvider函数获得的,它指定了加密算法的类型(例如 AES、3DES等)。
  2. BCRYPT_KEY_HANDLE *phKey: 这是一个指针变量,函数执行成功后会将生成的密钥的句柄写入到该指针指向的内存。通过这个句柄,可以使用密钥进行加密、解密操作。
  3. PUCHAR pbKeyObject: 这是一个指针变量,指向一个缓冲区,该缓冲区将存储密钥对象的内部表示。密钥对象的实际结构对调用者来说是不可见的,这就是为什么你需要提供一个足够大小的缓冲区来存储它。
  4. ULONG cbKeyObject: 正如您指出的,这是密钥对象的大小,单位是字节。这个大小应该足以容纳由hAlgorithm参数指定的算法生成的密钥对象。通常,这个大小是通过调用BCryptGetProperty函数来获取的,在调用BCryptGenerateSymmetricKey之前要先获取算法块大小(BCRYPT_OBJECT_LENGTH 属性)。
  5. PUCHAR pbSecret: 这是一个指针变量,指向一个缓冲区,该缓冲区包含用来生成密钥的原始材料——即密钥材料。对于对称加密,这将是密钥的字节值。
  6. ULONG cbSecret: 这是密钥材料的大小,单位是字节。这个大小应该和hAlgorithm指定的算法要求的密钥大小一致。
  7. ULONG dwFlags: 这是用来修改函数行为的标志。这可能会包括密钥用途标志、标志代表函数是否使用用户提供的密钥材料等。

在使用BCryptGenerateSymmetricKey函数时,开发者需要确保pbKeyObject指向的缓冲区大小(cbKeyObject)足够大,以容纳密钥对象;同时pbSecret应该包含指定的密钥材料,且其长度(cbSecret)应该符合指定算法的要求。成功调用该函数后,phKey将指向一个有效的密钥句柄,可用于之后的加密或解密操作

5.导出密钥BLOB

1.作用:

导出密钥 BLOB 的作用是当加密完成之后我们就会通过BCryptDestroyKey(hKey)来销毁 hkey

2.逆向使用:

当解密时,如果再想获取key,需要从保存的key BLOB(或者保存的pbBlob数据库)中通过BCryptImportKey函数获取hKey

BCryptExportKey(
      hKey,
      NULL,
      BCRYPT_OPAQUE_KEY_BLOB,
      NULL,
      0,
      &cbBlob,
      0)
       
// Allocate the buffer to hold the BLOB.
pbBlob = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbBlob);

BCryptExportKey(
  hKey,
  NULL,
  BCRYPT_OPAQUE_KEY_BLOB,
  pbBlob,
  cbBlob,
  &cbBlob,
  0)

BCryptGenerateSymmetricKeyBCryptImportKey这两个函数均可以用于创建或导入快捷工具。

导出密钥的 BLOB 的时候使用了两遍 BCryptExportKey API

1.第一遍是计算输出缓存的大小

2.第二遍是存入导出的密钥 BLOB(BLOB 是包含一个或多个固定长度报头结构以及上下文特定数据的)通用位序列)

6.获取存储密文的存储空间的大小

BCryptEncrypt(
      hKey,
      pbPlainText,
      cbPlainText,
      NULL,
      pbIV,
      cbBlockLen,
      NULL,
      0,
      &cbCipherText,
      BCRYPT_BLOCK_PADDING)

参数pcbResult指向 ULONG 指针的指针:

该指针接收复制到 pbOutput 亮度的字节数

如果 pbOutput(倒数第四个参数)为 NULL,将会接收密文所需的大小(以字节为单位)

7.使用生成的密钥进行 AES 加密

BCryptEncrypt(
      hKey,
      pbPlainText,
      cbPlainText,
      NULL,
      pbIV,
      cbBlockLen,
      pbCipherText,
      cbCipherText,
      &cbData,
      BCRYPT_BLOCK_PADDING)

密文会被存入pbCipherText指向的地址

通过指定BCRYPT_BLOCK_PADDING,默认使用 PKCS7Padding 填充模式(三位参数命名为 0,则为 No Padding)

参数pbIV,需要在加密期间使用 IV 的蜡烛图地址进行确定

参数cbIV确定的是大小。

可以通过调用 BCryptGetProperty 函数来BCRYPT_BLOCK_LENGTH获取 IV 的所需大小(正确的情况下应该是默认的 16 字节)

//计算 IV 所需的大小
BCryptGetProperty(
hAesAlg,
BCRYPT_BLOCK_LENGTH,
(PBYTE)&cbBlockLen,
sizeof(DWORD),
&cbData,
0)
pbIV = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbBlockLen);
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇