如何通过OpenSSL API生成去除前置零的DER格式RSA公钥
解决OpenSSL生成RSA公钥DER格式的前置零问题
你遇到的问题其实是DER编码规则和遗留系统的兼容性问题——OpenSSL严格遵循DER标准,给最高位为1的正整数INTEGER(也就是RSA的模数)添加了前置零,避免被解析成负数,但你的遗留系统不需要这个零。下面是几个不用写完整TLV解析器的可行方案:
方案1:手动修改生成的DER字节(最推荐)
RSA公钥的DER结构非常固定,我们可以直接定位到模数的前置零位置,去掉它并调整对应的长度字段,代码量小且可靠。
示例代码
// 先生成完整的DER格式公钥 unsigned char* der_buf = NULL; int der_len = i2d_RSAPublicKey(rsa.get(), &der_buf); if (der_len > 0 && der_buf[0] == 0x30) { // 外层是SEQUENCE标签 int pos = 1; int outer_seq_len; // 解析外层SEQUENCE的长度(处理短/长格式) if ((der_buf[pos] & 0x80) == 0) { outer_seq_len = der_buf[pos++]; } else { int len_bytes = der_buf[pos++] & 0x7F; outer_seq_len = 0; for (int i = 0; i < len_bytes; i++) { outer_seq_len = (outer_seq_len << 8) | der_buf[pos++]; } } // 定位到模数的INTEGER标签(0x02) if (pos + 2 < der_len && der_buf[pos] == 0x02) { int mod_len_pos = pos + 1; int mod_len; // 解析模数字段的长度 if ((der_buf[mod_len_pos] & 0x80) == 0) { mod_len = der_buf[mod_len_pos]; } else { // 320位模数的长度仅40字节,短格式足够,这里兼容长格式 int len_bytes = der_buf[mod_len_pos] & 0x7F; mod_len = 0; for (int i = 0; i < len_bytes; i++) { mod_len = (mod_len << 8) | der_buf[mod_len_pos + 1 + i]; } mod_len_pos += len_bytes; } int mod_start = mod_len_pos + 1; // 检查是否存在多余的前置零:长度>1,首字节是0x00,且第二个字节最高位为1 if (mod_len > 1 && der_buf[mod_start] == 0x00 && (der_buf[mod_start + 1] & 0x80) != 0) { // 调整模数长度 mod_len -= 1; if ((der_buf[mod_len_pos] & 0x80) == 0) { der_buf[mod_len_pos] = mod_len; } else { // 更新长格式长度字段 int len_bytes = der_buf[mod_len_pos] & 0x7F; for (int i = len_bytes - 1; i >= 0; i--) { der_buf[mod_len_pos + 1 + i] = (mod_len >> (8 * i)) & 0xFF; } } // 调整外层SEQUENCE的总长度 outer_seq_len -= 1; if ((der_buf[1] & 0x80) == 0) { der_buf[1] = outer_seq_len; } else { int len_bytes = der_buf[1] & 0x7F; for (int i = len_bytes - 1; i >= 0; i--) { der_buf[1 + 1 + i] = (outer_seq_len >> (8 * i)) & 0xFF; } } // 移除前置零字节,压缩DER数据 memmove(der_buf + mod_start, der_buf + mod_start + 1, der_len - (mod_start + 1)); der_len -= 1; } } } // 输出处理后的Hex格式公钥 std::cout << Convert::BinToHexString(der_buf, der_len); // 释放OpenSSL分配的内存 OPENSSL_free(der_buf);
方案2:用OpenSSL ASN.1原生API手动构造公钥
如果不想修改DER字节,可以直接用ASN.1的底层API手动组装公钥结构,精确控制每个INTEGER的编码:
示例代码
// 获取RSA的模数和指数 const BIGNUM* n = RSA_get0_n(rsa.get()); const BIGNUM* e = RSA_get0_e(rsa.get()); // 创建外层SEQUENCE ASN1_SEQUENCE* pubkey_seq = ASN1_SEQUENCE_new(); if (!pubkey_seq) { // 错误处理 return; } // 处理模数:获取原始字节(无前置零),手动创建ASN1_INTEGER int n_len = BN_num_bytes(n); unsigned char* n_buf = (unsigned char*)OPENSSL_malloc(n_len); BN_bn2bin(n, n_buf); ASN1_INTEGER* mod_int = ASN1_INTEGER_new(); mod_int->data = n_buf; mod_int->length = n_len; // 直接使用原始字节,不遵循DER的前置零规则 mod_int->type = V_ASN1_INTEGER; // 添加模数到SEQUENCE ASN1_SEQUENCE_add(pubkey_seq, (ASN1_VALUE*)mod_int, ASN1_INTEGER_it()); // 处理指数:直接用OpenSSL的API生成(指数一般是0x10001,最高位为0,不会有前置零) ASN1_INTEGER* exp_int = ASN1_INTEGER_new(); BN_to_ASN1_INTEGER(e, exp_int); ASN1_SEQUENCE_add(pubkey_seq, (ASN1_VALUE*)exp_int, ASN1_INTEGER_it()); // 序列化整个SEQUENCE到DER unsigned char* der_buf = NULL; int der_len = i2d_ASN1_SEQUENCE(pubkey_seq, &der_buf); // 输出结果 std::cout << Convert::BinToHexString(der_buf, der_len); // 释放资源 OPENSSL_free(der_buf); ASN1_SEQUENCE_free(pubkey_seq);
不推荐直接修改RSA的BIGNUM
你提到想通过设置V_ASN1_NEG来避免前置零,但这个思路不可行:
- BIGNUM是OpenSSL用于数值计算的核心结构,标记为负数会导致后续的RSA加密/验证逻辑出错
- OpenSSL的BIGNUM会自动维护数值的正确性,强行修改flags或内部数据可能被自动恢复,无法稳定生效
内容的提问来源于stack exchange,提问作者blech
相关产品推荐
相关产品推荐

