Skip to content

Instantly share code, notes, and snippets.

@eur0pa
Created August 1, 2015 19:58
Show Gist options
  • Save eur0pa/ec1685f0e75a294f1c06 to your computer and use it in GitHub Desktop.
Save eur0pa/ec1685f0e75a294f1c06 to your computer and use it in GitHub Desktop.

Revisions

  1. europa created this gist Aug 1, 2015.
    143 changes: 143 additions & 0 deletions PassHash.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,143 @@
    #!/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