#!/usr/bin/python2 # -*- coding: utf-8 -*- import sys import re import hashlib import hmac from base64 import b64encode class PassHash(object): def __init__(self, args): super(PassHash, self).__init__() self.site_tag = args.site_tag self.master_key = args.master_key self.hashword_size = args.length if args.length > 0 and args.length <= 27 else 27 self.require_digit = args.require_digit self.require_punctuation = args.require_punctuation self.require_mixedcase = args.require_mixedcase self.restrict_special = args.restrict_special self.restrict_digits = args.restrict_digits self.password = self.generate_hashword() def generate_hashword(self): s = b64encode(hmac.new(self.master_key, self.site_tag, hashlib.sha1).digest()) if s[-1] == '=': s = s[:-1] sum = 0 for i in s: sum += ord(i) if self.restrict_digits: s = self._convert_to_digits(s, sum) else: if self.require_digit: s = self._inject_special_character(s, 0, 4, sum, 48, 10) if self.require_punctuation and not self.restrict_special: s = self._inject_special_character(s, 1, 4, sum, 33, 15) if self.require_mixedcase: s = self._inject_special_character(s, 2, 4, sum, 65, 26) s = self._inject_special_character(s, 3, 4, sum, 97, 26) if self.restrict_special: s = self._remove_special_characters(s, sum) return s[:self.hashword_size] def _inject_special_character(self, sInput, offset, reserved, seed, cStart, cNum): pos0 = seed % self.hashword_size pos = (pos0 + offset) % self.hashword_size for i in xrange(self.hashword_size - reserved): i2 = (pos0 + reserved + i) % self.hashword_size c = ord(sInput[i2]) if c >= cStart and c < cStart + cNum: return sInput sHead = sInput[:pos] sInject = chr(((seed + ord(sInput[pos])) % cNum) + cStart) sTail = sInput[pos+1:] return sHead + sInject + sTail def _convert_to_digits(self, sInput, seed): s = '' for c in sInput: if not c.isdigit(): s += chr((seed + ord(c)) % 10 + 48) else: s += c return s def _remove_special_characters(self, sInput, seed): s = '' for c in sInput: if not c.isalnum(): s += chr((seed + len(s)) % 26 + 65) else: s += c return s if __name__ == '__main__': import argparse parser = argparse.ArgumentParser(description='Python2 implementation of PassHash') parser.add_argument( 'site_tag', type=str, help='site name') parser.add_argument( 'master_key', type=str, help='master password') parser.add_argument( '-l', '--length', type=int, default=10, help='length of the generated password [max 27]') parser.add_argument( '--no-digits', action='store_false', dest='require_digit', help='do not include at least one digit') parser.add_argument( '--no-punctuation', action='store_false', dest='require_punctuation', help='do not include at least one punctuation symbol') parser.add_argument( '--no-mixedcase', action='store_false', dest='require_mixedcase', help='do not make sure there\'s at least one upper and one lower case char') parser.add_argument( '--no-special', action='store_true', dest='restrict_special', help='exclude non-alphanumeric characters') parser.add_argument( '--digits-only', action='store_true', dest='restrict_digits', help='generate a digits-only password') args = parser.parse_args() print PassHash(args).password