Encrypting Sensitive Data with Perl
It’s not uncommon to have information submitted through a web form that you need to save, but don’t want to have lying around in plain text. Credit card numbers, bank routing information, missile launch codes, and so on. The trick is to do this in a unattended fashion; you don’t want to have the person submitting the form do anything special such as supply a password. Enter public key encryption.
In public key encryption there are two passwords, or keys, one which is used to encrypt information and one which is used to decrypt (there are additional ways to use the key pair, but that’s a topic for another day). Since the encryption key can not be used to decrypt sensitive data, can safely be made public. So in the case of a web form, we can make the public key available to our CGI, which protecting the private key for our use only.
The first step is to generate a key pair and password protect the private key. For this we’ll use OpenSSL which comes pre-installed on just about every Unix-like system (including OS X). OpenSSL provides a wide range of cryptographic functions including an implementation of the RSA public key encryption algorithm.
First we generate the private key. “2048” is the size of the key in bits, and, in this simple example, it controls the maximum number of bits we can encrypt . For more security, at the cost of more processor overhead, you can increase size, but you shouldn’t use a smaller number. We’ll need a password; I like to use Diceware, but you can generate it any way you like.
% openssl genrsa -des3 -out private.pem 2048
Generating RSA private key, 2048 bit long modulus
......+++
.+++
e is 65537 (0x10001)
Enter pass phrase for private.pem:
Verifying - Enter pass phrase for private.pem:
Then we extract the public key:
openssl rsa -in private.pem -out public.pem -outform PEM -pubout
Enter pass phrase for private.pem:
writing RSA key
Now a bit of code to encrypt a string using the public key:
#!/usr/bin/perl
use Crypt::OpenSSL::RSA;
use MIME::Base64;
use strict;
my $public_key = 'public.pem';
my $string = 'Hello World!';
print encryptPublic($public_key,$string);
exit;
sub encryptPublic {
my ($public_key,$string) = @_;
my $key_string;
open(PUB,$public_key) || die "$public_key: $!";
read(PUB,$key_string,-s PUB); # Suck in the whole file
close(PUB);
my $public =
Crypt::OpenSSL::RSA->new_public_key($key_string);
encode_base64($public->encrypt($string));
}
The function encryptPublic takes the path of the public key and a string to encrypt and returns the encrypt string. Because the encrypted string is binary it’s converted to text using Base64 to make it easier to handle. This is certainly not necessary, and if you were storing the string in a database that has a binary type you could change the last line of the function to:
$public->encrypt($string)
The code requires the CPAN module “Crypt::OpenSSL::RSA” which is a wrapper around the OpenSSL libraries.
Now to decrypt:
#!/usr/bin/perl
use Convert::PEM;
use Crypt::OpenSSL::RSA;
use MIME::Base64;
use strict;
my $encrypted_string =q(
P0/HHc8oo1vmUoeUbtF6EKqyptudy7XyWTtCuPXP8wZDKTO8K/pZ/77/EAz8HV4VJaL0KdMVm7iD
kCpsrS6Wg/gyKIzVwBE/bN5w8SwAGH93zUs4Vb0QsogJExHKvjY5KNE5FovdtPfUIwFJdcddK2M9
aLeDixCSb8T31rlJ5sRKCObQJ/M9Co1nSWBAtVufBREJPYYeLPZsw2DTFpsT5WAMuv9BpU4dViiT
U+2DmJFr0awzV3NthGVoKXVw2toVbiaAXK2Hot/0CFvbwD8YeKGc2HsCGZS6jdOHKhy8HJWqKPCy
gPbWtSSugV04m66JV7oxcHbf2NhLpg3HfeWluQ==
);
my $private_key = 'private.pem';
my $password = 'boost facile';
print decryptPrivate($private_key,$password,$encrypted_string), "\n";
exit;
sub decryptPrivate {
my ($private_key,$password,$string) = @_;
my $key_string = readPrivateKey($private_key,$password);
return(undef) unless ($key_string); # Decrypt failed.
my $private = Crypt::OpenSSL::RSA->new_private_key($key_string) ||
die "$!";
$private->decrypt(decode_base64($string));
}
sub readPrivateKey {
my ($file,$password) = @_;
my $key_string;
$key_string = decryptPEM($file,$password);
}
sub decryptPEM {
my ($file,$password) = @_;
my $pem = Convert::PEM->new(
Name => 'RSA PRIVATE KEY',
ASN => qq(
RSAPrivateKey SEQUENCE {
version INTEGER,
n INTEGER,
e INTEGER,
d INTEGER,
p INTEGER,
q INTEGER,
dp INTEGER,
dq INTEGER,
iqmp INTEGER
}
));
my $pkey =
$pem->read(Filename => $file, Password => $password);
return(undef) unless ($pkey); # Decrypt failed.
$pem->encode(Content => $pkey);
}
Here we have the function decryptPrivate which takes the path of the private key file, the private key password, and the (Base64 encoded) encrypted string and returns the decrypted string. The process is a bit more complex than encryption and, do to the limitations of “Crypt::OpenSSL::RSA”, we have to use an additional CPAN module, “Convert::PEM”.
Crypt::OpenSSL::RSA” lacks the ability to unlock (decrypt) the private key. It’s decrypt function expects to receive an already decrypted copy of the key. Fortunately for us “Convert::PEM” can decrypt the private key and return it in a format we can use.
As with encryption, you do not need to use Base64 encoded strings. Simply replace the line:
$private->decrypt(decode_base64($string));
with:
$private->decrypt($string);
On key size: As I said above the amount of data you can encrypt this way is limited by the key size minus 11 bytes of overhead (padding), here a 2048 bit key gives us 256 - 11 = 245 bytes. You could handle larger data by increasing the key size, but that would entail a potentially large performance hit and is not how key pairs are used. Instead you would generate a random password, use it to encrypt the data using symmetric-key encryption such as Triple DES or Blowfish, then use the public key to encrypt and store the random password.
One last note; there is another Perl Module “Crypt::RSA” which is a purl perl implementation of RSA public key encryption. On the plus side, it doesn’t require OpenSSL be installed and it has a much more complete API, including better key handling. On the minus side, while fast for perl, it’s considerably slower than OpenSSL and can not take advantage of encryption hardware; something that OpenSSL automatically does.
Comments