Skip to content

Instantly share code, notes, and snippets.

@s-c-p
Last active April 7, 2018 12:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save s-c-p/6d57dd7ff52740afdf7e2542f68eeea9 to your computer and use it in GitHub Desktop.
Save s-c-p/6d57dd7ff52740afdf7e2542f68eeea9 to your computer and use it in GitHub Desktop.
import copy
import hashlib
from lesspass__renderPwd import render_password
DEFAULT_PROFILE = {
"site": "",
"login": "",
"options": {
"uppercase": True,
"lowercase": True,
"digits": True,
"symbols": True,
"length": 16,
"counter": 1
},
"crypto": {
"method": "pbkdf2",
"iterations": 100000,
"keylen": 32,
"digest": "sha256"
}
}
import collections
dictType = collections.Mapping
def get_target_dct(dct, locations):
if len(locations) > 1:
k, *remaining = locations
return get_target_dct(dct[k], remaining)
else:
return dct[locations.pop()]
class UpdateDict:
def __init__(self, master_dict):
self._u = set()
self.dct = copy.deepcopy(master_dict)
self._check_uniqueness(self.dct)
return
def _check_uniqueness(self, dct):
for k, v in dct.items():
if k in self._u:
warn
else:
self._u.add(k)
if isinstance(v, dictType):
self._check_uniqueness(v)
return
def _update(self, *keys_and_val):
"""
>>> l = dict(zip(list('abc'), range(3)))
>>> l['b'] = dict(zip(list('abc'), range(3)))
>>> l['b']['c'] = dict(zip(list('abc'), range(3)))
>>> l['b']['c']['a'] = 100
(b, c, a, 100)
"""
if len(xxx) < 2:
raise NotEnoughInfo
value, *location = xxx[::-1]
location.reverse()
final_key = location.pop()
ptr__target_dct = get_target_dct(location)
ptr__target_dct[final_key] = value
return
def updates(self, *args):
if isinstance(args, list):
for an_update in args:
self._update(an_update)
else:
self._update(args)
return self.dct
def generate_password(user_prefrences, master_password):
final_profile = copy.deepcopy(DEFAULT_PROFILE)
final_profile.update(user_prefrences)
entropy = _calc_entropy(final_profile, master_password)
del master_password
lesspass_password = render_password(entropy, final_profile['options'])
return lesspass_password
def _calc_entropy(profile, master_password):
salt = profile['site'] + profile['login'] + \
str( int(str(profile['options']['counter']), 16) )
entropy = hashlib.pbkdf2_hmac(
password = master_password.encode('utf-8'),
salt = salt.encode('utf-8'),
dklen = profile['crypto']['keylen'],
hash_name = profile['crypto']['digest'],
iterations = profile['crypto']['iterations']
)
return entropy.hex()
def createFingerprint(string):
import hmac
hr = hmac.new(string.encode('utf-8'), digestmod="sha256")
return hr.digest().hex()
def isSupported():
try:
simple_profile = copy.deepcopy(DEFAULT_PROFILE)
simple_profile.update({'crypto': {'iterations': 1 }})
x = generate_password(simple_profile, "LessPass")
assert x == "n'LTsjPA#3E$e*2'"
except Exception as e:
pass
def test_calc_entropy():
master_password = "password"
test_profile = copy.deepcopy(DEFAULT_PROFILE)
# testing line 5
test_profile.update({
"site": "example.org",
"login": "contact@example.org"
})
test_val = _calc_entropy(test_profile, master_password)
assert test_val == "dc33d431bce2b01182c613382483ccdb0e2f66482cbba5e9d07dab34acc7eb1e"
# preparing for test @ line 50
p1 = copy.deepcopy(test_profile)
p2 = copy.deepcopy(test_profile)
# done preparing for test @ line 50
# testing line 29
test_profile["crypto"].update({
"iterations": 8192,
"digest": "sha512",
"keylen": 16
})
test_val = _calc_entropy(test_profile, master_password)
assert test_val == "fff211c16a4e776b3574c6a5c91fd252"
# testing line 50
p1["options"].update({
"counter": 1
})
t1 = _calc_entropy(p1, master_password)
p2["options"].update({
"counter": 2
})
t2 = _calc_entropy(p2, master_password)
assert t1 != t2
return
def test_generate_password():
master_password = "password"
test_profile = copy.deepcopy(DEFAULT_PROFILE)
test_profile.update({
"site": "example.org",
"login": "contact@example.org"
})
test_pwd = generate_password(test_profile, master_password)
assert test_pwd == "WHLpUL)e00[iHR+w"
test_profile["options"].update({
"counter": 2,
"symbols": False,
"length": 14
})
test_pwd = generate_password(test_profile, master_password)
assert test_pwd == "MBAsB7b1Prt8Sl"
assert len(test_pwd) == 14
return
def test_createFingerprint():
assert createFingerprint("password") == "e56a207acd1e6714735487c199c6f095844b7cc8e5971d86c003a7b6f36ef51e"
if __name__ == '__main__':
from tester import run_tests
run_tests()
import sys
import getpass
# RENDER PASSWORD ------------------------------------------------------------
import string
from collections import namedtuple
TransPassword = namedtuple("TransPassword", ["value", "entropy"])
CHAR_SET = {
"digits": string.digits,
"symbols": string.punctuation,
"uppercase": string.ascii_uppercase,
"lowercase": string.ascii_lowercase
}
def _get_active_rules(password_profile):
ORIGINAL_LESSPASS_S_RULE_FILTERATION_ORDER = \
['lowercase', 'uppercase', 'digits', 'symbols']
answer = list()
for a_rule in ORIGINAL_LESSPASS_S_RULE_FILTERATION_ORDER:
if password_profile[a_rule] is True:
answer.append(a_rule)
return answer
def _get_char_set(rules):
ans = str()
for a_rule in rules:
ans += CHAR_SET[a_rule]
return ans
def _getOneCharPerRule(entropy, rules):
ocpr = str()
for rule in rules:
password = consume_entropy("", entropy, CHAR_SET[rule], 1)
ocpr += password.value
entropy = password.entropy
return TransPassword(ocpr, entropy)
def _insertStringPseudoRandomly(generatedPassword, entropy, string):
for char in string:
quotient, remainder = divmod(entropy, len(generatedPassword))
generatedPassword = generatedPassword[0:remainder] + \
char + generatedPassword[remainder:]
entropy = quotient
return generatedPassword
def consume_entropy(generatedPassword, quotient, valid_chars, max_len):
if len(generatedPassword) >= max_len:
return TransPassword(generatedPassword, quotient)
quotient, remainder = divmod(quotient, len(valid_chars))
generatedPassword += valid_chars[remainder]
return consume_entropy(
generatedPassword, quotient, valid_chars, max_len
)
def render_password(entropy, options):
rules = _get_active_rules(options)
char_set = _get_char_set(rules)
password = consume_entropy(
generatedPassword = "",
valid_chars = char_set,
quotient = int(entropy, 16),
max_len = options["length"] - len(rules)
)
chars_to_add = _getOneCharPerRule(password.entropy, rules)
ans = _insertStringPseudoRandomly(
generatedPassword = password.value,
entropy = chars_to_add.entropy,
string = chars_to_add.value
)
return ans
# core -----------------------------------------------------------------------
import copy
import hashlib
DEFAULT_PROFILE = {
"site": "",
"login": "",
"options": {
"uppercase": True,
"lowercase": True,
"digits": True,
"symbols": True,
"length": 16,
"counter": 1
},
"crypto": {
"method": "pbkdf2",
"iterations": 100000,
"keylen": 32,
"digest": "sha256"
}
}
def generate_password(user_prefrences, master_password):
final_profile = copy.deepcopy(DEFAULT_PROFILE)
final_profile.update(user_prefrences)
entropy = _calc_entropy(final_profile, master_password)
del master_password
lesspass_password = render_password(entropy, final_profile['options'])
return lesspass_password
def _calc_entropy(profile, master_password):
salt = profile['site'] + profile['login'] + \
str( int(str(profile['options']['counter']), 16) )
entropy = hashlib.pbkdf2_hmac(
password = master_password.encode('utf-8'),
salt = salt.encode('utf-8'),
dklen = profile['crypto']['keylen'],
hash_name = profile['crypto']['digest'],
iterations = profile['crypto']['iterations']
)
return entropy.hex()
def test_generate_password():
master_password = "password"
test_profile = copy.deepcopy(DEFAULT_PROFILE)
test_profile.update({
"site": "example.org",
"login": "contact@example.org"
})
test_pwd = generate_password(test_profile, master_password)
assert test_pwd == "WHLpUL)e00[iHR+w"
test_profile["options"].update({
"counter": 2,
"symbols": False,
"length": 14
})
test_pwd = generate_password(test_profile, master_password)
assert test_pwd == "MBAsB7b1Prt8Sl"
assert len(test_pwd) == 14
return
# ----------------------------------------------------------------------------
def get_opts():
ans = dict()
curr = DEFAULT_PROFILE["options"]
print("* Password contains uppercase chars")
if input("press n to remove uppercase: ") in ['n', 'N']:
ans['uppercase'] = False
print("* Password contains lowercase")
if input("press n to remove lowercase: ") in ['n', 'N']:
ans['lowercase'] = False
print("* Password contains digits")
if input("press n to remove digits: ") in ['n', 'N']:
ans['digits'] = False
print("* Password contains symbols")
if input("press n to remove symbols: ") in ['n', 'N']:
ans['symbols'] = False
i = input("* Password length (default 16): ")
try: int(i)
except: pass
else: ans['length'] = i
i = input("* Password iteration (default 1st): ")
try: int(i)
except: pass
else: ans['counter'] = i
return ans
def main():
args = sys.argv[1:]
try:
site, login = args
except IndexError:
return
print("Site: ", site)
print("Login: ", login)
i = input(
"""
Press
[q] to quit and start over
[f] to fine tune settings
or just [Enter] to proceed
"""
)
if i in ["q", "Q"]:
return
session_pref = copy.deepcopy(DEFAULT_PROFILE)
session_pref.update({
"site": site,
"login": login
})
if i in ['f', 'F']:
session_pref["options"].update(get_opts())
master_password = getpass.getpass("Enter master password:\n")
ans = generate_password(session_pref, master_password)
del master_password
print("Generate password:")
print(ans)
return
if __name__ == '__main__':
test_generate_password()
main()
"""
Lesspass's render password module implemented in python
this file tries to mimic index.js, all necessary functions are defined in this
file itself
"""
import string
from collections import namedtuple
TransPassword = namedtuple("TransPassword", ["value", "entropy"])
##############################################################################
# file: chars.js
##############################################################################
CHAR_SET = {
"digits": string.digits,
"symbols": string.punctuation,
"uppercase": string.ascii_uppercase,
"lowercase": string.ascii_lowercase
}
def _get_active_rules(password_profile):
# earlier it was just--
# rules = list(filter(lambda k: options[k] is True, options))
# but I was getting strange bugs, first because
# Py's dict key odering problem and the fact illustrated below
ORIGINAL_LESSPASS_S_RULE_FILTERATION_ORDER = \
['lowercase', 'uppercase', 'digits', 'symbols']
# had to give this eye-catchy name because this ARBITARY order decided
# by lesspass authors decides order in which chars appear in
# `valid_chars` of render_password
answer = list()
for a_rule in ORIGINAL_LESSPASS_S_RULE_FILTERATION_ORDER:
if password_profile[a_rule] is True:
answer.append(a_rule)
return answer
def _get_char_set(rules):
ans = str()
for a_rule in rules:
ans += CHAR_SET[a_rule]
return ans
def _getOneCharPerRule(entropy, rules):
ocpr = str()
for rule in rules:
password = consume_entropy("", entropy, CHAR_SET[rule], 1)
ocpr += password.value
entropy = password.entropy
return TransPassword(ocpr, entropy)
def _insertStringPseudoRandomly(generatedPassword, entropy, string):
for char in string:
quotient, remainder = divmod(entropy, len(generatedPassword))
generatedPassword = generatedPassword[0:remainder] + \
char + generatedPassword[remainder:]
entropy = quotient
return generatedPassword
def test_getOneCharPerRule():
# test on-- https://bit.ly/2uxYPAg
test_val = _getOneCharPerRule(26*26, ["lowercase", "uppercase"])
assert test_val.value[:2] == "aA"
assert len(test_val.value) == 2
assert test_val.entropy == 1
return
def test_insertStringPseudoRandomly():
# test on-- https://bit.ly/2GJIBcg
test_val = _insertStringPseudoRandomly("123456", 7*6 + 2, "uT")
assert test_val == "T12u3456"
return
##############################################################################
# file: entropy.js
##############################################################################
def consume_entropy(generatedPassword, quotient, valid_chars, max_len):
if len(generatedPassword) >= max_len:
return TransPassword(generatedPassword, quotient)
quotient, remainder = divmod(quotient, len(valid_chars))
generatedPassword += valid_chars[remainder]
return consume_entropy(
generatedPassword, quotient, valid_chars, max_len
)
def test_consume_entropy():
# test on-- https://bit.ly/2GruTrj
test_val = consume_entropy("", 4*4 + 2, "abcd", 2)
assert test_val.value == "ca"
assert test_val.entropy == 1
return
##############################################################################
# file: index.js
##############################################################################
def render_password(entropy, options):
# see-- return of `https://bit.ly/2pZCCq3` is hexadecimal str only
rules = _get_active_rules(options)
char_set = _get_char_set(rules)
password = consume_entropy(
generatedPassword = "",
valid_chars = char_set,
quotient = int(entropy, 16), # BUG: possibly, cuz quot... is now decimal not 0xDEADBEEF kinda stuff
max_len = options["length"] - len(rules)
)
chars_to_add = _getOneCharPerRule(password.entropy, rules)
ans = _insertStringPseudoRandomly(
generatedPassword = password.value,
entropy = chars_to_add.entropy,
string = chars_to_add.value
)
return ans
def test_render_password():
# fails test-- https://bit.ly/2H11eGv
test_options = {
"length": 16,
"lowercase": True,
"uppercase": True,
"digits": True,
"symbols": True
}
test_entropy = "dc33d431bce2b01182c613382483ccdb0e2f66482cbba5e9d07dab34acc7eb1e"
check_value = render_password(test_entropy, test_options)
assert check_value[0] == "W"
assert check_value[1] == "H"
assert len(check_value) == 16
test_options.update({"length": 20})
check_value = render_password(test_entropy, test_options)
assert len(check_value) == 20
test_options.update({"length": 6})
check_value = render_password(test_entropy, test_options)
assert any([x.islower() for x in check_value])
assert any([x.isupper() for x in check_value])
assert any([x.isdigit() for x in check_value])
assert all(x for x in check_value if x in string.punctuation)
return
if __name__ == '__main__':
from tester import run_tests
run_tests()
import inspect
def run_tests():
fg = inspect.currentframe().f_back.f_globals
g = list(fg.keys())
for name in g:
func = fg[name]
if callable(func) and name.startswith("test"):
print(name, end=" ")
try:
func()
except Exception as e:
print("failed", end="\n\t")
print(repr(e))
else:
print("passed")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment