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/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
+ ![made-with-python](https://img.shields.io/badge/Made%20with-Python%203-1f425f.svg)
17
+ [![license: AGPL-3.0](https://img.shields.io/github/license/Perez-Herrera-Luna/DES-Python.svg)](https://github.com/Perez-Herrera-Luna/DES-Python/blob/main/LICENSE)
18
+ [![DES-Python](https://github.com/Perez-Herrera-Luna/DES-Python/actions/workflows/python-app.yml/badge.svg)](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
+ ![Demo](https://github.com/user-attachments/assets/aaa905df-d924-4d52-80f4-b06390c1b523)
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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any