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
|
class MockBlockCipher:
def __init__(self, key):
self.key = key
@staticmethod
def _pad(data):
"""Pad the data to make its length a multiple of 8."""
while len(data) % 8 != 0:
data += ' '
return data
@staticmethod
def _unpad(data):
"""Remove padding from the data."""
return data.rstrip()
def encrypt(self, plaintext):
"""Encrypt the plaintext using a simple mock block cipher."""
padded_plaintext = self._pad(plaintext)
ciphertext = ''
for char in padded_plaintext:
# Simple transformation: shift character by the key's length
encrypted_char = chr((ord(char) + len(self.key)) % 256)
ciphertext += encrypted_char
return ciphertext
def decrypt(self, ciphertext):
"""Decrypt the ciphertext back to plaintext."""
decrypted_text = ''
for char in ciphertext:
# Reverse the transformation: shift character back by the key's
# length
decrypted_char = chr((ord(char) - len(self.key)) % 256)
decrypted_text += decrypted_char
return self._unpad(decrypted_text)
def xor(a, b):
return ''.join(chr(ord(x) ^ ord(y)) for x, y in zip(a, b))
def ecb_encrypt(plaintext, key):
# Split the plaintext into 8-byte blocks
plaintext_blocks = []
for i in range(0, len(plaintext), 8):
plaintext_blocks.append(plaintext[i:i + 8])
# Encrypt each block separately
cyphertext_blocks = []
for block in plaintext_blocks:
# NOTE: I do not care about any block apart from the one I am
# encrypting.
cyphertext_blocks.append(MockBlockCipher(key).encrypt(block))
# Concatenate the encrypted blocks
return ''.join(cyphertext_blocks)
def ecb_decrypt(ciphertext, key):
# Split the ciphertext into 8-byte blocks
ciphertext_blocks = []
for i in range(0, len(ciphertext), 8):
ciphertext_blocks.append(ciphertext[i:i + 8])
# Decrypt each block separately
plaintext_blocks = []
for block in ciphertext_blocks:
plaintext_blocks.append(MockBlockCipher(key).decrypt(block))
# Concatenate the decrypted blocks
return ''.join(plaintext_blocks)
def cbc_encrypt(plaintext, key, iv):
# Split the plaintext into 8-byte blocks
plaintext_blocks = []
for i in range(0, len(plaintext), 8):
plaintext_blocks.append(plaintext[i:i + 8])
# Encrypt each block separately, chaining the previous block's
# cyphertext with the current block's plaintext.
cyphertext_blocks = []
previous_block = iv
for block in plaintext_blocks:
# NOTE: I xor the current block with the previous block so that
# the same plaintext block does not always encrypt to the same
# cyphertext block.
block = xor(block, previous_block)
cyphertext_blocks.append(MockBlockCipher(key).encrypt(block))
previous_block = cyphertext_blocks[-1]
return ''.join(cyphertext_blocks)
def cbc_decrypt(ciphertext, key, iv):
# Split the ciphertext into 8-byte blocks
ciphertext_blocks = []
for i in range(0, len(ciphertext), 8):
ciphertext_blocks.append(ciphertext[i:i + 8])
# Decrypt each block separately, chaining the previous block's
# cyphertext with the current block's plaintext.
plaintext_blocks = []
previous_block = iv
for block in ciphertext_blocks:
decrypted_block = MockBlockCipher(key).decrypt(block)
plaintext_blocks.append(xor(decrypted_block, previous_block))
previous_block = block
# Concatenate the decrypted blocks
return ''.join(plaintext_blocks)
def ctr(plaintext, key, nonce):
# Split the plaintext into 8-byte blocks
plaintext_blocks = []
for i in range(0, len(plaintext), 8):
plaintext_blocks.append(plaintext[i:i + 8])
cyphertext_blocks = []
for counter, block in enumerate(plaintext_blocks):
# NOTE: I use the nonce and the block's index to generate the
# counter_nonce. This ensures that the same plaintext block does
# not always encrypt to the same cyphertext block.
counter_nonce = nonce + counter
# Encrypt the counter_nonce to generate the keystream
keystream = MockBlockCipher(key).encrypt(chr(counter_nonce))
# XOR the block with the keystream
cyphertext_blocks.append(xor(block, keystream))
# Concatenate the encrypted blocks
return ''.join(cyphertext_blocks)
def example():
# The key is the secret bit that you do not want to share with anyone.
# (Much like a physical key for a lock.)
key = 'abc123'
# The IV (initialization vector) is a random value that you need to
# generate for each encryption. Used in CBC mode. It is sent along with
# the ciphertext.
iv = 'def456'
# The nonce (number used once) is a number that is used per encryption.
# It is sent along with the ciphertext. It is used in CTR mode.
# If you generate it randomly, your random number must be big enough
# that you do not repeat the same number in the future.
nonce = 0
plaintext = 'Hello, world!'
print('Plaintext:', plaintext)
# ECB (Electronic Code Book) mode
ecb_encrypted = ecb_encrypt(plaintext, key)
ecb_decrypted = ecb_decrypt(ecb_encrypted, key)
print('ECB encrypted:', ecb_encrypted)
print('ECB decrypted:', ecb_decrypted)
# CBC (Cipher Block Chaining) mode
cbc_encrypted = cbc_encrypt(plaintext, key, iv)
cbc_decrypted = cbc_decrypt(cbc_encrypted, key, iv)
print('CBC encrypted:', cbc_encrypted)
print('CBC decrypted:', cbc_decrypted)
# CTR (Counter) mode
ctr_encrypted = ctr(plaintext, key, nonce)
ctr_decrypted = ctr(ctr_encrypted, key, nonce)
print('CTR encrypted:', ctr_encrypted)
print('CTR decrypted:', ctr_decrypted)
if __name__ == '__main__':
example()
|