pdb

Simple password manager
Log | Files | Refs

commit 979d53d6c43dd8620bde4ecd2d4e16f2fa4f3887
Author: Michael Savage <mikejsavage@gmail.com>
Date:   Wed, 11 Dec 2013 19:30:16 +0000

Initial commit

Diffstat:
.gitignore | 1+
README.md | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
pdb | 179+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
pdbmenu | 9+++++++++
4 files changed, 245 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1 @@ +.tags diff --git a/README.md b/README.md @@ -0,0 +1,56 @@ +An encrypted password store with dmenu integration. + + +Security +-------- + +It uses AES-256 in CTR mode to encrypt your database, which is fine and +generates your 256 bit secret key from `/dev/random` and IVs from +urandom, which is also fine. + +It prompts you for your password when you are adding it rather than +passing it as a command line argument so people can't grab it from `ps`, +but it doesn't disable console echoing so someone looking over your +shoulder will be able to read your passwords. + + +Requirements +------------ + +lua, luafilesystem, luacrypto, lua-cjson for pdb +xdotool, dmenu for pdbmenu + + +Usage +----- + +pdb requires you to put a shared secret on each computer you want to +use the database on, but the database itself can be given to entities +you don't trust (Dropbox, etc) without revealing your passwords. + +You need to generate a private key for encrypting your password database +with `pdb genkey`. This should be copied manually between computers you +want to keep the database on and not given to anyone else. + +Initialise the database on one of your machines with `pdb init`. You can +then start playing with it (`pdb add`, `pdb list`, etc. type `pdb` by +itself for a full list). + +An example session: + + $ pdb genkey + Generating your private key. Go do something else while we gather entropy. + >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + Done! You should chmod 700 ~/.pdb/key + $ pdb init + Database initialised. + $ pdb add test + Enter a password for test: fdsa + $ pdb list + test + $ pdb get test + fdsa + +pdbmenu pipes `pdb list` to dmenu along with any command line arguments +you gave it, and then `pdb get`s the password you chose and types it for +you. diff --git a/pdb b/pdb @@ -0,0 +1,179 @@ +#! /usr/bin/lua + +-- libs +local crypto = require( "crypto" ) +local lfs = require( "lfs" ) +local json = require( "cjson.safe" ) + +-- config +local Cipher = "aes-256-ctr" +local KeyLength = 32 +local IVLength = 16 + +-- consts +local Help = + "Usage: " .. arg[ 0 ] .. " <command> [args]\n" + .. "where <command> is one of the following:\n" + .. "\n" + .. "genkey - generate a private key for encrypting your passwords\n" + .. "init - creates an initial empty database\n" + .. "add <name> - prompts you to enter a password for <name>\n" + .. "get <name> - prints the password stored under <name>\n" + .. "delete <name> - deleted the password stored under <name>\n" + .. "list - lists the names of all stored passwords\n" + .. "touch - decrypts, sanity checks and reencrypts the database\n" + +local commands = { "genkey", "init", "add", "get", "delete", "list", "touch" } + +for _, command in ipairs( commands ) do + commands[ command ] = true +end + +local dir = os.getenv( "HOME" ) .. "/.pdb/" +local paths = { db = dir .. "db", key = dir .. "key" } + +local random = assert( io.open( "/dev/random", "r" ) ) +local urandom = assert( io.open( "/dev/urandom", "r" ) ) + +-- we might need this later +local passwordToAdd + +-- some helpers +local function check( condition, form, ... ) + if not condition then + io.stdout:write( form:format( ... ) .. "\n" ) + + os.exit( 1 ) + end +end + +local function loadKey() + local file = assert( io.open( paths.key, "r" ) ) + + local key = file:read( "*all" ) + assert( key:len() == KeyLength, "bad key file" ) + + assert( file:close() ) + + return key +end + +local function loadDB( key ) + local file = assert( io.open( paths.db, "r" ) ) + + local contents = assert( file:read( "*all" ) ) + assert( file:close() ) + + local iv, c = contents:match( "^(" .. string.rep( ".", IVLength ) .. ")(.+)$" ) + assert( iv, "Corrupt DB." ) + + local m = crypto.decrypt( Cipher, c, key, iv ) + + return assert( json.decode( m ) ) +end + +local function writeDB( db, key ) + local iv = assert( urandom:read( IVLength ) ) + + local m = assert( json.encode( db ) ) + local c = crypto.encrypt( Cipher, m, key, iv ) + + local file = assert( io.open( paths.db, "w" ) ) + assert( file:write( iv .. c ) ) + assert( file:close() ) +end + +local function genkey() + local rfile = io.open( paths.key, "r" ) + check( not rfile, "You already have a private key." ) + + print( "Generating your private key. Go do something else while we gather entropy." ) + + local key = "" + + while key:len() < KeyLength do + key = key .. assert( random:read( 1 ) ) + + io.write( "\r" ) + for i = 1, 32 do + io.write( i <= key:len() and ">" or "-" ) + end + io.flush() + end + + print( "\a" ) + + local wfile = assert( io.open( dir .. "key", "w" ) ) + assert( wfile:write( key ) ) + assert( wfile:close() ) + + print( "Done! You should chmod 700 ~/.pdb/key" ) + + os.exit( 0 ) +end + +-- real code starts here + +check( arg[ 1 ] and commands[ arg[ 1 ] ], Help ) + +lfs.mkdir( dir ) + +if arg[ 1 ] == "genkey" then + local ok, err = pcall( genkey ) + + check( ok, "genkey failed: %s", err ) + + os.exit( 0 ); +end + +local ok_key, key = pcall( loadKey ) +check( ok_key, "Couldn't private key: %s", key ) + +if arg[ 1 ] == "init" then + local file = io.open( paths.db, "r" ) + check( not file, "You already have a password database." ) + + local ok, err_write = pcall( writeDB, { }, key ) + check( ok, "Couldn't write database: %s", err_write ) + + print( "Database initialised." ) + + os.exit( 0 ) +end + +if arg[ 1 ] == "add" then + check( arg[ 2 ], "add needs a password name." ) + + io.stdout:write( "Enter a password for " .. arg[ 2 ] .. ": " ) + io.stdout:flush() + + passwordToAdd = assert( io.stdin:read( "*l" ) ) + + check( passwordToAdd and passwordToAdd:len() > 0, "Nevermind." ) +end + +local ok_load, db = pcall( loadDB, key ) +check( ok_load, "Couldn't load database: %s", db ) + +if arg[ 1 ] == "get" then + check( arg[ 2 ], "get needs a password name." ) + + if db[ arg[ 2 ] ] then + print( db[ arg[ 2 ] ] ) + end +elseif arg[ 1 ] == "add" then + check( not db[ arg[ 2 ] ], "%s is already in the database", arg[ 2 ] ) + + db[ arg[ 2 ] ] = passwordToAdd +elseif arg[ 1 ] == "delete" then + check( arg[ 2 ], "delete needs a password name." ) + + db[ arg[ 2 ] ] = nil +elseif arg[ 1 ] == "list" then + for k in pairs( db ) do + print( k ) + end +end + +local ok_write, err_write = pcall( writeDB, db, key ) +check( ok_write, "Couldn't write database: %s", err_write ) diff --git a/pdbmenu b/pdbmenu @@ -0,0 +1,9 @@ +#! /bin/sh + +SITE=$(pdb list | dmenu $*) + +if [ "$SITE" != "" ]; then + PW=$(pdb get "$SITE") + + xdotool type "$PW" +fi