forked from EnAccess/OpenPAYGO-python
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtoken_shared.py
More file actions
132 lines (112 loc) · 4.18 KB
/
Copy pathtoken_shared.py
File metadata and controls
132 lines (112 loc) · 4.18 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
import codecs
import struct
import siphash
class TokenType(object):
ADD_TIME = 1
SET_TIME = 2
DISABLE_PAYG = 3
COUNTER_SYNC = 4
INVALID = 10
ALREADY_USED = 11
class OpenPAYGOTokenShared(object):
MAX_BASE = 999
MAX_ACTIVATION_VALUE = 995
PAYG_DISABLE_VALUE = 998
COUNTER_SYNC_VALUE = 999
TOKEN_VALUE_OFFSET = 1000
@classmethod
def get_token_base(cls, code):
return int(code) % cls.TOKEN_VALUE_OFFSET
@classmethod
def put_base_in_token(cls, token, token_base):
if token_base > cls.MAX_BASE:
Exception("INVALID_VALUE")
return token - cls.get_token_base(token) + token_base
@classmethod
def generate_next_token(cls, last_code, key):
conformed_token = struct.pack(">L", last_code) # We convert the token to bytes
conformed_token += conformed_token # We duplicate it to fit the minimum length
token_hash = cls.generate_hash(key, conformed_token) # We hash it
new_token = cls.convert_hash_to_token(
token_hash
) # We convert to token and return
return new_token
@classmethod
def convert_hash_to_token(cls, this_hash):
hash_int = struct.pack(">Q", this_hash) # We convert the hash to bytes
hi_hash = struct.unpack(">L", hash_int[0:4])[0] # We split it in two 32bits INT
lo_hash = struct.unpack(">L", hash_int[4:8])[0]
result_hash = (
hi_hash ^ lo_hash
) # We XOR the two together to get a single 32bits INT
token = cls._convert_to_29_5_bits(
result_hash
) # We convert the 32bits value to an INT no greater than 9 digits
return token
@classmethod
def generate_starting_code(cls, key):
# We make a hash of the key
starting_hash = OpenPAYGOTokenShared.generate_hash(key, key)
return OpenPAYGOTokenShared.convert_hash_to_token(starting_hash)
@classmethod
def load_secret_key_from_hex(cls, secret_key):
if isinstance(secret_key, (bytes, bytearray)):
secret_key_bytes = bytes(secret_key)
if len(secret_key_bytes) != 16:
raise ValueError(
"The secret key provided is not correctly formatted, it should be "
"16 "
"bytes. "
)
return secret_key_bytes
try:
decoded = codecs.decode(secret_key, "hex")
except Exception:
raise ValueError(
"The secret key provided is not correctly formatted, it should be 32 "
"hexadecimal characters. "
)
if len(decoded) != 16:
raise ValueError(
"The secret key provided is not correctly formatted, it should be 32 "
"hexadecimal characters. "
)
return decoded
@classmethod
def _convert_to_29_5_bits(cls, source):
mask = ((1 << (32 - 2 + 1)) - 1) << 2
temp = (source & mask) >> 2
if temp > 999999999:
temp = temp - 73741825
return temp
@classmethod
def convert_to_4_digit_token(cls, source):
restricted_digit_token = ""
bit_array = cls._bit_array_from_int(source, 30)
for i in range(15):
this_array = bit_array[i * 2 : (i * 2) + 2]
restricted_digit_token += str(cls._bit_array_to_int(this_array) + 1)
return int(restricted_digit_token)
@classmethod
def convert_from_4_digit_token(cls, source):
bit_array = []
for digit in str(source):
digit = int(digit) - 1
this_array = cls._bit_array_from_int(digit, 2)
bit_array += this_array
return cls._bit_array_to_int(bit_array)
@classmethod
def generate_hash(cls, key, value):
return siphash.SipHash_2_4(key, value).hash()
@classmethod
def _bit_array_to_int(cls, bit_array):
integer = 0
for bit in bit_array:
integer = (integer << 1) | bit
return integer
@classmethod
def _bit_array_from_int(cls, source, bits):
bit_array = []
for i in range(bits):
bit_array += [bool(source & (1 << (bits - 1 - i)))]
return bit_array