{"id":40766204,"url":"https://github.com/kyj9447/pixseal","last_synced_at":"2026-01-24T09:03:18.685Z","repository":{"id":327365248,"uuid":"1107692443","full_name":"kyj9447/Pixseal","owner":"kyj9447","description":"Cryptographic image integrity \u0026 authenticity verification tool. Detects any image modification via pixel-level validation.","archived":false,"fork":false,"pushed_at":"2026-01-20T17:20:46.000Z","size":130281,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-20T21:10:30.119Z","etag":null,"topics":["cryptography","digital-forensics","image-authenticity","image-integrity-check","image-verification","steganography-tool","tamper-detection"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kyj9447.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2025-12-01T13:40:38.000Z","updated_at":"2026-01-20T13:18:46.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/kyj9447/Pixseal","commit_stats":null,"previous_names":["kyj9447/pixseal"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/kyj9447/Pixseal","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyj9447%2FPixseal","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyj9447%2FPixseal/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyj9447%2FPixseal/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyj9447%2FPixseal/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kyj9447","download_url":"https://codeload.github.com/kyj9447/Pixseal/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyj9447%2FPixseal/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28721989,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-24T08:27:05.734Z","status":"ssl_error","status_checked_at":"2026-01-24T08:27:01.197Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["cryptography","digital-forensics","image-authenticity","image-integrity-check","image-verification","steganography-tool","tamper-detection"],"created_at":"2026-01-21T18:03:44.458Z","updated_at":"2026-01-24T09:03:18.674Z","avatar_url":"https://github.com/kyj9447.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n\u003cimg src=\"https://raw.githubusercontent.com/kyj9447/Pixseal/main/assets/logo/Pixseal.png\" width=\"200px\"/\u003e\n\u003c/p\u003e\n\n# Pixseal\n### Prove what you published — and what you didn’t.\nPixseal is a Python-based **image integrity and authenticity verification tool**\ndesigned to **detect whether an image has been modified since signing.**\n\nPixseal embeds a **cryptographically verifiable integrity seal** into an image in an\ninvisible manner. During verification, **any modification** — including editing,\nfiltering, cropping, resizing, re-encoding — will cause verification to **fail**.\n\nPixseal signs the payload and image hash with an RSA private key. Verification uses\nthe matching RSA public key or an X.509 certificate that contains it.\n\nPixseal is not a visual watermarking or branding tool.\nThe watermark exists solely as a **means to achieve strict, deterministic image\ntamper detection**.\nPixseal prioritizes tamper sensitivity over robustness against intentional adversarial manipulation.\n\n- GitHub: https://github.com/kyj9447/Pixseal\n- Changelog: https://github.com/kyj9447/Pixseal/blob/main/CHANGELOG.md\n\n## Features\n- **Image Integrity Verification**\n  - Cryptographically proves that an image remains in its original, unmodified state\n  - Detects single-pixel changes with deterministic verification results\n\n- **Tamper Detection**\n  - Detects image modifications such as:\n    - editing\n    - filters and color adjustments\n    - cropping and resizing\n    - re-encoding and recompression\n    - pixel-level changes\n\n- **Invisible Integrity Seal**\n  - Embeds verification data without any visible watermark\n  - Preserves the original visual appearance of the image\n\n- **RSA Signatures + Certificate Support**\n  - Signs payloads and image hashes with an RSA private key\n  - Validates with RSA public keys or X.509 certificates (PEM/DER)\n\n- **Flexible Key Inputs**\n  - Accepts key/cert objects, PEM/DER bytes, or file paths\n\n- **Fully Local \u0026 Offline**\n  - No external servers or network dependencies\n  - Pure Python implementation\n\n- **Lossless Format Support**\n  - Supports PNG and BMP (24-bit) images\n  - Lossy formats (e.g., JPEG, WebP) are intentionally excluded to preserve integrity guarantees\n\n## Installation\n\n```bash\npip install Pixseal\n# or for local development\npip install -e ./pip_package\n```\n\nPython 3.8+ is required. Wheels published to PyPI already include the compiled\nCython extension, so `pip install Pixseal` automatically selects the right build\nfor your operating system and CPU.\n\n### Building the Cython extension\n\nIf you cloned the repository (or downloaded the source), run the helper script\nto compile the `simpleImage_ext` extension for your environment:\n\n```bash\ngit clone https://github.com/kyj9447/Pixseal.git\ncd Pixseal\npython3 -m pip install -r requirements.txt\n./compile_extension.sh\n```\n\nThis command regenerates the C source via Cython and invokes your local C\ncompiler (`clang` or `gcc`) to produce `pip_package/Pixseal/simpleImage_ext*.so`.\nYou still need a working build toolchain (`gcc`/`clang` and Python headers)\ninstalled through your OS package manager. If you skip this step, Pixseal falls\nback to the pure Python implementation, which works but is significantly slower.\n\n## Quick start\n\n### Sign an image\n\n```python\nfrom Pixseal import signImage\n\nsigned = signImage(\n    imageInput=\"assets/original.png\",\n    payload=\"AutoTest123!\",\n    private_key=\"assets/CA/pixseal-dev-final.key\",\n    keyless=False,  # default: key-based channel selection\n)\nsigned.save(\"assets/signed_original.png\")\n```\n\n- The payload is looped if it runs out before the image ends, so even small files carry the full sentinel/payload/end pattern.\n\n### Validate a signed image\n\n```python\nfrom Pixseal import validateImage\n\nreport = validateImage(\n    imageInput=\"assets/signed_original.png\",\n    publicKey=\"assets/CA/pixseal-dev-final.crt\",  # cert or public key\n    keyless=False,  # default: key-based channel selection\n)\n\nprint(report[\"verdict\"])\n```\n\n## Key and certificate inputs\n\nPixseal accepts multiple input formats so you can keep the calling code minimal.\n\n- `signImage(..., private_key=...)` accepts:\n  - `RSAPrivateKey`\n  - PEM/DER bytes (`bytes`, `bytearray`, `memoryview`)\n  - file path (`str` or `Path`)\n\n- `validateImage(..., publicKey=...)` accepts:\n  - `RSAPublicKey`\n  - `x509.Certificate`\n  - PEM/DER bytes (`bytes`, `bytearray`, `memoryview`)\n  - file path (`str` or `Path`)\n\nIf a certificate is provided, Pixseal extracts the embedded RSA public key and\nverifies the signatures. Certificate chain validation is the responsibility of\nthe calling application.\n\n## Channel selection mode\n\nBoth `signImage()` and `validateImage()` accept a `keyless` flag.\n\n- `keyless=False` (default): key-based channel selection using raw public-key bytes.\n- `keyless=True`: pixel-based channel selection.\n\nKeyless mode is provided as an option and differs in extractability:\n\n1. Keyless-signed images: payload extraction is possible without a key; but verification fails.\n2. Key-based-signed images: without the key, Pixseal cannot even recognize that\n   it was applied, and extraction is impossible; verification fails too.\n\n## Payload structure\n\nPixseal embeds a compact JSON payload with the signed data and image hash:\n\n```json\n{\n  \"payload\": \"AutoTest123!\",\n  \"payloadSig\": \"BASE64_SIGNATURE\",\n  \"imageHash\": \"SHA256_HEX\",\n  \"imageHashSig\": \"BASE64_SIGNATURE\"\n}\n```\n\n- `payload`: user-provided text\n- `payloadSig`: RSA signature of `payload` (Base64)\n- `imageHash`: SHA256 hex digest computed over the signed image buffer.\n- `imageHashSig`: RSA signature of `imageHash` (Base64)\n\n## Embedded sequence layout\n\nPixseal writes the following newline-delimited sequence into the image:\n\n```\n\u003cSTART-VALIDATION signature\u003e\n\u003cpayload JSON\u003e\n\u003cpayload JSON\u003e\n\u003cpayload JSON\u003e\n\u003cpayload JSON\u003e\n...(Repeated until it fills the entire image)...\n\u003cpayload JSON\u003e   # truncated tail (prefix of payload JSON)\n\u003cEND-VALIDATION signature\u003e\n```\n\nDuring extraction, Pixseal deduplicates the sequence and typically returns four\nlines in order: start signature, full payload JSON, truncated payload prefix,\nand end signature. \n\nFor a valid image, deduplication results in four extracted\nlines. \n\n```\n\u003cSTART-VALIDATION signature\u003e\n\u003cpayload JSON\u003e\n\u003cpayload JSON\u003e   # truncated tail\n\u003cEND-VALIDATION signature\u003e\n```\n\u003csub\u003e※ In rare edge cases, the truncated payload prefix may be absent, in which\ncase only three lines are returned.\u003c/sub\u003e\n\n## Validation output\n\nValidation Report\n\n- `lengthCheck`\n  - `length` : Length of deduplication result array.\n  - `result` : True for 4 or 3 (valid deduplication cases).\n- `tailCheck`\n  - `full` : Full payload intact. (output truncated)\n  - `tail` : Truncated payload intact. (output truncated)\n  - `result` : True when the full and truncated payload portions match.\n- `startVerify` : Verification result of the first SIG against \"START-VALIDATION\"\n- `endtVerify` : Verification result of the last SIG against \"END-VALIDATION\"\n- `payloadVerify` : Verification result of the \"payload\" against \"payloadSig\"\n- `imageHashVerify` : Verification result of the \"imageHash\" against \"imageHashSig\"\n- `imageHashCompareCheck`\n  - `extractedHash` : Value of \"imageHash\" from extracted payload\n  - `computedHash` : Image hash computed directly from the image\n  - `result` : True when extractedHash and computedHash are identical\n- `verdict` : True when all validation checks pass.\n\n## CLI demo script\n\n`python testRun.py` offers an interactive flow:\n\nBefore the menu, it prompts for the SimpleImage backend\n(Enter/1=cython, 2=python fallback) and sets `PIXSEAL_SIMPLEIMAGE_BACKEND`.\n\n1. Choose **1** to sign an image. It reads `assets/original.png` and writes `assets/signed_original.png`.\n2. Choose **2** to validate. It reads `assets/signed_original.png` and prints the validation report.\n3. Choose **3** to run the failure test. It reads `assets/currupted_signed_original.png`.\n4. Choose **4** to benchmark performance (sign + validate with timings).\n5. Choose **5** to benchmark performance in keyless mode.\n6. Choose **6** to test signing and validation using in-memory bytes.\n7. Choose **7** to run the optional line-profiler demo.\n8. Choose **8** to run validation multi-pass tests.\n\nOption **7** requires the optional dependency `line_profiler` and must be run via\n`kernprof -l testRun.py` so that `builtins.profile` is provided. Without\n`line_profiler` installed the script will continue to work, but the profiling\noption will display an informative message instead of running.\n\n## API reference\n\n| Function | Description |\n| --- | --- |\n| `signImage(imageInput, payload, private_key, keyless=False)` | Loads a PNG/BMP from a filesystem path or raw bytes, injects `payload` plus sentinels, and signs the payload/hash using the RSA private key. Returns a `SimpleImage` that you can `save()` or `saveBmp()`. |\n| `validateImage(imageInput, publicKey, keyless=False)` | Reads the hidden bit stream from a path or raw bytes, rebuilds the payload JSON, verifies signatures and the computed image hash, and returns a validation report. Accepts RSA public keys or X.509 certificates. |\n\n## Examples\n\n| Original | Signed (`AutoTest123!`) |\n| --- | --- |\n| \u003cimg src=\"https://raw.githubusercontent.com/kyj9447/Pixseal/main/assets/original.png\" width=\"400px\"/\u003e | \u003cimg src=\"https://raw.githubusercontent.com/kyj9447/Pixseal/main/assets/signed_original.png\" width=\"400px\"/\u003e |\n\nValidation output (success):\n\n```\nValidation Report\n\n{'lengthCheck': {'length': 4, 'result': True},\n 'tailCheck': {'full': '{\"payload\":\"AutoTest...lgu9lUM+s7OHUZywYqYYOYIFVTWCmq...',\n               'tail': '{\"payload\":\"AutoTest...lgu9lUM+s7',\n               'result': True},\n 'startVerify': True,\n 'endtVerify': True,\n 'payloadVerify': True,\n 'imageHashVerify': True,\n 'imageHashCompareCheck': {'extractedHash': '2129e43456029f39b20bbe96340dce6827c0ad2288107cb92c0b92136fec48d6',\n                           'computedHash': '2129e43456029f39b20bbe96340dce6827c0ad2288107cb92c0b92136fec48d6',\n                           'result': True},\n 'verdict': True}\n```\n\n| Corrupted after signing |\n| --- |\n| \u003cimg src=\"https://raw.githubusercontent.com/kyj9447/Pixseal/main/assets/currupted_signed_original.png\" width=\"400px\"/\u003e |\n\nValidation output (failure):\n\n```\nValidation Report\n\n{'lengthCheck': {'length': 31, 'result': False},\n 'tailCheck': {'result': 'Not Required'},\n 'startVerify': True,\n 'endtVerify': True,\n 'payloadVerify': True,\n 'imageHashVerify': True,\n 'imageHashCompareCheck': {'extractedHash': '68d500c751dfa298d55dfc1cd2ab5c9f43ec139f02f6a11027211c4d144c2870',\n                           'computedHash': '43fd2108f5aa16045f4b64d70a0ce05991043cba6878f66d82abd3e7edb9d51e',\n                           'result': False},\n 'verdict': False}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkyj9447%2Fpixseal","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkyj9447%2Fpixseal","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkyj9447%2Fpixseal/lists"}