Document signing and verification system via steganography, asymmetric cryptography and digital watermarking.
StegoSign: Document authentication system based on steganography, asymmetric cryptography (Ed25519) and QR watermarking
Develop a web system that allows signing and verifying the authenticity of digital documents by combining steganography, Ed25519 elliptic curve cryptography, and QR watermarking — guaranteeing document integrity without significantly altering its visual appearance.
- Implement an Ed25519 digital signature mechanism that cryptographically associates a document with its author.
- Develop a steganography system that embeds the signature payload directly into the document bytes, without requiring an external auxiliary file.
- Generate and insert a QR code as a watermark in PDF documents, linking the physical document to its registry entry via a unique 6-character verification code.
- Build a REST API in Rust with Axum exposing sign, file-based verification and code-based verification endpoints, with full forensic logging in a database.
- Implement a reactive frontend in Leptos (WebAssembly) that allows users to sign documents, verify their authenticity via file upload or code, and consult the complete audit registry.
- Deploy the complete system using Docker containers with service separation: PostgreSQL database, MinIO/AIStor object storage, and web application.
Document authenticity is a critical problem in legal, academic and institutional contexts. Traditional signing mechanisms — physical stamps, digitized handwritten signatures, or even simple electronic signatures embedded in PDFs — present significant limitations: they are easily forgeable, depend on third-party infrastructure such as certificate authorities, or do not offer a verification mechanism accessible without specialized software.
StegoSign proposes a different approach: instead of relying on external metadata or certificate chains, the document itself is the carrier of its authenticity evidence. The cryptographic payload — which includes the SHA-256 hash, the Ed25519 signature, and the document identifier — is embedded directly into the file bytes using appended-block steganography, a technique that does not alter the visual content or functionality of the document.
A second accessibility layer is added: for PDF documents, the system automatically generates a QR code inserted as a visible watermark. This code points to a verification URL that anyone can scan with a phone without installing any software, democratizing document authenticity verification.
From a technical standpoint, Rust was chosen as the primary language for its memory safety guarantees without a garbage collector, its performance close to C level, and the availability of mature crates for cryptography, image processing, and PDF manipulation. The Leptos + WebAssembly frontend allows sharing types between client and server in the same language, reducing the integration error surface.
The result is an auditable, self-contained system deployable on any Docker-capable infrastructure, requiring no external third-party service dependencies to run in production.
| layer | technology |
|---|---|
| backend api | Rust + Axum 0.8 |
| frontend | Leptos 0.8 + WebAssembly |
| database | PostgreSQL 17 |
| object storage | MinIO AIStor | AWS S3 |
| cryptography | Ed25519 (ed25519-dalek) + SHA-256 |
| containerization | Docker + Docker Compose |
browser
│
└─► :55548 (stego-app — Leptos SSR + WASM)
│
└─► /api/* → internal proxy → :4000 (stego-server — Axum)
│
┌─────────┼──────────┐
postgres s3/aistor audit log
The browser never communicates directly with the server. All /api/* requests pass through the app's internal proxy, which forwards them to the server on the internal Docker network.
The landing page introduces the system and its three-step pipeline: Sign, Verify, and Audit. It displays real-time statistics — total documents signed, verifications performed, tampering events detected, and objects in storage.
How a document gets signed — step by step
- File Upload — the file is received via multipart form. Any format is supported: PDF, PNG, DOCX, binary, etc. The original bytes are preserved unchanged.
- SHA-256 Hash — a cryptographic hash of the original file bytes is computed. This 256-bit fingerprint uniquely identifies the file content.
- Ed25519 Signature — the SHA-256 hash is signed using the Ed25519 private key, producing a 64-byte digital signature that proves authorship and integrity.
- Steganographic Embedding — a JSON payload containing the document ID, hash, signature and author is appended to the file using magic delimiters
>>STEGO::PAYLOAD<< ... >>STEGO::END<<. The signed file looks identical to the original. - QR Watermark (PDF only) — a QR code pointing to the verification URL is generated and inserted as a visible watermark in the PDF.
- Registry & Storage — the original and signed files are uploaded to S3/AIStor. The document is registered in PostgreSQL with its hash, signature, author, and object keys for full traceability.
How a signed document gets verified — step by step
- Payload Extraction — the pipeline scans the file for the magic delimiter
>>STEGO::PAYLOAD<<. If not found, the file is classified asINVALIDimmediately. - Hash Recomputation — the SHA-256 hash of the current file bytes (without the payload) is recomputed and compared against the hash stored in the embedded payload.
- Signature Verification — the Ed25519 public key is used to verify the embedded signature against the original hash. A mismatch means the file was tampered with.
- Registry Cross-check — the document ID and hash are cross-checked against the PostgreSQL registry. A valid signature with no registry entry is classified as
UNREGISTERED. - Verdict — final classification:
VALID,TAMPERED,UNREGISTERED, orINVALID.
Each signed PDF contains a QR code with a unique 6-character verification code (e.g. ABC-123). This code can be entered directly in the verify page without uploading the file, enabling instant verification from any device by scanning the QR.
Every verification attempt is recorded in the audit log with its verdict, timestamp and file hash — providing a complete forensic trail.
The registry page shows the complete forensic history of the system: all signed documents and all verification attempts, with status, author, timestamp and SHA-256 hash.
stego-sign/
├── server/ # rust + axum REST api
│ ├── src/
│ │ ├── handlers/ # sign, verify, documents, stats, registry
│ │ ├── services/ # crypto, stego, storage, watermark, qr
│ │ ├── repositories/
│ │ └── config/
│ └── migrations/ # schema_files.sql, schema_app.sql
├── app/ # leptos + wasm frontend
│ └── src/
│ ├── features/ # home, sign, verify, documents
│ └── config.rs # api_base_url (ssr/csr)
├── docker-compose.yml
├── docker-compose.swarm.yml
├── Makefile
└── .env
# rust
rustup target add wasm32-unknown-unknown
cargo install cargo-leptos --locked
# docker
docker compose versioncp .env.example .env
# edit .env — at minimum set POSTGRES_PASSWORD, STORAGE_ACCESS_KEY, STORAGE_SECRET_KEYmake up-infra # starts db + aistor# terminal 1
make dev-server # cargo run in /server — http://localhost:4000
# terminal 2
make dev-app # cargo leptos watch in /app — http://localhost:3000make keygen
# copy SIGNING_KEY and VERIFY_KEY to .envSee DEPLOY.md for the complete deployment guide including Docker Compose, first-run migrations, key generation, and Docker Swarm / Dokploy instructions.
make deploy-aistor
make migrate
make keygen # copy keys to .env
make recreate-servermake deploy-aws
make migrate
make keygen # copy keys to .env
make recreate-server| method | route | description |
|---|---|---|
GET |
/health |
server, db and storage status |
POST |
/api/v1/sign |
sign a document |
POST |
/api/v1/verify |
verify document integrity |
GET |
/api/v1/verify/code/{code} |
verify by QR code |
GET |
/api/v1/documents |
list all documents |
GET |
/api/v1/documents/{id} |
get document by id |
GET |
/api/v1/documents/{id}/audit |
audit log for a document |
GET |
/api/v1/documents/{id}/download |
download signed file |
GET |
/api/v1/stats |
global statistics |
GET |
/api/v1/registry |
full document registry |
GET |
/api/v1/admin/keygen |
generate Ed25519 key pair (dev only) |
make help # show all available commands
make up # start all services
make deploy-aistor # deploy with local aistor storage
make deploy-aws # deploy with aws s3 (no aistor)
make migrate # apply database migrations
make migrate-fresh # drop and re-apply all schemas
make keygen # generate ed25519 key pair
make recreate-server # force recreate server (picks up new env vars)
make logs s=server # follow server logs
make down-v # stop all and remove volumesImages referenced in this README are located in resources/ with the following mapping:
| file | description |
|---|---|
navbar.png |
Navigation bar |
home.png |
Hero section |
how-it-works.png |
Three-step pipeline section |
stats.png |
Live statistics counters |
footer.png |
Footer with stack and links |
sign-page.png |
Sign page — empty state |
sign-result.png |
Sign page — success result |
how-signing-works.png |
Signing pipeline modal |
verify-page.png |
Verify page — upload tab |
verify-valid.png |
Verify result — VALID |
verify-invalid.png |
Verify result — INVALID |
verify-code.png |
Verify by code tab |
verify-code-valid.png |
Verify by code result — VALID |
verify-code-invalid.png |
Verify by code result — INVALID |
how-verification-works.png |
Verification pipeline modal |
documents-signed.png |
Documents registry — Signed tab |
documents-verifications.png |
Documents registry — Verifications tab |
Academic project — IS-444 Seguridad Informática · UNSCH · 2026
















