Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ecf0d306f | ||
|
|
5824df28cb | ||
|
|
938239ef72 | ||
|
|
f3c0b2b85d | ||
|
|
dd0c0bc0a0 | ||
|
|
40560d2deb |
@@ -7,6 +7,7 @@ backend-path = ["tools"]
|
|||||||
name = "aeg"
|
name = "aeg"
|
||||||
dynamic = ["version"]
|
dynamic = ["version"]
|
||||||
description = "AEGIS encryption easy to use Python binding. Wheels for major platforms."
|
description = "AEGIS encryption easy to use Python binding. Wheels for major platforms."
|
||||||
|
readme = {file = "README.md", content-type = "text/markdown"}
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Programming Language :: Python :: Implementation :: CPython",
|
"Programming Language :: Python :: Implementation :: CPython",
|
||||||
@@ -23,6 +24,7 @@ Repository = "https://github.com/LeoVasanko/aegis-python"
|
|||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
dev = [
|
dev = [
|
||||||
|
"auditwheel>=6.5.0",
|
||||||
"pytest>=8.4.2",
|
"pytest>=8.4.2",
|
||||||
"ruff>=0.14.4",
|
"ruff>=0.14.4",
|
||||||
"setuptools>=80.9.0",
|
"setuptools>=80.9.0",
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import importlib
|
||||||
|
|
||||||
|
from ._ciphers import CIPHERS, CipherName
|
||||||
|
from ._typing import Cipher
|
||||||
|
|
||||||
|
__all__ = ["cipher", "CIPHERS", "Cipher", "CipherName"]
|
||||||
|
|
||||||
|
|
||||||
|
def cipher(alg: CipherName) -> Cipher:
|
||||||
|
"""Acquire a cipher module by name."""
|
||||||
|
name = alg.lower().replace("-", "")
|
||||||
|
if name == "aegis128":
|
||||||
|
name = "aegis128l" # AEGIS-128 is dead, the user meant AEGIS-128L
|
||||||
|
if name in CIPHERS.values():
|
||||||
|
return importlib.import_module(f".{name}", __package__) # type: ignore[return-value]
|
||||||
|
raise ValueError(f"Unknown algorithm {alg!r}. Valid options: {', '.join(CIPHERS)}")
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# This file is generated by tools/generate.py. Do not edit.
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
CipherName = Literal[
|
||||||
|
"AEGIS-128L",
|
||||||
|
"AEGIS-128X2",
|
||||||
|
"AEGIS-128X4",
|
||||||
|
"AEGIS-256",
|
||||||
|
"AEGIS-256X2",
|
||||||
|
"AEGIS-256X4",
|
||||||
|
]
|
||||||
|
|
||||||
|
CIPHERS: dict[CipherName, str] = {
|
||||||
|
"AEGIS-128L": "aegis128l",
|
||||||
|
"AEGIS-128X2": "aegis128x2",
|
||||||
|
"AEGIS-128X4": "aegis128x4",
|
||||||
|
"AEGIS-256": "aegis256",
|
||||||
|
"AEGIS-256X2": "aegis256x2",
|
||||||
|
"AEGIS-256X4": "aegis256x4",
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
from typing import TYPE_CHECKING, Protocol
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .util import Buffer
|
||||||
|
|
||||||
|
__all__ = ["Cipher"]
|
||||||
|
|
||||||
|
|
||||||
|
class _Mac(Protocol):
|
||||||
|
def reset(self) -> None: ...
|
||||||
|
def clone(self) -> "_Mac": ...
|
||||||
|
def update(self, data: "Buffer") -> None: ...
|
||||||
|
def final(self, into: "Buffer | None" = None) -> bytearray | memoryview: ...
|
||||||
|
def digest(self) -> bytes: ...
|
||||||
|
def hexdigest(self) -> str: ...
|
||||||
|
def verify(self, mac: "Buffer") -> None: ...
|
||||||
|
|
||||||
|
|
||||||
|
class _Encryptor(Protocol):
|
||||||
|
def update(
|
||||||
|
self, message: "Buffer", into: "Buffer | None" = None
|
||||||
|
) -> bytearray | memoryview: ...
|
||||||
|
def final(self, into: "Buffer | None" = None) -> bytearray | memoryview: ...
|
||||||
|
|
||||||
|
|
||||||
|
class _Decryptor(Protocol):
|
||||||
|
def update(
|
||||||
|
self, ct: "Buffer", into: "Buffer | None" = None
|
||||||
|
) -> bytearray | memoryview: ...
|
||||||
|
def final(self, mac: "Buffer") -> None: ...
|
||||||
|
|
||||||
|
|
||||||
|
class Cipher(Protocol):
|
||||||
|
NAME: str
|
||||||
|
KEYBYTES: int
|
||||||
|
NONCEBYTES: int
|
||||||
|
MACBYTES: int
|
||||||
|
MACBYTES_LONG: int
|
||||||
|
ALIGNMENT: int
|
||||||
|
RATE: int
|
||||||
|
|
||||||
|
Mac: type[_Mac]
|
||||||
|
Encryptor: type[_Encryptor]
|
||||||
|
Decryptor: type[_Decryptor]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def random_key() -> bytearray: ...
|
||||||
|
@staticmethod
|
||||||
|
def random_nonce() -> bytearray: ...
|
||||||
|
@staticmethod
|
||||||
|
def encrypt_detached(
|
||||||
|
key: "Buffer",
|
||||||
|
nonce: "Buffer",
|
||||||
|
message: "Buffer",
|
||||||
|
ad: "Buffer | None" = None,
|
||||||
|
*,
|
||||||
|
maclen: int = ...,
|
||||||
|
ct_into: "Buffer | None" = None,
|
||||||
|
mac_into: "Buffer | None" = None,
|
||||||
|
) -> tuple[bytearray | memoryview, bytearray | memoryview]: ...
|
||||||
|
@staticmethod
|
||||||
|
def decrypt_detached(
|
||||||
|
key: "Buffer",
|
||||||
|
nonce: "Buffer",
|
||||||
|
ct: "Buffer",
|
||||||
|
mac: "Buffer",
|
||||||
|
ad: "Buffer | None" = None,
|
||||||
|
*,
|
||||||
|
into: "Buffer | None" = None,
|
||||||
|
) -> bytearray | memoryview: ...
|
||||||
|
@staticmethod
|
||||||
|
def encrypt(
|
||||||
|
key: "Buffer",
|
||||||
|
nonce: "Buffer",
|
||||||
|
message: "Buffer",
|
||||||
|
ad: "Buffer | None" = None,
|
||||||
|
*,
|
||||||
|
maclen: int = ...,
|
||||||
|
into: "Buffer | None" = None,
|
||||||
|
) -> bytearray | memoryview: ...
|
||||||
|
@staticmethod
|
||||||
|
def decrypt(
|
||||||
|
key: "Buffer",
|
||||||
|
nonce: "Buffer",
|
||||||
|
ct: "Buffer",
|
||||||
|
ad: "Buffer | None" = None,
|
||||||
|
*,
|
||||||
|
maclen: int = ...,
|
||||||
|
into: "Buffer | None" = None,
|
||||||
|
) -> bytearray | memoryview: ...
|
||||||
|
@staticmethod
|
||||||
|
def stream(
|
||||||
|
key: "Buffer",
|
||||||
|
nonce: "Buffer | None",
|
||||||
|
length: int | None = None,
|
||||||
|
*,
|
||||||
|
into: "Buffer | None" = None,
|
||||||
|
) -> "bytearray | Buffer": ...
|
||||||
|
@staticmethod
|
||||||
|
def encrypt_unauthenticated(
|
||||||
|
key: "Buffer",
|
||||||
|
nonce: "Buffer",
|
||||||
|
message: "Buffer",
|
||||||
|
*,
|
||||||
|
into: "Buffer | None" = None,
|
||||||
|
) -> bytearray | memoryview: ...
|
||||||
|
@staticmethod
|
||||||
|
def decrypt_unauthenticated(
|
||||||
|
key: "Buffer",
|
||||||
|
nonce: "Buffer",
|
||||||
|
ct: "Buffer",
|
||||||
|
*,
|
||||||
|
into: "Buffer | None" = None,
|
||||||
|
) -> bytearray | memoryview: ...
|
||||||
|
@staticmethod
|
||||||
|
def mac(
|
||||||
|
key: "Buffer",
|
||||||
|
nonce: "Buffer",
|
||||||
|
data: "Buffer",
|
||||||
|
maclen: int = ...,
|
||||||
|
into: "Buffer | None" = None,
|
||||||
|
) -> bytearray | memoryview: ...
|
||||||
Regular → Executable
+32
-1
@@ -1,3 +1,4 @@
|
|||||||
|
#!/usr/bin/env -S uv run
|
||||||
"""Generate CFFI cdef and Python modules from libaegis C sources."""
|
"""Generate CFFI cdef and Python modules from libaegis C sources."""
|
||||||
|
|
||||||
import pathlib
|
import pathlib
|
||||||
@@ -273,6 +274,23 @@ def generate_python_modules(
|
|||||||
return updated, unchanged
|
return updated, unchanged
|
||||||
|
|
||||||
|
|
||||||
|
def generate_ciphers_module(constants: Dict[str, Dict[str, int]]) -> str:
|
||||||
|
labels = [algo_label(variant) for variant in constants]
|
||||||
|
literal_items = ", ".join(f'"{label}"' for label in labels)
|
||||||
|
lines = [
|
||||||
|
"# This file is generated by tools/generate.py. Do not edit.",
|
||||||
|
"from typing import Literal",
|
||||||
|
"",
|
||||||
|
f"CipherName = Literal[{literal_items}]",
|
||||||
|
"",
|
||||||
|
"CIPHERS: dict[CipherName, str] = {",
|
||||||
|
]
|
||||||
|
for variant in constants:
|
||||||
|
lines.append(f' "{algo_label(variant)}": "{variant}",')
|
||||||
|
lines.append("}")
|
||||||
|
return "\n".join(lines) + "\n"
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
root = pathlib.Path(__file__).parent.parent
|
root = pathlib.Path(__file__).parent.parent
|
||||||
libaegis_src_dir = root / "libaegis" / "src"
|
libaegis_src_dir = root / "libaegis" / "src"
|
||||||
@@ -304,7 +322,20 @@ def main() -> int:
|
|||||||
cdef_path.write_bytes(cdef_content.encode())
|
cdef_path.write_bytes(cdef_content.encode())
|
||||||
print(f" - Updated {cdef_path}", file=sys.stderr)
|
print(f" - Updated {cdef_path}", file=sys.stderr)
|
||||||
|
|
||||||
print("Step 3: Generating Python modules...", file=sys.stderr)
|
print("Step 3: Generating _ciphers.py...", file=sys.stderr)
|
||||||
|
ciphers_path = pyaegis_dir / "_ciphers.py"
|
||||||
|
ciphers_content = generate_ciphers_module(constants)
|
||||||
|
|
||||||
|
if (
|
||||||
|
ciphers_path.exists()
|
||||||
|
and ciphers_path.read_text(encoding="utf-8") == ciphers_content
|
||||||
|
):
|
||||||
|
print(f" - No changes to {ciphers_path.name}", file=sys.stderr)
|
||||||
|
else:
|
||||||
|
ciphers_path.write_bytes(ciphers_content.encode())
|
||||||
|
print(f" - Updated {ciphers_path.name}", file=sys.stderr)
|
||||||
|
|
||||||
|
print("Step 4: Generating Python modules...", file=sys.stderr)
|
||||||
try:
|
try:
|
||||||
updated, unchanged = generate_python_modules(
|
updated, unchanged = generate_python_modules(
|
||||||
pyaegis_dir / "aegis256x4.py", pyaegis_dir, constants
|
pyaegis_dir / "aegis256x4.py", pyaegis_dir, constants
|
||||||
|
|||||||
Regular → Executable
+66
-16
@@ -1,26 +1,30 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env -S uv run
|
||||||
"""Build wheels for all supported Python versions using uv."""
|
"""Build wheels for all supported Python versions using uv."""
|
||||||
|
|
||||||
|
import platform
|
||||||
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tomllib
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import shutil
|
|
||||||
from packaging.specifiers import SpecifierSet
|
|
||||||
from packaging.version import Version
|
from packaging.version import Version
|
||||||
|
|
||||||
# Import generate module from same directory
|
# Import generate module from same directory
|
||||||
sys.path.insert(0, str(Path(__file__).parent))
|
sys.path.insert(0, str(Path(__file__).parent))
|
||||||
import generate
|
import generate
|
||||||
|
|
||||||
|
PYTHON_VERSIONS = [
|
||||||
def get_python_versions():
|
"3.10",
|
||||||
"""Get supported Python versions."""
|
"3.11",
|
||||||
pyproject_toml = Path(__file__).parent.parent / "pyproject.toml"
|
"3.12",
|
||||||
data = tomllib.loads(pyproject_toml.read_text(encoding="utf-8"))
|
"3.13",
|
||||||
spec = SpecifierSet(data["project"]["requires-python"])
|
"3.14",
|
||||||
# Generate versions that match the specifier (up to Python 3.14)
|
"3.14t",
|
||||||
return [f"3.{minor}" for minor in range(10, 15) if f"3.{minor}" in spec]
|
"3.15",
|
||||||
|
"3.15t",
|
||||||
|
"pypy3.10",
|
||||||
|
"pypy3.11",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def get_version_from_scm():
|
def get_version_from_scm():
|
||||||
@@ -90,9 +94,6 @@ def make_release_message(version):
|
|||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
|
||||||
PYTHON_VERSIONS = get_python_versions()
|
|
||||||
|
|
||||||
|
|
||||||
def run_command(cmd, description):
|
def run_command(cmd, description):
|
||||||
"""Run a command and handle errors."""
|
"""Run a command and handle errors."""
|
||||||
print(f"\n{'=' * 70}")
|
print(f"\n{'=' * 70}")
|
||||||
@@ -202,7 +203,15 @@ def main():
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# Find the wheel for this version
|
# Find the wheel for this version
|
||||||
wheel_pattern = f"aeg-*-cp{py_version.replace('.', '')}-*.whl"
|
if py_version.startswith("pypy"):
|
||||||
|
# PyPy wheels use pp3XX format
|
||||||
|
wheel_pattern = (
|
||||||
|
f"aeg-*-pp{py_version.replace('pypy', '').replace('.', '')}-*.whl"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
wheel_pattern = (
|
||||||
|
f"aeg-*-cp{py_version.replace('.', '').replace('t', '')}-*.whl"
|
||||||
|
)
|
||||||
wheels = list(dist_dir.glob(wheel_pattern))
|
wheels = list(dist_dir.glob(wheel_pattern))
|
||||||
if not wheels:
|
if not wheels:
|
||||||
print(f"✗ Could not find wheel for Python {py_version}", file=sys.stderr)
|
print(f"✗ Could not find wheel for Python {py_version}", file=sys.stderr)
|
||||||
@@ -211,6 +220,47 @@ def main():
|
|||||||
|
|
||||||
wheel = wheels[0]
|
wheel = wheels[0]
|
||||||
|
|
||||||
|
# Repair wheel with auditwheel for manylinux compatibility (Linux only)
|
||||||
|
if platform.system() == "Linux":
|
||||||
|
repair_cmd = [
|
||||||
|
"uv",
|
||||||
|
"run",
|
||||||
|
"auditwheel",
|
||||||
|
"repair",
|
||||||
|
str(wheel),
|
||||||
|
"-w",
|
||||||
|
str(dist_dir),
|
||||||
|
]
|
||||||
|
if not run_command(
|
||||||
|
repair_cmd, f"Repairing wheel for Python {py_version} with auditwheel"
|
||||||
|
):
|
||||||
|
print(
|
||||||
|
f"✗ Auditwheel repair failed for Python {py_version}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
failed_builds.append(py_version)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Find the repaired wheel (it will have a different name)
|
||||||
|
all_wheels = list(
|
||||||
|
dist_dir.glob(f"aeg-*-cp{py_version.replace('.', '')}-*.whl")
|
||||||
|
)
|
||||||
|
repaired_wheels = [w for w in all_wheels if "linux_x86_64" not in str(w)]
|
||||||
|
if not repaired_wheels:
|
||||||
|
print(
|
||||||
|
f"✗ Could not find repaired (manylinux) wheel for Python {py_version}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
failed_builds.append(py_version)
|
||||||
|
continue
|
||||||
|
|
||||||
|
wheel = repaired_wheels[0] # Use the repaired wheel for testing
|
||||||
|
|
||||||
|
# Remove the unrepaired linux_x86_64 wheels
|
||||||
|
for w in all_wheels:
|
||||||
|
if "linux_x86_64" in str(w):
|
||||||
|
w.unlink()
|
||||||
|
|
||||||
# Test the wheel with pytest (use --isolated to avoid .venv conflicts)
|
# Test the wheel with pytest (use --isolated to avoid .venv conflicts)
|
||||||
test_cmd = [
|
test_cmd = [
|
||||||
"uv",
|
"uv",
|
||||||
|
|||||||
Reference in New Issue
Block a user