在实施 RSA 时要小心。事实上,您可能根本不应该使用 RSA。 (Use libsodium instead!)
即使您使用的是库(例如直接使用 PHP 的 OpenSSL 扩展,或者直到最近,Zend\Crypt),仍然有很多地方可能出错。特别是:
- PKCS1v1.5 填充是默认(在许多情况下是唯一支持的填充模式),容易受到称为填充预言的一类选择密文攻击。这是由 Daniel Bleichenbacher 首次发现的。 1998 年。
- RSA 不适合加密大消息,所以实现者经常做的是将长消息分成固定大小的块,然后分别加密每个块。这不仅速度慢,而且类似于对称密钥加密的the dreaded ECB mode。
最好的办法,使用 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)导致你的密码学无用时,不要向 StackOverflow 哭泣。
一个旨在提供简单易用的 RSA 加密的选项(不附带补充的 JavaScript 实现,请不要要求提供)是paragonie/easyrsa。
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
);