使用CryptDecodeObjectEx解析RSA公钥时的长度与内容异常问题
RSA公钥解码问题解决
问题根源
你调用CryptDecodeObjectEx时使用了RSA_CSP_PUBLICKEYBLOB作为解码类型,这会将公钥数据解码为Windows CSP专用的PUBLICKEYBLOB结构,而非证书管理器显示的原始RSA公钥(仅包含模数n和指数e)。该结构包含6字节额外头部元数据,这就是解码结果比证书管理器公钥长6字节的原因,同时结构内的额外信息也导致内容不匹配。
修正方案
1. 解码为原始RSA公钥结构
将解码类型替换为szOID_RSA_RSA,直接解码出包含模数和指数的RSA公钥结构,再提取对应数据转换成十六进制字符串。
2. 修复ByteToStr函数的长度检查错误
原函数中用strlen(sz) < cb检查缓冲区长度是错误的——malloc分配的内存未初始化,strlen会读取到随机位置的终止符,导致判断失效。改为传入缓冲区的实际大小进行检查。
修正后代码示例
// 修复后的ByteToStr函数 BOOL ByteToStr(DWORD cb, void* pv, LPSTR sz, DWORD szBufferSize, int* size_sz, BOOL addSpaces, BOOL upperCaseHexDigits) { BYTE* pb = (BYTE*)pv; DWORD i; int b; int outCharCount = 0; // 检查缓冲区是否足够:每个字节占2个十六进制字符,若加空格则多1个,最后加终止符 DWORD requiredSize = cb * (addSpaces ? 3 : 2) + 1; if (szBufferSize < requiredSize) { MyHandleError(L"缓冲区长度不足,无法容纳转换后的字符串"); return FALSE; } for (i = 0; i < cb; i++) { // 处理高4位 b = (*pb & 0xF0) >> 4; *sz++ = (b <= 9) ? (b + '0') : ((upperCaseHexDigits) ? (b - 10 + 'A') : (b - 10 + 'a')); outCharCount++; // 处理低4位 b = *pb & 0x0F; *sz++ = (b <= 9) ? (b + '0') : ((upperCaseHexDigits) ? (b - 10 + 'A') : (b - 10 + 'a')); outCharCount++; pb++; if (addSpaces && i != cb - 1) { // 最后一个字节后不加空格 *sz++ = ' '; outCharCount++; } } *sz++ = 0; outCharCount++; *size_sz = outCharCount; return TRUE; } // 解码RSA公钥的核心代码 publicKeyInfo = pCertContext->pCertInfo->SubjectPublicKeyInfo; RSAPUBKEY* pRsaPubKey = NULL; DWORD cbRsaPubKey = 0; // 解码为RSA公钥结构 if (!CryptDecodeObjectEx( X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, szOID_RSA_RSA, publicKeyInfo.PublicKey.pbData, publicKeyInfo.PublicKey.cbData, CRYPT_ENCODE_ALLOC_FLAG, NULL, (void**)&pRsaPubKey, &cbRsaPubKey)) { MyHandleError(L"解码RSA公钥失败"); return; } // 提取模数:紧跟在RSAPUBKEY结构之后,长度为密钥位数/8 DWORD cbModulus = pRsaPubKey->bitlen / 8; BYTE* pModulus = (BYTE*)pRsaPubKey + sizeof(RSAPUBKEY); // 提取指数:转换为大端字节序(去除前导0) BYTE expBytes[4]; expBytes[3] = (pRsaPubKey->pubexp >> 0) & 0xFF; expBytes[2] = (pRsaPubKey->pubexp >> 8) & 0xFF; expBytes[1] = (pRsaPubKey->pubexp >> 16) & 0xFF; expBytes[0] = (pRsaPubKey->pubexp >> 24) & 0xFF; DWORD cbExp = 4; // 跳过前导0 while (cbExp > 0 && expBytes[4 - cbExp] == 0) { cbExp--; } BYTE* pExp = expBytes + (4 - cbExp); // 合并模数和指数,准备转换为十六进制字符串 DWORD totalLen = cbModulus + cbExp; BYTE* combinedKey = (BYTE*)malloc(totalLen); memcpy(combinedKey, pModulus, cbModulus); memcpy(combinedKey + cbModulus, pExp, cbExp); // 转换为空格分隔的十六进制字符串 DWORD strBufferSize = totalLen * 3 + 1; LPSTR sz = (char*)malloc(strBufferSize); int size_sz = 0; BOOL success = ByteToStr(totalLen, combinedKey, sz, strBufferSize, &size_sz, TRUE, FALSE); // 此时sz就是与证书管理器一致的公钥十六进制字符串 // 这里可以添加输出或使用sz的逻辑 // 释放内存 free(sz); free(combinedKey); LocalFree((HANDLE)pRsaPubKey);
说明
- 解码后的
RSAPUBKEY结构包含公钥的位数和指数,模数数据紧跟在结构之后。 - 指数存储为DWORD类型,需要转换为大端字节序并去除前导0,才能匹配证书管理器显示的短格式指数。
- 修复后的ByteToStr函数会正确检查缓冲区长度,避免内存越界,同时最后一个字节后不再添加多余空格。
内容的提问来源于stack exchange,提问作者John Rainey
相关产品推荐
相关产品推荐

