Aaron J. Mackey


Data::Encrypted - Transparently store encrypted data via RSA


  # functional interface:
  use Data::Encrypted file => "./.$0-encrypted-data", qw(encrypted);

  # note: 'login' and 'password' are not *really* the login and
  # password values, only the desired prompt!
  my $login = encrypted('login');
  my $password = encrypted('password');

  # script continues, connecting to some secure resource (database,
  # website, etc).


  # alternative, OO interface:
  use Data::Encrypted;

  my $enc = new Data::Encrypted file => "./.$0-encrypted-data";
  my $login = $enc->encrypted('login');
  my $password = $enc->encrypted('password');
  $enc->finished(); # close and release lock on storage file

  # script continues, connecting to some secure resource (database,
  # website, etc).


  [ then, back at the command line: ]

  % myscript.pl
  Data::Encrypted value for 'login' not found, please enter: *****
  Data::Encrypted value for 'password' not found, please enter: ********
  [ script merrily continues ... ]

  % myscript.pl
  [ script merrily continues, no prompting this time ... ]


Often when dealing with external resources (database engines, ftp, telnet, websites, etc), your Perl script must supply a password, or other sensitive data, to the other system. This requires you to either continually prompt the user for the data, or to store the information (in plaintext) within your script. You'd rather not have to remember the connection details to all your different resources, so you'd like to store the data somewhere. And if you share your script with anyone (as any good open-source developer would), you'd rather not have your password or other sensitive information floating around.

Data::Encrypted attempts to fill this small void with a simple, yet functional solution to this common predicament. It works by prompting you (via Term::ReadPassword) once for each required value, but only does so the first time you run your script; thereafter, the data is stored encrypted in a secondary file. Subsequent executions of your script use the encrypted data directly, if possible; otherwise it again prompts for the data. Currently, Data::Encrypted achieves encryption via an RSA public-key cryptosystem implemented by Crypt::RSA, using (by default) your own SSH1 public and private keys.

RSA Authentication

Data::Encrypted uses RSA authentication to encrypt and decrypt its data. It achieves this by reading the user's public and private RSA keys. By default, Data::Encrypted assumes these files are stored in the .ssh subdirectory of their home directory (found using File::HomeDir), but you can provide alternative key files yourself, either by supplying alternative key filenames, or by building Crypt::RSA::Key's yourself:

  use Data::Encrypted
  use Crypt::RSA::Key;

  my $public  = new Crypt::RSA::Key::Public::SSH
                   Filename => '~/.ssh/identity.pub';
  # my private key includes a passphrase!
  my $private = new Crypt::RSA::Key::Private::SSH
                   Filename => '~/.ssh/identity',
                   Password => q(These are the times that try men's souls);

  my $d = new Data::Encrypted (FILE => "~/.secret", PK => $public, SK => $private);
  my $password = $d->encrypted('password');

Or, more directly via the functional interface:

  use Data::Encrypted (FILE => './secret',
                       PK => '~/.ssh/identity.pub',
                       SK => '~/.ssh/identity',
                       PW => q(These are the times that try men's souls)
                      ), qw(encrypted);

  my $password = encrypted('password');

SSH1 vs. SSH2 and other public-key considerations

Data::Encrypted utilizes the facilities made available by Crypt::RSA, and so is limited only by Crypt::RSA's ability to read and utilize various public key formats. Currently that means that only SSH version 1 keys are usable. Furthermore, keys which have been themselves encrypted via use of a 'passphrase' are currently unusable by Data::Encrypted -- future versions may overcome this limitation.

Encrypted Data Storage via Inline::Files

You may provide Data::Encrypted with a filename or already opened filehandle for encrypted data storage; alternatively you may use "virtual files" at the end of your script for encrypted data storage via Inline::Files:

  use Inline::Files;
  use Data::Encrypted;

  open(ENCRYPT, '+<');

  my $enc = new Data::Encrypted fh => \*ENCRYPT;
  my $password = $enc->encrypted('password');

Then, Data::Encrypted will read/write it's data from the special __ENCRYPT__ virtual file (see Inline::Files for more information and a better description of virtual files). This way everything stays contained within your script; no external storage file is necessary. If you send the script to someone else, and they try to run it, the RSA authentication will fail, but they will simply be prompted for the values as you were when you first ran the script. When they enter the values the __ENCRYPT__ virtual file will be rewritten, and they may continue to use the script. In this way Data::Encrypted could be though of as a "personalization" utility, ensuring that the encrypted data can only be utilized by one person.

Resetting Encrypted Values

If, after the initial execution and value specification, you need to reset or clear the stored values once so that you may specify new ones, you can invoke your script like so:

  perl -MData::Encrypted=reset myscript.pl

Of course you could always just delete the already-encrypted data from storage.

Alternatively, simply add the 'reset' imperative to your use statement:

  use Data::Encrypted file => './secret', qw(encrypted reset);

This would force the user to enter the required data on every invocation (which might be useful for yourself while you tried to rememeber just what that lost database password was ...); even though the encrypted data is available, it will be stored anew upon each invocation.

Storage File Locking Issues

Data::Encrypted opens the storage file immediately upon specification (via the use statement, or new object creation), and locks it for exclusive use (or blocks until such a lock can be obtained). It holds this lock until the object is destroyed, or the script ends. If you wish the file to be closed and the lock to be released (so that other scripts may use the file while your program continues running), you should either undefine the encryption object you created, or call finish() if using the functional interface:

# OO interface example:

my $enc = new Data::Encrypted file => "~/.sharedsecrets"; # ... use $enc->encrypted to retrieve encrypted data undef $enc; # done using $enc, release the file lock # ... continue running program


# functional interface example:

use Data::Encrypted qw(encrypted finish), file => "~/.sharedsecrets"; # ... use encrypted() to retrieve encrypted data finish(); # done getting encrypted data, release the file lock # ... continue running program

Real Life Examples

example #1 - a DBI session, utilizing virtual file storage:

  use DBI;
  use Inline::Files;
  use Data::Encrypted;

  open(ENCRYPT, '+<') or die $!;
  my $encryptor = new Data::Encrypted FH => \*ENCRYPT';

  my $dbh = DBI->connect('dbi:mysql:mydatabase',
                         $encryptor->encrypted('user name'),
                         $encryptor->encrypted('db password'),
                         { RaiseError => 1, AutoCommit => 1}
  [.. continue using $dbh ...]

example #2 - an FTP session, with 'reset' (will *always* prompt for data until 'reset' is removed from use statement):

  use Net::FTP;
  use Data::Encrypted FILE => './.ftplogin', qw(encrypted reset);

  my $ftp = new Net::FTP 'ftp.somewhere.com';
  $ftp->login(encrypted('ftp user'),
              encrypted('ftp password'));
  [... continue using $ftp ...]

example #3 - a POP3 email client session with keys in unguessable places:

  use Mail::POP3Client;
  use Crypt::RSA::Key;
  use Data::Encrypted;

  my $public = new Crypt::RSA::Key::Public::SSH
                 Filename => '~/.ssh/mykeys/public';

  my $public = new Crypt::RSA::Key::Private::SSH
                 Filename => '~/.ssh/mykeys/private'
                 Password => 'The Only Good Computer Is A Dead Computer';

  my $encryptor = new Data::Encrypted ( FILE => './.pop3email',
                                        SK   => $private,
                                        PK   => $public

  my $pop3 = new Mail::POP3Client
               ( USER     => $encryptor->encrypted('user'),
                 PASSWORD => $encryptor->encrypted('password'),
                 HOST     => $encryptor->encrypted('host')

example #4 - build a script to send to Bob that allows him to ftp files from you, without passing along your connection information in clear text; note that you yourself won't be able to actually use the script without entering the data each time. Also note that Bob could easily read the encrypted information, so it is not secure from him. It is only secure from prying third-parties.

  use Data::Encrypted;
  use Net::FTP;
  use Inline::Files;

  open(ENCRYPT, "+<") or die $!;
  my $encryptor = new Data::Encrypted FH => \*ENCRYPT,
                                      PK => '~/pubkeys/bob.pub';
  my $ftp = new Net::FTP "ftp.mysite.org";
  $ftp->cwd($encryptor->encrypted(q{Bob's directory}));
  $ftp->get($encryptor->encrypted(q{What Bob gets to download}));


Inline::Files won't (yet) allow one package (i.e. Data::Encrypted) to work with virtual files in another package (i.e. main); as a result, you have to feed your virtual storage file to Data::Encrypted manually. Not so much a bug as an interface drawback.

Our usage of Inline::Files-generated filehandles is a bit noisy - especially when first using "empty" virtual files (a known bug in Inline::Files). Damian Conway has said he'd think about trying to fix it someday.

This idea could be extended to the Tie::EncryptedHash or other Crypt::CBC-employing methodology, but would lose the fundamental advantage of not requiring any clear text password or passphrase to remain associated with the script. Perhaps people wouldn't mind typing one "universal" password or passphrase to get at their saved, encrypted data ... ?

When Data::Encrypted prompts for unknown data, it could ask the user to repeat their data entry for validation, to cut down on the possibility of typos.

Currently the data is keyed via the prompt - hence if you use the same prompt twice, the second piece of data will overwrite the first.

The data could be made to be "application-specific", so that the same encrypted data storage file could be used for multiple applications (without overwriting each other's data). This could be as simple as adding an additional dimenion to the hash, keying on $0 ...

When someone calls finish(), we close everything up ... but when encrypted() is called, we don't ever check whether we've already called finish, so this behavior is, uhmm, undefined I guess.


Copyright (c) 2001 Aaron J Mackey. All rights reserved.

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.


Aaron J Mackey, amackey@virginia.edu


William R. Pearson, wrp@virginia.edu


Crypt::RSA, Inline::Files, Term::ReadPassword, File::HomeDir, perl(1).