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 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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,5 @@
1
+ from .core import solve
2
+
3
+ __all__ = ["solve"]
4
+
5
+ __version__ = "0.1.0"
@@ -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,9 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/franre/__init__.py
5
+ src/franre/core.py
6
+ src/franre.egg-info/PKG-INFO
7
+ src/franre.egg-info/SOURCES.txt
8
+ src/franre.egg-info/dependency_links.txt
9
+ src/franre.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ franre