Windows平台RSA加解密

RSA加密算法是当前常用的非对称加密算法;windows平台有RSA相关API,不需要借助openssl等三方库即可在C/C++代码进行RSA密钥生成及加解密处理。windows在Vista版本后提供了全新加解密API,本文结合实现代码介绍这些API的使用方法。

生成密钥

#include “windows.h”
#include “bcrypt.h”
#pragma comment(lib, "Bcrypt.lib")

void generate_rsakey() {
  BCRYPT_ALG_HANDLE alg;
  BCRYPT_KEY_HANDLE  keyHandle;
  BCryptOpenAlgorithmProvider(&alg, BCRYPT_RSA_ALGORITHM, NULL, 0);

  BCryptGenerateKeyPair(alg, &keyHandle, 1024, 0);
  BCryptFinalizeKeyPair(keyHandle, 0); /* 生成密钥后一定要调用此函数,否则无法导出 */

  UCHAR output[1024];
  ULONG len;
  BCryptExportKey(keyHandle, NULL, BCRYPT_RSAPUBLIC_BLOB, output, sizeof(output), &len, 0);  /* 如果要导出公钥,则使用BCRYPT_RSAPRIVATE_BLOB类型导出 */

  /* 导出后可以存储到文件... */

  /* 释放资源 */
  BCryptDestroyKey(keyHandle);
  BCryptCloseAlgorithmProvider(alg, 0);
}

密钥格式

PKCS8及ASN.1编码

网上很多资料都写PKCS8是用于私钥格式,但实际上公钥同样可以使用PKCS8;openssl工具生成的公钥就是这种格式,而且一般是base64化后的文本形式,其原始二进制(即DER)是ASN.1编码格式。如果需要了解ASN.1及PKCS细节,可以进一步查看参考资料。

简而言之,密钥可以认为“密钥头部 + 密钥 + 密钥指数“三部分组成:相同长度的密钥其头部封装是相同的,指数由算法确定

BCryptExportKey函数输出信息

BCryptExportKey函数的输出信息(上文中output),公钥由三部分组成

BCRYPT_RSAKEY_BLOB
PublicExponent[cbPublicExp] // Big-endian.
Modulus[cbModulus] // Big-endian.

BCRYPT_RSAKEY_BLOB结构格式如下

typedef struct _BCRYPT_RSAKEY_BLOB {
  ULONG Magic;
  ULONG BitLength;
  ULONG cbPublicExp; //密钥指数长度
  ULONG cbModulus;
  ULONG cbPrime1;
  ULONG cbPrime2;
} BCRYPT_RSAKEY_BLOB;

根据上述信息,可以输出需要的最终密钥文件

生成1024长度密钥的完整代码参考

DWORD RsaGenKey() {
  UCHAR pkcs8header[] = { 0x30, 0x81, 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x81, 0x8d, 0x00, 0x30, 0x81, 0x89, 0x02, 0x81, 0x81, 0x00 };

  BCRYPT_ALG_HANDLE alg;
  BCRYPT_KEY_HANDLE  keyHandle;
  BCryptOpenAlgorithmProvider(&alg, BCRYPT_RSA_ALGORITHM, NULL, 0);

  BCryptGenerateKeyPair(alg, &keyHandle, 1024, 0);
  BCryptFinalizeKeyPair(keyHandle, 0);

  UCHAR output[2 * 1024];
  ULONG len;
  char base64[2 * 1024];
  ULONG base64len = sizeof(base64);
  BCryptExportKey(keyHandle, NULL, BCRYPT_RSAPRIVATE_BLOB, output, sizeof(output), &len, 0);

  HANDLE xfile = CreateFileA("rsa", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  WriteFile(xfile, output, len, &len, NULL);
  CloseHandle(xfile);

  BCryptExportKey(keyHandle, NULL, BCRYPT_RSAPUBLIC_BLOB, output + 1000, sizeof(output) - 1000, &len, 0);
   _BCRYPT_RSAKEY_BLOB* info = (_BCRYPT_RSAKEY_BLOB*)&output[1000];
   /* for 1024bit der */
  UCHAR* p = output + 1000 + len;
   //以下三行是指数的ASN.1编码,0x02表示整数,接着长度和值
  *p = 0x02;
  *(p + 1) = info->cbPublicExp;
  memcpy(p + 2, info + 1, info->cbPublicExp);

  p = (UCHAR*)(info + 1) + info->cbPublicExp - sizeof(pkcs8header);
  memcpy(p, pkcs8header, sizeof(pkcs8header));
  CryptBinaryToStringA(p, 0x9F + 3, CRYPT_STRING_BASE64, base64, &base64len); //base64转化为字符串

  xfile = CreateFileA("pkcs8.pub", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  WriteFile(xfile, base64, base64len, &len, NULL);
  CloseHandle(xfile);

  BCryptDestroyKey(keyHandle);
  BCryptCloseAlgorithmProvider(alg, 0);

  return 0;
}

加解密

string RsaDecrypt(const UCHAR* decrypt, int len) {
  BCRYPT_ALG_HANDLE alg;
  BCRYPT_KEY_HANDLE hKey;

  BCryptOpenAlgorithmProvider(&alg, BCRYPT_RSA_ALGORITHM, NULL, 0);

  UCHAR key[1024];
  ULONG xlen;
  /* ...从文件读入密钥到数组key */

  BCryptImportKeyPair(alg, NULL, BCRYPT_RSAPRIVATE_BLOB, &hKey, key, xlen, 0);

  UCHAR plain[1024];
  BCryptDecrypt(hKey, bin, len, NULL, NULL, 0, plain, sizeof(plain), &xlen, BCRYPT_PAD_PKCS1); //填充方式必须和加密端相同,此处是BCRYPT_PAD_PKCS1
  plain[xlen] = 0; // 如果明文是字符串,必须增加字符串结尾0

  /* 释放资源 */
  BCryptDestroyKey(hKey);
  BCryptCloseAlgorithmProvider(alg, 0);

  return (char*)plain;
}

参考资料

微软官方API说明

PKCS1与PKCS8的小知识 - 简书 (jianshu.com)

密码学基础3:密钥文件格式完全解析 - 简书 (jianshu.com)

RFC 8017 - PKCS #1: RSA Cryptography Specifications Version 2.2 (ietf.org)

openssl - How is OID 2a 86 48 86 f7 0d parsed as 1.2.840.113549? - Cryptography Stack Exchange

版权声明:文章版权归作者所有,转载请保留保留本站链接

THE END
分享
二维码
< <上一篇
下一篇>>