Skip to content

Commit c4039d5

Browse files
mtingersclaude
andcommitted
add cross-verification test suite against python-rsa, CI improvements
- Add tests/test_crossverify.py with 14 cross-library tests (key interop, encrypt/decrypt, sign/verify) - CI: add fail-fast: false and pip install rsa for cross-verify tests - pyproject.toml: bump status to Beta, add project.urls Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 05270d7 commit c4039d5

3 files changed

Lines changed: 132 additions & 1 deletion

File tree

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ jobs:
1010
test:
1111
runs-on: ubuntu-latest
1212
strategy:
13+
fail-fast: false
1314
matrix:
1415
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "pypy-3.10"]
1516
steps:
@@ -18,6 +19,7 @@ jobs:
1819
with:
1920
python-version: ${{ matrix.python-version }}
2021
- run: pip install -e ".[dev]"
22+
- run: pip install rsa || true
2123
- run: pytest tests/ -v
2224

2325
lint:

pyproject.toml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ requires-python = ">=3.9"
1010
license = "MIT"
1111
keywords = ["rsa", "crypto", "encryption", "signing", "pkcs1", "pure-python"]
1212
classifiers = [
13-
"Development Status :: 3 - Alpha",
13+
"Development Status :: 4 - Beta",
1414
"Topic :: Security :: Cryptography",
1515
"Typing :: Typed",
1616
"Programming Language :: Python :: 3",
@@ -22,6 +22,10 @@ classifiers = [
2222
"License :: OSI Approved :: MIT License",
2323
]
2424

25+
[project.urls]
26+
Homepage = "https://github.com/agentine/arcanum"
27+
Repository = "https://github.com/agentine/arcanum"
28+
2529
[project.optional-dependencies]
2630
dev = [
2731
"pytest",
@@ -45,3 +49,8 @@ select = ["E", "F", "W", "I", "N", "UP", "B", "SIM"]
4549

4650
[tool.pytest.ini_options]
4751
testpaths = ["tests"]
52+
53+
[dependency-groups]
54+
dev = [
55+
"rsa>=4.9.1",
56+
]

tests/test_crossverify.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
"""Cross-verification tests — verify arcanum against python-rsa.
2+
3+
These tests generate keys/signatures with python-rsa and verify with arcanum
4+
(and vice versa). Skipped when python-rsa is not installed.
5+
"""
6+
7+
from __future__ import annotations
8+
9+
import pytest
10+
11+
import arcanum
12+
13+
try:
14+
import rsa # type: ignore[import-untyped]
15+
HAS_RSA = True
16+
except ImportError:
17+
HAS_RSA = False
18+
19+
pytestmark = pytest.mark.skipif(not HAS_RSA, reason="python-rsa not installed")
20+
21+
22+
class TestKeyInterop:
23+
"""Keys generated by one library can be loaded by the other."""
24+
25+
def test_arcanum_pubkey_loadable_by_rsa(self) -> None:
26+
pub, _priv = arcanum.newkeys(512)
27+
pem = pub.save_pkcs1("PEM")
28+
rsa_pub = rsa.PublicKey.load_pkcs1(pem)
29+
assert rsa_pub.n == pub.n
30+
assert rsa_pub.e == pub.e
31+
32+
def test_rsa_pubkey_loadable_by_arcanum(self) -> None:
33+
pub, _priv = rsa.newkeys(512)
34+
pem = pub.save_pkcs1("PEM")
35+
arc_pub = arcanum.PublicKey.load_pkcs1(pem)
36+
assert arc_pub.n == pub.n
37+
assert arc_pub.e == pub.e
38+
39+
def test_arcanum_privkey_loadable_by_rsa(self) -> None:
40+
_pub, priv = arcanum.newkeys(512)
41+
pem = priv.save_pkcs1("PEM")
42+
rsa_priv = rsa.PrivateKey.load_pkcs1(pem)
43+
assert rsa_priv.n == priv.n
44+
assert rsa_priv.d == priv.d
45+
46+
def test_rsa_privkey_loadable_by_arcanum(self) -> None:
47+
_pub, priv = rsa.newkeys(512)
48+
pem = priv.save_pkcs1("PEM")
49+
arc_priv = arcanum.PrivateKey.load_pkcs1(pem)
50+
assert arc_priv.n == priv.n
51+
assert arc_priv.d == priv.d
52+
53+
def test_arcanum_pubkey_der_loadable_by_rsa(self) -> None:
54+
pub, _priv = arcanum.newkeys(512)
55+
der = pub.save_pkcs1("DER")
56+
rsa_pub = rsa.PublicKey.load_pkcs1(der, "DER")
57+
assert rsa_pub.n == pub.n
58+
59+
def test_rsa_pubkey_der_loadable_by_arcanum(self) -> None:
60+
pub, _priv = rsa.newkeys(512)
61+
der = pub.save_pkcs1("DER")
62+
arc_pub = arcanum.PublicKey.load_pkcs1(der, "DER")
63+
assert arc_pub.n == pub.n
64+
65+
66+
class TestEncryptDecryptInterop:
67+
"""Ciphertext from one library can be decrypted by the other."""
68+
69+
def test_arcanum_encrypt_rsa_decrypt(self) -> None:
70+
pub, priv = rsa.newkeys(512)
71+
# Load the rsa key into arcanum
72+
arc_pub = arcanum.PublicKey.load_pkcs1(pub.save_pkcs1("PEM"))
73+
ct = arcanum.encrypt(b"hello from arcanum", arc_pub)
74+
assert rsa.decrypt(ct, priv) == b"hello from arcanum"
75+
76+
def test_rsa_encrypt_arcanum_decrypt(self) -> None:
77+
pub, priv = rsa.newkeys(512)
78+
arc_priv = arcanum.PrivateKey.load_pkcs1(priv.save_pkcs1("PEM"))
79+
ct = rsa.encrypt(b"hello from rsa", pub)
80+
assert arcanum.decrypt(ct, arc_priv) == b"hello from rsa"
81+
82+
83+
class TestSignVerifyInterop:
84+
"""Signatures from one library can be verified by the other."""
85+
86+
def test_arcanum_sign_rsa_verify(self) -> None:
87+
pub, priv = arcanum.newkeys(512)
88+
rsa_pub = rsa.PublicKey.load_pkcs1(pub.save_pkcs1("PEM"))
89+
sig = arcanum.sign(b"test data", priv, "SHA-256")
90+
assert rsa.verify(b"test data", sig, rsa_pub) == "SHA-256"
91+
92+
def test_rsa_sign_arcanum_verify(self) -> None:
93+
pub, priv = rsa.newkeys(512)
94+
arc_pub = arcanum.PublicKey.load_pkcs1(pub.save_pkcs1("PEM"))
95+
sig = rsa.sign(b"test data", priv, "SHA-256")
96+
assert arcanum.verify(b"test data", sig, arc_pub) == "SHA-256"
97+
98+
def test_arcanum_sign_rsa_verify_sha1(self) -> None:
99+
pub, priv = arcanum.newkeys(512)
100+
rsa_pub = rsa.PublicKey.load_pkcs1(pub.save_pkcs1("PEM"))
101+
sig = arcanum.sign(b"sha1 test", priv, "SHA-1")
102+
assert rsa.verify(b"sha1 test", sig, rsa_pub) == "SHA-1"
103+
104+
def test_rsa_sign_arcanum_verify_sha1(self) -> None:
105+
pub, priv = rsa.newkeys(512)
106+
arc_pub = arcanum.PublicKey.load_pkcs1(pub.save_pkcs1("PEM"))
107+
sig = rsa.sign(b"sha1 test", priv, "SHA-1")
108+
assert arcanum.verify(b"sha1 test", sig, arc_pub) == "SHA-1"
109+
110+
def test_arcanum_sign_rsa_verify_md5(self) -> None:
111+
pub, priv = arcanum.newkeys(512)
112+
rsa_pub = rsa.PublicKey.load_pkcs1(pub.save_pkcs1("PEM"))
113+
sig = arcanum.sign(b"md5 test", priv, "MD5")
114+
assert rsa.verify(b"md5 test", sig, rsa_pub) == "MD5"
115+
116+
def test_rsa_sign_arcanum_verify_md5(self) -> None:
117+
pub, priv = rsa.newkeys(512)
118+
arc_pub = arcanum.PublicKey.load_pkcs1(pub.save_pkcs1("PEM"))
119+
sig = rsa.sign(b"md5 test", priv, "MD5")
120+
assert arcanum.verify(b"md5 test", sig, arc_pub) == "MD5"

0 commit comments

Comments
 (0)