-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhashUp.py
More file actions
147 lines (120 loc) · 4.59 KB
/
hashUp.py
File metadata and controls
147 lines (120 loc) · 4.59 KB
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
145
146
147
# std:
import time
import json
import hashlib
import hmac
import urllib.parse
# pip-int:
import dotsi
# pip-ext:
# n/a
# loc:
import utils
class SignatureInvalidError(Exception):
pass
ms_now = lambda: int(time.time() * 1000)
ms_delta_toDays = lambda msCount: msCount / (1000 * 60 * 60 * 24.0)
# ^-- 1000 ms/sec, 60 sec/min, 60min/hr, 24hr/day.
def buildHasher(saltPrefix="", digestmod=hashlib.sha512):
"Returns a dotsi.Dict() w/ .hash, .check etc. functions."
def hash(msg, salt):
keyBytes = (saltPrefix + salt).encode("utf8")
msgBytes = msg.encode("utf8")
return hmac.HMAC(key=keyBytes, msg=msgBytes, digestmod=digestmod).hexdigest()
def check(expectedHash, msg, salt, digestmod=hashlib.sha512):
return expectedHash == hash(msg, salt)
def signDetached(data, msTs, secret):
tData = {"data": data, "msTs": msTs}
tDataStr = json.dumps(tData)
# print("tDataStr = ", tDataStr);
return hash(tDataStr, secret)
def checkDetachedSign(sig, data, msTs, secret):
return sig == signDetached(data, msTs, secret)
def signWrap(data, secret):
"Signs json-stringifiable `data` using `secret`. Produces wrapped string."
msTs = ms_now()
sig = signDetached(data, msTs, secret)
signWrappedData = {"data": data, "msTs": msTs, "sig": sig}
signWrappedDataStr = json.dumps(signWrappedData)
signWrappedDataStrQ = utils.quote(signWrappedDataStr)
# print("signWrappedDataStr = ", signWrappedDataStr);
return signWrappedDataStrQ
# ^----
# |
# V
def _signUnwrap(signWrappedDataStrQ, secret, maxExpiryInDays=30):
# Note: maxExpiryInDays=30 can be changed w/ each call.
"Unwraps and reads signWrappedDataStr, using secret."
signWrappedDataStr = utils.unquote(signWrappedDataStrQ)
signWrappedData = json.loads(signWrappedDataStr)
# ^-- Failure would raise json.decoder.JSONDecodeError
# Unwrapping:
data = signWrappedData["data"]
# ^-- Failure would raise KeyError
msTs = signWrappedData["msTs"]
sig = signWrappedData["sig"]
# Validate signature:
assert checkDetachedSign(sig, data, msTs, secret)
# ^-- Failure would raise AssertionError
# ==> SIGNATURE FORMAT OK.
msSinceSigned = ms_now() - msTs
# print("msSinceSigned = ", msSinceSigned);
daysSinceSigned = ms_delta_toDays(msSinceSigned)
# print("daysSinceSigned = ", daysSinceSigned);
assert daysSinceSigned <= maxExpiryInDays
# ^-- Failure would raise AssertionError
# ==> SIGNATURE EXPIRY OK.
return dotsi.fy(data) if type(data) is dict else data
def signUnwrap(signWrappedDataStr, secret, maxExpiryInDays=30):
# Note: maxExpiryInDays=30 can be changed w/ each call.
try:
return _signUnwrap(signWrappedDataStr, secret, maxExpiryInDays)
except (json.decoder.JSONDecodeError, KeyError, AssertionError) as e:
raise SignatureInvalidError("Supplied signature is invalid.")
# Export:
return dotsi.fy(
{
"hash": hash,
"check": check,
"signDetached": "signDetached",
"signWrap": signWrap,
"_signUnwrap": _signUnwrap,
"signUnwrap": signUnwrap,
"SignatureInvalidError": SignatureInvalidError,
# ^-- Exported the error class, for access via built dotsiDict.
}
)
defaultHasher = buildHasher()
# Default hasher.
def test_quick():
h = buildHasher()
msg = "This is a test message."
salt = "sample_salt"
hashedMsg = h.hash(msg, salt)
assert h.check(hashedMsg, msg, salt)
assert not h.check(hashedMsg + "--edit", msg, salt)
secret = "super_secret"
# It's just another salt.
signedMsg = h.signWrap(msg, secret)
assert h.signUnwrap(signedMsg, secret) == msg
badSignDetected = False
# Not yet detected.
try:
h.signUnwrap(signedMsg + "--edit", secret)
except SignatureInvalidError as e:
badSignDetected = True
assert badSignDetected
badSignDetected = False
# Again, not yet detected.
time.sleep(0.01)
# ^-- Letting time pass between creating and reading signed data.
try:
h.signUnwrap(signedMsg, secret, maxExpiryInDays=0)
# ^-- Then checking if sign has expired, expect to be expired.
except SignatureInvalidError as e:
badSignDetected = True
assert badSignDetected
# TODO: Write more tests.
if __name__ == "__main__":
test_quick()
print("Tests ran successfully.")