project-des 0.1.0__tar.gz

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.
@@ -0,0 +1,111 @@
1
+ Metadata-Version: 2.4
2
+ Name: project-des
3
+ Version: 0.1.0
4
+ Summary: Educational Python implementation of the Data Encryption Standard (DES)
5
+ Author-email: shrimp2845 <goldencheng15@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/shrimp2845-tw/project_DES
8
+ Project-URL: Repository, https://github.com/shrimp2845-tw/project_DES
9
+ Keywords: des,cryptography,education,feistel
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Requires-Python: >=3.10
14
+ Description-Content-Type: text/markdown
15
+
16
+ **_Warning: This implementation is intended for self learning purposes only and should not be used for real-world security._**
17
+
18
+ ## About
19
+
20
+ - **Author:** shrimp2845
21
+ - **Version:** 0.1.0
22
+ - **License:** MIT
23
+
24
+ This is an educational Python implementation of the Data Encryption Standard (DES). This project breaks down the complex Feistel network into human-readable modules following the official NIST specifications, and supports ECB and CBC modes for real file encryption.
25
+
26
+ ## Installation & Usage
27
+
28
+ ### install
29
+ Requires Python 3.10+
30
+ ```bash
31
+ pip install project-des
32
+ ```
33
+ ### usage
34
+ ```bash
35
+ python -c "from project_DES import DES; cipher = DES(b'key'); print(cipher.encrypt(b'data').hex())"
36
+ ```
37
+
38
+ ## Docs
39
+
40
+ ```
41
+ class initialize
42
+ ----------------------
43
+ project_DES.DES(key, mode='ECB')
44
+
45
+ key
46
+ -> 8 bytes or key file name
47
+ containing an encryption key,
48
+ if key is str, it extract
49
+ first 8 bytes in the file
50
+
51
+ mode
52
+ -> optional, block cipher mode of
53
+ operation that used,ECB and CBC
54
+ available in current version
55
+
56
+
57
+ methods
58
+ ----------------------
59
+
60
+ encrypt(data: bytes) -> bytes,
61
+ decrypt(data: bytes) -> bytes
62
+ -> return encrypt/decrypt bytes data
63
+
64
+ encrypt_file(name: str, new_name: str),
65
+ decrypt_file(name: str, new_name: str)
66
+ -> encrypt/decrypt target file 'name'
67
+ and save it as 'new_name'
68
+
69
+ get_key(), get_mode()
70
+ -> get current using key/mode of
71
+ operation
72
+
73
+ change_key(key: str or bytes),
74
+ change_mode(mode: str)
75
+ -> change current using key/mode of
76
+ operation
77
+
78
+ ```
79
+
80
+ ## Examples
81
+ Project provides two examples: [string_cipher.py](https://github.com/shrimp2845-tw/project_DES/blob/main/examples/string_cipher.py) & [file_cipher.py](https://github.com/shrimp2845-tw/project_DES/blob/main/examples/file_cipher.py)
82
+
83
+ you can find samples of [file_cipher.py](https://github.com/shrimp2845-tw/project_DES/blob/main/examples/file_cipher.py) at:
84
+ [ecb_sample](https://github.com/shrimp2845-tw/project_DES/tree/main/examples/ecb_sample) & [cbc_sample](https://github.com/shrimp2845-tw/project_DES/tree/main/examples/cbc_sample)
85
+
86
+ ### explanation for samples
87
+ **mumei.jpg**: plaintext file
88
+
89
+ **mumei.bin**: encrypt mumei.jpg with key ‘berries’ and mode ECB
90
+
91
+ **mumei_correct_decrypt**.jpg: decrypt mumei.bin with key ‘berries’ and mode ECB
92
+
93
+ **mumei_wrong_decrypt.jpg**: decrypt mumei.bin with key ‘friend’ and mode ECB
94
+
95
+ **ina.jpg**: plaintext file
96
+
97
+ **ina.bin**: encrypt ina.jpg with key ‘takodachi’ and mode CBC
98
+
99
+ **ina_correct_decrypt.jpg**: decrypt ina.bin with key ‘takodachi’ and mode CBC
100
+
101
+ **ina_wrong_decrypt.jpg**: decrypt ina.bin with key ‘violet’ and mode CBC
102
+
103
+
104
+ ## Performance
105
+ This project is designed for learning and understanding how DES works rather than for practical encryption use. The implementation prioritizes readability and modular structure over performance.
106
+
107
+ As a result, encryption process is **very slow**. It takes nearly 2 minutes to encrypt 1MB file.
108
+
109
+
110
+
111
+
@@ -0,0 +1,96 @@
1
+ **_Warning: This implementation is intended for self learning purposes only and should not be used for real-world security._**
2
+
3
+ ## About
4
+
5
+ - **Author:** shrimp2845
6
+ - **Version:** 0.1.0
7
+ - **License:** MIT
8
+
9
+ This is an educational Python implementation of the Data Encryption Standard (DES). This project breaks down the complex Feistel network into human-readable modules following the official NIST specifications, and supports ECB and CBC modes for real file encryption.
10
+
11
+ ## Installation & Usage
12
+
13
+ ### install
14
+ Requires Python 3.10+
15
+ ```bash
16
+ pip install project-des
17
+ ```
18
+ ### usage
19
+ ```bash
20
+ python -c "from project_DES import DES; cipher = DES(b'key'); print(cipher.encrypt(b'data').hex())"
21
+ ```
22
+
23
+ ## Docs
24
+
25
+ ```
26
+ class initialize
27
+ ----------------------
28
+ project_DES.DES(key, mode='ECB')
29
+
30
+ key
31
+ -> 8 bytes or key file name
32
+ containing an encryption key,
33
+ if key is str, it extract
34
+ first 8 bytes in the file
35
+
36
+ mode
37
+ -> optional, block cipher mode of
38
+ operation that used,ECB and CBC
39
+ available in current version
40
+
41
+
42
+ methods
43
+ ----------------------
44
+
45
+ encrypt(data: bytes) -> bytes,
46
+ decrypt(data: bytes) -> bytes
47
+ -> return encrypt/decrypt bytes data
48
+
49
+ encrypt_file(name: str, new_name: str),
50
+ decrypt_file(name: str, new_name: str)
51
+ -> encrypt/decrypt target file 'name'
52
+ and save it as 'new_name'
53
+
54
+ get_key(), get_mode()
55
+ -> get current using key/mode of
56
+ operation
57
+
58
+ change_key(key: str or bytes),
59
+ change_mode(mode: str)
60
+ -> change current using key/mode of
61
+ operation
62
+
63
+ ```
64
+
65
+ ## Examples
66
+ Project provides two examples: [string_cipher.py](https://github.com/shrimp2845-tw/project_DES/blob/main/examples/string_cipher.py) & [file_cipher.py](https://github.com/shrimp2845-tw/project_DES/blob/main/examples/file_cipher.py)
67
+
68
+ you can find samples of [file_cipher.py](https://github.com/shrimp2845-tw/project_DES/blob/main/examples/file_cipher.py) at:
69
+ [ecb_sample](https://github.com/shrimp2845-tw/project_DES/tree/main/examples/ecb_sample) & [cbc_sample](https://github.com/shrimp2845-tw/project_DES/tree/main/examples/cbc_sample)
70
+
71
+ ### explanation for samples
72
+ **mumei.jpg**: plaintext file
73
+
74
+ **mumei.bin**: encrypt mumei.jpg with key ‘berries’ and mode ECB
75
+
76
+ **mumei_correct_decrypt**.jpg: decrypt mumei.bin with key ‘berries’ and mode ECB
77
+
78
+ **mumei_wrong_decrypt.jpg**: decrypt mumei.bin with key ‘friend’ and mode ECB
79
+
80
+ **ina.jpg**: plaintext file
81
+
82
+ **ina.bin**: encrypt ina.jpg with key ‘takodachi’ and mode CBC
83
+
84
+ **ina_correct_decrypt.jpg**: decrypt ina.bin with key ‘takodachi’ and mode CBC
85
+
86
+ **ina_wrong_decrypt.jpg**: decrypt ina.bin with key ‘violet’ and mode CBC
87
+
88
+
89
+ ## Performance
90
+ This project is designed for learning and understanding how DES works rather than for practical encryption use. The implementation prioritizes readability and modular structure over performance.
91
+
92
+ As a result, encryption process is **very slow**. It takes nearly 2 minutes to encrypt 1MB file.
93
+
94
+
95
+
96
+
@@ -0,0 +1,2 @@
1
+ from .project_DES import DES
2
+ __all__ = ["DES"]
@@ -0,0 +1 @@
1
+ from .crypto import BasicDES
@@ -0,0 +1,35 @@
1
+
2
+ meticulous_mode = True
3
+
4
+ def xor(b1: list[int], b2: list[int]) -> list[int]:
5
+ if meticulous_mode and (len(b1) != len(b2)):
6
+ raise ValueError('xor:invalid input')
7
+ op = [int(not(i == j)) for i, j in zip(b1, b2)]
8
+ return op
9
+
10
+ def bytes_to_bits(byd: bytes) -> list[int]:
11
+ btd = [int(bit) for byte in byd for bit in format(byte, '08b')]
12
+ return btd
13
+
14
+ def bits_to_bytes(btd: list[int]) -> bytes:
15
+ if meticulous_mode and len(btd)%8 != 0:
16
+ raise ValueError('bits_to_bytes:invalid input')
17
+ byd = bytes(int("".join(map(str, btd[i:i+8])), 2) for i in range(0, len(btd), 8))
18
+ return byd
19
+
20
+ def int_to_bits(bid: int, length: int) -> list[int]:
21
+ btd = [int(bit) for bit in format(bid, f'0{length}b')]
22
+ if meticulous_mode and len(format(bid, 'b')) > length:
23
+ raise ValueError('int_to_bits:invalid input')
24
+ return btd
25
+
26
+ def bits_to_int(btd: list[int]) -> int:
27
+ bid = int(''.join(map(str,btd)), 2)
28
+ return bid
29
+
30
+ def main():
31
+ pass
32
+
33
+ if __name__ == "__main__":
34
+ main()
35
+
@@ -0,0 +1,59 @@
1
+ from . import bit_utils as b
2
+ from . import permute as p
3
+ from . import key as k
4
+ from . import mangler as m
5
+ from . import feistel as f
6
+
7
+ meticulous_mode = True
8
+
9
+ def ip(btd: list[int]) -> list[int]:
10
+ if meticulous_mode and len(btd) != 64:
11
+ raise ValueError('ip:invalid input')
12
+ t = [58, 50, 42, 34, 26, 18, 10, 2,
13
+ 60, 52, 44, 36, 28, 20, 12, 4,
14
+ 62, 54, 46, 38, 30, 22, 14, 6,
15
+ 64, 56, 48, 40, 32, 24, 16, 8,
16
+ 57, 49, 41, 33, 25, 17, 9, 1,
17
+ 59, 51, 43, 35, 27, 19, 11, 3,
18
+ 61, 53, 45, 37, 29, 21, 13, 5,
19
+ 63, 55, 47, 39, 31, 23, 15, 7]
20
+ nbtd = p.permutation(btd, t)
21
+ return nbtd
22
+
23
+ def fp(btd: list[int]) -> list[int]:
24
+ if meticulous_mode and len(btd) != 64:
25
+ raise ValueError('fp:invalid input')
26
+ t = [40, 8, 48, 16, 56, 24, 64, 32,
27
+ 39, 7, 47, 15, 55, 23, 63, 31,
28
+ 38, 6, 46, 14, 54, 22, 62, 30,
29
+ 37, 5, 45, 13, 53, 21, 61, 29,
30
+ 36, 4, 44, 12, 52, 20, 60, 28,
31
+ 35, 3, 43, 11, 51, 19, 59, 27,
32
+ 34, 2, 42, 10, 50, 18, 58, 26,
33
+ 33, 1, 41, 9, 49, 17, 57, 25]
34
+ nbtd = p.permutation(btd, t)
35
+ return nbtd
36
+
37
+ class BasicDES:
38
+ def __init__(self, main_key: bytes):
39
+ self.rks = k.generate_round_key(main_key)
40
+
41
+ def encrypt(self, block: bytes) -> bytes:
42
+ if len(block) != 8:
43
+ raise ValueError('encrypt:invalid block')
44
+ plaintext = b.bytes_to_bits(block)
45
+ cipher_text = fp(f.feistel(ip(plaintext), self.rks, m.f))
46
+ return b.bits_to_bytes(cipher_text)
47
+
48
+ def decrypt(self, block: bytes) -> bytes:
49
+ if len(block) != 8:
50
+ raise ValueError('decrypt:invalid block')
51
+ cipher_text = b.bytes_to_bits(block)
52
+ plaintext = fp(f.feistel(ip(cipher_text), self.rks[::-1], m.f))
53
+ return b.bits_to_bytes(plaintext)
54
+
55
+ def main():
56
+ pass
57
+
58
+ if __name__ == "__main__":
59
+ main()
@@ -0,0 +1,23 @@
1
+ from . import bit_utils as b
2
+ from typing import Callable
3
+
4
+ meticulous_mode = True
5
+
6
+ def feistel(btd:list[list[int]], rks: list[list[int]], f: Callable[[list[int], list[int]], list[int]]) -> list[int]:
7
+ if meticulous_mode and len(btd) % 2 != 0:
8
+ raise ValueError("feistel:invalid input")
9
+ def one_round(l: list[int], r: list[int], rk: list[int]) -> tuple[list[int], list[int]]:
10
+ nl = r
11
+ nr = b.xor(l, f(r, rk))
12
+ return nl, nr
13
+ l, r = btd[:len(btd)//2], btd[len(btd)//2:]
14
+ for rk in rks:
15
+ l, r = one_round(l, r, rk)
16
+ nbtd = r+l
17
+ return nbtd
18
+
19
+ def main():
20
+ pass
21
+
22
+ if __name__ == "__main__":
23
+ main()
@@ -0,0 +1,63 @@
1
+ from . import bit_utils as b
2
+ from . import permute as p
3
+
4
+ meticulous_mode = True
5
+
6
+ def pc1(btd: list[int]) -> list[int]:
7
+ if meticulous_mode and len(btd) != 64:
8
+ raise ValueError('pc1:invalid input')
9
+ t = [57, 49, 41, 33, 25, 17, 9,
10
+ 1, 58, 50, 42, 34, 26, 18,
11
+ 10, 2, 59, 51, 43, 35, 27,
12
+ 19, 11, 3, 60, 52, 44, 36,
13
+ 63, 55, 47, 39, 31, 23, 15,
14
+ 7, 62, 54, 46, 38, 30, 22,
15
+ 14, 6, 61, 53, 45, 37, 29,
16
+ 21, 13, 5, 28, 20, 12, 4]
17
+ nbtd = p.permutation(btd, t)
18
+ return nbtd
19
+
20
+ def pc2(btd: list[int]) -> list[int]:
21
+ if meticulous_mode and len(btd) != 56:
22
+ raise ValueError('pc2:invalid input')
23
+ t = [14, 17, 11, 24, 1, 5,
24
+ 3, 28, 15, 6, 21, 10,
25
+ 23, 19, 12, 4, 26, 8,
26
+ 16, 7, 27, 20, 13, 2,
27
+ 41, 52, 31, 37, 47, 55,
28
+ 30, 40, 51, 45, 33, 48,
29
+ 44, 49, 39, 56, 34, 53,
30
+ 46, 42, 50, 36, 29, 32]
31
+ nbtd = p.permutation(btd, t)
32
+ return nbtd
33
+
34
+ def lcs(btd: list[int], t: int) -> list[int]:
35
+ op = btd[t:]+btd[:t]
36
+ return op
37
+
38
+
39
+ def generate_round_key(mk: bytes) -> list[list[int]]:
40
+ if meticulous_mode and len(mk) != 8:
41
+ raise ValueError('generate_round_key:invalid main key')
42
+ def generate_ki(rd: int, lsk: list[int]) -> tuple[list[int], list[int]]:#rd -> 1 ~ 16
43
+ if meticulous_mode and len(lsk) != 56:
44
+ raise ValueError('generate_ki:invalid sub key')
45
+ l, r = lsk[:28], lsk[28:]
46
+ lcs_t = [None, 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]
47
+ l, r = lcs(l, lcs_t[rd]), lcs(r, lcs_t[rd])
48
+ nsk = l+r
49
+ ki = pc2(nsk)
50
+ return ki, nsk
51
+ bmk = b.bytes_to_bits(mk)
52
+ sk = pc1(bmk)
53
+ rkl = []
54
+ for i in range(1, 17):
55
+ ki, sk = generate_ki(i, sk)
56
+ rkl.append(ki)
57
+ return rkl
58
+
59
+ def main():
60
+ pass
61
+
62
+ if __name__ == "__main__":
63
+ main()
@@ -0,0 +1,97 @@
1
+ from . import bit_utils as b
2
+ from . import permute as p
3
+
4
+ meticulous_mode = True
5
+
6
+ def p_box(btd: list[int]) -> list[int]:
7
+ if meticulous_mode and len(btd) != 32:
8
+ raise ValueError('p_box:invalid input')
9
+ t = [16, 7, 20, 21,
10
+ 29, 12, 28, 17,
11
+ 1, 15, 23, 26,
12
+ 5, 18, 31, 10,
13
+ 2, 8, 24, 14,
14
+ 32, 27, 3, 9,
15
+ 19, 13, 30, 6,
16
+ 22, 11, 4, 25]
17
+ nbtd = p.permutation(btd, t)
18
+ return nbtd
19
+
20
+ def s_box(btd: list[int]) -> list[int]:
21
+ if meticulous_mode and len(btd) != 48:
22
+ raise ValueError('s_box:invalid input')
23
+ t ={1: [[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7],
24
+ [0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8],
25
+ [4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0],
26
+ [15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13]],
27
+ 2: [[15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10],
28
+ [3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5],
29
+ [0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15],
30
+ [13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9]],
31
+ 3: [[10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8],
32
+ [13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1],
33
+ [13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7],
34
+ [1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12]],
35
+ 4: [[7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15],
36
+ [13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9],
37
+ [10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4],
38
+ [3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14]],
39
+ 5: [[2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9],
40
+ [14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6],
41
+ [4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14],
42
+ [11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3]],
43
+ 6: [[12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11],
44
+ [10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8],
45
+ [9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6],
46
+ [4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13]],
47
+ 7: [[4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1],
48
+ [13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6],
49
+ [1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2],
50
+ [6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12]],
51
+ 8: [[13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7],
52
+ [1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2],
53
+ [7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8],
54
+ [2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11]],
55
+ }
56
+ def si(bd: list[int], box: int) -> list[int]:
57
+ nonlocal t
58
+ if meticulous_mode and (len(bd) != 6 or box > 8):
59
+ raise ValueError(f'si:invalid input')
60
+ row = b.bits_to_int([bd[0], bd[-1]])
61
+ column = b.bits_to_int(bd[1:-1])
62
+ nbd = b.int_to_bits(t[box][row][column], 4)
63
+ return nbd
64
+ sbtd = [btd[i:i+6] for i in range(0, 48, 6)]
65
+ nbtd = []
66
+ for i in range(8):
67
+ nbtd += si(sbtd[i], i+1)
68
+ return nbtd
69
+
70
+ def ep(btd: list[int]) -> list[int]:
71
+ if meticulous_mode and len(btd) != 32:
72
+ raise ValueError('ep:invalid input')
73
+ t = [32, 1, 2, 3, 4, 5,
74
+ 4, 5, 6, 7, 8, 9,
75
+ 8, 9, 10, 11, 12, 13,
76
+ 12, 13, 14, 15, 16, 17,
77
+ 16, 17, 18, 19, 20, 21,
78
+ 20, 21, 22, 23, 24, 25,
79
+ 24, 25, 26, 27, 28, 29,
80
+ 28, 29, 30, 31, 32, 1]
81
+ nbtd = p.permutation(btd, t)
82
+ return nbtd
83
+
84
+ def f(btd: list[int], rk: list[int]) -> list[int]:
85
+ if meticulous_mode and len(btd) != 32 or len(rk) != 48:
86
+ raise ValueError('f:invalid input')
87
+ ep_btd = ep(btd)
88
+ rk_btd = b.xor(ep_btd, rk)
89
+ s_btd = s_box(rk_btd)
90
+ nbtd = p_box(s_btd)
91
+ return nbtd
92
+
93
+ def main():
94
+ pass
95
+
96
+ if __name__ == "__main__":
97
+ main()
@@ -0,0 +1,14 @@
1
+
2
+ meticulous_mode = True
3
+
4
+ def permutation(btd: list[int], table: list[int]) -> list[int]:
5
+ if meticulous_mode and len(btd) < max(table):
6
+ raise ValueError('permutation:index overflow')
7
+ n_btd = [btd[i-1] for i in table]
8
+ return n_btd
9
+
10
+ def main():
11
+ pass
12
+
13
+ if __name__ == "__main__":
14
+ main()
@@ -0,0 +1,158 @@
1
+ from .des_core import BasicDES
2
+ from typing import Union, Iterable
3
+ import os
4
+
5
+ def read_bytes(name: str, l = 0) -> bytes:
6
+ """read bytes from target file"""
7
+ bfile = open(name, 'rb')
8
+ data = bfile.read()
9
+ bfile.close()
10
+ if l == 0:
11
+ return data
12
+ return data[:l]
13
+
14
+ def write_bytes(name: str, data: bytes):
15
+ """write bytes into a new created file"""
16
+ bfile = open(name, 'wb')
17
+ bfile.write(data)
18
+ bfile.close()
19
+
20
+ class DES:
21
+ def __init__(self, key: Union[bytes, str], mode: str = 'ECB'):
22
+ """setting initial mode and cipher machine"""
23
+ self.modes = {'ECB', 'CBC'}
24
+ if isinstance(key, str):
25
+ key = read_bytes(key, 8)
26
+ if len(key) != 8:
27
+ raise ValueError('DES: invalid key')
28
+ self.cipher = BasicDES(key)
29
+ self.__key = key
30
+ if mode not in self.modes:
31
+ raise ValueError('DES: unknown mode of operation')
32
+ self.__mode = mode
33
+
34
+ @staticmethod
35
+ def __add_padding(file: bytes) -> bytes:
36
+ """add padding to a bytes string"""
37
+ if not isinstance(file, bytes):
38
+ raise TypeError
39
+ if len(file)%8 == 0:
40
+ return file + b'\x08'*8
41
+ return file + bytes([8-(len(file)%8)]*(8-(len(file)%8)))
42
+
43
+ @staticmethod
44
+ def __remove_padding(file: bytes) -> bytes:
45
+ """remove padding from a bytes string"""
46
+ if not isinstance(file, bytes):
47
+ raise TypeError
48
+ pl = file[-1]
49
+ if pl > 8 or pl < 1 or file[-pl:] != bytes([pl])*pl:
50
+ return file
51
+ return file[:-pl]
52
+
53
+ @staticmethod
54
+ def __split_data(file: bytes) -> list[bytes]:
55
+ """split a bytes string to a list contain bytes strings with length of 8"""
56
+ if (len(file)%8) != 0:
57
+ raise ValueError ('split_data: data must be splited perfectly')
58
+ return [file[i: i+8] for i in range(0, len(file), 8)]
59
+
60
+ @staticmethod
61
+ def __merge_data(blocks: Iterable[bytes]) -> bytes:
62
+ """merge a list contain bytes strings into bytes string"""
63
+ return b''.join(blocks)
64
+
65
+ @staticmethod
66
+ def __random_iv() -> bytes:
67
+ """generate a random initialization vector for cbc encryption"""
68
+ return os.urandom(8)
69
+
70
+ @staticmethod
71
+ def __xor(b1: bytes, b2: bytes):
72
+ """perform xor operation between two bytes string of equal length"""
73
+ return bytes(i^j for i, j in zip(b1, b2))
74
+
75
+ def __ecb(self, blocks: list[bytes], decrypt: bool = False) -> bytes:
76
+ """perform encrypt & decryption of ECB method"""
77
+ if not decrypt:
78
+ return self.__merge_data(self.cipher.encrypt(i) for i in blocks)
79
+ else:
80
+ return self.__remove_padding(self.__merge_data(self.cipher.decrypt(i) for i in blocks))
81
+
82
+ def __cbc(self, blocks: list[bytes], decrypt: bool = False) -> bytes:
83
+ """perform encryption & decryption of CBC method"""
84
+ if not decrypt:
85
+ v = self.__random_iv()
86
+ cipher_data = [v]
87
+ for i in blocks:
88
+ v = self.cipher.encrypt(self.__xor(i, v))
89
+ cipher_data.append(v)
90
+ return self.__merge_data(cipher_data)
91
+ else:
92
+ if len(blocks) < 2:
93
+ raise ValueError("cbc: ciphertext too short")
94
+ va, blocks = blocks[:-1], blocks[1:]
95
+ return self.__remove_padding(self.__merge_data(self.__xor(self.cipher.decrypt(i), j) for i, j in zip(blocks, va)))
96
+
97
+ def get_modes(self) -> list[str]:
98
+ """get avaliable cipher mode of operation"""
99
+ return self.modes
100
+
101
+ def get_mode(self) -> str:
102
+ """get current cipher mode of operation"""
103
+ return self.__mode
104
+
105
+ def change_mode(self, mode: str):
106
+ """change current cipher mode of operation"""
107
+ if mode not in self.modes:
108
+ raise ValueError('change_mode: unknown mode of operation')
109
+ self.__mode = mode
110
+
111
+ def get_key(self) -> bytes:
112
+ """get current using key"""
113
+ return self.__key
114
+
115
+ def change_key(self, key: Union[bytes, str]):
116
+ """change current using key"""
117
+ if isinstance(key, str):
118
+ key = read_bytes(key, 8)
119
+ if len(key) != 8:
120
+ raise ValueError('change_key: invalid key')
121
+ self.cipher = BasicDES(key)
122
+ self.__key = key
123
+
124
+ def encrypt(self, plaintext: bytes) -> bytes:
125
+ """encrypt bytes data"""
126
+ blocks = self.__split_data(self.__add_padding(plaintext))
127
+ mode = self.__mode
128
+ if mode == 'ECB':
129
+ return self.__ecb(blocks)
130
+ elif mode == 'CBC':
131
+ return self.__cbc(blocks)
132
+
133
+ def decrypt(self, ciphertext: bytes) -> bytes:
134
+ """decrypt bytes data"""
135
+ blocks = self.__split_data(ciphertext)
136
+ mode = self.__mode
137
+ if mode == 'ECB':
138
+ return self.__ecb(blocks, decrypt = True)
139
+ elif mode == 'CBC':
140
+ return self.__cbc(blocks, decrypt = True)
141
+
142
+ def encrypt_file(self, name: str, new_name: str):
143
+ """encrypt target file and save it as 'new_name' """
144
+ plaintext = read_bytes(name)
145
+ ciphertext = self.encrypt(plaintext)
146
+ write_bytes(new_name, ciphertext)
147
+
148
+ def decrypt_file(self, name: str, new_name: str):
149
+ """decrypt target file and save it as 'new_name' """
150
+ ciphertext = read_bytes(name)
151
+ plaintext = self.decrypt(ciphertext)
152
+ write_bytes(new_name, plaintext)
153
+
154
+ def main():
155
+ pass
156
+
157
+ if __name__ == "__main__":
158
+ main()
@@ -0,0 +1,111 @@
1
+ Metadata-Version: 2.4
2
+ Name: project-des
3
+ Version: 0.1.0
4
+ Summary: Educational Python implementation of the Data Encryption Standard (DES)
5
+ Author-email: shrimp2845 <goldencheng15@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/shrimp2845-tw/project_DES
8
+ Project-URL: Repository, https://github.com/shrimp2845-tw/project_DES
9
+ Keywords: des,cryptography,education,feistel
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Requires-Python: >=3.10
14
+ Description-Content-Type: text/markdown
15
+
16
+ **_Warning: This implementation is intended for self learning purposes only and should not be used for real-world security._**
17
+
18
+ ## About
19
+
20
+ - **Author:** shrimp2845
21
+ - **Version:** 0.1.0
22
+ - **License:** MIT
23
+
24
+ This is an educational Python implementation of the Data Encryption Standard (DES). This project breaks down the complex Feistel network into human-readable modules following the official NIST specifications, and supports ECB and CBC modes for real file encryption.
25
+
26
+ ## Installation & Usage
27
+
28
+ ### install
29
+ Requires Python 3.10+
30
+ ```bash
31
+ pip install project-des
32
+ ```
33
+ ### usage
34
+ ```bash
35
+ python -c "from project_DES import DES; cipher = DES(b'key'); print(cipher.encrypt(b'data').hex())"
36
+ ```
37
+
38
+ ## Docs
39
+
40
+ ```
41
+ class initialize
42
+ ----------------------
43
+ project_DES.DES(key, mode='ECB')
44
+
45
+ key
46
+ -> 8 bytes or key file name
47
+ containing an encryption key,
48
+ if key is str, it extract
49
+ first 8 bytes in the file
50
+
51
+ mode
52
+ -> optional, block cipher mode of
53
+ operation that used,ECB and CBC
54
+ available in current version
55
+
56
+
57
+ methods
58
+ ----------------------
59
+
60
+ encrypt(data: bytes) -> bytes,
61
+ decrypt(data: bytes) -> bytes
62
+ -> return encrypt/decrypt bytes data
63
+
64
+ encrypt_file(name: str, new_name: str),
65
+ decrypt_file(name: str, new_name: str)
66
+ -> encrypt/decrypt target file 'name'
67
+ and save it as 'new_name'
68
+
69
+ get_key(), get_mode()
70
+ -> get current using key/mode of
71
+ operation
72
+
73
+ change_key(key: str or bytes),
74
+ change_mode(mode: str)
75
+ -> change current using key/mode of
76
+ operation
77
+
78
+ ```
79
+
80
+ ## Examples
81
+ Project provides two examples: [string_cipher.py](https://github.com/shrimp2845-tw/project_DES/blob/main/examples/string_cipher.py) & [file_cipher.py](https://github.com/shrimp2845-tw/project_DES/blob/main/examples/file_cipher.py)
82
+
83
+ you can find samples of [file_cipher.py](https://github.com/shrimp2845-tw/project_DES/blob/main/examples/file_cipher.py) at:
84
+ [ecb_sample](https://github.com/shrimp2845-tw/project_DES/tree/main/examples/ecb_sample) & [cbc_sample](https://github.com/shrimp2845-tw/project_DES/tree/main/examples/cbc_sample)
85
+
86
+ ### explanation for samples
87
+ **mumei.jpg**: plaintext file
88
+
89
+ **mumei.bin**: encrypt mumei.jpg with key ‘berries’ and mode ECB
90
+
91
+ **mumei_correct_decrypt**.jpg: decrypt mumei.bin with key ‘berries’ and mode ECB
92
+
93
+ **mumei_wrong_decrypt.jpg**: decrypt mumei.bin with key ‘friend’ and mode ECB
94
+
95
+ **ina.jpg**: plaintext file
96
+
97
+ **ina.bin**: encrypt ina.jpg with key ‘takodachi’ and mode CBC
98
+
99
+ **ina_correct_decrypt.jpg**: decrypt ina.bin with key ‘takodachi’ and mode CBC
100
+
101
+ **ina_wrong_decrypt.jpg**: decrypt ina.bin with key ‘violet’ and mode CBC
102
+
103
+
104
+ ## Performance
105
+ This project is designed for learning and understanding how DES works rather than for practical encryption use. The implementation prioritizes readability and modular structure over performance.
106
+
107
+ As a result, encryption process is **very slow**. It takes nearly 2 minutes to encrypt 1MB file.
108
+
109
+
110
+
111
+
@@ -0,0 +1,15 @@
1
+ README.md
2
+ pyproject.toml
3
+ project_DES/__init__.py
4
+ project_DES/project_DES.py
5
+ project_DES/des_core/__init__.py
6
+ project_DES/des_core/bit_utils.py
7
+ project_DES/des_core/crypto.py
8
+ project_DES/des_core/feistel.py
9
+ project_DES/des_core/key.py
10
+ project_DES/des_core/mangler.py
11
+ project_DES/des_core/permute.py
12
+ project_des.egg-info/PKG-INFO
13
+ project_des.egg-info/SOURCES.txt
14
+ project_des.egg-info/dependency_links.txt
15
+ project_des.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ project_DES
@@ -0,0 +1,31 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "project-des"
7
+ version = "0.1.0"
8
+ description = "Educational Python implementation of the Data Encryption Standard (DES)"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = {text = "MIT"}
12
+
13
+ authors = [
14
+ {name = "shrimp2845", email = "goldencheng15@gmail.com"}
15
+ ]
16
+
17
+ keywords = ["des", "cryptography", "education", "feistel"]
18
+
19
+ classifiers = [
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.10",
22
+ "License :: OSI Approved :: MIT License",
23
+ ]
24
+
25
+ [project.urls]
26
+ Homepage = "https://github.com/shrimp2845-tw/project_DES"
27
+ Repository = "https://github.com/shrimp2845-tw/project_DES"
28
+
29
+ [tool.setuptools.packages.find]
30
+ where = ["."]
31
+ include = ["project_DES*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+