From f843df2d7a3bcd17a5ea69c5807aac0631b7da4c Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Mon, 28 Apr 2008 09:34:56 -0700 Subject: [PATCH] initial commit --- README | 39 +++++++++++++++++++ hmac.rb | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ passy | 72 +++++++++++++++++++++++++++++++++++ 3 files changed, 225 insertions(+) create mode 100644 README create mode 100644 hmac.rb create mode 100755 passy diff --git a/README b/README new file mode 100644 index 0000000..06fffa0 --- /dev/null +++ b/README @@ -0,0 +1,39 @@ +About Passy +=========== +passy solves the problem of managing secure, unique passwords. + +passy does NOT store anything to any disk. This means if your disk crashes +you will not lose ANY of your passwords. + +passy requires you to keep one, private, secret passkey. All of your unique +passwords are generated from this one passkey. + +Actual passwords are generated by HMAC-SHA1. For example, to calculate your +password for a certain website, passy uses something similar to: + + hmac_sha1('your secret key','slashdot.org') + +passy provides four passwords for each 'site'. This way, if your password +is ever expired, as some systems stupidly do, you can rotate to the next. + +The 'site' doesn't have to be an actual web site, of course, it could be +anything you want, like "bank password", "work login", etc. + +NOTE: The list of Sites is currently stored directly in the script! + + +Ideas for Improvement +===================== +* make variant passwords use more features (mixes in case, etc.). +* use optionparser (or similar) to clean up the CLI +* add ability to ask for a specific site +* add ability to cache master key +* use termios (or similar) to clean up the tty - but retain Windows compat +* gemify it (and gemify my simple hmac separately?) +* have passy look for ~/.passysites for the list of sites +* wrap in a multi-platform GUI so idiots can use it too (wxRuby, RubyQT, FX, etc.) +* add a "change master password" mode that will prompt for two passwords, and + provide the results for both [after which the user much change their password + at every site, etc.] +* store state as to which variant is current at each site + diff --git a/hmac.rb b/hmac.rb new file mode 100644 index 0000000..333dae6 --- /dev/null +++ b/hmac.rb @@ -0,0 +1,114 @@ +#!/usr/bin/env ruby + +# +# Copyright (c) 2008 Stephen Waits +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +require 'digest/md5' +require 'digest/rmd160' +require 'digest/sha1' +require 'digest/sha2' + +# helper to make a 'length' long string filled with 'byte' +def byte_string(byte, length) + ([byte]*length).pack('C*') +end + +# hmac algorithm - returns text version of hmac +def hmac(key, data, digest_object, blocksize) + # if key is too long, hash it + key = digest_object.digest(key) if key.size > blocksize + + # pad key out to blocksize + key += byte_string(0,blocksize - key.size) if key.size < blocksize + + # create pads + ipad = byte_string(0x36,blocksize) + opad = byte_string(0x5c,blocksize) + blocksize.times { |i| ipad[i] ^= key[i]; opad[i] ^= key[i] } + + # do the HMAC and return + digest_object.hexdigest( opad + digest_object.digest( ipad + data ) ) +end + +# hmac-md5 +def hmac_md5(key, data) + hmac(key, data, Digest::MD5, 64) +end + +# hmac-rmd160 +def hmac_rmd160(key, data) + hmac(key, data, Digest::RMD160, 64) +end + +# hmac-sha1 +def hmac_sha1(key, data) + hmac(key, data, Digest::SHA1, 64) +end + +# hmac-sha256 +def hmac_sha256(key, data) + hmac(key, data, Digest::SHA256, 64) +end + +# hmac-sha384 +def hmac_sha384(key, data) + hmac(key, data, Digest::SHA384, 64) +end + +# hmac-sha512 +def hmac_sha512(key, data) + hmac(key, data, Digest::SHA512, 128) +end + +# test vectors courtesy of RFC2202 +TestVectorsHMACMD5 = [ + [ byte_string(0xb,16),'Hi There','9294727a3638bb1c13f48ef8158bfc9d' ], + [ 'Jefe','what do ya want for nothing?','750c783e6ab0b503eaa86e310a5db738' ], + [ byte_string(0xaa,16),byte_string(0xdd,50),'56be34521d144c88dbb8c733f0e8b3f6' ], + [ [1,2,3,4,5,6,7,8,9,0xa,0xb,0xc,0xd,0xe,0xf,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19].pack('C*'),byte_string(0xcd,50),'697eaf0aca3a3aea3a75164746ffaa79' ], + [ byte_string(0xc,16),'Test With Truncation','56461ef2342edc00f9bab995690efd4c' ], + [ byte_string(0xaa,80),'Test Using Larger Than Block-Size Key - Hash Key First','6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd' ], + [ byte_string(0xaa,80),'Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data','6f630fad67cda0ee1fb1f562db3aa53e' ], +] +TestVectorsHMACSHA1 = [ + [ byte_string(0xb,20),'Hi There','b617318655057264e28bc0b6fb378c8ef146be00' ], + [ 'Jefe','what do ya want for nothing?', 'effcdf6ae5eb2fa2d27416d5f184df9c259a7c79' ], + [ byte_string(0xaa,20),byte_string(0xdd,50), '125d7342b9ac11cd91a39af48aa17b4f63f175d3' ], + [ [1,2,3,4,5,6,7,8,9,0xa,0xb,0xc,0xd,0xe,0xf,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19].pack('C*'),byte_string(0xcd,50),'4c9007f4026250c6bc8414f9bf50c86c2d7235da' ], + [ byte_string(0xc,20),'Test With Truncation','4c1a03424b55e07fe7f27be1d58bb9324a9a5a04' ], + [ byte_string(0xaa,80),'Test Using Larger Than Block-Size Key - Hash Key First','aa4ae5e15272d00e95705637ce8a3b55ed402112' ], + [ byte_string(0xaa,80),'Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data','e8e99d0f45237d786d6bbaa7965c7808bbff1a91' ], +] + +# test all of the above vectors +def hmac_test + TestVectorsHMACMD5.each do |t| + key,data,result = t + return false if hmac_md5(key,data) != result + end + TestVectorsHMACSHA1.each do |t| + key,data,result = t + return false if hmac_sha1(key,data) != result + end + return true +end + diff --git a/passy b/passy new file mode 100755 index 0000000..cbc7dec --- /dev/null +++ b/passy @@ -0,0 +1,72 @@ +#!/usr/bin/env ruby + +# +# Copyright (c) 2008 Stephen Waits +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +require 'hmac' + +# list of sites we visit +Sites = %w( + paypal.com + boardgamegeek.com + google.com + mahaenergy.com + ma.gnolia.com +) + +# make sure our hmac is working properly +if !hmac_test + puts 'ERROR: HMAC test failed!' + exit 1 +end + +# make sure no arguments specified (as these will mess up 'gets' below) +if ARGV.size > 0 + puts 'ERROR: Do not specify any arguments.' + exit 1 +end + +# prompt for password +print ' Enter Password: ' +Key = gets.chomp +print 'Verity Password: ' +if Key != gets.chomp + puts 'Error: passwords do not match.' + exit 1 +end + +# helper to split an HMAC-SHA1 into four 10-character keys +def make_passwords(key,site) + hmac_sha1(key,site.downcase).unpack('A10A10A10A10') +end + +# output report +puts +Format = '%-30s %10s %10s %10s %10s' +puts Format % [ 'Site', 'Password 1', 'Password 2', 'Password 3', 'Password 4' ] +puts Format % [ '-'*30, '----------', '----------', '----------', '----------' ] +Sites.sort.each do |s| + p = make_passwords(Key,s) + puts Format % [ s, p[0], p[1], p[2], p[3] ] +end + +