用 Javascript 加密,用 PHP 解密,使用公钥加密

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12457234/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 08:00:11  来源:igfitidea点击:

Encrypt in Javascript, decrypt in PHP, using public-key cryptography

javascriptphprsapublic-keypgp

提问by Tiberiu-Ionu? Stan

I'd like to encrypt in JavaScript, decrypt in PHP, using public-key cryptography. I've been trying to find libraries that can accomplish this, but am having issues.

我想在 JavaScript 中加密,在 PHP 中解密,使用公钥加密。我一直在寻找可以实现这一点的库,但遇到了问题。

I am currently looking at openpgpjs, but I need support in all browsers, and even the test page has errrors on the only listed as supported browser (Google Chrome).

我目前正在查看openpgpjs,但我需要所有浏览器的支持,甚至测试页面在唯一列为受支持的浏览器(谷歌浏览器)上也有错误。

Notes about the final goal:

关于最终目标的说明:

The TCP connection is already protected by SSL. The main purpose of this layer of protection is defending against intentional or unintentional webserver logging, crash dumps, etc.

TCP 连接已受 SSL 保护。这层保护的主要目的是防御有意或无意的网络服务器日志记录、故障转储等。

On the PHP side, a temporary private key will be generated (it will expire after a short time). The caller (in Javascript) is responsible for asking for a new public key when it expires. The reason for private key expiration is to prevent logged encrypted data decryption, in case the server which stores the private key is later compromised.

在 PHP 端,会生成一个临时私钥(它会在很短的时间后过期)。调用者(在 Javascript 中)负责在过期时请求新的公钥。私钥过期的原因是为了防止记录的加密数据解密,以防存储私钥的服务器后来遭到破坏。

Servers compromised scenario: someone gets his hands on backups for all machines except the database server (and cannot access the database due to firewalling, even if he finds out the user and password). Since the private key which encrypted the logged data no longer exists, there is nothing the attacker can do.

服务器受损场景:某人获得了除数据库服务器之外的所有机器的备份(并且由于防火墙无法访问数据库,即使他找到了用户和密码)。由于加密记录数据的私钥不再存在,攻击者无能为力。

采纳答案by Ja?ck

I've used something similar for my login page; it encrypts login credentials using the given public key information (N, e) which can be decrypted in PHP.

我在登录页面上使用了类似的东西;它使用可以在 PHP 中解密的给定公钥信息 (N, e) 来加密登录凭据。

It uses the following files that are part of JSBN:

它使用以下属于 的文件JSBN

  • jsbn.js- to work with big integers
  • rsa.js- for RSA encryption only (uses jsbn.js)
  • rng.js- basic entropy collector
  • prng4.js- ARC4 RNG backend
  • jsbn.js- 处理大整数
  • rsa.js- 仅用于 RSA 加密(使用 jsbn.js)
  • rng.js- 基本熵收集器
  • prng4.js- ARC4 RNG 后端

To encrypt data:

加密数据:

$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>

This is how you would decode the sent data:

这是解码发送数据的方式:

$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;
}

回答by Vlad Balmos

Check out node-rsa.

查看node-rsa

It's a node.js module

这是一个 node.js 模块

This module provides access to RSA public-key routines from OpenSSL. Support is limited to RSAES-OAEP and encryption with a public key, decryption with a private key.

该模块提供对来自 OpenSSL 的 RSA 公钥例程的访问。支持仅限于 RSAES-OAEP 和使用公钥加密、使用私钥解密。

Maybe you can port it to run in the browser.

也许您可以将其移植到浏览器中运行。

UPDATE

更新

RSA client side library for javascript: (pidcrypt has been officially discontinued and the website domain is expired - see @Hyman's answer which contains the same libraries as pidcrypt contained). https://www.pidder.com/pidcrypt/?page=rsa

用于 javascript 的 RSA 客户端库:(pidcrypt 已正式停止使用,网站域已过期 - 请参阅 @Hyman 的答案,其中包含与包含的 pidcrypt 相同的库)https://www.pidder.com/pidcrypt/?page=rsa

PHP server side component: http://phpseclib.sourceforge.net/

PHP 服务器端组件:http: //phpseclib.sourceforge.net/

Good luck!

祝你好运!

回答by Scott Arciszewski

Be careful with implementing RSA. In fact, you probably shouldn't use RSA at all. (Use libsodium instead!)

实施 RSA 时要小心。事实上,您可能根本不应该使用 RSA。(改用 libsodium!

Even if you're using a library (e.g. PHP's OpenSSL extension directly or, until recently, Zend\Crypt), there's still plenty that can go wrong. In particular:

即使您正在使用一个库(例如直接使用 PHP 的 OpenSSL 扩展,或者直到最近,使用Zend\Crypt),仍然有很多可能出错。特别是:

  • PKCS1v1.5 padding, which is the default(and in many cases the only supported padding mode), is vulnerable to a class of chosen-ciphertext attacks called a padding oracle. This was first discovered by Daniel Bleichenbacher. In 1998.
  • RSA is not suitable for encrypting large messages, so what implementors often do is take a long message, break it up into fixed-size blocks, and encrypt each block separately. Not only is this slow, it's analogous to the dreaded ECB modefor symmetric-key cryptography.
  • PKCS1v1.5 填充是默认值(并且在许多情况下是唯一支持的填充模式),容易受到称为填充预言机的一类选择密文攻击。这是由 Daniel Bleichenbacher 首次发现的。1998 年。
  • RSA 不适合加密大消息,所以实现者经常做的是将长消息分成固定大小的块,然后分别加密每个块。这不仅很慢,而且类似于对称密钥加密的可怕的 ECB 模式

The Best Thing to Do, with Libsodium

最好的事情,与 Libsodium

You might want to read JavaScript Cryptography Considered Harmfula few times before going down this route. But that said...

在沿着这条路线前进之前,您可能想阅读几次JavaScript Cryptography 被认为是有害的。但那说...

  1. Use TLSv1.2 with HSTS and HPKP, preferably with ChaCha20-Poly1305 and/or AES-GCM and an ECDSA-P256 certificate (important: when the IETF christens Curve25519 and Ed25519, switch to that instead).
  2. Add libsodium.jsto your project.
  3. Use crypto_box_seal()with a public key to encrypt your messages, client-side.
  4. In PHP, use \Sodium\crypto_box_seal_open()with the corresponding secret key for the public key to decrypt the message.
  1. 将 TLSv1.2 与 HSTS 和 HPKP 一起使用,最好与 ChaCha20-Poly1305 和/或 AES-GCM 以及 ECDSA-P256 证书一起使用(重要的是:当 IETF 命名为 Curve25519 和 Ed25519 时,请改为使用该证书)。
  2. libsodium.js添加到您的项目中。
  3. 使用crypto_box_seal()公钥加密您的消息,客户端。
  4. 在 PHP 中,使用\Sodium\crypto_box_seal_open()对应的私钥作为公钥来解密消息。

I need to use RSA to solve this problem.

我需要使用 RSA 来解决这个问题。

Please don't. Elliptic curve cryptography is faster, simpler, and far easier to implement without side-channels. Most libraries do this for you already. (Libsodium!)

请不要。椭圆曲线密码学更快、更简单,并且在没有侧信道的情况下更容易实现。大多数图书馆已经为你做了这件事。(Libsodium!)

But I reallywant to use RSA!

但我真的很想用RSA!

Fine, follow these recommendations to the letterand don't come crying to StackOverflow when you make a mistake (like SaltStack did) that renders your cryptography useless.

好吧,请严格按照这些建议进行操作,当您犯了一个错误(如SaltStack 所做的)导致您的密码学无用时,不要向 StackOverflow 哭泣。

One option (which does not come with a complementary JavaScript implementation, and please don't ask for one) that aims to provide simple and easy RSA encryption is paragonie/easyrsa.

一种旨在提供简单易用的 RSA 加密的选项(不附带补充的 JavaScript 实现,请不要要求)是paragonie/easyrsa

  • It avoids the padding oracles by using RSA-OAEP with MGF1+SHA256instead of PKCS1v1.5.
  • It avoids the ECB mode by clever protocol design:

The EasyRSA Encryption Protocol

EasyRSA 加密协议

  1. EasyRSA generates a random 128-bit key for symmetric key cryptography (via AES).
  2. Your plaintext message is encrypted with defuse/php-encryption.
  3. Your AES key is encrypted with RSA, provided by phpseclib, using the correct mode (mentioned above).
  4. This information is packed together as a simple string (with a checksum).
  1. EasyRSA 为对称密钥加密(通过 AES)生成一个随机的 128 位密钥。
  2. 您的纯文本消息使用defuse/php-encryption 加密
  3. 您的 AES 密钥使用phpseclib提供的 RSA 加密,使用正确的模式(如上所述)。
  4. 这些信息被打包成一个简单的字符串(带有校验和)。

But, really, if you find a valid use case for public key cryptography, you want libsodium instead.

但是,实际上,如果您找到公钥密码术的有效用例,则需要使用 libsodium。

Bonus: Encryption with JavaScript, Decryption with PHP

奖励:使用 JavaScript 加密,使用 PHP 解密

We're going to use sodium-plusto accomplish this goal. (Adopted from 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>");
    });
}

And then the congruent PHP code:

然后是一致的 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
);

回答by Tiberiu-Ionu? Stan

RSA example usage for pidCrypt(js) and phpseclib(php).

pidCrypt(js) 和phpseclib(php) 的RSA 示例用法。

Do not reuse the private key in this working example.

不要在此工作示例中重复使用私钥。

pidCrypt encryption

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 decryption

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);

回答by Pum Walters

This is based on the Tiny Encryption Algorithm, which is a symmetric (private key) encryption system. It may nevertheless be of use to you because of its light weight.

这是基于Tiny Encryption Algorithm,它是一种对称(私钥)加密系统。尽管如此,它可能对您有用,因为它重量轻。

This is now at: http://babelfish.nl/Projecten/JavascriptPhpEncryption

现在位于:http: //babelfish.nl/Projecten/JavascriptPhpEncryption