-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsequential_des.py
More file actions
311 lines (268 loc) · 10.8 KB
/
sequential_des.py
File metadata and controls
311 lines (268 loc) · 10.8 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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
###################################
# Authors:
# Slava Kagan
# Liad Khamdadash
###################################
# This class execute data encryption standard algorithm while using sequential implementation
# Encryption of a long text (famous story) and after that decryption to the initial text
#####################
# General python functions we used in this file:
# int() method returns an integer object from any number or string
# chr() method returns a character (a string) from an integer (represents unicode code
# point of the character).
# str() function returns the string version of the given object.
# range() function returns a sequence of numbers, starting from 0 by default, and increments by 1.
# (by default), and ends at a specified number.
# len() function returns the number of items (length) in an object.
# list() constructor returns a list in Python
# zip() function takes iterables (can be zero or more), aggregates them in a tuple, and return it
# ord() function returns an integer representing the Unicode character.
# bin() method converts and returns the binary equivalent string of a given integer.
# isinstance() function checks if the object (first argument) is an instance or subclass
# of class info class (second argument).
# find() method finds the first occurrence of the specified value.
# join() method takes all items in an iterable and joins them into one string.
# raise Exception() allows to force a specified exception to occur.
#####################
from typing import Tuple, List, Iterable
from enum import Enum
from AlgorithmTables import Tables
class Cryptography(Enum):
ENCRYPT = 1 # For encrypting
DECRYPT = 0 # For decrypting
class Des:
def __init__(self, des_key: str):
"""
:param des_key: string
"""
if len(des_key) < 8:
raise Exception("Key Should be 8 bytes long")
elif len(des_key) > 8:
# If key size is above 8bytes, cut to be 8bytes long
des_key = des_key[:8]
# Generate all the keys
self._keys = self.generate_keys(des_key)
# Encrypting
def encrypt(self, plaintext: str) -> str:
"""
Encryption of a given text
:param plaintext: string
:return: return the cipher text
"""
return self.run(plaintext, Cryptography.ENCRYPT)
# Decrypting
def decrypt(self, ciphertext: str) -> str:
"""
Decryption of a given ciphertext
:param ciphertext: string
return: return the given story text
"""
return self.run(ciphertext, Cryptography.DECRYPT)
def run(self, text1: str, action: Cryptography) -> str:
"""
Run the algorithm sequential way
:param text1: string
:param action: Enum
return: string
"""
chunks, chunk_size = len(text1), 8
return "".join([self.run_block(text1[i:i + chunk_size], action)
for i in range(0, chunks, chunk_size)])
def run_block(self, des_text: str, action=Cryptography.ENCRYPT) -> str:
"""
Implement the algorithm
:param des_text: string
:param action: Enum
return: String
"""
if action == Cryptography.ENCRYPT and len(des_text) != 8:
des_text = self.add_padding(des_text)
# If not padding specified data size must be multiple of 8 bytes
elif len(des_text) % 8 != 0:
raise Exception("Data size should be multiple of 8")
# Split the text in blocks of 8 bytes so 64 bits
text_blocks = self.n_split(des_text, 8)
result = list()
# Loop over all the blocks of data
for block in text_blocks:
# Convert the block in bit array
block = self.string_to_bit_array(block)
# Apply the initial permutation
block = self.permutation_expand(block, Tables.IP_TABLE)
left, right = self.n_split(block, 32)
tmp = None
# Do the 16 rounds
for i in range(16):
# Expand right to match
d_e = self.permutation_expand(right, Tables.E_BIT_SELECTION_TABLE)
# Ki size (48bits)
if action == Cryptography.ENCRYPT:
# If encrypt use Ki
tmp = self.xor(self._keys[i], d_e)
else:
# If decrypt start by the last key
tmp = self.xor(self._keys[15 - i], d_e)
# Method that will apply the SBOXes
tmp = self.substitute(tmp)
tmp = self.permutation_expand(tmp, Tables.P_TABLE)
tmp = self.xor(left, tmp)
left = right
right = tmp
# Do the last permutation and append the result to result
result += self.permutation_expand(right + left, Tables.IP_1_TABLE)
final_res = self.bit_array_to_string(result)
if action == Cryptography.DECRYPT and final_res[7] == '\0':
# Remove the padding if decrypt and padding is true
return self.remove_padding(final_res)
else:
# Return the final string of data ciphered/deciphered
return final_res
"##################### CLASS METHODS #####################"
# @class method - returns a class method for the given function methods
# that are bound to a class rather than its object.
@classmethod
def generate_keys(cls, des_key: str) -> List[list]:
"""
Algorithm that generates all the keys
:param cls: string
:param des_key: string
:return: list of keys after generation
"""
keys = []
des_key = cls.string_to_bit_array(des_key)
# Apply the initial Permutation on the key
des_key = cls.permutation_expand(des_key, Tables.PC_1_TABLE)
# Split it in to LEFT,RIGHT
left, right = cls.n_split(des_key, 28)
# Apply the 16 rounds
for i in range(16):
# Apply the shift associated with the round (not always 1)
left, right = cls.shift(left, right, Tables.SHIFT_ARRAY[i])
# Merge them
tmp = left + right
# Apply the Permutation to get the Ki
keys.append(cls.permutation_expand(tmp, Tables.PC_2_TABLE))
return keys
"##################### STATIC METHODS #####################"
# @staticmethod - returns a static method for a given function methods
# that are bound to a class rather than its object.
@staticmethod
def permutation_expand(block, table: List) -> List[chr]:
"""
Permutation the given block using the given table
:param block: block list
:param table: some table
:return: list after permutation
"""
return [block[x - 1] for x in table]
@staticmethod
def shift(left: str, right: str, n: int) -> Tuple[str, str]:
"""
Shift a list of the given value
:param left: some string
:param right: some string
:param n: integer
:return: a tuple after shifting
"""
return left[n:] + left[:n], right[n:] + right[:n]
@staticmethod
def add_padding(text1: str) -> str:
"""
Add padding to the data using PKCS5 spec
:param text1: string data
:return: string after padding
"""
pad_len = 8 - (len(text1) % 8)
return text1 + (pad_len * '\0')
@staticmethod
def remove_padding(data: str) -> str:
"""
Remove the padding of the plain text (it assume there is padding)
:param data: a string
:return: string without padding
"""
return data[:data.find('\0')]
@staticmethod
def bin_value(val: str, bits_size: int) -> str:
"""
Get the value and size expected of a string and convert
it to binary with padding '0'
:param val: The value need to convert to binary
:param bits_size: The size is expected to get
:raise exception: If no binary value is larger than the expected size
:return: The binary value of a given string with padding '0'
"""
bin_val = (bin(val) if isinstance(val, int) else bin(ord(val)))[2:]
if len(bin_val) > bits_size:
raise Exception("Binary value larger than the expected size")
while len(bin_val) < bits_size:
# Add as many 0 as needed to get the wanted size
bin_val = "0" + bin_val
return bin_val
@staticmethod
def n_split(text1: Iterable, n: int) -> list:
"""
Split a list into sub lists of size n
:param text1: get list
:param n: size of sublist
:return: sub lists of the general list
"""
return [text1[k:k + n] for k in range(0, len(text1), n)]
@staticmethod
def xor(t1, t2):
"""
Apply a xor and return the resulting list
:param t1: object
:param t2: object
:return: list after implementing xor function
"""
return [x ^ y for x, y in zip(t1, t2)]
@staticmethod
def string_to_bit_array(text_string: str) -> list:
"""
Convert a string into a list of bits
:param text_string: string that need to convert to list of bits
:return: list of bits
"""
array = list()
for char in text_string:
# Get the char value on one byte
bin_val = Des.bin_value(char, 8)
# Add the bits to the final list
array.extend([int(x) for x in list(bin_val)])
return array
@staticmethod
def substitute(d_e: Iterable) -> List[int]:
"""
Substitute bytes using S_BOX table
:param d_e: bit array
:return: list of bits
"""
# Split bit array into sublist of 6 bits
sub_blocks = Des.n_split(d_e, 6)
result = list()
# For all the subLists
for i in range(len(sub_blocks)):
block = sub_blocks[i]
# Get the row with the first and last bit
row = int(str(block[0]) + str(block[5]), 2)
# Column is the 2,3,4,5th bits
column = int(''.join([str(x) for x in block[1:][:-1]]), 2)
# Take the value in the S_BOX appropriated for the round (i)
val = Tables.S_BOX_TABLES[i][row][column]
# Convert the value to binary
bin_attr = Des.bin_value(val, 4)
# And append it to the resulting list
result += [int(x) for x in bin_attr]
return result
@staticmethod
def bit_array_to_string(array: Iterable) -> str:
"""
Recreate the string from the bit list
:param array: Iterable, list of bit
:return: string from bit list
"""
res = ''.join(
[chr(int(y, 2)) for y in [''.join([str(x) for x in _bytes])
for _bytes in Des.n_split(array, 8)]])
return res