实施 RSA 时要小心。事实上,您可能根本不应该使用 RSA。(改用 libsodium!)
即使您正在使用一个库(例如直接使用 PHP 的 OpenSSL 扩展,或者直到最近,使用Zend\Crypt
),仍然有很多可能出错。特别是:
- PKCS1v1.5 填充是默认值(在许多情况下是唯一支持的填充模式),它容易受到称为填充预言机的一类选择密文攻击。这是由 Daniel Bleichenbacher 首次发现的。1998 年。
- RSA 不适合加密大消息,所以实现者经常做的是将长消息分成固定大小的块,然后分别加密每个块。这不仅很慢,而且类似于对称密钥加密的可怕的 ECB 模式。
最好的事情,与 Libsodium
在沿着这条路线前进之前,您可能想阅读几次JavaScript Cryptography 被认为是有害的。但那说...
- 将 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 来解决这个问题。
请不要。椭圆曲线密码学更快、更简单,并且在没有侧信道的情况下更容易实现。大多数图书馆已经为你做了这件事。(Libsodium!)
但我真的很想用RSA!
好吧,请严格按照这些建议进行操作,当您犯了一个错误(如SaltStack 所做的)导致您的密码学无用时,不要向 StackOverflow 哭泣。
一种旨在提供简单易用的 RSA 加密的选项(不附带补充性 JavaScript 实现,请不要要求)是paragonie/easyrsa。
EasyRSA 加密协议
- EasyRSA 为对称密钥加密(通过 AES)生成一个随机的 128 位密钥。
- 您的纯文本消息使用defuse/php-encryption 加密。
- 您的 AES 密钥使用phpseclib提供的 RSA 加密,使用正确的模式(如上所述)。
- 这些信息被打包成一个简单的字符串(带有校验和)。
但是,实际上,如果您找到公钥密码术的有效用例,则需要使用 libsodium。
奖励:使用 JavaScript 加密,使用 PHP 解密
我们将使用钠加来实现这一目标。(摘自这篇文章。)
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
);