My password "system" used to be that I had three different passwords, all of which were variations on the same theme. I maintained a list of sites I had accounts on and for each site gave a hint which of the three passwords I used. What a terrible scheme.
A couple weeks ago I decided to do something about it. I wanted, above all, to only have to remember a single password. Being lately security-conscious, I also recognized the need for a unique password on every site.
In this post I'll show how I used python to create a password management system that allows me to use a single "master" password to generate unique passwords for all the sites and services I use.
I realized I could use a hash function to transform the unique combination of a service (like "reddit") and my master password ("p@ssw0rd") to create a unique password that would not be susceptible to dictionary-style cracking. Using python, this was super easy.
The way it works is:
The first version I wrote was a simple command-line utility. I realized that I would need something web-based so I could generate passwords from my phone, but for simplicity I'll just describe the basic python code and leave it to you to build a webservice if you're interested (you could use flask!).
hashlib module provides implementations of several cryptographic
hash functions, including SHA-256. A
first stab at generating the password might look like this:
>>> from hashlib import sha256 >>> def get_hexdigest(salt, password): ... return sha256(salt + password).hexdigest()
Here is what it looks like when I try to generate my password for reddit:
>>> get_hexdigest('reddit', 'p@ssw0rd') 'afb0e072194a3f4e8ed39290e16bc2f458426714fc308dc28565d6205453eaa5'
OK, cool, this is a good start -- the
get_hexdigest function takes two
strings and generates a hex representation of the hash. Since the master password
and service name may be guessable, I want to add just a little extra magic to
the mix in the form of a secret key, which will be combined with the service
name and master password.
Here is the "improved" version:
def get_hexdigest(salt, plaintext): return sha256(salt + plaintext).hexdigest() SECRET_KEY = 's3cr3t' def make_password(plaintext, service): salt = get_hexdigest(SECRET_KEY, service)[:20] hsh = get_hexdigest(salt, plaintext) return ''.join((salt, hsh))
This is an improvement, but there are still problems. It generates really long passwords that are made of '0-9' and 'a-f' lowercase. Since some sites require a mixture of upper and lower-case, numbers or symbols, we need to convert this hex digest into something suitable for a password.
The solution I came up with was to convert the hexadecimal representation into a base-N number, then treat the digits of the base-N number as indices into an alphabet of valid characters. This alphabet would contain all characters that comprise a valid password and might be:
This comes out to a total of 74 characters. Here is how I convert the hexadecimal number into a base-74 representation using my special alphabet:
ALPHABET = ('abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' '0123456789!@#$%^&*()-_') def password(plaintext, service, length=10, alphabet=ALPHABET): raw_hexdigest = make_password(plaintext, service) # Convert the hexdigest into decimal num = int(raw_hexdigest, 16) # What base will we convert `num` into? num_chars = len(alphabet) # Build up the new password one "digit" at a time, # up to a certain length chars =  while len(chars) < length: num, idx = divmod(num, num_chars) chars.append(alphabet[idx]) return ''.join(chars)
Here is what the generated passwords look like now:
>>> password('p@ssw0rd', 'reddit') '&7Ulu!UXdj'
Note that even one small change will completely alter the generated password:
>>> password('p@ssw0rD', 'reddit') '^Zv3a&uxCG' >>> password('p@ssw0rd', 'Reddit') '_!p(v0mS*V'
This seems like a pretty good scheme, but there are a couple problems:
from peewee import * db = SqliteDatabase('accounts.db') class Service(Model): name = CharField() length = IntegerField(default=8) symbols = BooleanField(default=True) alphabet = CharField(default='') class Meta: database = db # create the table if it does not exist db.create_table(Service, True)
As you can see from the class definition, I am storing the name of the
service, how long the generated password should be,
whether we can include symbols, and lastly an override in case we want to
specify a custom "alphabet" from which to generate the passwords. I've also
implemented some convenience methods on the
class Service(Model): name = CharField() length = IntegerField(default=8) symbols = BooleanField(default=True) alphabet = CharField(default='') class Meta: database = db def get_alphabet(self): if self.alphabet: return self.alphabet alpha = ('abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' '0123456789') if self.symbols: alpha += '!@#$%^&*()-_' return alpha def password(self, plaintext): return password(plaintext, self.name, self.length, self.get_alphabet()) @classmethod def search(cls, q): # A convenient way to search for services by name. return cls.select().where(cls.name ** ('%%%s%%' % q))
This is how I would interact with the new service class:
>>> reddit = Service.create(name='reddit', length=8, symbols=True) >>> reddit.password('p@ssw0rd') '&7Ulu!UX'
I didn't realize how much I use the phone to browse the web until I implemented this password scheme. At first it was a pain trying to get my phone's browser logged into the various sites I used.
I decided to build out a web-based interface which allows me to manage my list of services and generate passwords. If you do this, here are a couple recommendations:
Thanks for taking the time to read this post, I hope it was interesting. Please feel free to leave any suggestions or comments below.
I choose a similar (albeit much simpler path) with
just to suggest that adding a Chrome extension really made my day!
Commenting has been closed, but please feel free to contact me