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

前端 未结 5 1888
不思量自难忘°
不思量自难忘° 2020-11-30 19:47

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.

<
相关标签:
5条回答
  • 2020-11-30 20:24

    Check out node-rsa.

    It's a node.js module

    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.

    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 @jack's answer which contains the same libraries as pidcrypt contained). https://www.pidder.com/pidcrypt/?page=rsa

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

    Good luck!

    0 讨论(0)
  • 2020-11-30 20:26

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

    Do not reuse the private key in this working example.

    pidCrypt encryption

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

    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);
    
    0 讨论(0)
  • 2020-11-30 20:28

    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.

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

    0 讨论(0)
  • 2020-11-30 20:35

    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.

    It uses the following files that are part of 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

    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;
    }
    
    0 讨论(0)
  • 2020-11-30 20:41

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

    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:

    • 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 mode for symmetric-key cryptography.

    The Best Thing to Do, with Libsodium

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

    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.js to 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.

    I need to use RSA to solve this problem.

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

    But I really want to use RSA!

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

    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.

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

    The EasyRSA Encryption Protocol

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

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

    Bonus: Encryption with JavaScript, Decryption with PHP

    We're going to use sodium-plus to 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
    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
    );
    
    0 讨论(0)
提交回复
热议问题