cgacryptpy 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.
- cgacryptpy-0.1.0/.gitignore +7 -0
- cgacryptpy-0.1.0/LICENSE +21 -0
- cgacryptpy-0.1.0/PKG-INFO +79 -0
- cgacryptpy-0.1.0/README.md +67 -0
- cgacryptpy-0.1.0/pyproject.toml +22 -0
- cgacryptpy-0.1.0/src/pycryptlib/__init__.py +0 -0
- cgacryptpy-0.1.0/src/pycryptlib/classical.py +137 -0
- cgacryptpy-0.1.0/src/pycryptlib/des.py +92 -0
- cgacryptpy-0.1.0/src/pycryptlib/encoding.py +80 -0
- cgacryptpy-0.1.0/src/pycryptlib/lfsr.py +89 -0
- cgacryptpy-0.1.0/src/pycryptlib/numtheory.py +67 -0
- cgacryptpy-0.1.0/src/pycryptlib/rsa.py +72 -0
- cgacryptpy-0.1.0/src/pycryptlib/stream_otp.py +50 -0
- cgacryptpy-0.1.0/src/pycryptlib/tables.py +89 -0
- cgacryptpy-0.1.0/src/tests/BACHER_exercise3.py +146 -0
- cgacryptpy-0.1.0/src/tests/Bacher - Crypto Lab 1.py +228 -0
- cgacryptpy-0.1.0/src/tests/Bacher - Crypto Lab 2.py +151 -0
- cgacryptpy-0.1.0/src/tests/Bacher_PA4.py +168 -0
- cgacryptpy-0.1.0/src/tests/Bacher_PA5.py +173 -0
- cgacryptpy-0.1.0/src/tests/Bacher_PA6.py +116 -0
- cgacryptpy-0.1.0/src/tests/Bacher_PA7.py +220 -0
- cgacryptpy-0.1.0/src/tests/desktop.ini +7 -0
- cgacryptpy-0.1.0/wheelcheck/pycryptlib/__init__.py +0 -0
- cgacryptpy-0.1.0/wheelcheck/pycryptlib/classical.py +137 -0
- cgacryptpy-0.1.0/wheelcheck/pycryptlib/des.py +92 -0
- cgacryptpy-0.1.0/wheelcheck/pycryptlib/encoding.py +80 -0
- cgacryptpy-0.1.0/wheelcheck/pycryptlib/lfsr.py +89 -0
- cgacryptpy-0.1.0/wheelcheck/pycryptlib/numtheory.py +67 -0
- cgacryptpy-0.1.0/wheelcheck/pycryptlib/rsa.py +72 -0
- cgacryptpy-0.1.0/wheelcheck/pycryptlib/stream_otp.py +50 -0
- cgacryptpy-0.1.0/wheelcheck/pycryptlib/tables.py +89 -0
- cgacryptpy-0.1.0/wheelcheck/pycryptlib-0.1.0.dist-info/METADATA +79 -0
- cgacryptpy-0.1.0/wheelcheck/pycryptlib-0.1.0.dist-info/RECORD +13 -0
- cgacryptpy-0.1.0/wheelcheck/pycryptlib-0.1.0.dist-info/WHEEL +4 -0
- cgacryptpy-0.1.0/wheelcheck/pycryptlib-0.1.0.dist-info/licenses/LICENSE +21 -0
cgacryptpy-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SethRider02
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: cgacryptpy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: This library contains a multitude of cryptography functions that can be used in python.
|
|
5
|
+
Project-URL: Homepage, https://github.com/SethRider02/pycryptlib
|
|
6
|
+
Author-email: Seth Bacher <sethrider02@gmail.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# pycryptlib
|
|
14
|
+
|
|
15
|
+
Multiple cryptologic functions ranging from a simple caesar cipher up to DES!
|
|
16
|
+
|
|
17
|
+
[](https://pypi.org/project/pycryptlib/)
|
|
18
|
+
[](https://pypi.org/project/pycryptlib/)
|
|
19
|
+
[](LICENSE)
|
|
20
|
+
|
|
21
|
+
> **Security notice:** This library has not been independently audited.
|
|
22
|
+
> It is intended for learning and experimentation. For production systems,
|
|
23
|
+
> use a well-established library such as `cryptography`.
|
|
24
|
+
|
|
25
|
+
## Features
|
|
26
|
+
|
|
27
|
+
- Affine Cipher
|
|
28
|
+
- Viginere Cipher
|
|
29
|
+
- LFSR
|
|
30
|
+
- KidRSA
|
|
31
|
+
- RSA
|
|
32
|
+
- DES
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install pycryptlib
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Requires Python 3.9 or newer.
|
|
41
|
+
|
|
42
|
+
## Quick start
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from pycryptlib.lfsr import lfsr_encrypt
|
|
46
|
+
|
|
47
|
+
seed_3 = [1,0,0,0,1]
|
|
48
|
+
|
|
49
|
+
ciphertext, keystream = lfsr_encrypt("ACADEMY", seed_3)
|
|
50
|
+
print("7. Ciphertext bits:", ciphertext)
|
|
51
|
+
print("7. Keystream:", keystream)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
## Supported algorithms
|
|
55
|
+
|
|
56
|
+
| Algorithm | Module | Notes |
|
|
57
|
+
|-----------|--------|---------------|
|
|
58
|
+
| DES | `pycryptlib.des` | 64-bit keys |
|
|
59
|
+
| LSFR | `pycryptlib.lsfr` | |
|
|
60
|
+
| Classical | `pycryptlib.classical` | Caesar, stream, etc |
|
|
61
|
+
| KidRSA | `pycryptlib.rsa` |
|
|
62
|
+
| RSA | `pycryptlib.rsa` |
|
|
63
|
+
|
|
64
|
+
## Feedback / Security Concerns
|
|
65
|
+
If you have any concerns regarding usage or issues (ie. function not working or not helpful explanation.),
|
|
66
|
+
feel free to email me or message me at sethrider02@gmail.com
|
|
67
|
+
|
|
68
|
+
## Development
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
git clone https://github.com/SethRider02/pycryptlib
|
|
72
|
+
cd pycryptlib
|
|
73
|
+
pip install -e ".[dev]"
|
|
74
|
+
pytest
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
|
|
79
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# pycryptlib
|
|
2
|
+
|
|
3
|
+
Multiple cryptologic functions ranging from a simple caesar cipher up to DES!
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/pycryptlib/)
|
|
6
|
+
[](https://pypi.org/project/pycryptlib/)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
> **Security notice:** This library has not been independently audited.
|
|
10
|
+
> It is intended for learning and experimentation. For production systems,
|
|
11
|
+
> use a well-established library such as `cryptography`.
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- Affine Cipher
|
|
16
|
+
- Viginere Cipher
|
|
17
|
+
- LFSR
|
|
18
|
+
- KidRSA
|
|
19
|
+
- RSA
|
|
20
|
+
- DES
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install pycryptlib
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Requires Python 3.9 or newer.
|
|
29
|
+
|
|
30
|
+
## Quick start
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from pycryptlib.lfsr import lfsr_encrypt
|
|
34
|
+
|
|
35
|
+
seed_3 = [1,0,0,0,1]
|
|
36
|
+
|
|
37
|
+
ciphertext, keystream = lfsr_encrypt("ACADEMY", seed_3)
|
|
38
|
+
print("7. Ciphertext bits:", ciphertext)
|
|
39
|
+
print("7. Keystream:", keystream)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
## Supported algorithms
|
|
43
|
+
|
|
44
|
+
| Algorithm | Module | Notes |
|
|
45
|
+
|-----------|--------|---------------|
|
|
46
|
+
| DES | `pycryptlib.des` | 64-bit keys |
|
|
47
|
+
| LSFR | `pycryptlib.lsfr` | |
|
|
48
|
+
| Classical | `pycryptlib.classical` | Caesar, stream, etc |
|
|
49
|
+
| KidRSA | `pycryptlib.rsa` |
|
|
50
|
+
| RSA | `pycryptlib.rsa` |
|
|
51
|
+
|
|
52
|
+
## Feedback / Security Concerns
|
|
53
|
+
If you have any concerns regarding usage or issues (ie. function not working or not helpful explanation.),
|
|
54
|
+
feel free to email me or message me at sethrider02@gmail.com
|
|
55
|
+
|
|
56
|
+
## Development
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
git clone https://github.com/SethRider02/pycryptlib
|
|
60
|
+
cd pycryptlib
|
|
61
|
+
pip install -e ".[dev]"
|
|
62
|
+
pytest
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## License
|
|
66
|
+
|
|
67
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[tool.hatch.build.targets.wheel]
|
|
6
|
+
packages = ["src/pycryptlib"]
|
|
7
|
+
|
|
8
|
+
[project]
|
|
9
|
+
name = "cgacryptpy"
|
|
10
|
+
version = "0.1.0"
|
|
11
|
+
description = "This library contains a multitude of cryptography functions that can be used in python."
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.9"
|
|
14
|
+
license = { text = "MIT" }
|
|
15
|
+
authors = [{ name = "Seth Bacher", email = "sethrider02@gmail.com" }]
|
|
16
|
+
dependencies = []
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Homepage = "https://github.com/SethRider02/pycryptlib"
|
|
File without changes
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
from .encoding import digitize, undigitize
|
|
2
|
+
from .numtheory import mod_inverse
|
|
3
|
+
import math
|
|
4
|
+
|
|
5
|
+
#==================================#
|
|
6
|
+
# SHIFT CIPHER FUNCTIONS #
|
|
7
|
+
#==================================#
|
|
8
|
+
|
|
9
|
+
def shift_encrypt(plaintext, key):
|
|
10
|
+
"""Encrypts plaintext using a shift key from 0 to 25. Returns the ciphertext."""
|
|
11
|
+
cnumbs = []
|
|
12
|
+
for n in digitize(plaintext):
|
|
13
|
+
cnumbs.append((n+key)%26)
|
|
14
|
+
return undigitize(cnumbs)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def shift_decrypt(ciphertext, key):
|
|
18
|
+
"""Decrypts ciphertext when the shift key is known. Returns the plaintext."""
|
|
19
|
+
pnumbs = []
|
|
20
|
+
for n in digitize(ciphertext):
|
|
21
|
+
pnumbs.append((n-key)%26)
|
|
22
|
+
return undigitize(pnumbs)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def brute_force_shift(ciphertext):
|
|
26
|
+
"""Returns all 26 possible keys and corresponding plaintext outputs."""
|
|
27
|
+
bruteval = ""
|
|
28
|
+
for maykey in range(0,26):
|
|
29
|
+
pnumbs = shift_decrypt(ciphertext,maykey)
|
|
30
|
+
bruteval = bruteval+f"{pnumbs} Key = {maykey}\n"
|
|
31
|
+
return bruteval
|
|
32
|
+
|
|
33
|
+
#==================================#
|
|
34
|
+
# AFFINE CIPHER FUNCTIONS #
|
|
35
|
+
#==================================#
|
|
36
|
+
|
|
37
|
+
def affine_encrypt(plaintext, a, b):
|
|
38
|
+
"""Encrypts plaintext with affine key (a, b). Returns ciphertext upon success."""
|
|
39
|
+
if math.gcd(a,26) != 1:
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
dctext = []
|
|
43
|
+
for numb in digitize(plaintext):
|
|
44
|
+
dctext.append((a*numb + b)%26)
|
|
45
|
+
|
|
46
|
+
return undigitize(dctext)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def affine_decrypt(ciphertext, a, b):
|
|
50
|
+
"""Decrypts ciphertext with a known affine key (a, b). Returns plaintext upon success."""
|
|
51
|
+
if math.gcd(a,b) != 1:
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
dptext = []
|
|
55
|
+
a_inv = mod_inverse(a,26)
|
|
56
|
+
|
|
57
|
+
if a_inv == None:
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
for numb in digitize(ciphertext):
|
|
61
|
+
dptext.append(a_inv * (numb-b) % 26)
|
|
62
|
+
|
|
63
|
+
return undigitize(dptext)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def brute_force_affine(ciphertext):
|
|
67
|
+
"""
|
|
68
|
+
Returns every valid affine key and its candidate plaintext.
|
|
69
|
+
Valid a values are exactly those for which gcd(a, 26) == 1.
|
|
70
|
+
"""
|
|
71
|
+
bruteval = ''
|
|
72
|
+
for a in range(26):
|
|
73
|
+
for b in range(26):
|
|
74
|
+
plaintext = affine_decrypt(ciphertext,a,b)
|
|
75
|
+
if plaintext != None:
|
|
76
|
+
bruteval = bruteval + f'{plaintext} Key = ({a},{b})\n'
|
|
77
|
+
return bruteval
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
#==================================#
|
|
81
|
+
# VIGENERE CIPHER FUNCTIONS #
|
|
82
|
+
#==================================#
|
|
83
|
+
|
|
84
|
+
def vigenere_encrypt(plaintext, key):
|
|
85
|
+
"""Encrypts plaintext using a Vigenere key. Returns the ciphertext."""
|
|
86
|
+
dkey = digitize(key)
|
|
87
|
+
dctext = []
|
|
88
|
+
i = 0
|
|
89
|
+
|
|
90
|
+
for number in digitize(plaintext):
|
|
91
|
+
dctext.append((number+dkey[i%len(key)])%26)
|
|
92
|
+
i = i+1
|
|
93
|
+
|
|
94
|
+
return undigitize(dctext)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def vigenere_decrypt(ciphertext, key):
|
|
98
|
+
"""Decrypt ciphertext when the Vigenere key is known. Return plaintext."""
|
|
99
|
+
dkey = digitize(key)
|
|
100
|
+
dptext = []
|
|
101
|
+
i = 0
|
|
102
|
+
|
|
103
|
+
for number in digitize(ciphertext):
|
|
104
|
+
dptext.append((number-dkey[i%len(key)])%26)
|
|
105
|
+
i = i+1
|
|
106
|
+
|
|
107
|
+
return undigitize(dptext)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def brute_force_vigenere(ciphertext, known_word):
|
|
111
|
+
"""
|
|
112
|
+
Tries every three-letter key and print candidates containing known_word.
|
|
113
|
+
Return both the key and candidate plaintext when a match is found.
|
|
114
|
+
WARNING: Only works with 3 letter Vigenere keys!
|
|
115
|
+
"""
|
|
116
|
+
pptext = ''
|
|
117
|
+
pkey = [0,0,0]
|
|
118
|
+
key = ''
|
|
119
|
+
|
|
120
|
+
while pkey[0] != 26:
|
|
121
|
+
|
|
122
|
+
if known_word in pptext:
|
|
123
|
+
break
|
|
124
|
+
|
|
125
|
+
key = undigitize(pkey)
|
|
126
|
+
pptext = vigenere_decrypt(ciphertext,key)
|
|
127
|
+
if pkey[2] == 25:
|
|
128
|
+
pkey[1]+=1
|
|
129
|
+
pkey[2] = 0
|
|
130
|
+
else:
|
|
131
|
+
pkey[2]+=1
|
|
132
|
+
|
|
133
|
+
if pkey[1] == 25:
|
|
134
|
+
pkey[0]+=1
|
|
135
|
+
pkey[1] = 0
|
|
136
|
+
|
|
137
|
+
return key, pptext
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Contains functions required for DES (Data Encryption Standard)
|
|
2
|
+
import tables as T
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def xor_bits(left, right):
|
|
7
|
+
"""Provided helper: bitwise XOR of two equal-length binary strings."""
|
|
8
|
+
return ''.join('1' if a != b else '0' for a, b in zip(left, right))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def permute(bits, table):
|
|
12
|
+
"""Return bits reordered using the given permutation table. Use [T.] before your table
|
|
13
|
+
to correctly grab from the tables file."""
|
|
14
|
+
permutation = ""
|
|
15
|
+
for index in table:
|
|
16
|
+
permutation = f"{permutation}{bits[index-1:index]}"
|
|
17
|
+
return permutation
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def sbox_substitution(bits48):
|
|
21
|
+
"""Takes a 48 bit string and passes them through all S Boxes. Returns 32 bit string."""
|
|
22
|
+
sbox_output = ""
|
|
23
|
+
i = 0
|
|
24
|
+
for table in T.SBOXES:
|
|
25
|
+
block = bits48[i*6:(i+1)*6]
|
|
26
|
+
|
|
27
|
+
sbox_row = f"{block[0:1]}{block[5:]}"
|
|
28
|
+
sbox_row = int(sbox_row,2)
|
|
29
|
+
|
|
30
|
+
sbox_col = f"{block[1:5]}"
|
|
31
|
+
sbox_col = int(sbox_col,2)
|
|
32
|
+
|
|
33
|
+
sbox_index = table[sbox_row][sbox_col]
|
|
34
|
+
bin_value = bin(sbox_index)[2:]
|
|
35
|
+
|
|
36
|
+
while len(bin_value) < 4:
|
|
37
|
+
bin_value = f"0{bin_value}"
|
|
38
|
+
sbox_output = f"{sbox_output}{bin_value}"
|
|
39
|
+
|
|
40
|
+
i = i+1
|
|
41
|
+
|
|
42
|
+
return sbox_output
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def feistel(right32, round_key48):
|
|
46
|
+
"""Takes the right 32 bits and specified round key through a feistal round. Returns the right
|
|
47
|
+
32 bits after primitive permutation (P) function."""
|
|
48
|
+
E_right48 = permute(right32,T.E)
|
|
49
|
+
xor48 = xor_bits(E_right48,round_key48)
|
|
50
|
+
S_right32 = sbox_substitution(xor48)
|
|
51
|
+
P_right32 = permute(S_right32,T.P)
|
|
52
|
+
return P_right32
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def rotate_left(bits, steps):
|
|
56
|
+
"""Shifts bits left by X amount of steps. Shift loops (ie. 1st bit becomes 32nd bit).
|
|
57
|
+
Returns result of shifting."""
|
|
58
|
+
return bits[steps:] + bits[:steps]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def des_round_keys(key64):
|
|
62
|
+
"""Generate round keys given the original 64-bit key. Returns list of keys."""
|
|
63
|
+
key56 = permute(key64, T.PC1)
|
|
64
|
+
c, d = key56[:28], key56[28:]
|
|
65
|
+
keys = []
|
|
66
|
+
for shift in T.SHIFTS:
|
|
67
|
+
c = rotate_left(c, shift)
|
|
68
|
+
d = rotate_left(d, shift)
|
|
69
|
+
keys.append(permute(c + d, T.PC2))
|
|
70
|
+
return keys
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def des_encrypt_block(plain64, key64):
|
|
74
|
+
"""Takes a 64-bit block and encrypts with a 64-bit key. Returns cipher of 64-bits."""
|
|
75
|
+
initial = permute(plain64, T.IP)
|
|
76
|
+
left, right = initial[:32], initial[32:]
|
|
77
|
+
for round_key in des_round_keys(key64):
|
|
78
|
+
left, right = right, xor_bits(left, feistel(right, round_key))
|
|
79
|
+
# DES swaps final halves before the inverse initial permutation.
|
|
80
|
+
return permute(right + left, T.IP_INV)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def des_decrypt_block(cipher64,key64):
|
|
84
|
+
"""Decrypts a 64-bit cipher block with a given key. Returns 64-bit plaintext."""
|
|
85
|
+
cipher64 = permute(cipher64,T.IP)
|
|
86
|
+
left, right = cipher64[:32], cipher64[32:]
|
|
87
|
+
reverse_keys = des_round_keys(key64)
|
|
88
|
+
for i in range(0,16):
|
|
89
|
+
# print('Using Key: ',(-1-i)%16)
|
|
90
|
+
round_key = reverse_keys[-1-i]
|
|
91
|
+
left, right = right, xor_bits(left, feistel(right, round_key))
|
|
92
|
+
return permute(right+left,T.IP_INV)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Contains various encoding schemes used in cryptography
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def digitize(my_string):
|
|
5
|
+
"""Convert uppercase A-Z text into integers: A=0, ..., Z=25."""
|
|
6
|
+
num_list = []
|
|
7
|
+
for char in my_string:
|
|
8
|
+
if not "A" <= char <= "Z":
|
|
9
|
+
raise ValueError("Only uppercase letters A-Z are allowed.")
|
|
10
|
+
num_list.append(ord(char) - ord("A"))
|
|
11
|
+
return num_list
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def undigitize(num_list):
|
|
15
|
+
"""Convert a list of integers from 0 to 25 into uppercase text."""
|
|
16
|
+
my_string = ""
|
|
17
|
+
for value in num_list:
|
|
18
|
+
if not 0 <= value <= 25:
|
|
19
|
+
raise ValueError("Each recovered letter value must be 0 through 25.")
|
|
20
|
+
my_string += chr(int(value) + ord("A"))
|
|
21
|
+
return my_string
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def text_to_bits(text):
|
|
25
|
+
"""Encodes a string as UTF-8 and returns its bits as a list of 0s and 1s."""
|
|
26
|
+
return [
|
|
27
|
+
(byte >> shift) & 1
|
|
28
|
+
for byte in text.encode("utf-8")
|
|
29
|
+
for shift in range(7, -1, -1)
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def bits_to_text(bits):
|
|
34
|
+
"""Converts a bit list whose length is a multiple of 8 back into text."""
|
|
35
|
+
if len(bits) % 8 != 0:
|
|
36
|
+
raise ValueError("The number of bits must be a multiple of 8.")
|
|
37
|
+
|
|
38
|
+
byte_values = []
|
|
39
|
+
for start in range(0, len(bits), 8):
|
|
40
|
+
value = 0
|
|
41
|
+
for bit in bits[start:start + 8]:
|
|
42
|
+
value = (value << 1) | bit
|
|
43
|
+
byte_values.append(value)
|
|
44
|
+
|
|
45
|
+
return bytes(byte_values).decode("utf-8")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def ascii_to_bits(ascii_string):
|
|
49
|
+
"""Converts given ascii text into a string of bits. Returns full string of bits with no delimeter."""
|
|
50
|
+
ascii_bytes = ' '.join(format(ord(c),'08b') for c in ascii_string)
|
|
51
|
+
ascii_bytes = ascii_bytes.replace(' ','')
|
|
52
|
+
return ascii_bytes
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def bits_to_ascii(bits):
|
|
56
|
+
"""Converts a string of bits into an ASCII string. Returns ASCII string if successful, or None if unsuccessful."""
|
|
57
|
+
if len(bits) % 8 != 0:
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
ascii_string = ''
|
|
61
|
+
byte_size = int(len(bits)/8)
|
|
62
|
+
ascii_list = []
|
|
63
|
+
for i in range(0,byte_size):
|
|
64
|
+
ascii_bin = bits[i*8:(i+1)*8]
|
|
65
|
+
ascii_list.append(int(ascii_bin,2))
|
|
66
|
+
|
|
67
|
+
for code in ascii_list:
|
|
68
|
+
ascii_string = f'{ascii_string}{format(code,'c')}'
|
|
69
|
+
|
|
70
|
+
return ascii_string
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def hex_to_bits(hex_string, width=64):
|
|
74
|
+
"""Given hexadecimal text, return exactly width binary characters."""
|
|
75
|
+
return format(int(hex_string, 16), f"0{width}b")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def bits_to_hex(bits):
|
|
79
|
+
"""Given binary text, return uppercase hexadecimal text (with leading zeros)."""
|
|
80
|
+
return format(int(bits, 2), f"0{len(bits) // 4}X")
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Contains various functions dealing with Linear Feedback Shift Registers (LFSR's)
|
|
2
|
+
|
|
3
|
+
def text_to_bits(text):
|
|
4
|
+
"""Convert uppercase A-Z text into one flat list of 5-bit values."""
|
|
5
|
+
bits = []
|
|
6
|
+
for char in text:
|
|
7
|
+
value = ord(char) - ord("A")
|
|
8
|
+
if value < 0 or value > 25:
|
|
9
|
+
raise ValueError("Only uppercase letters A-Z are allowed.")
|
|
10
|
+
bits.extend([
|
|
11
|
+
(value >> 4) & 1,
|
|
12
|
+
(value >> 3) & 1,
|
|
13
|
+
(value >> 2) & 1,
|
|
14
|
+
(value >> 1) & 1,
|
|
15
|
+
value & 1
|
|
16
|
+
])
|
|
17
|
+
return bits
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def bits_to_text(bits):
|
|
21
|
+
"""Convert a flat list of 5-bit letter values back into uppercase text."""
|
|
22
|
+
if len(bits) % 5 != 0:
|
|
23
|
+
raise ValueError("The number of bits must be a multiple of 5.")
|
|
24
|
+
|
|
25
|
+
text = ""
|
|
26
|
+
for i in range(0, len(bits), 5):
|
|
27
|
+
group = bits[i:i + 5]
|
|
28
|
+
value = (
|
|
29
|
+
group[0] * 16 + group[1] * 8 + group[2] * 4
|
|
30
|
+
+ group[3] * 2 + group[4]
|
|
31
|
+
)
|
|
32
|
+
if value > 25:
|
|
33
|
+
raise ValueError("5-bit value does not represent A-Z.")
|
|
34
|
+
text += chr(value + ord("A"))
|
|
35
|
+
return text
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def clock_lfsr(seed, clocks):
|
|
39
|
+
"""Shifts a 5-bit seed 'clocks' amount of clock cycles. Returns shifted bits as a list."""
|
|
40
|
+
cipher = []
|
|
41
|
+
for i in range(len(seed)):
|
|
42
|
+
cipher.append(seed[i])
|
|
43
|
+
|
|
44
|
+
for i in range(clocks):
|
|
45
|
+
tempbit = (cipher[2] + cipher[0]) % 2
|
|
46
|
+
cipher.append(tempbit)
|
|
47
|
+
cipher.pop(0)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
return cipher
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def lfsr(seed, length):
|
|
54
|
+
"""Generates a key of a specified length given a seed. Returns the keystream as a list of bits."""
|
|
55
|
+
keystream = []
|
|
56
|
+
for i in range(len(seed)):
|
|
57
|
+
keystream.append(seed[i])
|
|
58
|
+
|
|
59
|
+
for i in range(5,length):
|
|
60
|
+
tempbit = (keystream[i-5] + keystream[i-3]) % 2
|
|
61
|
+
keystream.append(tempbit)
|
|
62
|
+
|
|
63
|
+
return keystream
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def lfsr_encrypt(plaintext, seed):
|
|
67
|
+
"""Encrypts a string plaintext with a seed list of five bits. Returns tuple with ciphertext as a list
|
|
68
|
+
of bits and keystream as a list of bits."""
|
|
69
|
+
plainbits = text_to_bits(plaintext)
|
|
70
|
+
cipherbits = []
|
|
71
|
+
keystream = lfsr(seed,(len(plainbits)))
|
|
72
|
+
|
|
73
|
+
for i in range(0,len(plainbits)):
|
|
74
|
+
tempbit = (plainbits[i] + keystream[i]) % 2
|
|
75
|
+
cipherbits.append(tempbit)
|
|
76
|
+
|
|
77
|
+
return (cipherbits,keystream)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def lfsr_decrypt(ciphertext_bits, keystream):
|
|
81
|
+
"""Decrypts a list of ciphertext bits using a keystream list of bits. Returns a string of text."""
|
|
82
|
+
plainbits = []
|
|
83
|
+
for i in range(0,len(ciphertext_bits)):
|
|
84
|
+
tempbit = (ciphertext_bits[i] + keystream[i]) % 2
|
|
85
|
+
plainbits.append(tempbit)
|
|
86
|
+
|
|
87
|
+
plaintext = bits_to_text(plainbits)
|
|
88
|
+
|
|
89
|
+
return plaintext
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Contains various functions pertaining to number theory.
|
|
2
|
+
import math
|
|
3
|
+
|
|
4
|
+
def mod_inverse(a, n):
|
|
5
|
+
"""Finds mod inverse of a mod(n). Returns inverse if found or none if not found."""
|
|
6
|
+
gcd = math.gcd(a,n)
|
|
7
|
+
if gcd == 1:
|
|
8
|
+
for x in range(1,n):
|
|
9
|
+
inverse = (a*x)%n
|
|
10
|
+
if inverse == 1:
|
|
11
|
+
break
|
|
12
|
+
else:
|
|
13
|
+
continue
|
|
14
|
+
else:
|
|
15
|
+
return None
|
|
16
|
+
return x
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def bin_exp(exponent):
|
|
20
|
+
"""Calculates the binary expansion of a given exponent. Returns a list of the power of 2's for the exponent."""
|
|
21
|
+
twopower = []
|
|
22
|
+
while(exponent != 0):
|
|
23
|
+
i = 0
|
|
24
|
+
while(exponent >= (2**i)):
|
|
25
|
+
i=i+1
|
|
26
|
+
i=i-1
|
|
27
|
+
twopower.append(2**i)
|
|
28
|
+
exponent = exponent - (2**i)
|
|
29
|
+
|
|
30
|
+
return twopower
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def fast_mod_exp(base, exponent, modulus):
|
|
34
|
+
"""Calculates a value given a base, positive exponent, and a modulus. Returns the value in the respective modulus."""
|
|
35
|
+
if modulus <= 0:
|
|
36
|
+
raise ValueError("modulus must be a positive integer.")
|
|
37
|
+
if exponent < 0:
|
|
38
|
+
raise ValueError("exponent must be nonnegative.")
|
|
39
|
+
|
|
40
|
+
powers = bin_exp(exponent)
|
|
41
|
+
result = 1
|
|
42
|
+
current_power = 1
|
|
43
|
+
current_value = base % modulus
|
|
44
|
+
|
|
45
|
+
while current_power <= exponent:
|
|
46
|
+
if current_power in powers:
|
|
47
|
+
result = (result * current_value) % modulus
|
|
48
|
+
current_value = (current_value * current_value) % modulus
|
|
49
|
+
current_power = current_power * 2
|
|
50
|
+
|
|
51
|
+
return result % modulus
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def euler_totient(n):
|
|
55
|
+
"""Returns factors from 1 to n that are relatively prime to n."""
|
|
56
|
+
factors = []
|
|
57
|
+
for a in range(1,n):
|
|
58
|
+
gcd = math.gcd(a,n)
|
|
59
|
+
if gcd == 1:
|
|
60
|
+
factors.append(a)
|
|
61
|
+
else:
|
|
62
|
+
continue
|
|
63
|
+
|
|
64
|
+
phi = len(factors)
|
|
65
|
+
return phi
|
|
66
|
+
|
|
67
|
+
|