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.
@@ -0,0 +1,2 @@
1
+ def main() -> None:
2
+ print("Hello from affine-cipher!")
affine_cipher/case.py ADDED
@@ -0,0 +1,5 @@
1
+ def restore_char_case(original: str, transformed: str) -> str:
2
+ if original.islower():
3
+ return transformed.lower()
4
+ else:
5
+ return transformed
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))
@@ -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)
@@ -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} ")
@@ -0,0 +1,9 @@
1
+ import math
2
+
3
+
4
+ def validate_affine_key(a: int, m: int) -> None:
5
+ if math.gcd(a, m) != 1:
6
+ compute_valid_key = ", ".join(
7
+ str(i) for i in range(1, m) if math.gcd(i, m) == 1
8
+ )
9
+ raise ValueError(f"This key is invalid. Try again. {compute_valid_key}")