PHP中手动将RSA公钥指数与模数转换为PEM格式
手动将RSA模数/指数二进制缓冲区转换为PEM公钥(PHP)
要手动实现这个转换,核心是按照ASN.1 DER编码规则构建RSA公钥的标准结构,再封装成PEM格式。以下是分步实现方案:
核心原理
RSA公钥的PEM格式本质是:Base64编码(DER编码的SubjectPublicKeyInfo结构) + PEM头/尾
SubjectPublicKeyInfo的ASN.1结构定义:
SubjectPublicKeyInfo ::= SEQUENCE { algorithm AlgorithmIdentifier, subjectPublicKey BIT STRING } AlgorithmIdentifier ::= SEQUENCE { algorithm OBJECT IDENTIFIER (1.2.840.113549.1.1.1), -- RSA加密算法OID parameters NULL } RSAPublicKey ::= SEQUENCE { modulus INTEGER, -- 大端序二进制缓冲区转DER INTEGER publicExponent INTEGER -- 大端序二进制缓冲区转DER INTEGER }
分步代码实现
1. 工具函数:将大端序二进制缓冲区转为DER编码的INTEGER
DER编码INTEGER需要注意:
- 如果二进制最高位是
1(即首字节≥0x80),需要在前面加一个0x00前缀,避免被解析为负数 - 去掉前导的
0x00字节(除非整个数是0,但RSA模数/指数不会是0)
function binaryToDerInteger(string $binary): string { // 去掉前导零 $binary = ltrim($binary, "\x00"); // 处理全零的极端情况 if ($binary === '') { return "\x02\x01\x00"; } // 最高位为1时补0x00前缀 if (ord($binary[0]) >= 0x80) { $binary = "\x00" . $binary; } // 构造DER INTEGER:类型标记0x02 + 长度编码 + 内容 $length = strlen($binary); return "\x02" . packLength($length) . $binary; } // 辅助函数:DER编码长度(兼容短格式和长格式) function packLength(int $length): string { if ($length < 0x80) { return chr($length); } $hexLength = dechex($length); if (strlen($hexLength) % 2 !== 0) { $hexLength = '0' . $hexLength; } $lengthBytes = hex2bin($hexLength); return chr(0x80 + strlen($lengthBytes)) . $lengthBytes; }
2. 构建RSAPublicKey的DER序列
将模数和指数的DER INTEGER打包成SEQUENCE:
function buildRsaPublicKeyDer(string $modulusBinary, string $exponentBinary): string { $modulusDer = binaryToDerInteger($modulusBinary); $exponentDer = binaryToDerInteger($exponentBinary); // 构造SEQUENCE:类型标记0x30 + 总长度 + 两个INTEGER $content = $modulusDer . $exponentDer; $length = strlen($content); return "\x30" . packLength($length) . $content; }
3. 构建SubjectPublicKeyInfo的DER序列
将RSAPublicKey打包进BIT STRING,再结合算法标识符构造最终的公钥DER:
function buildSubjectPublicKeyInfoDer(string $rsaPublicKeyDer): string { // 构造AlgorithmIdentifier:RSA OID + NULL参数 $rsaOid = "\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01"; // 1.2.840.113549.1.1.1的DER编码 $nullParam = "\x05\x00"; // NULL类型的DER编码 $algorithmDer = "\x30" . packLength(strlen($rsaOid) + strlen($nullParam)) . $rsaOid . $nullParam; // 构造BIT STRING:类型标记0x03 + 长度 + 0x00(无未使用位)+ RSA公钥内容 $bitStringContent = "\x00" . $rsaPublicKeyDer; $bitStringDer = "\x03" . packLength(strlen($bitStringContent)) . $bitStringContent; // 构造SubjectPublicKeyInfo SEQUENCE $content = $algorithmDer . $bitStringDer; $length = strlen($content); return "\x30" . packLength($length) . $content; }
4. 转换为PEM格式
将DER编码base64编码,分割为每行64字符,加上PEM头/尾:
function derToPem(string $der): string { $base64 = base64_encode($der); // 分割成每行64个字符 $lines = str_split($base64, 64); $pem = "-----BEGIN PUBLIC KEY-----\n" . implode("\n", $lines) . "\n-----END PUBLIC KEY-----"; return $pem; }
5. 完整调用示例
假设你已经有$modulusBinary(大端序模数二进制)和$exponentBinary(大端序指数二进制):
// 示例:指数为65537(0x010001)的大端二进制 // $exponentBinary = "\x01\x00\x01"; // 模数为从其他渠道获取的大端二进制缓冲区 $rsaPublicKeyDer = buildRsaPublicKeyDer($modulusBinary, $exponentBinary); $spkiDer = buildSubjectPublicKeyInfoDer($rsaPublicKeyDer); $pemPublicKey = derToPem($spkiDer); // 验证可用性:用openssl扩展测试 // if (openssl_pkey_get_public($pemPublicKey)) { // echo "PEM公钥格式合法"; // }
验证方法
可以通过以下方式确认生成的PEM是否正确:
- 使用
openssl_pkey_get_public($pemPublicKey),返回资源则说明格式合法 - 将PEM保存为文件,执行
openssl rsa -pubin -in public.pem -text命令,查看输出的模数和指数是否与原二进制一致
内容的提问来源于stack exchange,提问作者Louis Bernard
相关产品推荐
相关产品推荐

