-
Notifications
You must be signed in to change notification settings - Fork 0
/
generator.py
147 lines (107 loc) · 5.19 KB
/
generator.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
145
146
import yaml
import base64
import struct
import skip32
import code128
import os
class BarcodeNumberGenerator:
def __init__(self, yaml_file):
with open(yaml_file, 'r') as stream:
self.badge_types = yaml.load(stream)
def generate_csv(self, filename):
badge_types = self.badge_types['badge_types']
lines = []
for badge_type, ranges in badge_types.items():
range_start = int(ranges['range_start'])
range_end = int(ranges['range_end'])
lines = lines + self.generate_barcode_nums(range_start, range_end)
f = open(filename,'w')
for line in lines:
f.write(line + os.linesep)
f.close()
def generate_barcode_nums(self, range_start, range_end):
generated_lines = []
seen_barcodes = []
for badge_num in range(range_start, range_end+1):
barcode_num = BarcodeNumberGenerator.generate_barcode_from_badge_num(
badge_num=int(badge_num),
event_id=self.event_id,
salt=self.salt,
key=self.secret_key
)
line = "{badge_num},{barcode_num}".format(
badge_num=badge_num,
barcode_num=barcode_num,
)
generated_lines.append(line)
# ensure that we haven't seen this value before
if barcode_num in seen_barcodes:
raise ValueError('COLLISION: generated a badge# that\'s already been seen')
else:
seen_barcodes.append(barcode_num)
return generated_lines
@staticmethod
def generate_barcode_from_badge_num(badge_num, event_id, salt, key):
# packed data going to be encrypted is:
# byte 1 - 8bit event ID, usually 1 char
# byte 2,3,4 - 24bit badge number
salted_val = badge_num + (0 if not salt else salt)
if salted_val > 0xFFFFFF:
raise Exception("badge_number is too high " + badge_num)
data_to_encrypt = struct.pack('>BI', event_id, salted_val)
# remove the highest byte in that integer (2nd byte)
data_to_encrypt = bytearray([data_to_encrypt[0], data_to_encrypt[2], data_to_encrypt[3], data_to_encrypt[4]])
if len(data_to_encrypt) != 4:
raise Exception("data to encrypt should be 4 bytes")
if len(key) != 10:
raise Exception("key length should be exactly 10 bytes")
encrypted_string = encrypt(data_to_encrypt, key=key)
# check to make sure it worked.
decrypted = BarcodeNumberGenerator.get_badge_num_from_barcode(encrypted_string, salt, key)
if decrypted['badge_num'] != badge_num or decrypted['event_id'] != event_id:
raise Exception("didn't encode correctly")
# check to make sure this barcode number is valid for Code 128 barcode
BarcodeNumberGenerator.verify_barcode_is_valid_code128(encrypted_string)
return encrypted_string
@staticmethod
def verify_barcode_is_valid_code128(encrypted_string):
for c in encrypted_string:
if c not in code128._charset_b:
raise Exception("contains a char not valid in a code128 barcode")
@staticmethod
def get_badge_num_from_barcode(barcode_num, salt, key):
decrypted = decrypt(barcode_num, key=key)
result = dict()
result['event_id'] = struct.unpack('>B', bytearray([decrypted[0]]))[0]
badge_bytes = bytearray(bytes([0, decrypted[1], decrypted[2], decrypted[3]]))
result['badge_num'] = struct.unpack('>I', badge_bytes)[0] - salt
return result
def encrypt(value, key):
# skip32 generates 4 bytes output from 4 bytes input
_encrypt = True
skip32.skip32(key, value, _encrypt)
# raw bytes aren't suitable for a Code 128 barcode though,
# so convert it to base58 encoding
# which is just some alphanumeric and numeric chars and is
# designed to be vaguely human. this takes our 4 bytes and turns it into 11ish bytes
encrypted_value = base64.encodebytes(value).decode('ascii')
# important note: because we are not an even multiple of 3 bytes, base64 needs to pad
# the resulting string with equals signs. we can strip them out knowing that our length is 4 bytes
# IF YOU CHANGE THE LENGTH OF THE ENCRYPTED DATA FROM 4 BYTES, THIS WILL NO LONGER WORK.
encrypted_value = encrypted_value.replace('==\n', '')
return encrypted_value
def decrypt(value, key):
# raw bytes aren't suitable for a Code 128 barcode though,
# so convert it to base64 encoding
# which is just some alphanumeric and numeric chars and is
# designed to be vaguely human. this takes our 4 bytes and turns it into 6ish bytes
# important note: because we are not an even multiple of 3 bytes, base64 needs to pad
# the resulting string with equals signs. we can strip them out knowing that our length is 4 bytes
# IF YOU CHANGE THE LENGTH OF THE ENCRYPTED DATA FROM 4 BYTES, THIS WILL NO LONGER WORK.
value += '==\n'
decoded = base64.decodebytes(value.encode('ascii'))
# skip32 generates 4 bytes output from 4 bytes input
_encrypt = False
decrytped = bytearray(decoded)
skip32.skip32(key, decrytped, _encrypt)
return decrytped