{"id":50765647,"url":"https://github.com/trygu/primeval","last_synced_at":"2026-06-11T13:31:52.230Z","repository":{"id":355784401,"uuid":"1229575802","full_name":"trygu/primeval","owner":"trygu","description":"A tool for reconstructing usable RSA keys and cryptographic identities from raw prime factors","archived":false,"fork":false,"pushed_at":"2026-05-05T07:41:35.000Z","size":8,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-05T09:32:13.068Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/trygu.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-05T07:22:38.000Z","updated_at":"2026-05-05T07:41:39.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/trygu/primeval","commit_stats":null,"previous_names":["trygu/primeval"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/trygu/primeval","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trygu%2Fprimeval","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trygu%2Fprimeval/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trygu%2Fprimeval/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trygu%2Fprimeval/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/trygu","download_url":"https://codeload.github.com/trygu/primeval/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trygu%2Fprimeval/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34201840,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-11T02:00:06.485Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-11T13:31:51.296Z","updated_at":"2026-06-11T13:31:52.212Z","avatar_url":"https://github.com/trygu.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Primeval\n\n[![CI](https://github.com/trygu/primeval/actions/workflows/ci.yml/badge.svg)](https://github.com/trygu/primeval/actions/workflows/ci.yml)\n\n**Primeval** is a toolchain for the technical recovery of historical RSA keys (512-bit or shorter). The purpose is purely historical and aimed at digital archaeology: these key lengths are today considered cryptographically broken and are no longer in active use. The tool enables reconstruction of private keys that have been lost to history.\n\nRecovered output should be handled like any other private key.\n\n---\n\n## Requirements\n\n- Python 3.10–3.12 (`pgpy` is not yet compatible with 3.13+)\n- [uv](https://docs.astral.sh/uv/) — `curl -LsSf https://astral.sh/uv/install.sh | sh`\n- Docker + Docker Compose (for CADO-NFS)\n- **Linux x86_64** for the factorization step — the CADO-NFS image uses AVX2/AVX-512 instructions and will crash with \"Illegal instruction\" under Rosetta 2 on Apple Silicon\n\n## Setup\n\n```bash\ngit clone \u003crepo\u003e\ncd primeval\nuv sync --extra dev\n```\n\n## Usage\n\nAll tools are available through the `primeval` CLI:\n\n```bash\nuv run primeval parse KEY [-o data/metadata.json]\nuv run primeval modulus [-m data/metadata.json]\nuv run primeval import-factors [data/factors.txt] [-m data/metadata.json] [-p data/p.txt] [-q data/q.txt]\nuv run primeval reconstruct [-m data/metadata.json] [-p data/p.txt] [-q data/q.txt] [-o private_key.asc]\n```\n\n**Step 1 — Parse the public key:**\n\n```bash\nuv run primeval parse primeval/publickey.asc\n```\n\nWrites `data/metadata.json`.\n\n**Step 2 — Start the CADO-NFS container:**\n\n```bash\ndocker compose up --build -d\n```\n\n**Step 3 — Run factorization:**\n\n```bash\nN=$(uv run primeval modulus)\ndocker compose exec cado-engine bash -c \\\n  \"cado-nfs.py $N --workdir /work/work \\\n   2\u003e \u003e(tee /work/factorization.log \u003e\u00262) \\\n   | tee /work/factors.txt\"\n```\n\nFor 512-bit keys: minutes to hours on modern hardware. CADO-NFS can be interrupted and resumed — state is saved in `data/work/`.\n\n**Step 4 — Store factors (`p`, `q`) directly:**\n\nWhere to find them:\n\n- `data/factors.txt` (preferred): usually one line with two integers: `p q`\n- `data/factorization.log` (fallback): look near the end; factors are typically printed as two large integers\n\n```bash\nuv run primeval import-factors\n```\n\nIf `factors.txt` is unavailable, use the log:\n\n```bash\nuv run primeval import-factors data/factorization.log\n```\n\nWrites `data/p.txt` and `data/q.txt`. The command validates the pair against\n`data/metadata.json` when metadata is available.\n\n**Step 5 — Reconstruct the private key:**\n\n```bash\nuv run primeval reconstruct\nchmod 600 private_key.asc\n```\n\nWrites `private_key.asc` in the working directory. Despite the `.asc` suffix,\nthis file is a PEM-encoded RSA private key, not an OpenPGP secret-key packet.\n\n---\n\n## Background and motivation\n\nModern cryptography libraries refuse to import RSA keys shorter than 1024-bit and do not support legacy PGP packet formats (v2/v3). Reconstructing a historical private key therefore requires a complete rebuild of the key object from scratch, based on the arithmetic prime components $p$ and $q$ — the prime factors of the modulus $n = p \\cdot q$.\n\nGiven $n$ and $e$ from the public key, and the factors $p$ and $q$ from a factorization run, one can compute:\n\n$$d \\equiv e^{-1} \\pmod{\\phi(n)}, \\quad \\phi(n) = (p-1)(q-1)$$\n\nalong with the CRT components:\n\n$$d_p = d \\bmod (p-1), \\quad d_q = d \\bmod (q-1), \\quad q_{\\text{inv}} = q^{-1} \\bmod p$$\n\nThese six values $\\{n, e, d, p, q, d_p, d_q, q_{\\text{inv}}\\}$ form a complete `RSAPrivateNumbers` structure that can be serialized to a modern PKCS#1 PEM format.\n\n---\n\n## Pipeline\n\n```mermaid\n%%{init: {\"theme\": \"base\", \"themeVariables\": {\"background\": \"#ffffff\", \"fontFamily\": \"ui-sans-serif, system-ui, sans-serif\", \"lineColor\": \"#475569\", \"textColor\": \"#111827\"}}}%%\nC4Container\n    title Primeval recovery pipeline\n\n    Person(operator, \"Operator\", \"Runs the recovery workflow\")\n    System_Ext(pubkey, \"OpenPGP public key\", \".asc / armored input\")\n    System_Ext(cado, \"CADO-NFS\", \"Docker container\", \"GNFS factorization\")\n\n    System_Boundary(primeval, \"Primeval\") {\n        Container(cli, \"primeval\", \"Python CLI\", \"Subcommands: parse, modulus, import-factors, reconstruct\")\n    }\n\n    System_Boundary(files, \"Working files\") {\n        ContainerDb(metadata, \"data/metadata.json\", \"JSON\", \"n, e, user IDs, creation time\")\n        ContainerDb(cado_output, \"data/factors.txt / factorization.log\", \"Text\", \"CADO-NFS output\")\n        ContainerDb(work_state, \"data/work/\", \"CADO workdir\", \"Resumable factorization state\")\n        ContainerDb(factor_files, \"data/p.txt / data/q.txt\", \"Text\", \"Recovered factors\")\n        ContainerDb(private_key, \"private_key.asc\", \"PKCS#1 PEM\", \"Reconstructed private key\")\n    }\n\n    Rel(operator, cli, \"runs\", \"uv run primeval ...\")\n    Rel(pubkey, cli, \"input to parse\")\n    Rel(cli, metadata, \"writes and reads\")\n    Rel(cli, cado, \"passes n to\", \"docker compose exec cado-engine\")\n    Rel(cado, cado_output, \"writes factors and logs\")\n    Rel(cado, work_state, \"writes resumable state\")\n    Rel(cli, cado_output, \"imports factors from\")\n    Rel(cli, factor_files, \"writes p and q\")\n    Rel(cli, private_key, \"writes reconstructed key\")\n\n    UpdateElementStyle(operator, $bgColor=\"#eff6ff\", $fontColor=\"#172554\", $borderColor=\"#2563eb\")\n    UpdateElementStyle(pubkey, $bgColor=\"#eff6ff\", $fontColor=\"#172554\", $borderColor=\"#2563eb\")\n    UpdateElementStyle(cli, $bgColor=\"#ecfdf5\", $fontColor=\"#064e3b\", $borderColor=\"#059669\")\n    UpdateElementStyle(cado, $bgColor=\"#fff7ed\", $fontColor=\"#7c2d12\", $borderColor=\"#ea580c\")\n    UpdateElementStyle(metadata, $bgColor=\"#f8fafc\", $fontColor=\"#0f172a\", $borderColor=\"#64748b\")\n    UpdateElementStyle(cado_output, $bgColor=\"#f8fafc\", $fontColor=\"#0f172a\", $borderColor=\"#64748b\")\n    UpdateElementStyle(work_state, $bgColor=\"#f8fafc\", $fontColor=\"#0f172a\", $borderColor=\"#64748b\")\n    UpdateElementStyle(factor_files, $bgColor=\"#f8fafc\", $fontColor=\"#0f172a\", $borderColor=\"#64748b\")\n    UpdateElementStyle(private_key, $bgColor=\"#f5f3ff\", $fontColor=\"#3b0764\", $borderColor=\"#7c3aed\")\n\n    UpdateRelStyle(operator, cli, $textColor=\"#334155\", $lineColor=\"#64748b\")\n    UpdateRelStyle(pubkey, cli, $textColor=\"#334155\", $lineColor=\"#64748b\")\n    UpdateRelStyle(cli, metadata, $textColor=\"#334155\", $lineColor=\"#64748b\")\n    UpdateRelStyle(cli, cado, $textColor=\"#334155\", $lineColor=\"#64748b\")\n    UpdateRelStyle(cado, cado_output, $textColor=\"#334155\", $lineColor=\"#64748b\")\n    UpdateRelStyle(cado, work_state, $textColor=\"#334155\", $lineColor=\"#64748b\")\n    UpdateRelStyle(cli, cado_output, $textColor=\"#334155\", $lineColor=\"#64748b\")\n    UpdateRelStyle(cli, factor_files, $textColor=\"#334155\", $lineColor=\"#64748b\")\n    UpdateRelStyle(cli, private_key, $textColor=\"#334155\", $lineColor=\"#64748b\")\n```\n\n---\n\n## Technical walkthrough\n\n### Phase 1 — Parsing (`parse.py`)\n\nThe script reads an ASCII-armored OpenPGP public key and extracts modulus $n$ and public exponent $e$.\n\n**For PGP v4+ keys**, the `pgpy` library is used. The key's internal `keymaterial` attribute exposes the MPI fields directly.\n\n**For PGP v2/v3 keys** (from the early 1990s), the format is not supported by `pgpy`. A manual packet parser decodes CTB headers (Cipher Type Byte), iterates over packets, and interprets MPI-encoded (Multi-Precision Integer) integers per the RFC 1991 specification.\n\nOutputs:\n\n- `data/metadata.json` — JSON with $n$, $e$, UserID, and creation timestamp\n\n---\n\n### Phase 2 — Factorization (CADO-NFS via Docker)\n\nCADO-NFS is an implementation of the General Number Field Sieve (GNFS), the asymptotically fastest known algorithm for factoring composite integers.\n\nThe algorithm operates in four phases:\n\n| Phase | Name | Description |\n|-------|------|-------------|\n| 1 | **Polynomial Selection** | Finds a polynomial pair $(f_1, f_2)$ over $\\mathbb{Z}$ that minimizes sieving cost |\n| 2 | **Sieving** | Searches a lattice for *smooth* relations — numbers whose prime factors all lie below a given bound (factor base) |\n| 3 | **Linear Algebra** | Gaussian elimination over $\\mathbb{F}_2$ on the relations matrix to find kernel vectors |\n| 4 | **Square Root** | Uses kernel vectors to compute $\\sqrt{\\prod r_i} \\bmod n$, yielding a non-trivial divisor $\\gcd(\\cdot, n) = p$ |\n\nOutputs:\n\n- `data/factors.txt` — CADO-NFS stdout: `p q` on the last line\n- `data/factorization.log` — full progress log\n\n---\n\n### Phase 3 — Factor handoff\n\nUse CADO's factor output and write the two factors to files consumed by reconstruction:\n\n```bash\nuv run primeval import-factors\n```\n\nRequired files:\n\n- `data/p.txt`\n- `data/q.txt`\n\n---\n\n### Phase 4 — Reconstruction (`reconstruct.py`)\n\nThe script reads $n$, $e$, $p$, and $q$ and builds a complete RSA private key via `cryptography.hazmat`:\n\n```python\nphi  = (p - 1) * (q - 1)\nd    = pow(e, -1, phi)      # private exponent\ndp   = d % (p - 1)          # CRT exponent for p\ndq   = d % (q - 1)          # CRT exponent for q\nqinv = pow(q, -1, p)        # CRT coefficient (modular inverse)\n```\n\n`RSAPrivateNumbers(p, q, d, dp, dq, qinv, RSAPublicNumbers(e, n))` validates internal consistency before building the key object. The key is serialized to PKCS#1 PEM format (TraditionalOpenSSL encoding).\n\nOutput:\n\n- `private_key.asc` — PEM-encoded RSA private key (PKCS#1); set permissions with `chmod 600 private_key.asc`\n\n---\n\n## Limits\n\n- The factorization workflow is intended for historical RSA keys of 512 bits or\n  shorter. Larger keys are outside the practical scope of this project.\n- The Dockerized CADO-NFS step requires native Linux x86_64 with compatible CPU\n  instructions. Apple Silicon via Rosetta is expected to fail.\n- Reconstruction currently writes an unencrypted PKCS#1 PEM key, not a legacy\n  OpenPGP secret-key packet.\n- Tests exercise parsing and reconstruction with generated keys; they do not run\n  a full CADO-NFS factorization.\n\n---\n\n## Project structure\n\n```\nprimeval/\n  cli.py          — unified command dispatcher\n  parse.py        — extracts modulus + metadata from PGP public key\n  modulus.py      — prints n from metadata.json\n  import_factors.py — extracts p and q from CADO-NFS output\n  reconstruct.py  — assembles RSA private key from p, q, e\n  publickey.asc   — target PGP public key\ndata/\n  metadata.json   — key metadata including n, e, UserID (written by parse.py)\n  p.txt           — prime factor p (written manually from CADO output)\n  q.txt           — prime factor q (written manually from CADO output)\n  factors.txt     — captured CADO-NFS stdout (generated)\n  factorization.log — captured CADO-NFS stderr/progress log (generated)\n  work/           — CADO-NFS working directory (Docker volume, resumable)\ncado-src/\n  Dockerfile      — wraps registry.gitlab.inria.fr/cado-nfs/cado-nfs/factoring-full:latest\n  entrypoint.sh   — minimal entrypoint (mkdir /work; exec \"$@\")\ntests/            — pytest suite\n```\n\n---\n\n## Dependencies\n\n| Package | Purpose |\n| --------- | --------- |\n| `pgpy \u003e= 0.6.0` | Parsing PGP v4+ key structures |\n| `cryptography \u003e= 3.4` | RSA arithmetic and PEM serialization via `hazmat` API |\n| CADO-NFS (Docker) | General Number Field Sieve factorization |\n\n---\n\n## Tests\n\n```bash\nuv run pytest\n```\n\n| Test file | Coverage |\n| --------- | --------- |\n| `tests/test_cli.py` | Unified CLI commands and custom paths |\n| `tests/test_parse.py` | PGP key parsing (v4 and v2/v3) |\n| `tests/test_reconstruct_unit.py` | RSA key assembly |\n| `tests/test_pipeline_e2e.py` | End-to-end with an ephemeral 1024-bit key pair |\n\n---\n\n## License\n\nSee [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftrygu%2Fprimeval","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftrygu%2Fprimeval","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftrygu%2Fprimeval/lists"}