affine-cipher 0.1.0__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.
- affine_cipher/__init__.py +2 -0
- affine_cipher/case.py +5 -0
- affine_cipher/cli.py +37 -0
- affine_cipher/decrypt.py +97 -0
- affine_cipher/encrypt.py +48 -0
- affine_cipher/key_validation.py +9 -0
- affine_cipher-0.1.0.dist-info/METADATA +1428 -0
- affine_cipher-0.1.0.dist-info/RECORD +11 -0
- affine_cipher-0.1.0.dist-info/WHEEL +4 -0
- affine_cipher-0.1.0.dist-info/entry_points.txt +3 -0
- affine_cipher-0.1.0.dist-info/licenses/LICENSE +21 -0
affine_cipher/case.py
ADDED
affine_cipher/cli.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
3
|
+
from affine_cipher.decrypt import run_decrypt
|
|
4
|
+
from affine_cipher.encrypt import run_encrypt
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def main() -> None:
|
|
8
|
+
|
|
9
|
+
parser = argparse.ArgumentParser(prog="affine-cipher")
|
|
10
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
11
|
+
encrypt_parser = subparsers.add_parser("encrypt")
|
|
12
|
+
decrypt_parser = subparsers.add_parser("decrypt")
|
|
13
|
+
|
|
14
|
+
for p in [encrypt_parser]:
|
|
15
|
+
p.add_argument("-a", type=int, required=True)
|
|
16
|
+
p.add_argument("-b", type=int, required=True)
|
|
17
|
+
|
|
18
|
+
for p in [decrypt_parser]:
|
|
19
|
+
p.add_argument("-a", type=int, required=False)
|
|
20
|
+
p.add_argument("-b", type=int, required=False)
|
|
21
|
+
p.add_argument("--brute-force", action="store_true")
|
|
22
|
+
|
|
23
|
+
args = parser.parse_args()
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
if args.command == "encrypt":
|
|
27
|
+
run_encrypt(args.a, args.b)
|
|
28
|
+
elif args.command == "decrypt":
|
|
29
|
+
if (args.a is None) != (args.b is None):
|
|
30
|
+
parser.error("-a and -b must be given together")
|
|
31
|
+
if (args.a is not None) and (args.b is not None) and (args.brute_force):
|
|
32
|
+
parser.error(
|
|
33
|
+
"-a and -b and --brute-force cannot all be selected together. Choose either -a and -b or --brute-force." # noqa: E501
|
|
34
|
+
)
|
|
35
|
+
run_decrypt(args.a, args.b, args.brute_force)
|
|
36
|
+
except ValueError as e:
|
|
37
|
+
parser.error(str(e))
|
affine_cipher/decrypt.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import string
|
|
2
|
+
|
|
3
|
+
from colorama import Fore, init
|
|
4
|
+
|
|
5
|
+
from affine_cipher.case import restore_char_case
|
|
6
|
+
from affine_cipher.key_validation import validate_affine_key
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# Function to get the Euclidean Algorithm
|
|
10
|
+
def extended_gcd(a: int, b: int) -> tuple[int, int, int]:
|
|
11
|
+
"""
|
|
12
|
+
extended Euclidean Algorithm to find the greatest common divisor and
|
|
13
|
+
coefficients x, y such that ax + by = gcd(a, b)
|
|
14
|
+
"""
|
|
15
|
+
if a == 0:
|
|
16
|
+
return (b, 0, 1)
|
|
17
|
+
else:
|
|
18
|
+
g, x, y = extended_gcd(b % a, a)
|
|
19
|
+
return (g, y - (b // a) * x, x)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# function to get the modular inverse
|
|
23
|
+
def modular_inverse(a: int, m: int) -> int:
|
|
24
|
+
"""
|
|
25
|
+
Compute the modular multiplicative inverse of a modulo m.
|
|
26
|
+
Raises an exception if the modular inverse does not exist.
|
|
27
|
+
"""
|
|
28
|
+
g, x, y = extended_gcd(a, m)
|
|
29
|
+
if g != 1:
|
|
30
|
+
raise ValueError("Modular inverse does not exist!")
|
|
31
|
+
else:
|
|
32
|
+
return x % m
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# Function to decrypt our message
|
|
36
|
+
def affine_decrypt(ciphertext: str, a: int, b: int) -> str:
|
|
37
|
+
"""
|
|
38
|
+
Decrypt a message with the Affine Cipher using the given key components a and b.
|
|
39
|
+
"""
|
|
40
|
+
alphabet = string.ascii_uppercase
|
|
41
|
+
m = len(alphabet)
|
|
42
|
+
validate_affine_key(a, m)
|
|
43
|
+
plaintext = ""
|
|
44
|
+
# Compute the multiplicative inverse of a
|
|
45
|
+
a_inv = modular_inverse(a, m)
|
|
46
|
+
# Iterate through each character in the ciphertext
|
|
47
|
+
for char in ciphertext:
|
|
48
|
+
# Check if the character is in the alphabet
|
|
49
|
+
if char.upper() in alphabet:
|
|
50
|
+
# If it's an alphabet letter, decrypt it
|
|
51
|
+
# Find the index of the character in the alphabet
|
|
52
|
+
c = alphabet.index(char.upper())
|
|
53
|
+
# Apply the decryption formula: a_inv * (c - b) mod m
|
|
54
|
+
p = (a_inv * (c - b)) % m
|
|
55
|
+
transformed = alphabet[p]
|
|
56
|
+
# Append the decrypted character to the plaintext
|
|
57
|
+
plaintext += restore_char_case(char, transformed)
|
|
58
|
+
else:
|
|
59
|
+
# If the character is not in the alphabet, keep it unchanged
|
|
60
|
+
plaintext += char
|
|
61
|
+
# Return the decrypted plaintext
|
|
62
|
+
return plaintext
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# Function to perform brute-force attack
|
|
66
|
+
def affine_brute_force(ciphertext: str) -> None:
|
|
67
|
+
"""
|
|
68
|
+
Brute-force attack to find possible keys for an Affine Cipher and print
|
|
69
|
+
potential decryptions for manual inspection.
|
|
70
|
+
"""
|
|
71
|
+
alphabet = string.ascii_uppercase
|
|
72
|
+
m = len(alphabet)
|
|
73
|
+
# Iterate through possible values for a
|
|
74
|
+
for a in range(1, m):
|
|
75
|
+
# Ensure a and m are coprime
|
|
76
|
+
if extended_gcd(a, m)[0] == 1:
|
|
77
|
+
# Iterate through possible values for b
|
|
78
|
+
for b in range(0, m):
|
|
79
|
+
# Decrypt using the current key
|
|
80
|
+
decrypted_text = affine_decrypt(ciphertext, a, b)
|
|
81
|
+
# Print potential decryption for manual inspection
|
|
82
|
+
print(f"Key a={a}, b={b}: {decrypted_text}")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def run_decrypt(a: int | None, b: int | None, brute_force: bool) -> None:
|
|
86
|
+
# Initialize Colorama
|
|
87
|
+
init()
|
|
88
|
+
|
|
89
|
+
ciphertext = input(f"{Fore.GREEN}[?] Enter message to decrypt: ")
|
|
90
|
+
if a is not None and b is not None:
|
|
91
|
+
decrypted_text = affine_decrypt(ciphertext, a, b)
|
|
92
|
+
print(f"{Fore.GREEN}[+] Decrypted Text: {decrypted_text} ")
|
|
93
|
+
elif a is None and b is None and not brute_force:
|
|
94
|
+
raise ValueError("Please either supply a key or --brute-force.")
|
|
95
|
+
else:
|
|
96
|
+
# Perform a brute-force attack to find a potential decrypted message.
|
|
97
|
+
affine_brute_force(ciphertext)
|
affine_cipher/encrypt.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import string
|
|
2
|
+
|
|
3
|
+
from colorama import Fore, init
|
|
4
|
+
|
|
5
|
+
from affine_cipher.case import restore_char_case
|
|
6
|
+
from affine_cipher.key_validation import validate_affine_key
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def affine_encryption(plaintext: str, a: int, b: int) -> str:
|
|
10
|
+
# Define the uppercase alphabet.
|
|
11
|
+
alphabet = string.ascii_uppercase
|
|
12
|
+
# Get the length of the alphabet
|
|
13
|
+
m = len(alphabet)
|
|
14
|
+
validate_affine_key(a, m)
|
|
15
|
+
# Initialize an empty string to store the ciphertext.
|
|
16
|
+
ciphertext = ""
|
|
17
|
+
# Iterate through each character in the plaintext.
|
|
18
|
+
for char in plaintext:
|
|
19
|
+
# Check if the character is in the alphabet.
|
|
20
|
+
if char.upper() in alphabet:
|
|
21
|
+
# If it's an alphabet letter, encrypt it.
|
|
22
|
+
# Find the index of the character in the alphabet.
|
|
23
|
+
p = alphabet.index(char.upper())
|
|
24
|
+
# Apply the encryption formula: (a * p + b) mod m.
|
|
25
|
+
c = (a * p + b) % m
|
|
26
|
+
# Append the encrypted character to the ciphertext.
|
|
27
|
+
transformed = alphabet[c]
|
|
28
|
+
ciphertext += restore_char_case(char, transformed)
|
|
29
|
+
else:
|
|
30
|
+
# If the character is not in the alphabet, keep it unchanged.
|
|
31
|
+
ciphertext += char
|
|
32
|
+
# Return the encrypted ciphertext.
|
|
33
|
+
return ciphertext
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def run_encrypt(a: int, b: int) -> None:
|
|
37
|
+
|
|
38
|
+
# Initialize Colorama
|
|
39
|
+
init()
|
|
40
|
+
|
|
41
|
+
# Define the plaintext and key components
|
|
42
|
+
plaintext = input(f"{Fore.GREEN}[?] Enter text to encrypt: ")
|
|
43
|
+
|
|
44
|
+
# Call the affine_crypt function with the specified parameters
|
|
45
|
+
encrypted_text = affine_encryption(plaintext, a, b)
|
|
46
|
+
# Print the original plaintext, the key components, and the encrypted text
|
|
47
|
+
print(f"{Fore.MAGENTA}[+] Plaintext: {plaintext}")
|
|
48
|
+
print(f"{Fore.GREEN}[+] Encrypted Text: {encrypted_text} ")
|