franre 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.
- franre-0.1.0/LICENSE +21 -0
- franre-0.1.0/PKG-INFO +54 -0
- franre-0.1.0/README.md +33 -0
- franre-0.1.0/pyproject.toml +33 -0
- franre-0.1.0/setup.cfg +4 -0
- franre-0.1.0/src/franre/__init__.py +5 -0
- franre-0.1.0/src/franre/core.py +204 -0
- franre-0.1.0/src/franre.egg-info/PKG-INFO +54 -0
- franre-0.1.0/src/franre.egg-info/SOURCES.txt +9 -0
- franre-0.1.0/src/franre.egg-info/dependency_links.txt +1 -0
- franre-0.1.0/src/franre.egg-info/top_level.txt +1 -0
franre-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 zyrokun
|
|
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.
|
franre-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: franre
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Franklin-Reiter RSA related-message solver
|
|
5
|
+
Author: zyrokun
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: repository, https://github.com/zyrokun/franre
|
|
8
|
+
Project-URL: issues, https://github.com/zyrokun/franre/issues
|
|
9
|
+
Keywords: rsa,cryptography,franklin-reiter,cryptanalysis
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# franre - python library to speedrun tasks that are related to Franklin-Reiter RSA
|
|
23
|
+
|
|
24
|
+
## how to download
|
|
25
|
+
|
|
26
|
+
1. download the archive
|
|
27
|
+
2. uhh, yeah
|
|
28
|
+
3. unarchive the archive
|
|
29
|
+
4. go to the folder where you've successfully unarchived the archive
|
|
30
|
+
5. run this:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
python3 -m pip install . --break-system-packages
|
|
34
|
+
```
|
|
35
|
+
6. you're done.
|
|
36
|
+
|
|
37
|
+
## example of usage
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
import franre
|
|
41
|
+
# or 'from franre import solve'
|
|
42
|
+
n = big numba
|
|
43
|
+
e = 67
|
|
44
|
+
m = another big numba
|
|
45
|
+
a = 1
|
|
46
|
+
b = 1234
|
|
47
|
+
c1 = ciphertext from your task
|
|
48
|
+
c2 = another ciphertext from your task
|
|
49
|
+
|
|
50
|
+
franre.solve(c1, c2, n, e, a=a, b=b) # or if you've used another option of importing: solve(c1, c2, n, e, a=a, b=b)
|
|
51
|
+
|
|
52
|
+
#if you've got the m, just import 'from Crypto.Util.number import long_to_bytes' and run print(long_to_bytes(m)) where m is the number that you've extracted from the output. glhf :3
|
|
53
|
+
|
|
54
|
+
```
|
franre-0.1.0/README.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# franre - python library to speedrun tasks that are related to Franklin-Reiter RSA
|
|
2
|
+
|
|
3
|
+
## how to download
|
|
4
|
+
|
|
5
|
+
1. download the archive
|
|
6
|
+
2. uhh, yeah
|
|
7
|
+
3. unarchive the archive
|
|
8
|
+
4. go to the folder where you've successfully unarchived the archive
|
|
9
|
+
5. run this:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
python3 -m pip install . --break-system-packages
|
|
13
|
+
```
|
|
14
|
+
6. you're done.
|
|
15
|
+
|
|
16
|
+
## example of usage
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
import franre
|
|
20
|
+
# or 'from franre import solve'
|
|
21
|
+
n = big numba
|
|
22
|
+
e = 67
|
|
23
|
+
m = another big numba
|
|
24
|
+
a = 1
|
|
25
|
+
b = 1234
|
|
26
|
+
c1 = ciphertext from your task
|
|
27
|
+
c2 = another ciphertext from your task
|
|
28
|
+
|
|
29
|
+
franre.solve(c1, c2, n, e, a=a, b=b) # or if you've used another option of importing: solve(c1, c2, n, e, a=a, b=b)
|
|
30
|
+
|
|
31
|
+
#if you've got the m, just import 'from Crypto.Util.number import long_to_bytes' and run print(long_to_bytes(m)) where m is the number that you've extracted from the output. glhf :3
|
|
32
|
+
|
|
33
|
+
```
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "franre"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Franklin-Reiter RSA related-message solver"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "zyrokun" }]
|
|
14
|
+
keywords = ["rsa", "cryptography", "franklin-reiter", "cryptanalysis"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
18
|
+
"Programming Language :: Python :: 3.10",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Programming Language :: Python :: 3.14",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.urls]
|
|
26
|
+
repository = "https://github.com/zyrokun/franre"
|
|
27
|
+
issues = "https://github.com/zyrokun/franre/issues"
|
|
28
|
+
|
|
29
|
+
[tool.setuptools]
|
|
30
|
+
package-dir = { "" = "src" }
|
|
31
|
+
|
|
32
|
+
[tool.setuptools.packages.find]
|
|
33
|
+
where = ["src"]
|
franre-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from math import comb, gcd
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class FranklinReiterError(Exception):
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class NoSolutionError(FranklinReiterError):
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AmbiguousSolutionError(FranklinReiterError):
|
|
15
|
+
def __init__(self, degree: int):
|
|
16
|
+
self.degree = degree
|
|
17
|
+
super().__init__(f"the polynomial gcd has degree {degree}, not 1")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class NonInvertibleCoefficientError(FranklinReiterError):
|
|
21
|
+
def __init__(self, coefficient: int, modulus: int, factor: int):
|
|
22
|
+
self.coefficient = coefficient
|
|
23
|
+
self.modulus = modulus
|
|
24
|
+
self.factor = factor
|
|
25
|
+
super().__init__(
|
|
26
|
+
f"coefficient {coefficient} is not invertible modulo {modulus}; "
|
|
27
|
+
f"gcd(coefficient, modulus) = {factor}"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
Polynomial = list[int]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _trim(poly: Polynomial, modulus: int) -> Polynomial:
|
|
35
|
+
result = [coefficient % modulus for coefficient in poly]
|
|
36
|
+
while result and result[-1] == 0:
|
|
37
|
+
result.pop()
|
|
38
|
+
return result
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _inverse(value: int, modulus: int) -> int:
|
|
42
|
+
value %= modulus
|
|
43
|
+
factor = gcd(value, modulus)
|
|
44
|
+
if factor != 1:
|
|
45
|
+
raise NonInvertibleCoefficientError(value, modulus, factor)
|
|
46
|
+
return pow(value, -1, modulus)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _poly_divmod(
|
|
50
|
+
dividend: Polynomial, divisor: Polynomial, modulus: int
|
|
51
|
+
) -> tuple[Polynomial, Polynomial]:
|
|
52
|
+
dividend = _trim(dividend, modulus)
|
|
53
|
+
divisor = _trim(divisor, modulus)
|
|
54
|
+
if not divisor:
|
|
55
|
+
raise ZeroDivisionError("polynomial division by zero")
|
|
56
|
+
if not dividend or len(dividend) < len(divisor):
|
|
57
|
+
return [], dividend
|
|
58
|
+
|
|
59
|
+
quotient = [0] * max(0, len(dividend) - len(divisor) + 1)
|
|
60
|
+
remainder = dividend[:]
|
|
61
|
+
inverse_lead = _inverse(divisor[-1], modulus)
|
|
62
|
+
|
|
63
|
+
while remainder and len(remainder) >= len(divisor):
|
|
64
|
+
shift = len(remainder) - len(divisor)
|
|
65
|
+
factor = remainder[-1] * inverse_lead % modulus
|
|
66
|
+
quotient[shift] = factor
|
|
67
|
+
for index, coefficient in enumerate(divisor):
|
|
68
|
+
remainder[index + shift] = (
|
|
69
|
+
remainder[index + shift] - factor * coefficient
|
|
70
|
+
) % modulus
|
|
71
|
+
remainder = _trim(remainder, modulus)
|
|
72
|
+
|
|
73
|
+
return _trim(quotient, modulus), remainder
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _poly_gcd(first: Polynomial, second: Polynomial, modulus: int) -> Polynomial:
|
|
77
|
+
first = _trim(first, modulus)
|
|
78
|
+
second = _trim(second, modulus)
|
|
79
|
+
while second:
|
|
80
|
+
_, remainder = _poly_divmod(first, second, modulus)
|
|
81
|
+
first, second = second, remainder
|
|
82
|
+
|
|
83
|
+
if not first:
|
|
84
|
+
return []
|
|
85
|
+
inverse_lead = _inverse(first[-1], modulus)
|
|
86
|
+
return _trim([coefficient * inverse_lead for coefficient in first], modulus)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _make_polynomials(
|
|
90
|
+
c1: int, c2: int, modulus: int, exponent: int, a: int, b: int
|
|
91
|
+
) -> tuple[Polynomial, Polynomial]:
|
|
92
|
+
first = [(-c1) % modulus] + [0] * (exponent - 1) + [1]
|
|
93
|
+
|
|
94
|
+
second = [
|
|
95
|
+
(comb(exponent, degree)
|
|
96
|
+
* pow(a, degree, modulus)
|
|
97
|
+
* pow(b, exponent - degree, modulus))
|
|
98
|
+
% modulus
|
|
99
|
+
for degree in range(exponent + 1)
|
|
100
|
+
]
|
|
101
|
+
second[0] = (second[0] - c2) % modulus
|
|
102
|
+
return _trim(first, modulus), _trim(second, modulus)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _check_candidate(
|
|
106
|
+
candidate: int,
|
|
107
|
+
c1: int,
|
|
108
|
+
c2: int,
|
|
109
|
+
modulus: int,
|
|
110
|
+
exponent: int,
|
|
111
|
+
a: int,
|
|
112
|
+
b: int,
|
|
113
|
+
) -> int:
|
|
114
|
+
candidate %= modulus
|
|
115
|
+
if pow(candidate, exponent, modulus) != c1 % modulus:
|
|
116
|
+
raise NoSolutionError("candidate does not reproduce the first ciphertext")
|
|
117
|
+
related = (a * candidate + b) % modulus
|
|
118
|
+
if pow(related, exponent, modulus) != c2 % modulus:
|
|
119
|
+
raise NoSolutionError("candidate does not reproduce the second ciphertext")
|
|
120
|
+
return candidate
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _crt_pair(first: int, first_modulus: int, second: int, second_modulus: int) -> int:
|
|
124
|
+
if gcd(first_modulus, second_modulus) != 1:
|
|
125
|
+
raise NoSolutionError("cannot combine non-coprime modulus factors")
|
|
126
|
+
step = (
|
|
127
|
+
(second - first)
|
|
128
|
+
* pow(first_modulus, -1, second_modulus)
|
|
129
|
+
) % second_modulus
|
|
130
|
+
return (first + first_modulus * step) % (first_modulus * second_modulus)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _attack_modulus(
|
|
134
|
+
c1: int, c2: int, modulus: int, exponent: int, a: int, b: int
|
|
135
|
+
) -> int:
|
|
136
|
+
first_poly, second_poly = _make_polynomials(
|
|
137
|
+
c1, c2, modulus, exponent, a, b
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
common = _poly_gcd(first_poly, second_poly, modulus)
|
|
142
|
+
except NonInvertibleCoefficientError as error:
|
|
143
|
+
factor = error.factor
|
|
144
|
+
other_factor = modulus // factor
|
|
145
|
+
if factor in (1, modulus) or gcd(factor, other_factor) != 1:
|
|
146
|
+
raise
|
|
147
|
+
|
|
148
|
+
first_residue = _attack_modulus(
|
|
149
|
+
c1, c2, factor, exponent, a, b
|
|
150
|
+
)
|
|
151
|
+
second_residue = _attack_modulus(
|
|
152
|
+
c1, c2, other_factor, exponent, a, b
|
|
153
|
+
)
|
|
154
|
+
candidate = _crt_pair(
|
|
155
|
+
first_residue, factor, second_residue, other_factor
|
|
156
|
+
)
|
|
157
|
+
return _check_candidate(candidate, c1, c2, modulus, exponent, a, b)
|
|
158
|
+
|
|
159
|
+
if len(common) == 1:
|
|
160
|
+
raise NoSolutionError("the ciphertext polynomials have no common root")
|
|
161
|
+
degree = len(common) - 1
|
|
162
|
+
if degree != 1:
|
|
163
|
+
raise AmbiguousSolutionError(degree)
|
|
164
|
+
|
|
165
|
+
candidate = -common[0] * _inverse(common[1], modulus) % modulus
|
|
166
|
+
return _check_candidate(candidate, c1, c2, modulus, exponent, a, b)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _execute(
|
|
170
|
+
c1: int,
|
|
171
|
+
c2: int,
|
|
172
|
+
n: int,
|
|
173
|
+
e: int,
|
|
174
|
+
*,
|
|
175
|
+
a: int,
|
|
176
|
+
b: int,
|
|
177
|
+
) -> int:
|
|
178
|
+
if not isinstance(n, int) or isinstance(n, bool) or n <= 1:
|
|
179
|
+
raise ValueError("n must be an integer greater than 1")
|
|
180
|
+
if not isinstance(e, int) or isinstance(e, bool) or e < 2:
|
|
181
|
+
raise ValueError("e must be an integer greater than or equal to 2")
|
|
182
|
+
if not all(isinstance(value, int) and not isinstance(value, bool)
|
|
183
|
+
for value in (c1, c2, a, b)):
|
|
184
|
+
raise TypeError("c1, c2, a, and b must be integers")
|
|
185
|
+
|
|
186
|
+
return _attack_modulus(c1, c2, n, e, a % n, b % n)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def solve(
|
|
190
|
+
c1: int,
|
|
191
|
+
c2: int,
|
|
192
|
+
n: int,
|
|
193
|
+
e: int,
|
|
194
|
+
*,
|
|
195
|
+
a: int,
|
|
196
|
+
b: int,
|
|
197
|
+
) -> None:
|
|
198
|
+
try:
|
|
199
|
+
message = _execute(c1, c2, n, e, a=a, b=b)
|
|
200
|
+
except (FranklinReiterError, TypeError, ValueError):
|
|
201
|
+
print("no solutions lol")
|
|
202
|
+
return
|
|
203
|
+
|
|
204
|
+
print(f"woah, its m : {message}")
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: franre
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Franklin-Reiter RSA related-message solver
|
|
5
|
+
Author: zyrokun
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: repository, https://github.com/zyrokun/franre
|
|
8
|
+
Project-URL: issues, https://github.com/zyrokun/franre/issues
|
|
9
|
+
Keywords: rsa,cryptography,franklin-reiter,cryptanalysis
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# franre - python library to speedrun tasks that are related to Franklin-Reiter RSA
|
|
23
|
+
|
|
24
|
+
## how to download
|
|
25
|
+
|
|
26
|
+
1. download the archive
|
|
27
|
+
2. uhh, yeah
|
|
28
|
+
3. unarchive the archive
|
|
29
|
+
4. go to the folder where you've successfully unarchived the archive
|
|
30
|
+
5. run this:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
python3 -m pip install . --break-system-packages
|
|
34
|
+
```
|
|
35
|
+
6. you're done.
|
|
36
|
+
|
|
37
|
+
## example of usage
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
import franre
|
|
41
|
+
# or 'from franre import solve'
|
|
42
|
+
n = big numba
|
|
43
|
+
e = 67
|
|
44
|
+
m = another big numba
|
|
45
|
+
a = 1
|
|
46
|
+
b = 1234
|
|
47
|
+
c1 = ciphertext from your task
|
|
48
|
+
c2 = another ciphertext from your task
|
|
49
|
+
|
|
50
|
+
franre.solve(c1, c2, n, e, a=a, b=b) # or if you've used another option of importing: solve(c1, c2, n, e, a=a, b=b)
|
|
51
|
+
|
|
52
|
+
#if you've got the m, just import 'from Crypto.Util.number import long_to_bytes' and run print(long_to_bytes(m)) where m is the number that you've extracted from the output. glhf :3
|
|
53
|
+
|
|
54
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
franre
|