实施 RSA 时要小心。事实上,您可能根本不应该使用 RSA。(改用 libsodium!)
即使您正在使用库(例如直接使用 PHP 的 OpenSSL 扩展,或者直到最近,Zend\Crypt
),仍然有很多可能出错的地方。尤其:
- PKCS1v1.5 填充是默认的(在许多情况下是唯一支持的填充模式),容易受到称为填充预言的一类选择密文攻击。这是由 Daniel Bleichenbacher 首次发现的。1998 年。
- RSA不适合加密大消息,所以实现者经常做的就是取一个长消息,把它分成固定大小的块,然后分别加密每个块。这不仅速度慢,而且类似于对称密钥加密技术中可怕的 ECB 模式。
最好的事情,用 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 来解决这个问题。
请不要。椭圆曲线密码学更快、更简单,并且在没有侧通道的情况下更容易实现。大多数图书馆已经为您这样做了。(钠!)
但是我真的很想用RSA!
好的,请严格遵守这些建议,当您犯了一个错误(就像SaltStack 所做的那样)时,不要向 StackOverflow 哭泣,这会使您的密码学无用。
paragonie/easyrsa是一种旨在提供简单易用的 RSA 加密的选项(不附带补充的 JavaScript 实现,请不要要求提供)。
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
);