Securing passwords

I have around 100 passwords for various websites and the like. Keeping track of them used to be a pain but it is now quick and convenient. I'm going to describe how I solved this problem with a simple secure solution you can implement for yourself with a 28 line bash script.

There are programs around that will do what I want (like pwsafe) but they are too complicated and I can't import my existing lists into them or add things that aren't strictly passwords (like tax file numbers).

I'm working with Ubuntu Linux but any Linux will do. I'm using the "Terminal" or command line too which is daunting if you are new to it. Persevere, because it will pay dividends in the long run. If you are new to scripting, treat this as a high level tutorial and use google to expand on things in it as necessary.

Looking at my old spreadsheet file, I have passwords with places and user names. I also have entries with product names and license keys, even tax file numbers and stuff. Each 'thing' sits nicely on one line so I'll use a single line per item. Having skimmed the manual entry on grep, I know that one item per line will make it easy to use grep to find the lines that I need. That is my data specification - a text file with one line per item.

Now I create somewhere to work and make a test file according to my specification. I try grep out on it to make sure my assumption is correct:

~ $ mkdir wk
~ $ cd wk
~/wk $ echo "bank noddy xy23bb personal bank account
...$ www.example.com freddy pass123
...$ www.somewhereelse.com Freddy pass456" > test
~/wk $ cat test | grep bank
bank noddy xy23bb personal bank account
~/wk $ cat test | grep -i freddy
www.example.com freddy pass123
www.somewhereelse.com Freddy pass456

Grep extracts the lines I want from my file. It can let me do powerful searches if I ever need them but the simple text search and the case insensitive option will cover most of my needs when looking for a password entry.

This test shows how my password file can be accessed to get at the information I want by piping its contents into grep. Unfortunately its not very secure. To make it secure I need to encrypt it.

"apropos encryption" leads me quickly to gpg and from "man gpg", I can see that it looks like a very powerful way to encrypt my file. Its a complicated looking command but skimming through its manual entry, I can see that I need to generate a key for my own use so its back to the command line to create the key and then do a little more experimenting. When following the prompts , accept the defaults if you are unsure of what they do. You can always check the manual later.

~/wk $ gpg --gen-key

Lets play at the command line and see if we can now encrypt and decrypt our file. Another skim of the manual entry for gpg gets me to the details. I'm not good at reading binary so I've chosen to keep the encrypted file in ASCII. This will also help avoid complications when copying it to other machines. Replace my name with the name you used for your own secret key!

~/wk $ echo "secret line" > test_gpg
~/wk $ gpg -ear 'Paul Whipp' test_gpg
~/wk $ ls | grep gpg
test_gpg
test_gpg.asc
~/wk $ cat test_gpg.asc
-----BEGIN PGP MESSAGE-----
Version: GnuPG v1.4.9 (GNU/Linux)

hQIOsgbfd5o7ua0mEAf/cMczUnLoYfIea+HJ7J1KFki...
...
WfJKCUukgsDWdx2WAOJYzUR9Ymu411VLLPgK9H7Gvc...
-----END PGP MESSAGE-----
~/wk $ gpg -d test_gpg.asc > result

You need a pass phrase to unlock the secret key for
user: "Paul Whipp 

Now you can encrypt any file so that it can only be read by you. Take care not to leave unencrypted copies lying around. Another "apropos" leads me to the shred command to make sure nothing will remain of the unencrypted information.

Before I bundle this up into a script, I need to be able to have that gpg key on both my PC and on my netbook. Luckily, GnuPG keyrings are files and we can copy them onto the other machine. If you have keys on both machines, use the gpg manual to create a master public and secret key ring you can use on each machine.

We need both the public and secret key on each machine. The public key encrypts our file and the secret key decrypts it.

Now we are ready to produce our script. To do this we start with looking at what we want to type to access our file. I want to be able to add passwords, get passwords and edit and manipulate the file. I could create scripts for each of these actions but there is a nice way of putting them altogether called a 'case' statement. Here's my first draft of the script:

#!/bin/bash
#
# Dispatch to command
case $1 in
 get )
 	echo "get pwd lines using grep with remaining args";;
 put )
 	echo "add remainder of line to end of pwd file";;
 edit )
	echo "open editor on decrypted file"
        echo "encrypt it again when done";;
 * )
	echo "$0: First parameter is not one of \
'get', 'put', or 'edit', "
	exit 1;;
esac

To test this script, copy it into a file with the name of the command you want to use (I use 'pw'). As I'm sure you'll be adding other scripts soon, we'll set up a special directory to keep them in and make sure that they are as easy as possible to use. Here's how:

~/wk $ mkdir ~/scripts
~/wk $ echo "PATH=$PATH\":~/scripts\"" >> ~/.bashrc
~/wk $ source .bashrc
~/wk $ mv pw ~/scripts

The first line creates a scripts directory in your home directory where you can keep scripts. This is not just to keep them tidy. "PATH=..." in the second line adds this directory to the path so you can type the names of your commands at the terminal without needing to include the full path. In its entirety, the second line adds this command to a special script file that bash runs every time it starts up. The third line runs the startup script file to set the path up in our current session. Finally we move the pw script into the scripts directory.

There is one more thing you have to do with the file to get it working. If you type "pw" at the command line now, you will be a permission denied message. This is because the file does not have execute permission on it so bash refuses to run it as a command. To change this, enter "chmod u+x ~/scripts/pw".

Now you can test the pw command by typing it in with the various arguments to see what happens. Hopefully you get something like this:

~/wk $ pw foo
~/scripts/pw: First parameter is not one of 
'get', 'put', or 'edit', 
~/wk $ pw put thinkingtoys.com whippy secret
add remainder of line to end of password file as a new entry
~/wk $ pw get
get password lines using grep with remaining arguments
~/wk $ pw edit
open editor on decrypted file
encrypt it again when done

Now we need to fill our skeletal script with the stuff we've learned.

Small verifiable steps are key to developing scripts that do what you need so we'll start with the encryption and the decryption of our file. For this, its time to create a 'real' passwords file. It does not have to have any real information in it yet but we want it to be named and placed in an appropriate 'permanent' location. I've chosen to put it in my home directory with the name '.passwords'. The dot at the start makes it a hidden file. That is not for security, its just that its not a file I want to see when I do a normal listing of my home directory. You can create your passwords file however you like - its just a text file where each line is a 'data entry'. For example, if you currently have a passwords spreadsheet you could export the sheet as a tab delimited text file and use that.

Substitute your own name and password file name in the script.

#!/bin/bash
#
# Set full key name as user and password file name
PWUSER='Paul Whipp'
PWFILE=~/.passwords
#
# Dispatch to command
case $1 in
 get )
 	echo "get pwd lines using grep with remaining args";;
 put )
 	echo "add remainder of line to end of pwd file";;
 edit )
	echo "open editor on decrypted file"
        echo "encrypt it again when done";;
 encrypt )
	gpg -ear "$PWUSER" $PWFILE >>
	shred -u $PWFILE $PWFILE~ 2> /dev/null;;
 decrypt )
	gpg -qd $PWFILE.asc > $PWFILE &&
	rm $PWFILE.asc;;
 * )
	echo "$0: First parameter is not one of \
'get', 'put', or 'edit', "
	echo "or, for advanced use only, 'encrypt' or 'decrypt'"
	exit 1;;
esac

This implements the crucial encryption and decryption bits and we can test them at the command line like this.

~/wk $ ls -A1 ~ | grep pass
.passwords
~/wk $ pw encrypt
~/wk $ ls -A1 ~ | grep pass
.passwords.asc
~/wk $ pw decrypt

You need a pass phrase to unlock the secret key for
user: "Paul Whipp 

The '&&' at the end of a line ensures that the next line will only execute if the preceding command succeeds. Apart from that we've left it up to the commands we're using to report errors except in the case of shred where we don't want it to keep hassling us about trying to shred non existent files so we direct its over zealous warnings into the void of /dev/null.

I left the file encrypted ready for the next step. Now to make our script do its full job by adding in the other commands almost exactly as we tested them earlier at the command line. The only differences are that we reference the variables rather than the literal names and decrypt and encrypt the file.

#!/bin/bash
#
# Set default parameters
PWUSER='Paul Whipp'
PWFILE=~/.passwords
#
# Dispatch to command
case $1 in
 get )
 	# treat remaining arguments as grep args
	pw decrypt
	cat $PWFILE | grep ${@:2}
	pw encrypt;;

 put )
 	# treat remaining arguments as a line to add
	pw decrypt
	echo ${@:2} >> $PWFILE
	pw encrypt;;
 
 edit )
 	pw decrypt
	vim -n $PWFILE
	pw encrypt;; 

 encrypt )
	gpg -ear "$PWUSER" $PWFILE &&
	shred -u $PWFILE $PWFILE~ 2> /dev/null;;

 decrypt )
	gpg -qd $PWFILE.asc > $PWFILE &&
	rm $PWFILE.asc;;

 * )
	echo "$0: First parameter is not one of \
'get', 'put', or 'edit', "
	echo "or, for advanced use only, 'encrypt' or 'decrypt'"
	exit 1;;
esac

I got the bash variable expression for grabbing the rest of the command line via Google to find the bash reference manual. I did not read it in detail though - I skimmed it for relevant terms (arguments, rest). I've added the reference manual to my list of insomnia cures. The manual shows how to do a lot of cool stuff but I'm really only interested in reading it when I have cool stuff I need to do.

Note how we use the pw encrypt and pw decrypt commands in the script so that we don't have to repeat ourselves. Avoid repetition in scripts because it keeps them shorter and clearer. Beware of making the script too complicated in order to avoid repetition though. For example, I almost created two case statements to avoid repeating the pw encrypt and pw decrypt statements for each of the basic commands but I left it as one case statement because I favor the simplicity over avoiding the repetition.

Take care to remove any traces of the unencrypted files or workings once you have finished (you can use shred on the files but remember to explicitly shred any hidden files you no longer want because they are not expanded by default).

Now you have an encrypted .passwords.asc file you can copy safely anywhere. The file can only be decrypted on a machine that has your secret key. Even then, you will probably need your secret pass phrase. You can set your machine to remember this phrase either forever or for some period of time in your preferences depending upon just how secure you need your passwords to be. I set mine to be remembered for five minutes which is a good compromise between security and convenience.


Comments

There are currently no comments

New Comment

required

required (not published)

optional

Australia: 07 3103 2894

International: +61 410 545 357