des-PurePy 1.0.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- des_PurePy/__init__.py +3 -0
- des_PurePy/constants.py +499 -0
- des_PurePy/des.py +382 -0
- des_purepy-1.0.6.dist-info/METADATA +53 -0
- des_purepy-1.0.6.dist-info/RECORD +7 -0
- des_purepy-1.0.6.dist-info/WHEEL +4 -0
- des_purepy-1.0.6.dist-info/licenses/LICENSE +661 -0
des_PurePy/des.py
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
# Luna Perez-Herrera
|
|
2
|
+
# GNU AGPLv3
|
|
3
|
+
|
|
4
|
+
"""Module providing a DES class for a DES implementation"""
|
|
5
|
+
|
|
6
|
+
from . import constants
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DES:
|
|
10
|
+
"""Class for DES operations. Can encrypt or decrypt an input according to `key`
|
|
11
|
+
|
|
12
|
+
Parameters
|
|
13
|
+
----------
|
|
14
|
+
key : hex string or bytes object
|
|
15
|
+
Key to be used for encrypting or decrypting.
|
|
16
|
+
Can be a string representing a hex number or a bytes object.
|
|
17
|
+
Hex strings can have a leading '0x' and are case-insensitive.
|
|
18
|
+
`key` should be exactly the key size for DES (64 bits)
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
# pylint: disable=too-many-instance-attributes
|
|
22
|
+
# I could have used a a getter for each constant but I honestly didn't feel like it
|
|
23
|
+
|
|
24
|
+
def __init__(self, key):
|
|
25
|
+
"""Initializes a DES object with specified `key`
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
key : hex string or bytes object
|
|
30
|
+
Key to be used for encrypting or decrypting.
|
|
31
|
+
Can be a string representing a hex number or a bytes object.
|
|
32
|
+
Hex strings can have a leading '0x' and are case-insensitive.
|
|
33
|
+
`key` should be exactly the key size for DES (64 bits)
|
|
34
|
+
"""
|
|
35
|
+
if self.__is_bytes_object(key):
|
|
36
|
+
key = self.__bytes_object_to_hex(key)
|
|
37
|
+
|
|
38
|
+
self.__validate_key(key)
|
|
39
|
+
self.key = self.__format_key(key)
|
|
40
|
+
"""Key to be used for encrypting or decrypting.
|
|
41
|
+
Can be a string representing a hex number or a bytes object.
|
|
42
|
+
Hex strings can have a leading '0x' and are case-insensitive.
|
|
43
|
+
`key` should be exactly the key size for DES (64 bits)
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
self.__pc1 = constants.des_pc1()
|
|
47
|
+
self.__pc2 = constants.des_pc2()
|
|
48
|
+
self.__shifts = constants.des_shifts()
|
|
49
|
+
self.__ip = constants.des_ip()
|
|
50
|
+
self.__fp = constants.des_fp()
|
|
51
|
+
self.__ep = constants.des_ep()
|
|
52
|
+
self.__pf = constants.des_pf()
|
|
53
|
+
self.__sboxes = constants.des_sboxes()
|
|
54
|
+
self.__round_keys = self.__generate_round_keys()
|
|
55
|
+
self.__round_keys_reversed = self.__round_keys[::-1]
|
|
56
|
+
|
|
57
|
+
def encrypt(self, plaintext) -> str:
|
|
58
|
+
"""Encrypts the input plaintext by `key` according to DES
|
|
59
|
+
|
|
60
|
+
Parameters
|
|
61
|
+
----------
|
|
62
|
+
plaintext : hex string or bytes object
|
|
63
|
+
Plaintext to be encrypted.
|
|
64
|
+
Can be a string representing a hex number or a bytes object.
|
|
65
|
+
Hex strings can have a leading '0x' and are case-insensitive.
|
|
66
|
+
`plaintext` can be any length.
|
|
67
|
+
|
|
68
|
+
Returns
|
|
69
|
+
-------
|
|
70
|
+
ciphertext : str
|
|
71
|
+
Encrypted ciphertext as a hex number. Has a leading '0x'.
|
|
72
|
+
Length can vary according to length of input.
|
|
73
|
+
Will always be a multiple of the block size.
|
|
74
|
+
"""
|
|
75
|
+
if self.__is_bytes_object(plaintext):
|
|
76
|
+
plaintext = self.__bytes_object_to_hex(plaintext)
|
|
77
|
+
|
|
78
|
+
self.__validate_input(plaintext)
|
|
79
|
+
plaintext = self.__format_input(plaintext)
|
|
80
|
+
blocks = self.__split_blocks_encrypt(plaintext)
|
|
81
|
+
blocks[-1] = self.__pad_block(blocks[-1])
|
|
82
|
+
|
|
83
|
+
ciphertext = []
|
|
84
|
+
for block in blocks:
|
|
85
|
+
ciphertext.append(self.__encrypt_block(block))
|
|
86
|
+
|
|
87
|
+
return "0x" + "".join(ciphertext)
|
|
88
|
+
|
|
89
|
+
def decrypt(self, ciphertext) -> str:
|
|
90
|
+
"""Decrypts the input ciphertext by `key` according to DES
|
|
91
|
+
|
|
92
|
+
Parameters
|
|
93
|
+
----------
|
|
94
|
+
ciphertext : hex string or bytes object
|
|
95
|
+
Ciphertext to be encrypted.
|
|
96
|
+
Can be a string representing a hex number or a bytes object.
|
|
97
|
+
Hex strings can have a leading '0x' and are case-insensitive.
|
|
98
|
+
`ciphertext` length should be a multiple of the block size (64 bits)
|
|
99
|
+
|
|
100
|
+
Returns
|
|
101
|
+
-------
|
|
102
|
+
cleartext : str
|
|
103
|
+
Decrypted cleartext as a hex number. Has a leading '0x'.
|
|
104
|
+
"""
|
|
105
|
+
# Ciphertext should be a hex string or bytes object
|
|
106
|
+
if self.__is_bytes_object(ciphertext):
|
|
107
|
+
ciphertext = self.__bytes_object_to_hex(ciphertext)
|
|
108
|
+
|
|
109
|
+
self.__validate_input(ciphertext)
|
|
110
|
+
self.__validate_ciphertext(ciphertext)
|
|
111
|
+
ciphertext = self.__format_input(ciphertext)
|
|
112
|
+
blocks = self.__split_blocks_decrypt(ciphertext)
|
|
113
|
+
|
|
114
|
+
cleartext = []
|
|
115
|
+
for block in blocks:
|
|
116
|
+
cleartext.append(self.__decrypt_block(block))
|
|
117
|
+
|
|
118
|
+
cleartext[-1] = self.__strip_padding(cleartext[-1])
|
|
119
|
+
|
|
120
|
+
return "0x" + "".join(cleartext)
|
|
121
|
+
|
|
122
|
+
def __encrypt_block(self, plaintext: str) -> str:
|
|
123
|
+
plaintext = self.__format_block(plaintext)
|
|
124
|
+
|
|
125
|
+
# Preform initial permutation
|
|
126
|
+
plaintext = self.__permute(plaintext, self.__ip)
|
|
127
|
+
|
|
128
|
+
# Split into two halves
|
|
129
|
+
l = [plaintext[:32]]
|
|
130
|
+
r = [plaintext[32:]]
|
|
131
|
+
|
|
132
|
+
# Perform 16 rounds of DES
|
|
133
|
+
for i in range(16):
|
|
134
|
+
l.append(r[i])
|
|
135
|
+
r.append(self.__xor(l[i], self.__f(r[i], self.__round_keys[i])))
|
|
136
|
+
|
|
137
|
+
# Preform final permutation
|
|
138
|
+
ciphertext = self.__permute(r[16] + l[16], self.__fp)
|
|
139
|
+
|
|
140
|
+
return self.__format_binary_string_as_hex(ciphertext)
|
|
141
|
+
|
|
142
|
+
def __decrypt_block(self, ciphertext: str) -> str:
|
|
143
|
+
ciphertext = self.__format_block(ciphertext)
|
|
144
|
+
|
|
145
|
+
# Perform intial permutation
|
|
146
|
+
ciphertext = self.__permute(ciphertext, self.__ip)
|
|
147
|
+
|
|
148
|
+
# Split into two halves
|
|
149
|
+
l = [ciphertext[:32]]
|
|
150
|
+
r = [ciphertext[32:]]
|
|
151
|
+
|
|
152
|
+
# Perform 16 rounds of DES
|
|
153
|
+
for i in range(16):
|
|
154
|
+
l.append(r[i])
|
|
155
|
+
r.append(self.__xor(l[i], self.__f(r[i], self.__round_keys_reversed[i])))
|
|
156
|
+
|
|
157
|
+
# Preform final permutation
|
|
158
|
+
cleartext = self.__permute(r[16] + l[16], self.__fp)
|
|
159
|
+
|
|
160
|
+
return self.__format_binary_string_as_hex(cleartext)
|
|
161
|
+
|
|
162
|
+
# Generates the 16 rounds keys used in DES
|
|
163
|
+
def __generate_round_keys(self):
|
|
164
|
+
# Apply PC-1
|
|
165
|
+
temp_key = self.__permute(self.key, self.__pc1)
|
|
166
|
+
|
|
167
|
+
# Split into two halves
|
|
168
|
+
c_keys = [temp_key[:28]]
|
|
169
|
+
d_keys = [temp_key[28:]]
|
|
170
|
+
|
|
171
|
+
# Generate 16 'c' and 'd' keys
|
|
172
|
+
for i in range(16):
|
|
173
|
+
c_keys.append(self.__shift_left(c_keys[i], self.__shifts[i]))
|
|
174
|
+
d_keys.append(self.__shift_left(d_keys[i], self.__shifts[i]))
|
|
175
|
+
|
|
176
|
+
# List to store round keys
|
|
177
|
+
round_keys = []
|
|
178
|
+
|
|
179
|
+
# Append 'c' and 'd' keys to form round keys
|
|
180
|
+
for i in range(1, 17):
|
|
181
|
+
key_i = c_keys[i] + d_keys[i]
|
|
182
|
+
round_keys.append(self.__permute(key_i, self.__pc2)) # Permute with PC-2
|
|
183
|
+
|
|
184
|
+
return round_keys
|
|
185
|
+
|
|
186
|
+
# Permutes the input according to the table. Used for all the fancy permutations in DES
|
|
187
|
+
def __permute(self, key, table):
|
|
188
|
+
permuted_key = ""
|
|
189
|
+
|
|
190
|
+
# Permute the key according to the table
|
|
191
|
+
for i in table:
|
|
192
|
+
permuted_key += key[i - 1]
|
|
193
|
+
# The -1 is because all the tables are 1-indexed, but arrays are 0-indexed
|
|
194
|
+
|
|
195
|
+
return permuted_key
|
|
196
|
+
|
|
197
|
+
# Preforms the S-box substitution in the DES algorithm
|
|
198
|
+
def __s_box_substitution(self, r):
|
|
199
|
+
# Split into 8 blocks of 6 bits
|
|
200
|
+
blocks = [r[i : i + 6] for i in range(0, len(r), 6)]
|
|
201
|
+
|
|
202
|
+
# Perform S-box substitution
|
|
203
|
+
for i, block in enumerate(blocks):
|
|
204
|
+
row = int(block[0] + block[5], 2) # First and last bit
|
|
205
|
+
col = int(block[1:5], 2) # Middle 4 bits
|
|
206
|
+
blocks[i] = format(self.__sboxes[i][row][col], "04b") # Format to 4 bits
|
|
207
|
+
|
|
208
|
+
# Concatenates the blocks. Think of this as flattening the list
|
|
209
|
+
return "".join(blocks)
|
|
210
|
+
|
|
211
|
+
# Preforms the XOR operation on two binary strings
|
|
212
|
+
def __xor(self, a, b):
|
|
213
|
+
# XORs each bit one by one
|
|
214
|
+
return "".join(str(int(x) ^ int(y)) for x, y in zip(a, b))
|
|
215
|
+
|
|
216
|
+
# Preforms the f function in the DES algorithm
|
|
217
|
+
def __f(self, r, k):
|
|
218
|
+
# Perform expansion permutation
|
|
219
|
+
r = self.__permute(r, self.__ep)
|
|
220
|
+
|
|
221
|
+
# XOR with input
|
|
222
|
+
r = self.__xor(r, k)
|
|
223
|
+
|
|
224
|
+
# Perform S-box substitution
|
|
225
|
+
r = self.__s_box_substitution(r)
|
|
226
|
+
|
|
227
|
+
# Perform permutation
|
|
228
|
+
r = self.__permute(r, self.__pf)
|
|
229
|
+
|
|
230
|
+
return r
|
|
231
|
+
|
|
232
|
+
# Shifts a block to the left by n bits
|
|
233
|
+
def __shift_left(self, block, n):
|
|
234
|
+
return block[n:] + block[:n]
|
|
235
|
+
|
|
236
|
+
# Formats a binary number as a hex string
|
|
237
|
+
def __format_binary_string_as_hex(self, hex_str):
|
|
238
|
+
hex_str = int(hex_str, 2) # Converts to base 10
|
|
239
|
+
hex_str = hex(hex_str)[2:].zfill(16)
|
|
240
|
+
|
|
241
|
+
return hex_str
|
|
242
|
+
|
|
243
|
+
def __format_input(self, input_string: str):
|
|
244
|
+
input_string = input_string.lower()
|
|
245
|
+
|
|
246
|
+
if "0x" in input_string[:2]:
|
|
247
|
+
input_string = input_string[2:]
|
|
248
|
+
|
|
249
|
+
if len(input_string) % 2 == 1:
|
|
250
|
+
input_string = "0" + input_string
|
|
251
|
+
|
|
252
|
+
return input_string
|
|
253
|
+
|
|
254
|
+
def __format_block(self, block: str) -> str:
|
|
255
|
+
return bin(int(block, 16))[2:].zfill(64)
|
|
256
|
+
|
|
257
|
+
def __format_key(self, key: str) -> str:
|
|
258
|
+
return bin(int(key, 16))[2:].zfill(64)
|
|
259
|
+
|
|
260
|
+
def __bytes_object_to_hex(self, bytes_array: bytes) -> str:
|
|
261
|
+
return bytes_array.hex()
|
|
262
|
+
|
|
263
|
+
def __is_bytes_object(self, bytes_object) -> bool:
|
|
264
|
+
if isinstance(bytes_object, bytes):
|
|
265
|
+
return True
|
|
266
|
+
else:
|
|
267
|
+
return False
|
|
268
|
+
|
|
269
|
+
# Validates key, cleartext, and ciphertext input
|
|
270
|
+
def __validate_key(self, key: str):
|
|
271
|
+
if not isinstance(key, str):
|
|
272
|
+
raise TypeError(
|
|
273
|
+
"Invalid key type. Key should be a hex string or a bytes objects"
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
key = key.lower()
|
|
277
|
+
|
|
278
|
+
if "0x" in key[:2]:
|
|
279
|
+
key = key[2:]
|
|
280
|
+
|
|
281
|
+
if len(key) != 16:
|
|
282
|
+
raise ValueError("Key is of incorrect length")
|
|
283
|
+
|
|
284
|
+
self.__validate_hex_string(key)
|
|
285
|
+
|
|
286
|
+
def __validate_hex_string(self, hex_string: str):
|
|
287
|
+
valid_set = {
|
|
288
|
+
"0",
|
|
289
|
+
"1",
|
|
290
|
+
"2",
|
|
291
|
+
"3",
|
|
292
|
+
"4",
|
|
293
|
+
"5",
|
|
294
|
+
"6",
|
|
295
|
+
"7",
|
|
296
|
+
"8",
|
|
297
|
+
"9",
|
|
298
|
+
"a",
|
|
299
|
+
"b",
|
|
300
|
+
"c",
|
|
301
|
+
"d",
|
|
302
|
+
"e",
|
|
303
|
+
"f",
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
for char in hex_string:
|
|
307
|
+
if char not in valid_set:
|
|
308
|
+
raise ValueError(
|
|
309
|
+
"Invalid hex string. Characters in string should be 0-f"
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
def __validate_input(self, input_string: str):
|
|
313
|
+
if not isinstance(input_string, str):
|
|
314
|
+
raise TypeError(
|
|
315
|
+
"Invalid input type. Input should be a hex string or a bytes objects"
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
input_string = input_string.lower()
|
|
319
|
+
|
|
320
|
+
if "0x" in input_string[:2]:
|
|
321
|
+
input_string = input_string[2:]
|
|
322
|
+
|
|
323
|
+
self.__validate_hex_string(input_string)
|
|
324
|
+
|
|
325
|
+
def __validate_ciphertext(self, ciphertext: str):
|
|
326
|
+
ciphertext = ciphertext.lower()
|
|
327
|
+
|
|
328
|
+
if "0x" in ciphertext[:2]:
|
|
329
|
+
ciphertext = ciphertext[2:]
|
|
330
|
+
|
|
331
|
+
if not len(ciphertext) % 16 == 0:
|
|
332
|
+
raise ValueError("Decryption input is not multiple of block size (8 bytes)")
|
|
333
|
+
|
|
334
|
+
def __split_blocks_encrypt(self, input_string: str) -> list[str]:
|
|
335
|
+
n = len(input_string)
|
|
336
|
+
num_blocks = n // 16
|
|
337
|
+
remainder = n % 16
|
|
338
|
+
|
|
339
|
+
blocks = []
|
|
340
|
+
for i in range(num_blocks):
|
|
341
|
+
blocks.append(input_string[16 * i : 16 * (i + 1)])
|
|
342
|
+
|
|
343
|
+
if remainder == 0:
|
|
344
|
+
blocks.append("")
|
|
345
|
+
else:
|
|
346
|
+
blocks.append(input_string[16 * num_blocks : n])
|
|
347
|
+
|
|
348
|
+
return blocks
|
|
349
|
+
|
|
350
|
+
def __split_blocks_decrypt(self, input_string: str) -> list[str]:
|
|
351
|
+
n = len(input_string)
|
|
352
|
+
num_blocks = n // 16
|
|
353
|
+
|
|
354
|
+
blocks = []
|
|
355
|
+
for i in range(num_blocks):
|
|
356
|
+
blocks.append(input_string[16 * i : 16 * (i + 1)])
|
|
357
|
+
|
|
358
|
+
return blocks
|
|
359
|
+
|
|
360
|
+
def __pad_block(self, input_string: str) -> str:
|
|
361
|
+
full_block = 16
|
|
362
|
+
n = len(input_string)
|
|
363
|
+
pad_length = (full_block - n) // 2
|
|
364
|
+
|
|
365
|
+
pad_character = "0" + str(pad_length)
|
|
366
|
+
for _ in range(pad_length):
|
|
367
|
+
input_string += pad_character
|
|
368
|
+
|
|
369
|
+
return input_string
|
|
370
|
+
|
|
371
|
+
def __strip_padding(self, input_string: str) -> str:
|
|
372
|
+
strip_length = int(input_string[14:16], 16)
|
|
373
|
+
|
|
374
|
+
if strip_length > 8 or strip_length < 1:
|
|
375
|
+
# Invalid strip_length
|
|
376
|
+
return input_string
|
|
377
|
+
|
|
378
|
+
for i in range(16 - (strip_length * 2), 16, 2):
|
|
379
|
+
if int(input_string[i : i + 2], 16) != strip_length:
|
|
380
|
+
return input_string
|
|
381
|
+
|
|
382
|
+
return input_string[0 : 16 - (strip_length * 2)]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: des_PurePy
|
|
3
|
+
Version: 1.0.6
|
|
4
|
+
Summary: A pure python implementation of DES
|
|
5
|
+
Project-URL: Homepage, https://github.com/Perez-Herrera-Luna/DES-Python
|
|
6
|
+
Author-email: Luna Perez-Herrera <lunaperezherrer@gmail.com>
|
|
7
|
+
License-Expression: AGPL-3.0-only
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# DES-Python
|
|
15
|
+
|
|
16
|
+

|
|
17
|
+
[](https://github.com/Perez-Herrera-Luna/DES-Python/blob/main/LICENSE)
|
|
18
|
+
[](https://github.com/Perez-Herrera-Luna/DES-Python/actions/workflows/python-app.yml)
|
|
19
|
+
|
|
20
|
+
Data Encryption Standard (DES) implemented in pure Python
|
|
21
|
+
|
|
22
|
+

|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
Install using your Python package manager of choice:
|
|
27
|
+
```bash
|
|
28
|
+
pip install des_Py
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
Plaintext: 0x0123456789abcdef
|
|
33
|
+
Key: 0x133457799bbcdff1
|
|
34
|
+
Ciphertext: 0x85e813540f0ab405
|
|
35
|
+
Decrypted text: 0x0123456789abcdef
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
### 1. DES
|
|
41
|
+
Define a `DES` object while passing in your key. Key should be a hex string representing an 8 byte hexadecimal number.
|
|
42
|
+
```python
|
|
43
|
+
import des_Py
|
|
44
|
+
des = des_Py.DES("0x133457799bbcdff1")
|
|
45
|
+
```
|
|
46
|
+
You can encrypt by calling `encrypt()` and passing in a hex string representing an 8 byte hexadecimal number
|
|
47
|
+
```python
|
|
48
|
+
des.encrypt("0x0123456789abcdef") # -> "0x85e813540f0ab405"
|
|
49
|
+
```
|
|
50
|
+
You can simarly decrypt by calling `decrypt()` and passing in a hex string to decrypt
|
|
51
|
+
```python
|
|
52
|
+
des.decrypt("0x85e813540f0ab405") # -> "0x0123456789abcdef"
|
|
53
|
+
```
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
des_PurePy/__init__.py,sha256=nYeOSv5AigOzTNaT_JQJGwbyHAlv9jq1eLdgooAy_h4,136
|
|
2
|
+
des_PurePy/constants.py,sha256=ZqlG8ywg_cdGD6GO5S8ei6jo0-J3ccgWf0_BDe83Qwg,8694
|
|
3
|
+
des_PurePy/des.py,sha256=YA-V-mJ8Q-sjzgK1OU4TDgJ_oJoN7jdm3AADtSN-Mt8,12211
|
|
4
|
+
des_purepy-1.0.6.dist-info/METADATA,sha256=MuAebdPWPCwm5xvzyIPB_VngQIULQL8GmMfBK87Nc1Q,1838
|
|
5
|
+
des_purepy-1.0.6.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
6
|
+
des_purepy-1.0.6.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
|
|
7
|
+
des_purepy-1.0.6.dist-info/RECORD,,
|