[Bcc-ed to poster due to age of article]
In the previous article said:
I have written a Perl script that connects to various networking
devices and downloads their configurations to a TFTP server. This
is working great and I want to enhance its security by masking the
password somehow. Currently, I hardcode a variable to the password.
$password = 'mypassword';
In the interest of security, I want to find a way to mask this. I'm
not sure how to go about it though. I don't want to have a
plain-text password on the system anywhere.
Any suggestions?
I came in here looking for a generalized solution to this. With the
understanding that all that is being accomplished here is obfuscation,
NOT any genuine "security," I have a solution I am considering turning
into a module. I am interested in comments as to whether it would be
a useful module (including naming suggestions).
I have two functions: encryptpw and decryptpw. Only decryptpw actually
belongs in your program. Given a directory $ACCESSDIR, account name
$acctname, and password $pw, encryptpw creates a random key and stores
the key and password directly in file $ACCESSDIR/$acctname.
So, once you run encryptpw, you'll have a file that looks like this
(dummy data):
ab7458e29a2e01e4b566b8541dfe3a2326312a539fc4bb158eb1c83ff
53616c7465645f5f8d8ad43ea2bc5678a2c43c59a245d239abcb2ff3d67d1acf0eeb7d27
You only need to do this once (well, again whenever you change the
password, but you get the idea).
Set the file read permissions on the above file such that only
authorized users (e.g., group members, ACL-designated users, whatever)
can read it.
Then, in the program, run decryptpw to get the password back, and
store it in a variable.
The "encryptpw" function is currently actually a standalone program,
encrypt.pl. I have included a slightly sanitized encrypt.pl and
decryptpw below.
Once again, ALL this does for you is keep from having a password
sitting out there naked in the text of your script. Anyone with read
access to the "encrypted" password file who knows anything at all
about Perl can read the password directly with his own copy of
decryptpw and the print statement.
I know the storage/retrieval is a little kludgy, particularly the
0-padding. I am working on a new version that relies on Storable, now
that I know about Storable.
================ encrypt.pl:
#!/usr/bin/perl -w
use Crypt::CBC;
use Digest::MD5 qw(md5_hex);
$BASEDIR = "/my/basedir";
$ACCESSDIR = "$BASEDIR/access";
umask 077;
while ( 1 )
{
print "Please input an account name: ";
$acctname = <STDIN>;
chomp $acctname;
last if ( length($acctname) );
}
if ( ( $acctname =~ /^(en|de)crypt.pl$/ ) || ( $acctname eq "README" ) )
{
# I (stupidly? keep the scripts in $ACCESSDIR -- don't ever allow
# overwriting of the scripts!
print "FATAL ERROR: Cannot overwrite script $acctname\n";
exit -1;
}
system("stty -echo");
while ( 1 )
{
$pw1 = '';
while ( ! length($pw1) )
{
print "\n";
$strlen = length("Please input the password for account $acctname: ");
printf ("%${strlen}s",
"Please input the password for account $acctname: ");
chomp($pw1=<STDIN>);
}
$pw2 = '';
printf ("\n%${strlen}s", "Please verify the password: ");
chomp($pw2=<STDIN>);
print "\n";
last if ( $pw1 eq $pw2 );
print "\nPassword mismatch - try again\n";
}
system("stty echo");
# Pad $pw1 out to eight bytes if necessary:
$pw1 .= "\0"x(8 - length($pw1));
# Generate two 32-byte hex strings
$bigkey = md5_hex(rand) . md5_hex(rand);
# Take the first 56 characters of this, which will be our ASCII key:
$key = substr($bigkey, 0, 56);
$binkey = pack("H*", $key);
$cipher = Crypt::CBC->new( -key => $binkey,
-cipher => 'Blowfish',
-header => "randomiv",
);
$ciphertext = $cipher -> encrypt($pw1);
$ascii_armor = unpack("H*", $ciphertext);
if ( -e "$ACCESSDIR/$acctname" )
{
print "$ACCESSDIR/$acctname exists and will be overwritten, OK [y/N]? ";
chomp ($answer = <STDIN>);
# Acceptable affirmative answers are Y, y, Yes, yes:
if ( ! ( $answer =~ /^[Yy]([Ee][Ss])?$/ ) )
{
print "ABORTING, no action taken\n";
exit 0;
}
}
unless ( open(OUTFILE, ">$ACCESSDIR/$acctname") )
{
die "Unable to open $ACCESSDIR/$acctname for writing: $!";
}
print OUTFILE "$key\n$ascii_armor\n";
close OUTFILE;
================ decryptpw:
use Crypt::CBC;
$BASEDIR = "/must/be/same/as/used/by/encrypt.pl/of/course!";
$ACCESSDIR = "$BASEDIR/access";
sub decryptpw
{
# Takes one argument: the name of an account for which a password
# has been stored in $ACCESSDIR under the name of the account
$acctname = $_[0];
unless ( open(INFILE, "$ACCESSDIR/$acctname") )
{
die "Unable to open $ACCESSDIR/$acctname for reading: $!";
}
chomp ($key = <INFILE>)
or die "Cannot read key from $ACCESSDIR/$acctname";
chomp ($ascii_armor = <INFILE>)
or die "Cannot read pw hash from $ACCESSDIR/$acctname";
close INFILE;
$binkey = pack("H*", $key);
$ciphertext = pack("H*", $ascii_armor);
$cipher = Crypt::CBC->new( -key => $binkey,
-cipher => 'Blowfish'',
-header => "randomiv",
);
$pass = $cipher -> decrypt($ciphertext);
# Get rid of the null characters we used to pad the password during
# encryption:
$pass =~ s/\0*$//;
return $pass;
}