This document describes the cryptographic protocol used to sign photos at capture time and verify their integrity later. It is written so that any technically competent third party can independently audit a photo without using the AuditPic app.
The mobile app computes the SHA-256 hash of the raw image bytes before any upload or server contact.
sha256 = SHA-256(raw_image_bytes) # hex string, 64 characters
This ensures the server cannot substitute a different hash — the device is the sole source of truth for the hash.
capturedAt = current UTC time in ISO-8601 # e.g. 2026-03-27T14:30:00.000Z
The app sends (file, sha256, capturedAt) to POST /api/v1/photos.
The server records its own timestamp:
signedAt = current server UTC time
The server computes an HMAC-SHA256 over the concatenation of sha256 and signedAt:
payload = sha256 + ":" + signedAt.epochMilli
signature = HMAC-SHA256(payload, HMAC_SECRET) # hex string, 64 characters
The HMAC_SECRET is a server-side secret stored in environment variables; it is never exposed to clients.
The server stores in PostgreSQL:
verificationId UUID (public, shareable)
capturedAt from device
signedAt from server
sha256 from device
signature HMAC computed by server
storageKey path in MinIO
The original image bytes are stored in MinIO at storageKey.
verificationId (or scan QR){ sha256, capturedAt, signedAt, isAiGenerated } from the serverSHA-256(local_photo_bytes) and compares to the stored sha256You only need: the verificationId, the suspect photo file, and any SHA-256 tool (e.g., shasum -a 256 on macOS/Linux).
curl https://api.auditpic.com/api/v1/photos/{verificationId}
Response:
{
"verificationId": "...",
"capturedAt": "2026-03-27T14:30:00Z",
"signedAt": "2026-03-27T14:30:01Z",
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"isAiGenerated": false,
"aiConfidence": 0.02
}
shasum -a 256 /path/to/suspect_photo.jpg
# Output: <hex_hash> /path/to/suspect_photo.jpg
<hex_hash> == sha256 from step 1 → unalteredcurl https://api.auditpic.com/api/v1/photos/{verificationId}/download
# Returns: { "url": "https://minio.../..." }
curl -o original.jpg "<presigned_url>"
shasum -a 256 original.jpg # Should match sha256 from metadata
capturedAt) is user-controlled; signedAt (server time) is authoritative for tamper-evidenceisAiGenerated) is a probabilistic score, not a guarantee| Threat | Mitigated? | How |
|---|---|---|
| Photo edited after capture | Yes | SHA-256 mismatch |
| Photo replaced on server | Yes | HMAC signature ties hash+timestamp to server secret |
| User claims wrong capture time | Partial | signedAt (server time) is in the record |
| Server collusion | No | Requires trusting the server operator |
| AI-generated photo submitted as real | Partial | AI detection service (probabilistic) |
Reacties