如何用PHP根据模数和指数生成与JS一致的RSA公钥并加密
问题:PHP生成RSA公钥与Node.js不一致,无法匹配Postman加密逻辑
需求背景
现有Postman前置脚本实现了RSA加密登录逻辑,需用PHP复刻相同流程:先请求获取包含id、模数modulus、指数exponent的session,再将session.id + '**' + 密码加密后完成登录并获取Cookie。
Postman前置脚本
const modulus = session.rsa.M; const exponent = session.rsa.E; const rsaKey = pmlib.rs.KEYUTIL.getKey({ n: modulus, e: exponent }); const encodedPassword = pmlib.rs.KJUR.crypto.Cipher.encrypt(session.id + '**' + password, rsaKey, 'RSA');
Node.js成功实现代码
使用jsrsasign库可正常生成正确公钥:
const KEYUTIL = require('jsrsasign').KEYUTIL; const KJUR = require('jsrsasign'); // 示例模数(十六进制) const modulus = session.rsa.M; // ADA6FB0A9B123950AE71EB0A4A90426156CC7545A3F5617096E7A0C095773B413FCA9DF50C231CA4804991C76BB477D721E363E0C7B61852AAC66134D43E21F3A7433C275C62B05E1CA9C5E750FB13B5DD3E6FBC2415C0EE8D23119CA375CE35307DDF828D40FC15CBA2CA545786B7D236B4A45508775250CC5AC376B29DB129 // 示例指数(十六进制) const exponent = session.rsa.E; // 010001 const rsaKey = KEYUTIL.getKey({ n: modulus, e: exponent }); const pem = KJUR.KEYUTIL.getPEM(rsaKey); console.log(pem);
PHP当前实现(存在问题)
使用相同的模数和指数,PHP生成的公钥与Node.js不一致,导致加密逻辑失效:
use phpseclib3\Crypt\PublicKeyLoader; use phpseclib3\Math\BigInteger; $modulus = $session['rsa']['M']; $exponent = $session['rsa']['E']; $rsa_key = PublicKeyLoader::load([ 'e' => new BigInteger(base64_decode($exponent), 256), 'n' => new BigInteger(base64_decode($modulus), 256), ]); echo '<pre>'; echo ($rsa_key); echo '</pre>'; openssl_public_encrypt(data, $encoded, $rsa_key); $encoded = bin2hex($encoded);
公钥示例对比
JS生成的正确公钥
-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCtpvsKmxI5UK5x6wpKkEJhVsx1 RaP1YXCW56DAlXc7QT/KnfUMIxykgEmRx2u0d9ch42Pgx7YYUqrGYTTUPiHzp0M8 J1xisF4cqcXnUPsTtd0+b7wkFcDujSMRnKN1zjUwfd+CjUD8FcuiylRXhrfSNrSk VQh3UlDMWsN2sp2xKQIDAQAB -----END PUBLIC KEY-----
PHP生成的错误公钥
-----BEGIN PUBLIC KEY----- MIHgMA0GCSqGSIb3DQEBAQUAA4HOADCBygKBwA/RBAPQ/AfeOQOQtuQthAutwONR QtdONd9tvBfNfeNPBOxdRRduOAdAwee9+9QOtdBNtu/dvPAfQ+txfew9xNRevfRA de/e+A+d+d/eu+9OudRfRfAgvPOfPeOAtfQuAd+w9uhfeBNxfddQ9QdwOwOe9g+A QNdNwudt+/dRdhNPQhNQ9BQPdAhNfQewQuwew/egNNgAg++e+NQABPee+wNRe9Ou eguxffRfBOAweQdwhA9edfQQ+BNBetwuROghNQIFANNdNNM= -----END PUBLIC KEY-----
问题原因与修正方案
问题根源
PHP代码错误地对十六进制格式的模数和指数执行了base64_decode,导致解析出的BigInteger完全错误,生成的公钥自然和JS不一致。而JS的KEYUTIL.getKey是直接将十六进制字符串解析为BigInteger。
修正后的PHP代码
use phpseclib3\Crypt\PublicKeyLoader; use phpseclib3\Math\BigInteger; $modulus = $session['rsa']['M']; // 十六进制字符串 $exponent = $session['rsa']['E']; // 十六进制字符串 $password = '你的密码'; // 替换为实际密码 // 直接从十六进制字符串创建BigInteger,指定参数为16(表示输入是十六进制) $rsa_key = PublicKeyLoader::load([ 'e' => new BigInteger($exponent, 16), 'n' => new BigInteger($modulus, 16), ]); // 验证生成的公钥是否与JS一致 echo '<pre>'; echo $rsa_key; echo '</pre>'; // 按照Postman逻辑拼接加密内容 $data = $session['id'] . '**' . $password; // 使用PKCS#1 v1.5填充(与JS默认加密填充一致) $encoded = $rsa_key->encrypt($data, 'pkcs1'); // 转十六进制输出,匹配Postman的加密结果格式 $encodedPassword = bin2hex($encoded);
关键细节
- JS的
KJUR.crypto.Cipher.encrypt默认使用PKCS#1 v1.5填充,PHP中需明确指定'pkcs1',避免使用默认的OAEP填充,否则加密结果不匹配。 - 确保
$session['id']、$modulus、$exponent的取值与Postman/Node.js完全一致,避免字符编码或格式差异。
内容的提问来源于stack exchange,提问作者Elyas Behroozizade
相关产品推荐
相关产品推荐

