#!/usr/bin/perl -w # # Simsafe v0.1, Apr 2009 # by Philipp Heckel; No license, feel free use it! # # A simple command-line password safe based on GPG. # # Original blog post: # http://blog.philippheckel.com/2009/04/07/simsafe-simple-command-line-password-safe/ # # REQUIREMENTS # gpg # # USAGE # $ simsafe FILE # Create/Edit a password safe # # RECOVER WITHOUT SIMSAFE # $ gpg --decrypt FILE # ### CONFIG ######################################################################################### my $EDITOR = "vi"; my $CIPHER_ALGORITHM = "AES256"; # Type `gpg --version` to get a list of all # supported algorithms. ### MAIN PROGRAM ################################################################################### use Term::ReadKey; my $password; my $safeFile; my $tempSafeFile; my $tempPasswordFile; &initApplication(); if (not -f "$safeFile") { &createNewSafe(); } else { &decryptSafe(); } my $changed = &openSafeInEditor(); if ($changed) { &saveSafe(); } else { print "simsafe: Safe unchanged.\n"; } ## Cleanup `rm '$tempPasswordFile' 2> /dev/null`; `rm '$tempSafeFile' 2> /dev/null`; ### SUB-ROUTINES ################################################################################### # Define files and make sure that # new files are created with "600" privileges sub initApplication { if (not defined $ARGV[0]) { die("Usage: $0 SAFE-FILE.\n"); } umask(0177); $safeFile = $ARGV[0]; $tempSafeFile = "$safeFile.plain.tmp"; $tempSafeFile =~ s!(^|/)([^/]+)$!$1.$2!; $tempPasswordFile = "$safeFile.pass.tmp"; $tempPasswordFile =~ s!(^|/)([^/]+)$!$1.$2!; if (-f "$safeFile" and not -w "$safeFile") { die("SIMSAFE ERROR: Safe file must be writable.\n"); } } # Read and confirm a password from the command line # and create a new safe file. sub createNewSafe { print "simsafe: Creating a new password safe '$safeFile'\n"; # Get new password my $confirm; $password = readPassword("simsafe: Please enter the new password: "); $confirm = readPassword("simsafe: Please confirm the password: "); if ($password ne $confirm) { die("SIMSAFE ERROR: Passwords do not match. Exiting.\n"); } # Temp file `touch '$tempSafeFile'`; # Check success if ($?) { die("SIMSAFE ERROR: Unable to create new safe file.\n"); } } sub decryptSafe { $password = readPassword("simsafe: Please enter the safe password: "); # Create a password file <.safe-file.pass.tmp> makePasswordFile($password, $tempPasswordFile); # Decrypt and write plain text file <.safe-file.plain.tmp> print "simsafe: Decrypting with GPG ...\n"; qx( gpg \\ --quiet \\ --no-use-agent \\ --passphrase-file '$tempPasswordFile' \\ --output '$tempSafeFile' \\ --decrypt '$safeFile' \\ ); if ($?) { removePasswordFile($tempPasswordFile); die("SIMSAFE ERROR: Could not decrypt safe-file.\n"); } # Remove password file removePasswordFile($tempPasswordFile); } ## Open the temp. plain text safe file. If the temporary safe ## file has been modified, re-encrypt it and update the current safe. sub openSafeInEditor { print "simsafe: Executing editor $EDITOR ...\n"; # Get timestamp before opening my $mtime = (stat($tempSafeFile))[9]; # Open plain-text-file system("$EDITOR '$tempSafeFile'"); if (not -f $tempSafeFile) { `rm '$tempSafeFile' 2> /dev/null`; die("SIMSAFE ERROR: '$tempSafeFile' is gone. Unable to apply changes.\n"); } # Get timestamp after editor was opened and return CHANGED or UNCHANGED return ((stat($tempSafeFile))[9] != $mtime) ? 1 : 0; } sub saveSafe { print "simsafe: Encrypting with GPG ...\n"; makePasswordFile(); qx( cat '$tempSafeFile' | \\ gpg \\ --armor \\ --no-use-agent \\ --symmetric \\ --cipher-algo '$CIPHER_ALGORITHM' \\ --passphrase-file '$tempPasswordFile' \\ > '$safeFile' ); removePasswordFile(); } sub readPassword { my ($msg) = @_; my $pass; print $msg; ReadMode('noecho'); $pass = ReadLine(0); ReadMode('normal'); print "\n"; chomp $pass; return $pass; } sub makePasswordFile { `echo '$password' > '$tempPasswordFile'`; } sub removePasswordFile { `rm '$tempPasswordFile'`; }