Skip to content

Repository files navigation

Secret Image Link

A fully client-side browser tool for:

  • 🔐 Encrypting messages with AES-GCM 256 (key derived via PBKDF2-SHA256 with 310,000 iterations)
  • 🖼️ Hiding the encrypted message inside a PNG image using LSB steganography on the R, G and B channels
  • 🔗 Building short encrypted links carrying the payload in the URL fragment (#m=...)
  • 🔓 Recovering the message from an image or an encrypted link with a password
  • 📁 Downloading the encrypted payload as a file (.secret.txt) for messages that are too large for a link
  • 📱 Right-to-left (RTL), mobile-friendly design with built-in dark / light theme support

No data is ever sent to a server. The message, password and image are processed exclusively on your device. No analytics, tracking or external services are used.


Features

  • Five complete tabs: hide in image, recover from image, create link, open link, about / security notes
  • Drag & drop image upload with live preview
  • Image capacity meter and live estimate of the size required for the current message
  • Secure random password generator powered by Web Crypto
  • Client-side password strength meter with helpful hints
  • PBKDF2 with 310,000 iterations, fresh random salt and IV for every message
  • JPEG warning to flag images that may have been re-compressed
  • QR Code for the encrypted link (rendered locally with qrcode.react, no external service)
  • Graceful fallback when the payload is too large for a URL — a downloadable encrypted payload file is offered instead
  • Hash cleanup from the URL after a successful decryption
  • Toast notifications for success / error feedback (no ugly alert())
  • No React Router, no Redux, no heavy UI kit
  • TypeScript strict, fully type-safe
  • RTL layout with system font stack (no external font CDN)

Tech Stack

  • React 18 + TypeScript (strict)
  • Vite 5 for dev server and production build
  • Tailwind CSS 3 for styling
  • Web Crypto API for cryptography (no JS crypto library)
  • qrcode.react (installed locally via npm, no external API)
  • Zero heavy dependencies, no UI kit, no state manager

Run locally

npm install
npm run dev

Then open http://localhost:5173 in your browser.

Important: The Web Crypto API only works on HTTPS or localhost. Opening the page over plain HTTP will disable all cryptographic features.

Production build

npm run build

The output is written to the dist/ folder and is fully static.

Preview the production build

npm run preview

Type-check the project

npm run typecheck

Deploy to Vercel

  1. Push the repository to GitHub / GitLab / Bitbucket.
  2. In Vercel click Add New → Project and import the repository.
  3. Vercel will auto-detect everything:
    • Framework Preset: Vite
    • Build Command: npm run build
    • Output Directory: dist
  4. Click Deploy.

No environment variables are required. No serverless functions are used. The entire project is served as a static site from the Vercel CDN.

A vercel.json is included in the project root for SPA fallback. It is safe to keep; remove it only if you know you do not need it.


Deploy to other static hosts

Because the build output is just static files, you can deploy the dist/ folder to any static host:

  • Netlify: Build command: npm run build | Publish directory: dist
  • GitHub Pages: via GitHub Actions or gh-pages
  • Cloudflare Pages: Build command: npm run build | Output: dist

Security notes (please read)

⚠️ The encrypted link is NOT truly one-time-use. Anyone who has both the link and the password can open the message. The link can also leak via browser history, server logs, or URL-scanning tools.

  • Always send the password through a separate channel (for example via an end-to-end encrypted messenger or a phone call)
  • Send the PNG as the original file, not pasted into a chat
  • Do not post the image on social networks — most of them re-compress uploaded images, which destroys the hidden message
  • Do not use JPEG — JPEG uses lossy compression
  • Use a strong password (at least 8 characters; 12+ with mixed case, digits and symbols is much better)
  • Do not rely on this tool for high-stakes or corporate use — it is intended for educational, personal and casual use

Limitations

  • HTTPS or localhost is required for the Web Crypto API to be available
  • The URL payload is capped at about 2 KB of base64url. For larger messages, use the PNG image output or download the payload file
  • Older browsers may not support the Web Crypto API or some modern APIs
  • If you lose the original image, the hidden message is gone forever

Project structure

secret-image-react/
├── index.html
├── package.json
├── tsconfig.json
├── vite.config.ts
├── tailwind.config.js
├── postcss.config.js
├── vercel.json
├── README.md
└── src/
    ├── main.tsx
    ├── App.tsx
    ├── index.css
    ├── types/
    │   └── index.ts
    ├── constants/
    │   └── index.ts
    ├── lib/
    │   ├── crypto.ts        # AES-GCM + PBKDF2
    │   ├── base64url.ts     # base64url helpers
    │   ├── stego.ts         # LSB embedding / extraction
    │   ├── payload.ts       # link & filename builders
    │   ├── utils.ts         # download, formatBytes, etc.
    │   └── validators.ts    # input validation
    ├── hooks/
    │   ├── useToast.tsx
    │   ├── useTheme.ts
    │   ├── useClipboard.ts
    │   ├── usePasswordStrength.ts
    │   ├── useDragAndDrop.ts
    │   ├── useImageData.ts
    │   └── useHashPayload.ts
    └── components/
        ├── layout/
        │   ├── Container.tsx
        │   ├── Header.tsx
        │   ├── Tabs.tsx
        │   └── Footer.tsx
        ├── ui/
        │   ├── Button.tsx
        │   ├── Card.tsx
        │   ├── Input.tsx
        │   ├── TextArea.tsx
        │   ├── PasswordInput.tsx
        │   ├── PasswordStrength.tsx
        │   ├── DropZone.tsx
        │   ├── Alert.tsx
        │   ├── Toast.tsx
        │   ├── Spinner.tsx
        │   ├── CopyButton.tsx
        │   ├── QRBox.tsx
        │   ├── EmptyState.tsx
        │   ├── Modal.tsx
        │   └── CapacityMeter.tsx
        └── tabs/
            ├── EncodeImageTab.tsx
            ├── DecodeImageTab.tsx
            ├── CreateLinkTab.tsx
            ├── OpenLinkTab.tsx
            └── AboutTab.tsx

Encrypted payload format

stream  = 4 bytes payloadLength (big-endian) | payload
payload = 4 bytes magic "HMSG"
        | 1 byte  version (1)
        | 16 bytes salt
        | 12 bytes iv
        | 4 bytes ciphertextLength (big-endian)
        | ciphertext (AES-GCM output, includes the 16-byte auth tag)
  • All lengths are stored in big-endian byte order
  • PBKDF2-SHA256 with 310,000 iterations
  • AES-GCM with a 256-bit key
  • A fresh random salt and IV are generated for every message

License

This project is released for free use in personal, educational and commercial projects. No user data is ever collected.

About

Free open-source tool to encrypt a message and hide it inside a PNG image using LSB steganography, with encrypted link support. Everything runs in the browser: no server, no storage of sensitive data, AES-GCM encryption. Great for privacy, education, and secure message sharing. Persian RTL UI.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages