-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlesspass__renderPwd.py
144 lines (127 loc) · 5.02 KB
/
lesspass__renderPwd.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
"""
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()