应以二进制模式读取含PEM头尾的完整文件内容,用BIO_new_mem_buf传入原始PEM字节,再调用PEM_read_bio_PUBKEY解析;需校验EVP_PKEY_id及密钥用途,避免误用签名接口或弱曲线。

读取二进制文件里的 PEM 公钥,先别急着 fopen + fread
PEM 格式本质是 Base64 编码的 ASN.1 DER 数据,外加固定头尾(如 -----BEGIN PUBLIC KEY-----)。直接按二进制读取整块内容没问题,但后续解析必须识别并剥离这些 ASCII 边界——否则 d2i_PUBKEY_bio 或 PEM_read_bio_PUBKEY 会失败,报错类似 error:0906D06C:PEM routines:PEM_read_bio:no start line。
- 用
fopen(..., "rb")打开,确保不触发文本模式换行转换 - 读取全部字节到内存 buffer(注意末尾补
\0,因为 OpenSSL 的 BIO 接口常依赖 C 字符串终止符) - 别跳过首尾的 PEM 头尾行:它们不是“干扰”,而是解析器的定位标记
- 如果文件里混了多个 PEM 块(比如私钥+公钥),得手动按
-----BEGIN/-----END切分,不能一股脑喂给解析函数
OpenSSL 1.1.1+ 怎么安全加载 PEM 公钥到 EVP_PKEY*
推荐走 BIO 流程,比直接操作内存 buffer 更鲁棒。关键点在于:BIO 必须是内存型(BIO_s_mem()),且写入的是原始 PEM 字节(含头尾),不是 Base64 解码后的 DER。
- 调用
BIO_new_mem_buf(data, len),其中data指向完整 PEM 字符串(含\n),len是字节数(不含额外\0) - 用
PEM_read_bio_PUBKEY(bio, &pkey, nullptr, nullptr)解析;返回nullptr时检查ERR_get_error(),常见原因是 PEM 格式不标准或密钥类型不支持(如 Ed25519 在旧 OpenSSL 中不可用) - 不要用
d2i_PUBKEY_bio:它只吃 DER,喂 PEM 会静默失败 - 解析成功后务必检查
EVP_PKEY_id(pkey),确认是预期类型(EVP_PKEY_RSA、EVP_PKEY_EC等),避免后续签名/验签出错
为什么 std::ifstream 读出来总少几字节或乱码
问题几乎都出在没处理换行符一致性或误用了文本模式。Windows 下 \r\n 被 std::ifstream 默认转换成单个 \n,导致 PEM 头尾行对不上;Linux/macOS 若文件带 Windows 换行,也可能破坏 Base64 行长对齐(虽然不影响解码,但部分严格解析器会拒收)。
- 构造
std::ifstream时显式指定std::ios::binary模式 - 用
std::vector,别用buf(size); ifs.read(buf.data(), size); std::string+std::getline拼接——会丢\r且难以控制长度 - 读完后用
buf.size()当真实长度传给 BIO,不要用strlen(buf.data())—— PEM 里可能有嵌入的\0(极少见,但 DER 里真有) - 调试时用十六进制打印前 64 字节,确认开头确实是
2d 2d 2d 2d 2d 42 45 47 49 4e...(即-----BEGIN)
加密公钥 PEM 里可能藏非标准字段,怎么判断是否能用
所谓“加密公钥”通常指用于密钥封装(KEK)或混合加密的公钥(如 RSA-OAEP、ECIES),但 PEM 本身不携带算法标识。OpenSSL 解析出 EVP_PKEY* 后,必须结合业务场景验证其用途和参数。
- 调用
EVP_PKEY_can_sign(pkey)返回0是正常的——加密公钥本就不该用来签名 - 若需 RSA 加密,检查
RSA* rsa = EVP_PKEY_get1_RSA(pkey)是否非空,并用RSA_check_key(rsa)验证结构有效性(仅对 RSA) - EC 公钥要确认曲线名称:
EC_KEY* ec = EVP_PKEY_get1_EC_KEY(pkey)→EC_GROUP_get_curve_name(EC_KEY_get0_group(ec)),避免用 secp192r1 这类弱曲线 - 最易忽略的一点:某些硬件模块导出的 PEM 可能含自定义 OID 或 PKCS#8 封装,此时得用
PEM_read_bio_PrivateKey(即使你只想要公钥)——因为私钥 PEM 里也包含公钥信息,且兼容性更好
事情说清了就结束。PEM 解析的坑不在读文件,而在“以为读到了就等于能用”——头尾、换行、BIO 生命周期、密钥用途校验,漏一个都会让程序在某个环境里突然哑火。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











