There are many implementations of password managers/safes out there. But lots of them are black boxes, either because they are not open source, or because they have to much features and it gets complicated to understand the source (which is most likely not written in a happy programming language). You don’t know, what really happens with your passwords. So…

Do it yourself!

Do it with Ruby!

Do it in less than 250 lines ;)

Although this tutorial is for people who want to learn how to use Ruby, you should be familiar with the Ruby basics.

The article is divided into four/five phases, each with a code snippet and some explanations about some lines that might not be perspicuous at first glance.

Phase 0: What is it about?

What should be the purpose of the program? These are my thoughts

It should be a little command line utility, called pws

It should store many passwords for you, and protect them with a master password

It should encrypt the password store

It should be easy to use and especially be useful for every-day-use

It should stay simple!

Phase I: Encryption

Let’s dive into it with the most exciting part: Encryption (because it’s an important part). A quick search on the net reveals how to. Let’s modernize and refactor it into a handy, small module:

pws_1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 require ' openssl ' class PasswordSafe VERSION = ' 0.0.1 '. freeze end class << Encryptor = Module . new CIPHER = ' AES256 ' def decrypt ( data , pwhash ) crypt :decrypt , data , pwhash end def encrypt ( data , pwhash ) crypt :encrypt , data , pwhash end def hash ( plaintext ) OpenSSL :: Digest :: SHA512 . new ( plaintext ). digest end private def crypt ( decrypt_or_encrypt , data , pwhash ) c = OpenSSL :: Cipher . new CIPHER c . send decrypt_or_encrypt . to_sym c . key = pwhash c . update ( data ) << c . final end end if __FILE__ == $0 a = " data " b = Encryptor . hash ' password ' c = Encryptor . encrypt a , b puts ' Encrypted: ' + c . inspect d = Encryptor . decrypt c , b puts ' Decrypted: ' + d end

class << Encryptor = Module.new

Creates a new module and opens its eigenclass. It’s the same like: module Encryptor; class << self

c.send decrypt_or_encrypt.to_sym

send calls the method defined by the symbol given

c.update( data ) << c.final

OpenSSL API

__FILE__ == $0

Returns true , if the file is executed directly

Please note: The final code will use the better encryption cipher method cbc .

Phase II: Save data in a file & basic structure

Now, let’s build a simple PasswordSafe class and integrate the Encryptor .

pws_2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 require ' openssl ' require ' fileutils ' class PasswordSafe VERSION = ' 0.0.2 '. freeze def initialize ( filename = File . expand_path (' ~/.pws ') ) @pwfile = filename @pwdata = " example data " @pwhash = Encryptor . hash ' password ' access_safe read_safe end private def read_safe pwdata_encrypted = File . read @pwfile @pwdata = Encryptor . decrypt pwdata_encrypted , @pwhash end def write_safe pwdata_encrypted = Encryptor . encrypt @pwdata , @pwhash File . open ( @pwfile , ' w ' ){ | f | f . write pwdata_encrypted } end def access_safe if ! File . file? @pwfile puts " No password safe detected, creating one at #@pwfile " FileUtils . touch @pwfile write_safe end end class << Encryptor = Module . new CIPHER = ' AES256 ' def decrypt ( data , pwhash ) crypt :decrypt , data , pwhash end def encrypt ( data , pwhash ) crypt :encrypt , data , pwhash end def hash ( plaintext ) OpenSSL :: Digest :: SHA512 . new ( plaintext ). digest end private def crypt ( decrypt_or_encrypt , data , pwhash ) c = OpenSSL :: Cipher . new CIPHER c . send decrypt_or_encrypt . to_sym c . key = pwhash c . update ( data ) << c . final end end end if __FILE__ == $0 pws = PasswordSafe . new ' p2test ' print ' Enter data to encrypt: ' pws . instance_variable_set :@pwdata , gets . chop pws . send :write_safe puts " In safe: " + ( File . read pws . instance_variable_get :@pwfile ). inspect pws = PasswordSafe . new ' p2test ' pws . send :read_safe puts " Read from safe: " + pws . instance_variable_get ( :@pwdata ) end

def initialize( filename = File.expand_path('~/.pws') )

A new password safe is associated with a password file

pwdata_encrypted = File.read @pwfile

Reads the file into a string

File.open( @pwfile, 'w' ){ |f| f.write pwdata_encrypted }

Writes the string into a file

FileUtils.touch @pwfile

Creates an empty file just like the unix tool

pws.instance_variable_set :@pwdata, gets.chop

This is an example of the flexibility of Ruby: Access to all private variables

Also note: The Encryptor module is now within the PasswordSafe scope.

Phase III : Data structure & public api

This phase completes the basic functionality:

The passwords get saved in a hash of the Entry data structure and this hash gets saved in a file using the Marshal class for serializing

class for serializing When retrieving a password, it’s copied to the clipboard

Furthermore, the zucker gem is used to write some pieces of code more cleanly

pws_3 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 require ' rubygems ' if RUBY_VERSION [ 2 ] == ?8 require ' openssl ' require ' fileutils ' require ' clipboard ' require ' zucker/alias_for ' require ' zucker/egonil ' require ' zucker/kernel ' class PasswordSafe VERSION = " 0.0.3 ". freeze Entry = Struct . new :description , :password def initialize ( filename = File . expand_path (' ~/.pws ') ) @pwfile = filename access_safe read_safe end def add ( key , description = nil , password = nil ) @pwdata [ key ] = Entry . new @pwdata [ key ]. password = password || ask_for_password ( " please enter a password for #{key} " ) @pwdata [ key ]. description = description write_safe end aliases_for :add , :a , :set , :create , :update , :[]= def get ( key ) if pw_plaintext = @pwdata [ key ] && @pwdata [ key ]. password Clipboard . copy pw_plaintext puts " The password has been copied to your clipboard " else puts " No password entry found for #{key} " end end aliases_for :get , :g , :entry , :[] def remove ( key ) if @pwdata . delete key puts " #{key} has been removed " else puts " Nothing removed " end end aliases_for :remove , :r , :delete def show puts " Available passwords

" + if @pwdata . empty? ' (none) ' else @pwdata . map { | key , pwentry | " #{key} " + if pwentry . description then " : #{pwentry.description} " else ' ' end }*"

" end end aliases_for :show , :s , :list def description (* keys ) keys . each { | key | puts ( @pwdata [ key ] && @pwdata [ key ]. description ) || key } end def master @pwhash = Encryptor . hash ask_for_password ' please enter a new master password ' write_safe end aliases_for :master , :m private def read_safe pwdata_encrypted = File . read @pwfile pwdata_dump = Encryptor . decrypt ( pwdata_encrypted , @pwhash ) @pwdata = Marshal . load ( pwdata_dump ) || {} end def write_safe pwdata_dump = Marshal . dump @pwdata || {} pwdata_encrypted = Encryptor . encrypt pwdata_dump , @pwhash File . open ( @pwfile , ' w ' ){ | f | f . write pwdata_encrypted } end def access_safe if ! File . file? @pwfile puts " No password safe detected, creating one at #@pwfile " FileUtils . touch @pwfile @pwhash = Encryptor . hash ask_for_password ' please enter a new master password ' write_safe else @pwhash = Encryptor . hash ask_for_password ' master password ' end end def ask_for_password ( prompt = ' new password ') print " #{prompt} : ". capitalize system ' stty -echo ' pw_plaintext = ( $stdin . gets ||' '). chop system ' stty echo ' puts pw_plaintext end class << Encryptor = Module . new CIPHER = ' AES256 ' def decrypt ( data , pwhash ) crypt :decrypt , data , pwhash end def encrypt ( data , pwhash ) crypt :encrypt , data , pwhash end def hash ( plaintext ) OpenSSL :: Digest :: SHA512 . new ( plaintext ). digest end private def crypt ( decrypt_or_encrypt , data , pwhash ) c = OpenSSL :: Cipher . new CIPHER c . send decrypt_or_encrypt . to_sym c . key = pwhash c . update ( data ) << c . final end end end if standalone? pws = PasswordSafe . new ' p3test ' pws . send $* . shift . to_sym , * $* end

require 'rubygems' if RUBY_VERSION[2] == ?8

In 1.8, you need to require rubygems, in 1.9 gem paths are automatically added to your load path

Entry = Struct.new :description, :password

The Struct class is a quick way to generate classes for objects with some simple accessors

pwdata[key].password = password || ask_for_password

Assigns the value of password, unless it’s nil : then ask_for_password gets called instead

aliases_for :add, :a, :[]=, :set, :create

This lines uses my zucker gem to create many aliases for the add method. Without the gem, you have to write alias set add; alias a add; #...

@pwdata && @pwdata[key].password

Only return the description if the entry is present. You could also use the egonil.

pws.send $*.shift.to_sym, *$*

$* contains the command line arguments. We send it to the pws method named by the first argument and pass the remaining ones as method arguments.

Phase IV: Usability & bonus features

In this last step, we add some useful output messages for the user as well as some convenience features:

The password gets copied to the clipboard only for some seconds

Before saving, some redundant data is added to avoid known-plaintext attacks

More helper methods, e.g. generating a random password

#!/usr/bin/env ruby

This is the shebang, so you can now execute the file directly. But you have to make it executable, e.g. with: chmod +x pws

class NoAccess < StandardError; end

Our own ErrorClass that we can present the user instead of some OpenSSL ones

(1..length).map{ chars.sample }.join

This line generates a random password

$*.shift[/^-{0,2}(.*)$/, 1].to_sym

The regex is applied to the string and the “first group” substring is sliced: Allows calling the action with - and -- (because some people are used to it).

if PasswordSafe.public_instance_methods(false).include?( if RubyVersion.is?(1.8) then action.to_s else action end )

Only allow methods that we have defined ourselves

The output is different on 1.8/1.9, so it uses the zucker/version constant RubyVersion . You could also use the standard RUBY_VERSION constant.

Update

Now using the better cbc version of the aes encryption algorithm.

pws :)

There already is a follow-up announcement on a newer version of pws.

The final code is available on github and as gem.