Skip to content

Commit a1bf1f3

Browse files
authored
Merge pull request #2739 from keon/fix/security-crypto-eval
Fix security issues: remove eval(), use secrets for crypto, pin CI actions
2 parents e194a4c + f67f23c commit a1bf1f3

5 files changed

Lines changed: 50 additions & 45 deletions

File tree

.github/workflows/publish.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ jobs:
88
build:
99
runs-on: ubuntu-latest
1010
steps:
11-
- uses: actions/checkout@v4
11+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
1212

1313
- name: Set up Python
14-
uses: actions/setup-python@v5
14+
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
1515
with:
1616
python-version: "3.13"
1717

@@ -22,7 +22,7 @@ jobs:
2222
run: python -m build
2323

2424
- name: Upload build artifacts
25-
uses: actions/upload-artifact@v4
25+
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4
2626
with:
2727
name: dist
2828
path: dist/
@@ -35,10 +35,10 @@ jobs:
3535
id-token: write
3636
steps:
3737
- name: Download build artifacts
38-
uses: actions/download-artifact@v4
38+
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
3939
with:
4040
name: dist
4141
path: dist/
4242

4343
- name: Publish to PyPI
44-
uses: pypa/gh-action-pypi-publish@release/v1
44+
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # release/v1

algorithms/linked_list/kth_to_last.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def __init__(self, val: object = None) -> None:
2121

2222

2323
def kth_to_last_eval(head: Node, k: int) -> Node | bool:
24-
"""Find the kth to last element using eval (not safe for user input).
24+
"""Find the kth to last element using a safe iterative loop.
2525
2626
Args:
2727
head: Head of the linked list.
@@ -38,14 +38,15 @@ def kth_to_last_eval(head: Node, k: int) -> Node | bool:
3838
if not isinstance(k, int) or not head.val:
3939
return False
4040

41-
nexts = ".".join(["next" for _ in range(1, k + 1)])
42-
seeker = ".".join(["head", nexts])
43-
4441
while head:
45-
if eval(seeker) is None: # noqa: S307
42+
seeker = head
43+
for _ in range(k):
44+
if seeker is None:
45+
return False
46+
seeker = seeker.next
47+
if seeker is None:
4648
return head
47-
else:
48-
head = head.next
49+
head = head.next
4950

5051
return False
5152

algorithms/math/diffie_hellman_key_exchange.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from __future__ import annotations
1616

1717
import math
18-
from random import randint
18+
import secrets
1919

2020

2121
def _prime_check(num: int) -> bool:
@@ -112,7 +112,7 @@ def alice_private_key(p: int) -> int:
112112
Returns:
113113
A random private key.
114114
"""
115-
return randint(1, p - 1)
115+
return secrets.randbelow(p - 1) + 1
116116

117117

118118
def alice_public_key(a_pr_k: int, a: int, p: int) -> int:
@@ -126,7 +126,7 @@ def alice_public_key(a_pr_k: int, a: int, p: int) -> int:
126126
Returns:
127127
Alice's public key.
128128
"""
129-
return pow(a, a_pr_k) % p
129+
return pow(a, a_pr_k, p)
130130

131131

132132
def bob_private_key(p: int) -> int:
@@ -138,7 +138,7 @@ def bob_private_key(p: int) -> int:
138138
Returns:
139139
A random private key.
140140
"""
141-
return randint(1, p - 1)
141+
return secrets.randbelow(p - 1) + 1
142142

143143

144144
def bob_public_key(b_pr_k: int, a: int, p: int) -> int:
@@ -152,7 +152,7 @@ def bob_public_key(b_pr_k: int, a: int, p: int) -> int:
152152
Returns:
153153
Bob's public key.
154154
"""
155-
return pow(a, b_pr_k) % p
155+
return pow(a, b_pr_k, p)
156156

157157

158158
def alice_shared_key(b_pu_k: int, a_pr_k: int, p: int) -> int:
@@ -166,7 +166,7 @@ def alice_shared_key(b_pu_k: int, a_pr_k: int, p: int) -> int:
166166
Returns:
167167
The shared secret key.
168168
"""
169-
return pow(b_pu_k, a_pr_k) % p
169+
return pow(b_pu_k, a_pr_k, p)
170170

171171

172172
def bob_shared_key(a_pu_k: int, b_pr_k: int, p: int) -> int:
@@ -180,7 +180,7 @@ def bob_shared_key(a_pu_k: int, b_pr_k: int, p: int) -> int:
180180
Returns:
181181
The shared secret key.
182182
"""
183-
return pow(a_pu_k, b_pr_k) % p
183+
return pow(a_pu_k, b_pr_k, p)
184184

185185

186186
def diffie_hellman_key_exchange(a: int, p: int, option: int | None = None) -> bool:

algorithms/math/rabin_miller.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
from __future__ import annotations
1616

17-
import random
17+
import secrets
1818

1919

2020
def is_prime(n: int, k: int) -> bool:
@@ -34,7 +34,7 @@ def is_prime(n: int, k: int) -> bool:
3434
False
3535
"""
3636

37-
def _pow2_factor(num: int) -> tuple[int, float]:
37+
def _pow2_factor(num: int) -> tuple[int, int]:
3838
"""Factor num into 2^power * odd_part.
3939
4040
Args:
@@ -45,7 +45,7 @@ def _pow2_factor(num: int) -> tuple[int, float]:
4545
"""
4646
power = 0
4747
while num % 2 == 0:
48-
num /= 2
48+
num //= 2
4949
power += 1
5050
return power, num
5151

@@ -79,6 +79,6 @@ def _valid_witness(a: int) -> bool:
7979
r, d = _pow2_factor(n - 1)
8080

8181
return all(
82-
not _valid_witness(random.randrange(2, n - 2))
82+
not _valid_witness(secrets.randbelow(n - 4) + 2)
8383
for _ in range(k)
8484
)

algorithms/math/rsa.py

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,45 +13,50 @@
1313

1414
from __future__ import annotations
1515

16-
import random
16+
import secrets
17+
18+
19+
def _extended_gcd(a: int, b: int) -> tuple[int, int, int]:
20+
old_r, r = a, b
21+
old_s, s = 1, 0
22+
old_t, t = 0, 1
23+
while r != 0:
24+
q = old_r // r
25+
old_r, r = r, old_r - q * r
26+
old_s, s = s, old_s - q * s
27+
old_t, t = t, old_t - q * t
28+
return old_r, old_s, old_t
29+
30+
31+
def _modinv(a: int, m: int) -> int:
32+
g, x, _ = _extended_gcd(a, m)
33+
if g != 1:
34+
raise ValueError(f"Modular inverse does not exist: gcd({a}, {m}) = {g}")
35+
return x % m
1736

1837

1938
def generate_key(k: int, seed: int | None = None) -> tuple[int, int, int]:
2039
"""Generate an RSA key triplet (n, e, d).
2140
2241
Args:
2342
k: The number of bits in the modulus n.
24-
seed: Optional random seed for reproducibility.
43+
seed: Optional random seed for reproducibility
44+
(ignored, kept for API compatibility).
2545
2646
Returns:
2747
A tuple (n, e, d) where n is the modulus, e is the encryption
2848
exponent, and d is the decryption exponent.
2949
3050
Examples:
31-
>>> n, e, d = generate_key(16, seed=42)
51+
>>> n, e, d = generate_key(16)
3252
"""
3353

34-
def _modinv(a: int, m: int) -> int:
35-
"""Calculate the modular inverse of a mod m.
36-
37-
Args:
38-
a: The integer.
39-
m: The modulus.
40-
41-
Returns:
42-
b such that (a * b) % m == 1.
43-
"""
44-
b = 1
45-
while (a * b) % m != 1:
46-
b += 1
47-
return b
48-
4954
def _gen_prime(k: int, seed: int | None = None) -> int:
5055
"""Generate a random prime with k bits.
5156
5257
Args:
5358
k: The number of bits.
54-
seed: Optional random seed.
59+
seed: Unused, kept for API compatibility.
5560
5661
Returns:
5762
A prime number.
@@ -65,13 +70,12 @@ def _is_prime(num: int) -> bool:
6570
for i in range(2, int(num**0.5) + 1)
6671
)
6772

68-
random.seed(seed)
6973
while True:
70-
key = random.randrange(int(2 ** (k - 1)), int(2**k))
74+
key = secrets.randbelow(int(2**k) - int(2 ** (k - 1))) + int(2 ** (k - 1))
7175
if _is_prime(key):
7276
return key
7377

74-
p_size = k / 2
78+
p_size = k // 2
7579
q_size = k - p_size
7680

7781
e = _gen_prime(k, seed)

0 commit comments

Comments
 (0)