PWGen - a simple, secure password generator

In short

Download or use online

Notes: the Android .apk is unsigned, so it asks for your permission to install an unknown application and warns that the application is not checked. The browser version executes JavaScript to generate your password 'on the fly', nothing it stored in your browser and nothing is submitted to the server.

Description

PWGen is an Android and web app to generate passwords based on user-provided input. You enter an app name (a name you give the application or website you want to access) and a password, PWGen then generates a password using a simple algorithm based on a hashing mechanism (SHA-256). This way, all you have to do is remember the name for the app and a single password - there's no need to store the generated password anywhere, and none of the data you provide is stored either in the app or web storage, nor on any server.

The generated passwords have a high entropy ('randomness') and consist of 4 lower-case characters, 4 upper-case characters, 4 digits and 4 punctuation characters, so they should satisfy even the most demading password checks.

The algorithm

For those interested: the algorithm is quite simple: create an HMAC with the password as key and the app name as data, then walk through the result in pairs, using the first char of the pair to determine what set of characters is used (lowercase, uppercase, digit or punctuation), then the second char is used to determine the character of that set. Below is a fully working implementation in Python (used because it's easy to read, but the mechanism should work in all languages that have an HMAC/SHA-256 library - the app, for instance, uses JavaScript):

import hmac
import hashlib
import string
import getpass

appname = input('App name: ')
base_pw = getpass('Base password: ')

h = hmac.new(
    base_pw.encode('utf-8'),
    appname.encode('utf-8'),
    digestmod=hashlib.sha256)
hmac_result = h.digest()

charlists = [
    string.ascii_uppercase, string.ascii_lowercase,
    string.digits, string.punctuation]

passwordchars = []
charlists = None
for i in range(16):
    if not charlists:
        charlists = [
            string.ascii_uppercase, string.ascii_lowercase,
            string.digits, string.punctuation]
    # we could use charlists.pop(0) if i % 8 == 0, but now we have less code
    charlist = charlists.pop(hmac_result[i * 2] % len(charlists))
    char = charlist[hmac_result[i * 2 + 1] % len(charlist)]
    passwordchars.append(char)

print('Generated password:', ''.join(passwordchars)