Saturday, November 03, 2007

Encrypting to PGP / GnuPG / GPG using Javascript

In a previous post I wrote about receiving PGP-encrypted data and using GnuPG (GPG) from a PHP script to decrypt it. In this post I'm going to show the client-side, a webpage (or in my case, a Google Gadget) that uses Javascript to PGP-encrypt sensitive information and send it to a server. Yes, you could use SSL, but the destination I was sending it to didn't have an SSL certificate.

To do the Javascript encryption, I modified some code from Herbert Hanewinkel, and I'll post the necessary Javascript include files, which he makes available.
<!--Create a holding variable that will be used later -->
<input type="hidden" name="keyid" id="keyid" value="">

<!-- Drop your public key into an invisible textarea. It's called public for a reason; there's no danger in letting people see it. -->

<textarea id="public_key" name="public_key" style="display:none;">
Version: GnuPG v1.4.2 (GNU/Linux)



<!-- Include some required files. They are hyperlinked! Download them now! -->
<script language="Javascript" src="rsa.js" type="text/javascript"> </script>
<script language="Javascript" src="aes-enc.js" type="text/javascript"> </script>
<script language="Javascript" src="sha1.js" type="text/javascript"> </script>
<script language="Javascript" src="base64.js" type="text/javascript"> </script>
<script language="Javascript" src="mouse.js" type="text/javascript"> </script>
<script language="Javascript" src="PGencode.js" type="text/javascript"> </script>
<script language="Javascript" src="PGpubkey.js" type="text/javascript"> </script>

<script language="Javascript">

var keytyp = -1;
var keyid = '';
var pubkey = '';

function getkey()
    var pu = new getPublicKey( document.getElementById( "public_key" ).value );
    if(pu.vers == -1) return;

    document.getElementById("keyid").value = pu.keyid;

    /* I didn't end up needing this variable in the encrypt function, since I already knew the key type was Elgamal */
    /* document.getElementById( "pktype" ).value = pu.type; */

    pubkey = pu.pkey.replace(/\n/g,'');

/* Pass in the data to encrypt */
function encrypt(text)
    if( document.getElementById( "keyid" ).value.length )
        keyid=document.getElementById( "keyid" ).value;

    if(keyid.length != 16)
        alert('Invalid Key Id');

    /* Since I already knew my key type was Elgamal, I didn't need this block of code. I just set "keytype=1" below. */
    keytyp = -1;
    if( document.getElementById( "pktype" ).value == 'ELGAMAL')
        keytyp = 1;
    else if( document.getElementById( "pktype" ).value == 'RSA')
        keytyp = 0;

    if(keytyp == -1)
        alert('Unsupported Key Type');
    keytyp = 1;

    return doEncrypt(keyid, keytyp, pubkey, text);

var encrypted_data = encrypt("sensitive data here!");

Special Note: I was forced to send the data through a URL (i.e. a GET rather than a POST). This forced me to encode the data prior to attaching to URL. Contains "+", so we must use "encodeURIComponent()" instead of "escape()". (See below for how the server-side PHP script will handle the received data.) */
encrypted_data = encodeURIComponent(encrypted_data);

/* Do something with the data. */
window.location = ""+encrypted_data;

Here's what happens on the server side, in the PHP script that receives it. (In the previous blog post about the PHP side I just pulled the encrypted data from a file, but here's what I was really doing.)
/* Reverse the effects of the Javascript "encodeURIComponent" */
$encrypted_data = rawurldecode( $_GET['encrypted_data'] );

/* From here, jump right into the PHP script outlined in the previous post, commenting out the line where you're pulling the encrypted data from a file, since you just got it from the GET request. */


Xirax said...

What are you so secretive about, huh?

The Writer said...

I've been working on a Google Gadget that lets you view your Google Analytics statistics right on your iGoogle page. However, you have to enter your Analytics username and password, which then gets sent to my server where it uses that username and password to dig the data out of Google Analytics.

However, even though the odds are slim that someone will be sniffing network traffic and catch the gadget sending the username/password, to have complete security I couldn't allow that. Hence, I encrypt the username and password before sending it over the internet.

badams5116 said...

Your two articles were my guide. I needed to encrypt a password on the desktop in JS using a public key and decrypt it on the server in PHP. Before finding your blog, I found several RSA implementations, some in PHP and some in JS, but not both together. There was a lack of consistency in the kind of output produced and input expected, and I did not find a working solution.

GnuPG brings the consistency that was otherwise lacking. The fact that you use two different GnuPG implementations is fine, because both implement the same thing. Your articles both introduce GnuPG and present the solution to this problem.

The Writer said...

I'm glad it helped. I basically cobbled it together from what I could find on the web as well, and tried to make it easier to understand than the websites I dug up.

akcom said...

Exactly what modifications did you make to his code? The only difference I'm seeing is in the js on the actual page?

The Writer said...

I didn't modify any of the core files he created--all credit to him. Like you noted, I only modified how those files were used for my particular application.

Red said...

Very helpful!
For those who, like me, are running into version incompatibilities and difficulties installing all the required server-side code, here's how I have gotten my particular requirement working: a very tiny mod to s_server.c in openssl, write the GET request to a local file on the server. Use an iframe on the browser side, with style "display: none;" so it's invisible. Generate a GET request that contains the information you want to securely transmit, and it shows up in the server-side file. You an of course use any port, not necessary to hijack 443.

Alexander Willner said...

Thank you for your post. You might want to checkout and participate at our mobile OpenPGP javascript library and GUI:

yarrays said...

Thank you for the post...