ddes-dua-crypto 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.
- ddes_dua_crypto-0.1.0/PKG-INFO +90 -0
- ddes_dua_crypto-0.1.0/README.md +77 -0
- ddes_dua_crypto-0.1.0/pyproject.toml +25 -0
- ddes_dua_crypto-0.1.0/setup.cfg +4 -0
- ddes_dua_crypto-0.1.0/src/ddes/__init__.py +6 -0
- ddes_dua_crypto-0.1.0/src/ddes/cipher.py +183 -0
- ddes_dua_crypto-0.1.0/src/ddes/cli.py +61 -0
- ddes_dua_crypto-0.1.0/src/ddes/tables.py +74 -0
- ddes_dua_crypto-0.1.0/src/ddes/utils.py +58 -0
- ddes_dua_crypto-0.1.0/src/ddes_dua_crypto.egg-info/PKG-INFO +90 -0
- ddes_dua_crypto-0.1.0/src/ddes_dua_crypto.egg-info/SOURCES.txt +13 -0
- ddes_dua_crypto-0.1.0/src/ddes_dua_crypto.egg-info/dependency_links.txt +1 -0
- ddes_dua_crypto-0.1.0/src/ddes_dua_crypto.egg-info/entry_points.txt +2 -0
- ddes_dua_crypto-0.1.0/src/ddes_dua_crypto.egg-info/top_level.txt +1 -0
- ddes_dua_crypto-0.1.0/tests/test_cipher.py +42 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ddes-dua-crypto
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Dynamic Data Encryption Standard (D-DES) with a single dynamic S-box.
|
|
5
|
+
Author-email: Dua Amir <duaamir392004@gmail.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/duaamir39/d-des.git
|
|
7
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Topic :: Security :: Cryptography
|
|
11
|
+
Requires-Python: >=3.13
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# D-DES (Dynamic Data Encryption Standard)
|
|
15
|
+
|
|
16
|
+
> **⚠️ Security Disclaimer:** This is an experimental cryptographic tool for research and educational purposes. It is not intended for securing sensitive production data.
|
|
17
|
+
|
|
18
|
+
D-DES is a custom implementation of the classic DES (Data Encryption Standard) block cipher, modified to feature a **Dynamic S-Box**.
|
|
19
|
+
|
|
20
|
+
## Requirements
|
|
21
|
+
- **Python 3.13.3+**: This library leverages modern Python type hinting and bit-manipulation features. Ensure you are running Python 3.13.3 or higher.
|
|
22
|
+
|
|
23
|
+
## Features
|
|
24
|
+
- Standard 64-bit block size and 56-bit effective key.
|
|
25
|
+
- 16 Feistel rounds with standard permutation tables (IP, IP-1, E, P, PC-1, PC-2).
|
|
26
|
+
- **Dynamic S-Box**: Replaces the 8 static S-boxes with a single, dynamically generated $6 \times 4$ S-box completely seeded by the encryption key.
|
|
27
|
+
- **CBC Mode Support**: Prevents repeating data blocks by utilizing an Initialization Vector (IV).
|
|
28
|
+
- **PKCS#7 Padding**: Safely encrypts and decrypts files of any arbitrary size.
|
|
29
|
+
- **CLI Utility**: Built-in command line interface to seamlessly encrypt/decrypt any file on your computer.
|
|
30
|
+
- **Operational Logger**: Automatically and securely logs operation metadata to a `logs.json` file without leaking secrets or plaintext.
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
You can install the package directly from PyPI. In your terminal, run:
|
|
35
|
+
```bash
|
|
36
|
+
pip install ddes-dua-crypto
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## CLI Usage
|
|
40
|
+
|
|
41
|
+
The package installs a globally available `ddes-cli` tool.
|
|
42
|
+
|
|
43
|
+
**To encrypt a file (CBC mode):**
|
|
44
|
+
```bash
|
|
45
|
+
ddes-cli encrypt -k "8bytekey" -m CBC --iv "8byte_iv" -i plaintext.txt -o encrypted.bin
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**To decrypt a file:**
|
|
49
|
+
```bash
|
|
50
|
+
ddes-cli decrypt -k "8bytekey" -m CBC --iv "8byte_iv" -i encrypted.bin -o decrypted.txt
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Python API Usage
|
|
54
|
+
|
|
55
|
+
You can also use the cipher directly inside your own Python projects.
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from ddes import DDESCipher
|
|
59
|
+
|
|
60
|
+
# 8-byte key and IV
|
|
61
|
+
key = b'8bytekey'
|
|
62
|
+
iv = b'8byte_iv'
|
|
63
|
+
|
|
64
|
+
# Initialize in CBC mode
|
|
65
|
+
cipher = DDESCipher(key, mode='CBC', iv=iv)
|
|
66
|
+
|
|
67
|
+
# Encrypt
|
|
68
|
+
plaintext = b'Hello World! This is D-DES.'
|
|
69
|
+
ciphertext = cipher.encrypt(plaintext)
|
|
70
|
+
print(f"Ciphertext (Hex): {ciphertext.hex()}")
|
|
71
|
+
|
|
72
|
+
# Decrypt
|
|
73
|
+
decrypted = cipher.decrypt(ciphertext)
|
|
74
|
+
print(f"Decrypted: {decrypted.decode('utf-8')}")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Operational Logger
|
|
78
|
+
All CLI encryption and decryption commands automatically append an audit trail to `logs.json`. The logger securely stores the timestamp, operation type, mode, and success status. **It never stores keys or file contents.**
|
|
79
|
+
|
|
80
|
+
**Example Audit Log (`logs.json`):**
|
|
81
|
+
```json
|
|
82
|
+
[
|
|
83
|
+
{
|
|
84
|
+
"timestamp": "2026-05-08T19:02:15.123456",
|
|
85
|
+
"operation": "Encrypt",
|
|
86
|
+
"mode": "CBC",
|
|
87
|
+
"status": "Success"
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
```
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# D-DES (Dynamic Data Encryption Standard)
|
|
2
|
+
|
|
3
|
+
> **⚠️ Security Disclaimer:** This is an experimental cryptographic tool for research and educational purposes. It is not intended for securing sensitive production data.
|
|
4
|
+
|
|
5
|
+
D-DES is a custom implementation of the classic DES (Data Encryption Standard) block cipher, modified to feature a **Dynamic S-Box**.
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
- **Python 3.13.3+**: This library leverages modern Python type hinting and bit-manipulation features. Ensure you are running Python 3.13.3 or higher.
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
- Standard 64-bit block size and 56-bit effective key.
|
|
12
|
+
- 16 Feistel rounds with standard permutation tables (IP, IP-1, E, P, PC-1, PC-2).
|
|
13
|
+
- **Dynamic S-Box**: Replaces the 8 static S-boxes with a single, dynamically generated $6 \times 4$ S-box completely seeded by the encryption key.
|
|
14
|
+
- **CBC Mode Support**: Prevents repeating data blocks by utilizing an Initialization Vector (IV).
|
|
15
|
+
- **PKCS#7 Padding**: Safely encrypts and decrypts files of any arbitrary size.
|
|
16
|
+
- **CLI Utility**: Built-in command line interface to seamlessly encrypt/decrypt any file on your computer.
|
|
17
|
+
- **Operational Logger**: Automatically and securely logs operation metadata to a `logs.json` file without leaking secrets or plaintext.
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
You can install the package directly from PyPI. In your terminal, run:
|
|
22
|
+
```bash
|
|
23
|
+
pip install ddes-dua-crypto
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## CLI Usage
|
|
27
|
+
|
|
28
|
+
The package installs a globally available `ddes-cli` tool.
|
|
29
|
+
|
|
30
|
+
**To encrypt a file (CBC mode):**
|
|
31
|
+
```bash
|
|
32
|
+
ddes-cli encrypt -k "8bytekey" -m CBC --iv "8byte_iv" -i plaintext.txt -o encrypted.bin
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**To decrypt a file:**
|
|
36
|
+
```bash
|
|
37
|
+
ddes-cli decrypt -k "8bytekey" -m CBC --iv "8byte_iv" -i encrypted.bin -o decrypted.txt
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Python API Usage
|
|
41
|
+
|
|
42
|
+
You can also use the cipher directly inside your own Python projects.
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from ddes import DDESCipher
|
|
46
|
+
|
|
47
|
+
# 8-byte key and IV
|
|
48
|
+
key = b'8bytekey'
|
|
49
|
+
iv = b'8byte_iv'
|
|
50
|
+
|
|
51
|
+
# Initialize in CBC mode
|
|
52
|
+
cipher = DDESCipher(key, mode='CBC', iv=iv)
|
|
53
|
+
|
|
54
|
+
# Encrypt
|
|
55
|
+
plaintext = b'Hello World! This is D-DES.'
|
|
56
|
+
ciphertext = cipher.encrypt(plaintext)
|
|
57
|
+
print(f"Ciphertext (Hex): {ciphertext.hex()}")
|
|
58
|
+
|
|
59
|
+
# Decrypt
|
|
60
|
+
decrypted = cipher.decrypt(ciphertext)
|
|
61
|
+
print(f"Decrypted: {decrypted.decode('utf-8')}")
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Operational Logger
|
|
65
|
+
All CLI encryption and decryption commands automatically append an audit trail to `logs.json`. The logger securely stores the timestamp, operation type, mode, and success status. **It never stores keys or file contents.**
|
|
66
|
+
|
|
67
|
+
**Example Audit Log (`logs.json`):**
|
|
68
|
+
```json
|
|
69
|
+
[
|
|
70
|
+
{
|
|
71
|
+
"timestamp": "2026-05-08T19:02:15.123456",
|
|
72
|
+
"operation": "Encrypt",
|
|
73
|
+
"mode": "CBC",
|
|
74
|
+
"status": "Success"
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
```
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ddes-dua-crypto"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Dua Amir", email="duaamir392004@gmail.com" },
|
|
10
|
+
]
|
|
11
|
+
description = "Dynamic Data Encryption Standard (D-DES) with a single dynamic S-box."
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.13"
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3.13",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"Topic :: Security :: Cryptography",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
"Homepage" = "https://github.com/duaamir39/d-des.git"
|
|
23
|
+
|
|
24
|
+
[project.scripts]
|
|
25
|
+
ddes-cli = "ddes.cli:main"
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import random
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from . import tables
|
|
6
|
+
from . import utils
|
|
7
|
+
|
|
8
|
+
class DDESCipher:
|
|
9
|
+
def __init__(self, key: bytes, mode: str = 'ECB', iv: bytes = None):
|
|
10
|
+
"""
|
|
11
|
+
Initializes the D-DES cipher with a given 8-byte key.
|
|
12
|
+
"""
|
|
13
|
+
if len(key) != 8:
|
|
14
|
+
raise ValueError("Key must be exactly 8 bytes (64 bits)")
|
|
15
|
+
if mode not in ('ECB', 'CBC'):
|
|
16
|
+
raise ValueError("Mode must be 'ECB' or 'CBC'")
|
|
17
|
+
if mode == 'CBC':
|
|
18
|
+
if iv is None or len(iv) != 8:
|
|
19
|
+
raise ValueError("CBC mode requires exactly an 8-byte IV")
|
|
20
|
+
self.iv = iv
|
|
21
|
+
else:
|
|
22
|
+
self.iv = None
|
|
23
|
+
|
|
24
|
+
self.mode = mode
|
|
25
|
+
self.key_int = int.from_bytes(key, byteorder='big')
|
|
26
|
+
self.subkeys = self._generate_subkeys()
|
|
27
|
+
self.sbox = self._generate_dynamic_sbox(key)
|
|
28
|
+
|
|
29
|
+
def _generate_dynamic_sbox(self, key: bytes) -> List[int]:
|
|
30
|
+
"""
|
|
31
|
+
Generate one single 6x4 S-box (64 entries total, values 0-15) based on the key.
|
|
32
|
+
"""
|
|
33
|
+
# Derive a seed from the key using SHA-256
|
|
34
|
+
seed = int(hashlib.sha256(key).hexdigest(), 16)
|
|
35
|
+
|
|
36
|
+
# We need a 64-entry S-box with values 0-15.
|
|
37
|
+
# A typical S-box has 4 rows of 16 columns.
|
|
38
|
+
# For our dynamic S-box, we'll create an array of 64 elements,
|
|
39
|
+
# consisting of exactly four 0s, four 1s, ..., four 15s.
|
|
40
|
+
sbox = [i % 16 for i in range(64)]
|
|
41
|
+
|
|
42
|
+
# Shuffle the S-box deterministically using the derived seed
|
|
43
|
+
r = random.Random(seed)
|
|
44
|
+
r.shuffle(sbox)
|
|
45
|
+
|
|
46
|
+
return sbox
|
|
47
|
+
|
|
48
|
+
def _generate_subkeys(self) -> List[int]:
|
|
49
|
+
"""
|
|
50
|
+
Generate the 16 48-bit subkeys using the standard DES key schedule.
|
|
51
|
+
"""
|
|
52
|
+
subkeys = []
|
|
53
|
+
|
|
54
|
+
# Apply PC-1 to the 64-bit key
|
|
55
|
+
pc1_out = utils.permute(self.key_int, tables.PC_1, 64, 56)
|
|
56
|
+
|
|
57
|
+
# Split into two 28-bit halves
|
|
58
|
+
c, d = utils.split_half(pc1_out, 56)
|
|
59
|
+
|
|
60
|
+
for shift in tables.SHIFTS:
|
|
61
|
+
# Circular left shift each half
|
|
62
|
+
c = utils.left_shift(c, shift, 28)
|
|
63
|
+
d = utils.left_shift(d, shift, 28)
|
|
64
|
+
|
|
65
|
+
# Merge halves
|
|
66
|
+
cd = utils.merge_half(c, d, 28)
|
|
67
|
+
|
|
68
|
+
# Apply PC-2
|
|
69
|
+
subkey = utils.permute(cd, tables.PC_2, 56, 48)
|
|
70
|
+
subkeys.append(subkey)
|
|
71
|
+
|
|
72
|
+
return subkeys
|
|
73
|
+
|
|
74
|
+
def _feistel(self, right_half: int, subkey: int) -> int:
|
|
75
|
+
"""
|
|
76
|
+
The core Feistel function f(R, K).
|
|
77
|
+
"""
|
|
78
|
+
# 1. Expansion P-box
|
|
79
|
+
expanded = utils.permute(right_half, tables.E, 32, 48)
|
|
80
|
+
|
|
81
|
+
# 2. Key mixing
|
|
82
|
+
xored = expanded ^ subkey
|
|
83
|
+
|
|
84
|
+
# 3. Dynamic S-box substitution
|
|
85
|
+
# Split 48 bits into 8 6-bit blocks, pass each through the same single S-box
|
|
86
|
+
sbox_out = 0
|
|
87
|
+
for i in range(8):
|
|
88
|
+
# Extract 6 bits from left to right
|
|
89
|
+
shift_amount = 48 - 6 * (i + 1)
|
|
90
|
+
six_bit_block = (xored >> shift_amount) & 0x3F
|
|
91
|
+
|
|
92
|
+
# Use the 6 bits as an index into our 64-entry dynamic S-box
|
|
93
|
+
four_bit_out = self.sbox[six_bit_block]
|
|
94
|
+
|
|
95
|
+
# Append to the 32-bit output
|
|
96
|
+
sbox_out |= (four_bit_out << (32 - 4 * (i + 1)))
|
|
97
|
+
|
|
98
|
+
# 4. Permutation
|
|
99
|
+
return utils.permute(sbox_out, tables.P, 32, 32)
|
|
100
|
+
|
|
101
|
+
def _process_block(self, block: bytes, decrypt: bool = False) -> bytes:
|
|
102
|
+
"""
|
|
103
|
+
Encrypts or decrypts a single 64-bit block.
|
|
104
|
+
"""
|
|
105
|
+
if len(block) != 8:
|
|
106
|
+
raise ValueError("Block must be exactly 8 bytes (64 bits)")
|
|
107
|
+
|
|
108
|
+
block_int = int.from_bytes(block, byteorder='big')
|
|
109
|
+
|
|
110
|
+
# Initial Permutation (IP)
|
|
111
|
+
ip_out = utils.permute(block_int, tables.IP, 64, 64)
|
|
112
|
+
|
|
113
|
+
# Split into 32-bit halves
|
|
114
|
+
left, right = utils.split_half(ip_out, 64)
|
|
115
|
+
|
|
116
|
+
# 16 Feistel rounds
|
|
117
|
+
# Decryption uses subkeys in reverse order (K16 to K1)
|
|
118
|
+
subkeys = self.subkeys[::-1] if decrypt else self.subkeys
|
|
119
|
+
|
|
120
|
+
for subkey in subkeys:
|
|
121
|
+
next_left = right
|
|
122
|
+
next_right = left ^ self._feistel(right, subkey)
|
|
123
|
+
left, right = next_left, next_right
|
|
124
|
+
|
|
125
|
+
# Swap halves after the 16th round (before final permutation)
|
|
126
|
+
pre_output = utils.merge_half(right, left, 32)
|
|
127
|
+
|
|
128
|
+
# Final Permutation (IP-1)
|
|
129
|
+
final_out = utils.permute(pre_output, tables.IP_INV, 64, 64)
|
|
130
|
+
|
|
131
|
+
return final_out.to_bytes(8, byteorder='big')
|
|
132
|
+
|
|
133
|
+
def encrypt(self, data: bytes) -> bytes:
|
|
134
|
+
"""
|
|
135
|
+
Encrypt data using D-DES (ECB or CBC mode with PKCS#7 padding).
|
|
136
|
+
"""
|
|
137
|
+
# PKCS7 padding
|
|
138
|
+
pad_len = 8 - (len(data) % 8)
|
|
139
|
+
padded_data = data + bytes([pad_len] * pad_len)
|
|
140
|
+
|
|
141
|
+
ciphertext = bytearray()
|
|
142
|
+
prev_block = self.iv
|
|
143
|
+
|
|
144
|
+
for i in range(0, len(padded_data), 8):
|
|
145
|
+
block = padded_data[i:i+8]
|
|
146
|
+
|
|
147
|
+
if self.mode == 'CBC':
|
|
148
|
+
# XOR plaintext block with previous ciphertext block (or IV)
|
|
149
|
+
block = bytes(a ^ b for a, b in zip(block, prev_block))
|
|
150
|
+
|
|
151
|
+
enc_block = self._process_block(block, decrypt=False)
|
|
152
|
+
ciphertext.extend(enc_block)
|
|
153
|
+
prev_block = enc_block
|
|
154
|
+
|
|
155
|
+
return bytes(ciphertext)
|
|
156
|
+
|
|
157
|
+
def decrypt(self, data: bytes) -> bytes:
|
|
158
|
+
"""
|
|
159
|
+
Decrypt data using D-DES (ECB or CBC mode with PKCS#7 unpadding).
|
|
160
|
+
"""
|
|
161
|
+
if len(data) % 8 != 0:
|
|
162
|
+
raise ValueError("Ciphertext length must be a multiple of 8 bytes")
|
|
163
|
+
|
|
164
|
+
plaintext = bytearray()
|
|
165
|
+
prev_block = self.iv
|
|
166
|
+
|
|
167
|
+
for i in range(0, len(data), 8):
|
|
168
|
+
block = data[i:i+8]
|
|
169
|
+
dec_block = self._process_block(block, decrypt=True)
|
|
170
|
+
|
|
171
|
+
if self.mode == 'CBC':
|
|
172
|
+
# XOR decrypted block with previous ciphertext block (or IV)
|
|
173
|
+
dec_block = bytes(a ^ b for a, b in zip(dec_block, prev_block))
|
|
174
|
+
prev_block = block
|
|
175
|
+
|
|
176
|
+
plaintext.extend(dec_block)
|
|
177
|
+
|
|
178
|
+
# PKCS7 unpadding
|
|
179
|
+
pad_len = plaintext[-1]
|
|
180
|
+
if pad_len < 1 or pad_len > 8:
|
|
181
|
+
raise ValueError("Invalid padding detected during decryption.")
|
|
182
|
+
|
|
183
|
+
return bytes(plaintext[:-pad_len])
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import sys
|
|
3
|
+
from .cipher import DDESCipher
|
|
4
|
+
from .utils import Logger
|
|
5
|
+
|
|
6
|
+
def main():
|
|
7
|
+
parser = argparse.ArgumentParser(description="D-DES (Dynamic Data Encryption Standard) CLI")
|
|
8
|
+
|
|
9
|
+
parser.add_argument('action', choices=['encrypt', 'decrypt'], help="Action to perform")
|
|
10
|
+
parser.add_argument('-k', '--key', required=True, help="8-byte encryption key")
|
|
11
|
+
parser.add_argument('-i', '--input', required=True, help="Input file path")
|
|
12
|
+
parser.add_argument('-o', '--output', required=True, help="Output file path")
|
|
13
|
+
parser.add_argument('-m', '--mode', choices=['ECB', 'CBC'], default='CBC', help="Block cipher mode (default: CBC)")
|
|
14
|
+
parser.add_argument('--iv', help="8-byte initialization vector (required for CBC mode)")
|
|
15
|
+
|
|
16
|
+
args = parser.parse_args()
|
|
17
|
+
|
|
18
|
+
key_bytes = args.key.encode('utf-8')
|
|
19
|
+
if len(key_bytes) != 8:
|
|
20
|
+
print("Error: Key must be exactly 8 characters long.")
|
|
21
|
+
sys.exit(1)
|
|
22
|
+
|
|
23
|
+
iv_bytes = None
|
|
24
|
+
if args.mode == 'CBC':
|
|
25
|
+
if not args.iv:
|
|
26
|
+
print("Error: CBC mode requires an IV (--iv).")
|
|
27
|
+
sys.exit(1)
|
|
28
|
+
iv_bytes = args.iv.encode('utf-8')
|
|
29
|
+
if len(iv_bytes) != 8:
|
|
30
|
+
print("Error: IV must be exactly 8 characters long.")
|
|
31
|
+
sys.exit(1)
|
|
32
|
+
|
|
33
|
+
logger = Logger("logs.json")
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
cipher = DDESCipher(key=key_bytes, mode=args.mode, iv=iv_bytes)
|
|
37
|
+
|
|
38
|
+
with open(args.input, 'rb') as f:
|
|
39
|
+
data = f.read()
|
|
40
|
+
|
|
41
|
+
if args.action == 'encrypt':
|
|
42
|
+
result = cipher.encrypt(data)
|
|
43
|
+
print(f"Successfully encrypted {len(data)} bytes to {len(result)} bytes.")
|
|
44
|
+
print(f"\nCiphertext (Hex): {result.hex()}\n")
|
|
45
|
+
logger.log("Encrypt", args.mode, "Success")
|
|
46
|
+
else:
|
|
47
|
+
result = cipher.decrypt(data)
|
|
48
|
+
print(f"Successfully decrypted {len(data)} bytes to {len(result)} bytes.")
|
|
49
|
+
print(f"\nDecrypted Data (Hex): {result.hex()}\n")
|
|
50
|
+
logger.log("Decrypt", args.mode, "Success")
|
|
51
|
+
|
|
52
|
+
with open(args.output, 'wb') as f:
|
|
53
|
+
f.write(result)
|
|
54
|
+
|
|
55
|
+
except Exception as e:
|
|
56
|
+
print(f"Error: {e}")
|
|
57
|
+
logger.log(args.action.capitalize(), args.mode, f"Failure - {str(e)}")
|
|
58
|
+
sys.exit(1)
|
|
59
|
+
|
|
60
|
+
if __name__ == '__main__':
|
|
61
|
+
main()
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Initial Permutation (IP)
|
|
2
|
+
IP = [
|
|
3
|
+
58, 50, 42, 34, 26, 18, 10, 2,
|
|
4
|
+
60, 52, 44, 36, 28, 20, 12, 4,
|
|
5
|
+
62, 54, 46, 38, 30, 22, 14, 6,
|
|
6
|
+
64, 56, 48, 40, 32, 24, 16, 8,
|
|
7
|
+
57, 49, 41, 33, 25, 17, 9, 1,
|
|
8
|
+
59, 51, 43, 35, 27, 19, 11, 3,
|
|
9
|
+
61, 53, 45, 37, 29, 21, 13, 5,
|
|
10
|
+
63, 55, 47, 39, 31, 23, 15, 7
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
# Final Permutation (IP-1)
|
|
14
|
+
IP_INV = [
|
|
15
|
+
40, 8, 48, 16, 56, 24, 64, 32,
|
|
16
|
+
39, 7, 47, 15, 55, 23, 63, 31,
|
|
17
|
+
38, 6, 46, 14, 54, 22, 62, 30,
|
|
18
|
+
37, 5, 45, 13, 53, 21, 61, 29,
|
|
19
|
+
36, 4, 44, 12, 52, 20, 60, 28,
|
|
20
|
+
35, 3, 43, 11, 51, 19, 59, 27,
|
|
21
|
+
34, 2, 42, 10, 50, 18, 58, 26,
|
|
22
|
+
33, 1, 41, 9, 49, 17, 57, 25
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
# Expansion Permutation (E)
|
|
26
|
+
E = [
|
|
27
|
+
32, 1, 2, 3, 4, 5,
|
|
28
|
+
4, 5, 6, 7, 8, 9,
|
|
29
|
+
8, 9, 10, 11, 12, 13,
|
|
30
|
+
12, 13, 14, 15, 16, 17,
|
|
31
|
+
16, 17, 18, 19, 20, 21,
|
|
32
|
+
20, 21, 22, 23, 24, 25,
|
|
33
|
+
24, 25, 26, 27, 28, 29,
|
|
34
|
+
28, 29, 30, 31, 32, 1
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
# Permutation Function (P)
|
|
38
|
+
P = [
|
|
39
|
+
16, 7, 20, 21,
|
|
40
|
+
29, 12, 28, 17,
|
|
41
|
+
1, 15, 23, 26,
|
|
42
|
+
5, 18, 31, 10,
|
|
43
|
+
2, 8, 24, 14,
|
|
44
|
+
32, 27, 3, 9,
|
|
45
|
+
19, 13, 30, 6,
|
|
46
|
+
22, 11, 4, 25
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
# Permuted Choice 1 (PC-1)
|
|
50
|
+
PC_1 = [
|
|
51
|
+
57, 49, 41, 33, 25, 17, 9,
|
|
52
|
+
1, 58, 50, 42, 34, 26, 18,
|
|
53
|
+
10, 2, 59, 51, 43, 35, 27,
|
|
54
|
+
19, 11, 3, 60, 52, 44, 36,
|
|
55
|
+
63, 55, 47, 39, 31, 23, 15,
|
|
56
|
+
7, 62, 54, 46, 38, 30, 22,
|
|
57
|
+
14, 6, 61, 53, 45, 37, 29,
|
|
58
|
+
21, 13, 5, 28, 20, 12, 4
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
# Permuted Choice 2 (PC-2)
|
|
62
|
+
PC_2 = [
|
|
63
|
+
14, 17, 11, 24, 1, 5,
|
|
64
|
+
3, 28, 15, 6, 21, 10,
|
|
65
|
+
23, 19, 12, 4, 26, 8,
|
|
66
|
+
16, 7, 27, 20, 13, 2,
|
|
67
|
+
41, 52, 31, 37, 47, 55,
|
|
68
|
+
30, 40, 51, 45, 33, 48,
|
|
69
|
+
44, 49, 39, 56, 34, 53,
|
|
70
|
+
46, 42, 50, 36, 29, 32
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
# Shifts per round
|
|
74
|
+
SHIFTS = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import datetime
|
|
4
|
+
|
|
5
|
+
def permute(block: int, table: list[int], input_size: int, output_size: int) -> int:
|
|
6
|
+
"""Permute an integer block using the given table."""
|
|
7
|
+
output = 0
|
|
8
|
+
for i, bit_pos in enumerate(table):
|
|
9
|
+
# DES tables are 1-indexed, so we subtract 1.
|
|
10
|
+
# The bit position from the left in a block of size input_size
|
|
11
|
+
shift = input_size - bit_pos
|
|
12
|
+
bit = (block >> shift) & 1
|
|
13
|
+
output |= (bit << (output_size - 1 - i))
|
|
14
|
+
return output
|
|
15
|
+
|
|
16
|
+
def split_half(block: int, size: int) -> tuple[int, int]:
|
|
17
|
+
"""Split an integer block into two halves."""
|
|
18
|
+
half_size = size // 2
|
|
19
|
+
mask = (1 << half_size) - 1
|
|
20
|
+
left = block >> half_size
|
|
21
|
+
right = block & mask
|
|
22
|
+
return left, right
|
|
23
|
+
|
|
24
|
+
def merge_half(left: int, right: int, half_size: int) -> int:
|
|
25
|
+
"""Merge two integer halves into a single block."""
|
|
26
|
+
return (left << half_size) | right
|
|
27
|
+
|
|
28
|
+
def left_shift(block: int, shift: int, size: int) -> int:
|
|
29
|
+
"""Circular left shift an integer block by the given shift amount."""
|
|
30
|
+
mask = (1 << size) - 1
|
|
31
|
+
return ((block << shift) | (block >> (size - shift))) & mask
|
|
32
|
+
|
|
33
|
+
class Logger:
|
|
34
|
+
def __init__(self, log_file="logs.json"):
|
|
35
|
+
self.log_file = log_file
|
|
36
|
+
|
|
37
|
+
def log(self, operation: str, mode: str, status: str):
|
|
38
|
+
log_entry = {
|
|
39
|
+
"timestamp": datetime.datetime.now().isoformat(),
|
|
40
|
+
"operation": operation,
|
|
41
|
+
"mode": mode,
|
|
42
|
+
"status": status
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
logs = []
|
|
46
|
+
if os.path.exists(self.log_file):
|
|
47
|
+
try:
|
|
48
|
+
with open(self.log_file, "r", encoding="utf-8") as f:
|
|
49
|
+
content = f.read().strip()
|
|
50
|
+
if content:
|
|
51
|
+
logs = json.loads(content)
|
|
52
|
+
except json.JSONDecodeError:
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
logs.append(log_entry)
|
|
56
|
+
|
|
57
|
+
with open(self.log_file, "w", encoding="utf-8") as f:
|
|
58
|
+
json.dump(logs, f, indent=4)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ddes-dua-crypto
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Dynamic Data Encryption Standard (D-DES) with a single dynamic S-box.
|
|
5
|
+
Author-email: Dua Amir <duaamir392004@gmail.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/duaamir39/d-des.git
|
|
7
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Topic :: Security :: Cryptography
|
|
11
|
+
Requires-Python: >=3.13
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# D-DES (Dynamic Data Encryption Standard)
|
|
15
|
+
|
|
16
|
+
> **⚠️ Security Disclaimer:** This is an experimental cryptographic tool for research and educational purposes. It is not intended for securing sensitive production data.
|
|
17
|
+
|
|
18
|
+
D-DES is a custom implementation of the classic DES (Data Encryption Standard) block cipher, modified to feature a **Dynamic S-Box**.
|
|
19
|
+
|
|
20
|
+
## Requirements
|
|
21
|
+
- **Python 3.13.3+**: This library leverages modern Python type hinting and bit-manipulation features. Ensure you are running Python 3.13.3 or higher.
|
|
22
|
+
|
|
23
|
+
## Features
|
|
24
|
+
- Standard 64-bit block size and 56-bit effective key.
|
|
25
|
+
- 16 Feistel rounds with standard permutation tables (IP, IP-1, E, P, PC-1, PC-2).
|
|
26
|
+
- **Dynamic S-Box**: Replaces the 8 static S-boxes with a single, dynamically generated $6 \times 4$ S-box completely seeded by the encryption key.
|
|
27
|
+
- **CBC Mode Support**: Prevents repeating data blocks by utilizing an Initialization Vector (IV).
|
|
28
|
+
- **PKCS#7 Padding**: Safely encrypts and decrypts files of any arbitrary size.
|
|
29
|
+
- **CLI Utility**: Built-in command line interface to seamlessly encrypt/decrypt any file on your computer.
|
|
30
|
+
- **Operational Logger**: Automatically and securely logs operation metadata to a `logs.json` file without leaking secrets or plaintext.
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
You can install the package directly from PyPI. In your terminal, run:
|
|
35
|
+
```bash
|
|
36
|
+
pip install ddes-dua-crypto
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## CLI Usage
|
|
40
|
+
|
|
41
|
+
The package installs a globally available `ddes-cli` tool.
|
|
42
|
+
|
|
43
|
+
**To encrypt a file (CBC mode):**
|
|
44
|
+
```bash
|
|
45
|
+
ddes-cli encrypt -k "8bytekey" -m CBC --iv "8byte_iv" -i plaintext.txt -o encrypted.bin
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**To decrypt a file:**
|
|
49
|
+
```bash
|
|
50
|
+
ddes-cli decrypt -k "8bytekey" -m CBC --iv "8byte_iv" -i encrypted.bin -o decrypted.txt
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Python API Usage
|
|
54
|
+
|
|
55
|
+
You can also use the cipher directly inside your own Python projects.
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from ddes import DDESCipher
|
|
59
|
+
|
|
60
|
+
# 8-byte key and IV
|
|
61
|
+
key = b'8bytekey'
|
|
62
|
+
iv = b'8byte_iv'
|
|
63
|
+
|
|
64
|
+
# Initialize in CBC mode
|
|
65
|
+
cipher = DDESCipher(key, mode='CBC', iv=iv)
|
|
66
|
+
|
|
67
|
+
# Encrypt
|
|
68
|
+
plaintext = b'Hello World! This is D-DES.'
|
|
69
|
+
ciphertext = cipher.encrypt(plaintext)
|
|
70
|
+
print(f"Ciphertext (Hex): {ciphertext.hex()}")
|
|
71
|
+
|
|
72
|
+
# Decrypt
|
|
73
|
+
decrypted = cipher.decrypt(ciphertext)
|
|
74
|
+
print(f"Decrypted: {decrypted.decode('utf-8')}")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Operational Logger
|
|
78
|
+
All CLI encryption and decryption commands automatically append an audit trail to `logs.json`. The logger securely stores the timestamp, operation type, mode, and success status. **It never stores keys or file contents.**
|
|
79
|
+
|
|
80
|
+
**Example Audit Log (`logs.json`):**
|
|
81
|
+
```json
|
|
82
|
+
[
|
|
83
|
+
{
|
|
84
|
+
"timestamp": "2026-05-08T19:02:15.123456",
|
|
85
|
+
"operation": "Encrypt",
|
|
86
|
+
"mode": "CBC",
|
|
87
|
+
"status": "Success"
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/ddes/__init__.py
|
|
4
|
+
src/ddes/cipher.py
|
|
5
|
+
src/ddes/cli.py
|
|
6
|
+
src/ddes/tables.py
|
|
7
|
+
src/ddes/utils.py
|
|
8
|
+
src/ddes_dua_crypto.egg-info/PKG-INFO
|
|
9
|
+
src/ddes_dua_crypto.egg-info/SOURCES.txt
|
|
10
|
+
src/ddes_dua_crypto.egg-info/dependency_links.txt
|
|
11
|
+
src/ddes_dua_crypto.egg-info/entry_points.txt
|
|
12
|
+
src/ddes_dua_crypto.egg-info/top_level.txt
|
|
13
|
+
tests/test_cipher.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ddes
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from ddes.cipher import DDESCipher
|
|
3
|
+
|
|
4
|
+
class TestDDES(unittest.TestCase):
|
|
5
|
+
def test_encryption_decryption_ecb(self):
|
|
6
|
+
key = b'secret!!'
|
|
7
|
+
cipher = DDESCipher(key, mode='ECB')
|
|
8
|
+
|
|
9
|
+
plaintext = b'Cryptography is fascinating!'
|
|
10
|
+
ciphertext = cipher.encrypt(plaintext)
|
|
11
|
+
|
|
12
|
+
self.assertNotEqual(plaintext, ciphertext)
|
|
13
|
+
|
|
14
|
+
decrypted = cipher.decrypt(ciphertext)
|
|
15
|
+
self.assertEqual(plaintext, decrypted)
|
|
16
|
+
|
|
17
|
+
def test_encryption_decryption_cbc(self):
|
|
18
|
+
key = b'secret!!'
|
|
19
|
+
iv = b'init_vec'
|
|
20
|
+
cipher = DDESCipher(key, mode='CBC', iv=iv)
|
|
21
|
+
|
|
22
|
+
plaintext = b'Cryptography is fascinating! CBC mode is secure.'
|
|
23
|
+
ciphertext = cipher.encrypt(plaintext)
|
|
24
|
+
|
|
25
|
+
self.assertNotEqual(plaintext, ciphertext)
|
|
26
|
+
|
|
27
|
+
decrypted = cipher.decrypt(ciphertext)
|
|
28
|
+
self.assertEqual(plaintext, decrypted)
|
|
29
|
+
|
|
30
|
+
def test_different_keys_different_sboxes(self):
|
|
31
|
+
key1 = b'key_one!'
|
|
32
|
+
key2 = b'key_two!'
|
|
33
|
+
|
|
34
|
+
cipher1 = DDESCipher(key1)
|
|
35
|
+
cipher2 = DDESCipher(key2)
|
|
36
|
+
|
|
37
|
+
# Since the keys are different, their seeds are different,
|
|
38
|
+
# and therefore the shuffled S-boxes should be highly likely to differ.
|
|
39
|
+
self.assertNotEqual(cipher1.sbox, cipher2.sbox)
|
|
40
|
+
|
|
41
|
+
if __name__ == '__main__':
|
|
42
|
+
unittest.main()
|