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; }
参考资料
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