R
Robert Jacobson
Hi,
On a Windows platform, I typically use the program "Password Safe" to
keep all my passwords. But, as it only works on Windows, I was
looking for a cross-platform solution.
I wrote a program (below) that has the basic functionality of the
program -- it stores your passwords in a database, encrypting both the
keys (except the KEYCHECK key) and the values with blowfish. I guess
someone could brute-force the password by encrypting the string
"JUSTCHECKING" with blowfish, trying a bunch of different passphrases.
How long would that take, assuming, say, an 16 characters passphrase?
Also, the passphrase to encrypt/decrypt is obviously stored in memory.
I don't think there's a way around this -- but how easy would it be
to get it out of memory? AFAIK, the Windows version also stores the
password in memory.
I'm pretty new at perl, so I had hoped someone here could tell me
whether my program really was as secure as I thought. The script is
below. Hold no punches (polite punches appreciated :-D ), tell me how
it is. What works, what doesn't work... whatever.
---BEGIN script---
#!/usr/bin/perl
#------------------------------------------------------------------------
# pws.pl
#"password safe"-like program in perl
# released under the BSD license
# Copyright (c) 2003, Robert Jacobson
# All rights reserved.
#------------------------------------------------------------------------
use Crypt::CBC;
use Term::ReadKey;
$SIG{INT} = clean_up;
sub clean_up {
print "Caught SIGINT, cleaning up...\n";
dbmclose(%PWS) or die "could not close database: $!";
ReadMode 0;
exit;
}
# I have no idea how to use "tie", so I'm stuck with dbm...
dbmopen(%PWS,"pws",0600) or die "Could not open database: $!";
# hash key by title, record contains
# login, pass, notes(??)
$JOIN = " "; # That's a tab, not a space
#Check for correct password
if ($PWS{KEYCHECK} eq "") {
# New database
$keymatch = 0;
while ($keymatch == 0) {
$key = "";
while (length($key) < 8) {
print "Enter new database key: ";
ReadMode 2;
chomp($key = ReadLine);
ReadMode 0;
print "\n";
if (length($key) < 8) {print "Too short\n";}
}
print "Confirm database key: ";
ReadMode 2;
chomp($key2 = ReadLine);
ReadMode 0;
print "\n";
if ("$key" ne "$key2") {
print "Keys don't match, try again\n";
} else {
$keymatch = 1;
}
}
print "Creating database...";
# Create a entry just for checking right key
$plaintext = "JUSTCHECKING";
$cipher = Crypt::CBC->new( {'key' => "$key",
'cipher' => 'Blowfish'} );
$ciphertext = $cipher->encrypt($plaintext);
$PWS{KEYCHECK} = "$ciphertext";
print "\n";
} else {
# Existing database
print "Enter key to database: ";
ReadMode 2;
chomp($key = ReadLine);
ReadMode 0;
print "\n";
# Check for right key
$cipher = Crypt::CBC->new( {'key' => "$key",
'cipher' => 'Blowfish'} );
$plaintext = $cipher->decrypt($PWS{KEYCHECK});
if ($plaintext eq "JUSTCHECKING") {
#print "That's right\n";
} else {
print "incorrect key\n";
dbmclose(%PWS);
exit;
}
}
while (1) {
print "Options:\n";
print "\t1. New Entry\n";
print "\t2. Read Entry\n";
print "\t3. Delete Entry\n";
print "\t4. Exit\n";
print "\nWhich? ";
chomp ($choice = <STDIN>);
if ($choice == 1) {&new_entry};
if ($choice == 2) {&read_entry};
if ($choice == 3) {&delete_entry};
if ($choice == 4) {
dbmclose(%PWS);
exit;
}
}
sub new_entry {
print "Enter title: ";
chomp($title = <STDIN>);
print "Enter login: ";
chomp($login = <STDIN>);
$match = 0;
while ($match == 0) {
$pass1 = "";
print "Enter pass: ";
ReadMode 2;
chomp($pass1 = ReadLine);
ReadMode 0;
print "\n";
print "confirm pass: ";
ReadMode 2;
chomp($pass2 = ReadLine);
ReadMode 0;
print "\n";
#confirm they match
if ("$pass1" ne "$pass2") {
print "They don't match,try again!\n";
} else {
$match = 1;
}
}
&encrypt_string;
#Even the hash key (title) is encrypted
$encrypted_title = $cipher->encrypt("$title");
$PWS { $encrypted_title } = "$ciphertext";
}
sub read_entry {
$showall = 0;
print "Enter title (blank for all entries without passwords): ";
chomp($title = <STDIN>);
if ($title eq "") {
$showall = 1;
} else {
$encrypted_title = $cipher->encrypt("$title");
}
print "\n";
print "Title login\tpass\n";
print "----- -----\t----\n";
for $encrypted_titles (sort keys %PWS) {
#print "key is $encrypted_titles\n";
next if $encrypted_titles eq KEYCHECK;
unless ($showall) {
#print "skipping\n";
next if "$encrypted_titles" ne "$encrypted_title";
}
$plain_title = $cipher->decrypt("$encrypted_titles");
$plaintext = $cipher->decrypt("$PWS{$encrypted_titles}");
if ($showall) {
# Dont show the password
$plaintext =~ s/\t.*/\tHIDDEN/;
}
printf("%-21s%s\n",$plain_title,$plaintext);
}
print "\n";
}
sub delete_entry {
print "Enter title to delete: ";
chomp($title = <STDIN>);
$encrypted_title = $cipher->encrypt("$title");
delete($PWS { $encrypted_title });
}
sub encrypt_string {
# Blowfish encrypt the information
$string_to_encrypt = "$login" . "$JOIN" . "$pass1";
$ciphertext = $cipher->encrypt("$string_to_encrypt");
return 1;
}
On a Windows platform, I typically use the program "Password Safe" to
keep all my passwords. But, as it only works on Windows, I was
looking for a cross-platform solution.
I wrote a program (below) that has the basic functionality of the
program -- it stores your passwords in a database, encrypting both the
keys (except the KEYCHECK key) and the values with blowfish. I guess
someone could brute-force the password by encrypting the string
"JUSTCHECKING" with blowfish, trying a bunch of different passphrases.
How long would that take, assuming, say, an 16 characters passphrase?
Also, the passphrase to encrypt/decrypt is obviously stored in memory.
I don't think there's a way around this -- but how easy would it be
to get it out of memory? AFAIK, the Windows version also stores the
password in memory.
I'm pretty new at perl, so I had hoped someone here could tell me
whether my program really was as secure as I thought. The script is
below. Hold no punches (polite punches appreciated :-D ), tell me how
it is. What works, what doesn't work... whatever.
---BEGIN script---
#!/usr/bin/perl
#------------------------------------------------------------------------
# pws.pl
#"password safe"-like program in perl
# released under the BSD license
# Copyright (c) 2003, Robert Jacobson
# All rights reserved.
#------------------------------------------------------------------------
use Crypt::CBC;
use Term::ReadKey;
$SIG{INT} = clean_up;
sub clean_up {
print "Caught SIGINT, cleaning up...\n";
dbmclose(%PWS) or die "could not close database: $!";
ReadMode 0;
exit;
}
# I have no idea how to use "tie", so I'm stuck with dbm...
dbmopen(%PWS,"pws",0600) or die "Could not open database: $!";
# hash key by title, record contains
# login, pass, notes(??)
$JOIN = " "; # That's a tab, not a space
#Check for correct password
if ($PWS{KEYCHECK} eq "") {
# New database
$keymatch = 0;
while ($keymatch == 0) {
$key = "";
while (length($key) < 8) {
print "Enter new database key: ";
ReadMode 2;
chomp($key = ReadLine);
ReadMode 0;
print "\n";
if (length($key) < 8) {print "Too short\n";}
}
print "Confirm database key: ";
ReadMode 2;
chomp($key2 = ReadLine);
ReadMode 0;
print "\n";
if ("$key" ne "$key2") {
print "Keys don't match, try again\n";
} else {
$keymatch = 1;
}
}
print "Creating database...";
# Create a entry just for checking right key
$plaintext = "JUSTCHECKING";
$cipher = Crypt::CBC->new( {'key' => "$key",
'cipher' => 'Blowfish'} );
$ciphertext = $cipher->encrypt($plaintext);
$PWS{KEYCHECK} = "$ciphertext";
print "\n";
} else {
# Existing database
print "Enter key to database: ";
ReadMode 2;
chomp($key = ReadLine);
ReadMode 0;
print "\n";
# Check for right key
$cipher = Crypt::CBC->new( {'key' => "$key",
'cipher' => 'Blowfish'} );
$plaintext = $cipher->decrypt($PWS{KEYCHECK});
if ($plaintext eq "JUSTCHECKING") {
#print "That's right\n";
} else {
print "incorrect key\n";
dbmclose(%PWS);
exit;
}
}
while (1) {
print "Options:\n";
print "\t1. New Entry\n";
print "\t2. Read Entry\n";
print "\t3. Delete Entry\n";
print "\t4. Exit\n";
print "\nWhich? ";
chomp ($choice = <STDIN>);
if ($choice == 1) {&new_entry};
if ($choice == 2) {&read_entry};
if ($choice == 3) {&delete_entry};
if ($choice == 4) {
dbmclose(%PWS);
exit;
}
}
sub new_entry {
print "Enter title: ";
chomp($title = <STDIN>);
print "Enter login: ";
chomp($login = <STDIN>);
$match = 0;
while ($match == 0) {
$pass1 = "";
print "Enter pass: ";
ReadMode 2;
chomp($pass1 = ReadLine);
ReadMode 0;
print "\n";
print "confirm pass: ";
ReadMode 2;
chomp($pass2 = ReadLine);
ReadMode 0;
print "\n";
#confirm they match
if ("$pass1" ne "$pass2") {
print "They don't match,try again!\n";
} else {
$match = 1;
}
}
&encrypt_string;
#Even the hash key (title) is encrypted
$encrypted_title = $cipher->encrypt("$title");
$PWS { $encrypted_title } = "$ciphertext";
}
sub read_entry {
$showall = 0;
print "Enter title (blank for all entries without passwords): ";
chomp($title = <STDIN>);
if ($title eq "") {
$showall = 1;
} else {
$encrypted_title = $cipher->encrypt("$title");
}
print "\n";
print "Title login\tpass\n";
print "----- -----\t----\n";
for $encrypted_titles (sort keys %PWS) {
#print "key is $encrypted_titles\n";
next if $encrypted_titles eq KEYCHECK;
unless ($showall) {
#print "skipping\n";
next if "$encrypted_titles" ne "$encrypted_title";
}
$plain_title = $cipher->decrypt("$encrypted_titles");
$plaintext = $cipher->decrypt("$PWS{$encrypted_titles}");
if ($showall) {
# Dont show the password
$plaintext =~ s/\t.*/\tHIDDEN/;
}
printf("%-21s%s\n",$plain_title,$plaintext);
}
print "\n";
}
sub delete_entry {
print "Enter title to delete: ";
chomp($title = <STDIN>);
$encrypted_title = $cipher->encrypt("$title");
delete($PWS { $encrypted_title });
}
sub encrypt_string {
# Blowfish encrypt the information
$string_to_encrypt = "$login" . "$JOIN" . "$pass1";
$ciphertext = $cipher->encrypt("$string_to_encrypt");
return 1;
}