Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

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

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.

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.

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.

like image 260
Tiberiu-Ionuț Stan Avatar asked Sep 17 '12 10:09

Tiberiu-Ionuț Stan


3 Answers

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;
}
like image 140
Ja͢ck Avatar answered Nov 08 '22 00:11

Ja͢ck


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!

like image 24
Vlad Balmos Avatar answered Nov 07 '22 23:11

Vlad Balmos


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
);
like image 16
Scott Arciszewski Avatar answered Nov 08 '22 01:11

Scott Arciszewski