用 Javascript 加密,用 PHP 解密,使用公钥加密
Posted
技术标签:
【中文标题】用 Javascript 加密,用 PHP 解密,使用公钥加密【英文标题】:Encrypt in Javascript, decrypt in PHP, using public-key cryptography 【发布时间】:2012-09-09 13:11:41 【问题描述】:我想在 javascript 中加密,在 php 中解密,使用公钥加密。我一直在努力寻找可以完成此任务的库,但遇到了问题。
我目前正在查看openpgpjs,但我需要所有浏览器的支持,甚至测试页面在唯一列为支持的浏览器(谷歌浏览器)上也有错误。
关于最终目标的说明:
TCP 连接已受 SSL 保护。这层保护的主要目的是防止有意或无意的网络服务器日志记录、故障转储等。
在 PHP 端,会生成一个临时私钥(它会在短时间内过期)。调用者(在 Javascript 中)负责在新的公钥过期时请求它。私钥过期的原因是为了防止记录的加密数据被解密,以防存储私钥的服务器后来被破坏。
服务器受损情况:有人获得了除数据库服务器之外的所有机器的备份(并且由于防火墙而无法访问数据库,即使他找到了用户和密码)。由于加密记录数据的私钥不复存在,攻击者无能为力。
【问题讨论】:
希望您仍然对此感兴趣...您可以使用 PHP 7.2sodium
函数和 NaCL github.com/tonyg/js-nacl 客户端或任何类似的 libsosium javascript 实现。
【参考方案1】:
我的登录页面使用了类似的东西;它使用可以在 PHP 中解密的给定公钥信息 (N, e) 对登录凭据进行加密。
它使用以下属于JSBN
的文件:
jsbn.js
- 处理大整数
rsa.js
- 仅用于 RSA 加密(使用 jsbn.js)
rng.js
- 基本熵收集器
prng4.js
- ARC4 RNG 后端
加密数据:
$pk = '-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----';
$kh = openssl_pkey_get_private($pk);
$details = openssl_pkey_get_details($kh);
function to_hex($data)
return strtoupper(bin2hex($data));
?>
<script>
var rsa = new RSAKey();
rsa.setPublic('<?php echo to_hex($details['rsa']['n']) ?>', '<?php echo to_hex($details['rsa']['e']) ?>');
// encrypt using RSA
var data = rsa.encrypt('hello world');
</script>
这就是你将如何解码发送的数据:
$kh = openssl_pkey_get_private($pk);
$details = openssl_pkey_get_details($kh);
// convert data from hexadecimal notation
$data = pack('H*', $data);
if (openssl_private_decrypt($data, $r, $kh))
echo $r;
【讨论】:
+1 为简单起见。这是一个很好且干净的解决方案。请注意,pidCrypt 使用 jsbn(格式良好且“命名空间”)。 @Tiberiu-IonuțStan 感谢您提到 pidCrypt。我为自己的项目做了命名空间,但似乎我可以节省一些时间:) @jve 虽然有些库可能没有这个要求,但建议保留这些字符串 :) 仅供参考,这默认为 PKCS1v1.5 填充,最近导致CVE in Zend\Crypt。 在第一个代码块中是 openssl_pkey_get_public(因为您将使用公钥加密)而不是 openssl_pkey_get_private【参考方案2】:查看node-rsa。
这是一个 node.js 模块
此模块提供对来自 OpenSSL 的 RSA 公钥例程的访问。 支持仅限于 RSAES-OAEP 和使用公钥加密,使用私钥解密。
也许你可以将它移植到浏览器中运行。
更新
用于 javascript 的 RSA 客户端库:(pidcrypt 已正式停产,网站域已过期 - 请参阅 @jack 的答案,其中包含与 pidcrypt 所含相同的库)。 https://www.pidder.com/pidcrypt/?page=rsa
PHP 服务器端组件: http://phpseclib.sourceforge.net/
祝你好运!
【讨论】:
node.js 是服务器端。服务器端 javascript 模块不能在浏览器端重用,因为它依赖于 OpenSSL。 pidCrypt 仅提供对称加密或散列。我的问题是关于公钥加密。 我发布的链接指向 pidCrypt 不对称 rsa。怎么对称? 抱歉,我没有阅读支持方法列表中的 RSA 部分。现在检查一下。谢谢。 小心使用 RSA,即使使用 PHPSecLib。【参考方案3】:在实施 RSA 时要小心。事实上,您可能根本不应该使用 RSA。 (Use libsodium instead!)
即使您使用的是库(例如直接使用 PHP 的 OpenSSL 扩展,或者直到最近,Zend\Crypt
),仍然有很多地方可能出错。特别是:
最好的办法,使用 Libsodium
在沿着这条路线走之前,您可能需要阅读 JavaScript Cryptography Considered Harmful 几遍。不过话说回来……
-
将 TLSv1.2 与 HSTS 和 HPKP 一起使用,最好使用 ChaCha20-Poly1305 和/或 AES-GCM 以及 ECDSA-P256 证书(重要:当 IETF 命名 Curve25519 和 Ed25519 时,请改用该证书)。
将libsodium.js 添加到您的项目中。
在客户端使用
crypto_box_seal()
和公钥来加密您的消息。
在 PHP 中,使用\Sodium\crypto_box_seal_open()
和公钥对应的密钥来解密消息。
我需要使用 RSA 来解决这个问题。
Please don't。椭圆曲线密码学在没有侧通道的情况下更快、更简单、更容易实现。大多数图书馆已经为您这样做了。 (钠!)
但我真的想使用 RSA!
好的,请关注these recommendations to the letter,当你犯了一个错误(如SaltStack did)导致你的密码学无用时,不要向 *** 哭泣。
一个旨在提供简单易用的 RSA 加密的选项(不附带补充的 JavaScript 实现,请不要要求提供)是paragonie/easyrsa。
它通过使用 RSA-OAEP with MGF1+SHA256 而不是 PKCS1v1.5 来避免填充预言。 巧妙的协议设计避免了ECB模式:EasyRSA 加密协议
-
EasyRSA 为对称密钥加密(通过 AES)生成一个随机 128 位密钥。
您的明文消息使用defuse/php-encryption 加密。
您的 AES 密钥使用 RSA 加密,由 phpseclib 提供,使用正确的模式(如上所述)。
这些信息被打包成一个简单的字符串(带有校验和)。
但是,实际上,如果您发现公钥加密的有效用例,您需要 libsodium。
奖励:用 JavaScript 加密,用 PHP 解密
我们将使用sodium-plus 来实现这一目标。 (取自this post。)
const publicKey = X25519PublicKey.from('fb1a219011c1e0d17699900ef22723e8a2b6e3b52ddbc268d763df4b0c002e73', 'hex');
async function sendEncryptedMessage()
let key = await getExampleKey();
let message = $("#user-input").val();
let encrypted = await sodium.crypto_box_seal(message, publicKey);
$.post("/send-message", "message": encrypted.toString('hex'), function (response)
console.log(response);
$("#output").append("<li><pre>" + response.message + "</pre></li>");
);
然后是全等的 PHP 代码:
<?php
declare(strict_types=1);
require 'vendor/autoload.php'; // Composer
header('Content-Type: application/json');
$keypair = sodium_hex2bin(
'0202040a9fbf98e1e712b0be8f4e46e73e4f72e25edb72e0cdec026b370f4787' .
'fb1a219011c1e0d17699900ef22723e8a2b6e3b52ddbc268d763df4b0c002e73'
);
$encrypted = $_POST['message'] ?? null;
if (!$encrypted)
echo json_encode(
['message' => null, 'error' => 'no message provided'],
JSON_PRETTY_PRINT
);
exit(1);
$plaintext = sodium_crypto_box_seal_open(sodium_hex2bin($encrypted), $keypair);
echo json_encode(
['message' => $plaintext, 'original' => $encrypted],
JSON_PRETTY_PRINT
);
【讨论】:
libsodium.js 的文档在哪里?如果没有记录,即使它是更好的选择,也没有人会使用它。 @RedShift github.com/jedisct1/libsodium.js#usage-as-a-module + libsodium.gitbook.io/doc @RedShift 从那以后的几个月里,我创建了一个更安全的选项,记录在 here。 @ScottArciszewski 在前端和后端使用相同的密钥更安全?这里的私钥在哪里?【参考方案4】:pidCrypt (js) 和 phpseclib (php) 的 RSA 示例用法。
不要在此工作示例中重复使用私钥。
pidCrypt 加密
//From the pidCrypt example sandbox
function certParser(cert)
var lines = cert.split('\n');
var read = false;
var b64 = false;
var end = false;
var flag = '';
var retObj =
;
retObj.info = '';
retObj.salt = '';
retObj.iv;
retObj.b64 = '';
retObj.aes = false;
retObj.mode = '';
retObj.bits = 0;
for (var i = 0; i < lines.length; i++)
flag = lines[i].substr(0, 9);
if (i == 1 && flag != 'Proc-Type' && flag.indexOf('M') == 0)//unencrypted cert?
b64 = true;
switch (flag)
case '-----BEGI':
read = true;
break;
case 'Proc-Type':
if (read)retObj.info = lines[i];
break;
case 'DEK-Info:':
if (read)
var tmp = lines[i].split(',');
var dek = tmp[0].split(': ');
var aes = dek[1].split('-');
retObj.aes = (aes[0] == 'AES') ? true : false;
retObj.mode = aes[2];
retObj.bits = parseInt(aes[1]);
retObj.salt = tmp[1].substr(0, 16);
retObj.iv = tmp[1];
break;
case '':
if (read)b64 = true;
break;
case '-----END ':
if (read)
b64 = false;
read = false;
break;
default : if (read && b64)retObj.b64 += pidCryptUtil.stripLineFeeds(lines[i]);
return retObj;
var strCreditCardPublicKey="-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC\/tI7cw+gnUPK2LqWp50XboJ1i\njrLDn+4\/gPOe+pB5kz4VJX2KWwg9iYMG9UJ1M+AeN33qT7xt9ob2dxgtTh7Mug2S\nn1TLz4donuIzxCmW+SZdU1Y+WNDINds194hWsAVhMC1ClMQTfldUGzQnI5sXvZTF\nJWp\/9jheCNLDRIkAnQIDAQAB\n-----END PUBLIC KEY-----\n";
var objParams=certParser(strCreditCardPublicKey);
var binaryPrivateKey=pidCryptUtil.decodeBase64(objParams.b64);
var rsa=new pidCrypt.RSA();
var asn=pidCrypt.ASN1.decode(pidCryptUtil.toByteArray(key));
var tree=asn.toHexTree();
rsa.setPublicKeyFromASN(tree);
var strHexSensitiveDataEncrypted=rsa.encrypt("4111111111111111");
var strBase64SensitiveDataEncrypted=pidCryptUtil.fragment(pidCryptUtil.encodeBase64(pidCryptUtil.convertFromHex(strHexSensitiveDataEncrypted)), 64))
console.log(strBase64SensitiveDataEncrypted);
.
phpseclib解密
require_once("Crypt/RSA.php");
function decrypt($strBase64CipherText)
//CRYPT_RSA_MODE_INTERNAL is slow
//CRYPT_RSA_MODE_OPENSSL is fast, but requires openssl to be installed, configured and accessible.
define("CRYPT_RSA_MODE", CRYPT_RSA_MODE_INTERNAL);
$rsa=new Crypt_RSA();
//$strPrivateKey=file_get_contents("private.pem");
//This private key is for example purposes
//DO NOT REUSE
$strPrivateKey="-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDBNHK7R2CCYGqljipbPoj3Pwyz4cF4bL5rsm1t8S30gbEbMnKn
1gpzteoPlKp7qp0TnsgKab13Fo1d+Yy8u3m7JUd/sBrUa9knY6dpreZ9VTNul8Bs
p2LNnAXOIA5xwT10PU4uoWOo1v/wn8eMeBS7QsDFOzIm+dptHYorB3DOUQIDAQAB
AoGBAKgwGyxy702v10b1omO55YuupEU3Yq+NopqoQeCyUnoGKIHvgaYfiwu9sdsM
ZPiwxnqc/7Eo6Zlw1XGYWu61GTrOC8MqJKswJvzZ0LrO3oEb8IYRaPxvuRn3rrUz
K7WnPJyQ2FPL+/D81NK6SH1eHZjemb1jV9d8uGb7ifvha5j9AkEA+4/dZV+dZebL
dRKtyHLfbXaUhJcNmM+04hqN1DUhdLAfnFthoiSDw3i1EFixvPSiBfwuWC6h9mtL
CeKgySaOkwJBAMSdBhn3C8NHhsJA8ihQbsPa6DyeZN+oitiU33HfuggO3SVIBN/7
HmnuLibqdxpnDOtJT+9A+1D29TkNENlTWgsCQGjVIC8xtFcV4e2s1gz1ihSE2QmU
JU9sJ3YeGMK5TXLiPpobHsnCK8LW16WzQIZ879RMrkeDT21wcvnwno6U6c8CQQCl
dsiVvXUmyOE+Rc4F43r0VRwxN9QI7hy7nL5XZUN4WJoAMBX6Maos2Af7NEM78xHK
SY59+aAHSW6irr5JR351AkBA+o7OZzHIhvJfaZLUSwTPsRhkdE9mx44rEjXoJsaT
e8DYZKr84Cbm+OSmlApt/4d6M4YA581Os1eC8kopewpy
-----END RSA PRIVATE KEY-----
";
$strPrivateKey=preg_replace("/[ \t]/", "", $strPrivateKey);//this won't be necessary when loading from PEM
$rsa->loadKey($strPrivateKey);
$binaryCiphertext=base64_decode($strBase64CipherText);
$rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
$strBase64DecryptedData=$rsa->decrypt($binaryCiphertext);
return base64_decode($strBase64DecryptedData);
//The pidCrypt example implementation will output a base64 string of an encrypted base64 string which contains the original data, like this one:
$strBase64CipherText="JDlK7L/nGodDJodhCj4uMw0/LW329HhO2EvxNXNUuhe+C/PFcJBE7Gp5GWZ835fNekJDbotsUFpLvP187AFAcNEfP7VAH1xLhhlB2a9Uj/z4Hulr4E2EPs6XgvmLBS3MwiHALX2fES5hSKY/sfSUssRH10nBHHO9wBLHw5mRaeg=";
$binaryDecrypted=decrypt($strBase64CipherText);
//should output '4111111111111111'
var_export($binaryDecrypted);
【讨论】:
【参考方案5】:这是基于Tiny Encryption Algorithm,这是一个对称(私钥)加密系统。尽管如此,由于它的重量轻,它可能对您有用。
现在在:http://babelfish.nl/Projecten/JavascriptPhpEncryption
【讨论】:
该网站似乎有点过时......正如你所说......算法是对称的(这意味着记录的数据很容易破坏,如果密钥也被记录)。我什至不确定这是跨浏览器还是它是否适用于 UTF-8 和字节数组。无论如何谢谢:) 谢谢托尼。我更新了网站,但没有注意到这一点。这东西现在在babelfish.nl/Projecten/JavascriptPhpEncryption以上是关于用 Javascript 加密,用 PHP 解密,使用公钥加密的主要内容,如果未能解决你的问题,请参考以下文章