A modular Flask web service for hiding secret messages (text or images) inside cover images using advanced steganography techniques with MLEA encryption.
- Dual Payload Support: Hide both text messages and images
- MLEA Encryption: Multi-Layer Encryption Algorithm with SHA-256 key derivation
- LSB Steganography: Embeds data in blue channel least significant bits
- Deterministic Shuffling: Block-based shuffling with key-derived seeds
- Quality Metrics: RMSE and PSNR calculations for stego image quality
- Modern UI: Responsive design with custom CSS design system
- Interactive Gallery: Choose from predefined cover images or upload custom ones
- Real-time Preview: Live image previews during upload
- AJAX Integration: Seamless form submission without page reloads
- Error Handling: User-friendly error messages and validation
- REST API: Clean JSON endpoints for embedding and extraction
- Type Safety: Python 3.10+ with type hints throughout
- Comprehensive Tests: 45+ passing tests covering all functionality
The steganography algorithm implements:
- Cover Image Transformation: Horizontal flip → Transpose → RGB conversion
- Blue Channel Processing: Split into 4 horizontal blocks, shuffle deterministically
- MLEA Encryption: 3-round encryption with XOR, byte rotation, and reversal
- 64-bit Header Format:
- Magic number (0xC0DE, 16 bits)
- Version (8 bits)
- Payload type (8 bits: 0=text, 1=image)
- Encrypted length (32 bits)
- LSB Embedding: Round-robin embedding across shuffled blocks
- Reconstruction: Unshuffle, merge channels, reverse transformations
# Clone the repository
git clone <your-repo-url>
cd conceal
# Install dependencies
pip install -r requirements.txt
# Copy environment template
cp .env.sample .env
# (Optional) Edit .env with your configuration
nano .env# Development mode (recommended)
flask --app wsgi:app run --debug --port 5001
# Production mode
python wsgi.pyThe application will start on http://127.0.0.1:5001 by default.
Access the Web Interface:
- Homepage:
http://127.0.0.1:5001/ - Embed Secret:
http://127.0.0.1:5001/embed - Extract Secret:
http://127.0.0.1:5001/extract
API Endpoints:
- Base URL:
http://127.0.0.1:5001/api
# Run all tests
pytest -v
# Run specific test file
pytest tests/test_crypto.py -v
# Run with coverage
pytest --cov=app tests/The application includes a modern web interface for easy interaction with the steganography service.
- Navigate to
http://127.0.0.1:5001/embed - Choose Cover Image:
- Select "Use Predefined Image" and click on a gallery image, OR
- Select "Upload Image" and choose a custom cover image
- Select Secret Type:
- Choose "Text" and enter your secret message, OR
- Choose "Image" and upload a secret image
- Enter Encryption Key: Provide a strong key for encryption
- Optional: Set a custom output filename and shuffle seed
- Click "Embed Secret" to generate the stego image
- Download the result or copy the link to the generated stego image
- Navigate to
http://127.0.0.1:5001/extract - Upload Stego Image: Choose the image containing the hidden message
- Enter Key: Provide the same key used during embedding
- Click "Extract Secret" to reveal the hidden content
- View the extracted text or download the extracted image
- Gallery Selection: 5 predefined high-quality cover images available
- Image Preview: See thumbnails before embedding/extracting
- Real-time Validation: Form validation with helpful error messages
- Download Results: Direct download links for generated images
- Metrics Display: View RMSE and PSNR quality metrics after embedding
http://127.0.0.1:5001/api
GET /api/healthResponse:
{
"status": "ok"
}POST /api/embed
Content-Type: multipart/form-dataParameters:
| Field | Type | Required | Description |
|---|---|---|---|
cover_image |
file | No* | Cover image (png/jpg/jpeg) |
predefined_cover_image |
string | No* | Predefined image filename (e.g., "babylon.png") |
secret_text |
string | No** | Secret text message |
secret_image |
file | No** | Secret image (png/jpg/jpeg) |
key |
string | Yes | Encryption key |
seed |
integer | No | Custom shuffle seed (default: derived from key) |
output_name |
string | No | Output filename stem (default: UUID) |
* Exactly one of cover_image or predefined_cover_image must be provided.
** Exactly one of secret_text or secret_image must be provided.
Predefined Cover Images:
babylon.pngLena.pngpepper.pngstock_image1.jpgstock_image2.jpg
Example - Embed Text:
curl -X POST http://127.0.0.1:5001/api/embed \
-F "cover_image=@cover.png" \
-F "secret_text=This is my secret message!" \
-F "key=mySecretKey123"Example - Embed Text with Predefined Cover:
curl -X POST http://127.0.0.1:5001/api/embed \
-F "predefined_cover_image=babylon.png" \
-F "secret_text=This is my secret message!" \
-F "key=mySecretKey123"Example - Embed Image:
curl -X POST http://127.0.0.1:5001/api/embed \
-F "cover_image=@cover.png" \
-F "secret_image=@secret.png" \
-F "key=mySecretKey123" \
-F "output_name=my_stego"Success Response (200):
{
"stego_filename": "my_stego.png",
"width": 1024,
"height": 768,
"payload_type": "text",
"payload_size_bytes": 27,
"metrics": {
"rmse": 0.4523,
"psnr": 54.23
}
}Error Response (400):
{
"error": "Payload too large: need 524288 bits, capacity 196608 bits"
}POST /api/extract
Content-Type: multipart/form-dataParameters:
| Field | Type | Required | Description |
|---|---|---|---|
stego_image |
file | Yes | Stego image containing hidden message |
key |
string | Yes | Decryption key (must match embedding key) |
Example:
curl -X POST http://127.0.0.1:5001/api/extract \
-F "stego_image=@stego.png" \
-F "key=mySecretKey123"Success Response (200) - Text:
{
"payload_type": "text",
"secret_text": "This is my secret message!",
"payload_size_bytes": 27
}Success Response (200) - Image:
{
"payload_type": "image",
"secret_image_base64": "iVBORw0KGgoAAAANSUhEUgAA...",
"payload_size_bytes": 15234
}Error Response (400):
{
"error": "Invalid magic number: 0xABCD (expected 0xC0DE)"
}conceal/
├── app/
│ ├── __init__.py # Flask app factory
│ ├── config.py # Configuration
│ ├── routes/
│ │ ├── __init__.py
│ │ ├── frontend.py # Frontend routes (/, /embed, /extract)
│ │ └── stego.py # REST API endpoints
│ ├── services/
│ │ ├── __init__.py
│ │ ├── crypto.py # MLEA encryption
│ │ └── steganography.py # Embed/extract logic
│ ├── templates/ # Jinja2 HTML templates
│ │ ├── base.html # Base template with navbar
│ │ ├── index.html # Landing page
│ │ ├── embed.html # Embed secret page
│ │ └── extract.html # Extract secret page
│ └── utils/
│ ├── __init__.py
│ ├── image_codec.py # Image ↔ base64 conversion
│ ├── metrics.py # RMSE, PSNR
│ └── shuffle.py # Block shuffling
├── static/
│ ├── css/
│ │ └── style.css # Custom styles & design system
│ ├── js/
│ │ ├── main.js # Shared utilities
│ │ ├── embed.js # Embed page logic
│ │ └── extract.js # Extract page logic
│ ├── predefined/ # Predefined cover images
│ │ ├── babylon.png
│ │ ├── Lena.png
│ │ ├── pepper.png
│ │ ├── stock_image1.jpg
│ │ └── stock_image2.jpg
│ └── uploads/ # Generated stego images
├── tests/
│ ├── __init__.py
│ ├── test_api.py
│ ├── test_crypto.py
│ ├── test_image_codec.py
│ └── test_steganography.py
├── .env.sample # Environment template
├── .gitignore # Git ignore rules
├── requirements.txt # Python dependencies
├── wsgi.py # WSGI entry point
└── README.md # This file
import requests
# Embed text
with open('cover.png', 'rb') as f:
response = requests.post(
'http://127.0.0.1:5001/api/embed',
files={'cover_image': f},
data={
'secret_text': 'My secret message',
'key': 'my-encryption-key'
}
)
result = response.json()
print(f"Stego image: {result['stego_filename']}")
print(f"PSNR: {result['metrics']['psnr']} dB")
# Extract
with open(f"static/uploads/{result['stego_filename']}", 'rb') as f:
response = requests.post(
'http://127.0.0.1:5001/api/extract',
files={'stego_image': f},
data={'key': 'my-encryption-key'}
)
secret = response.json()
print(f"Extracted: {secret['secret_text']}")import requests
import base64
import cv2
import numpy as np
# Extract image
with open('stego.png', 'rb') as f:
response = requests.post(
'http://127.0.0.1:5001/api/extract',
files={'stego_image': f},
data={'key': 'my-key'}
)
result = response.json()
if result['payload_type'] == 'image':
# Decode base64 to image
img_bytes = base64.b64decode(result['secret_image_base64'])
img_array = np.frombuffer(img_bytes, dtype=np.uint8)
secret_img = cv2.imdecode(img_array, cv2.IMREAD_COLOR)
# Save or display
cv2.imwrite('extracted_secret.png', secret_img)Environment variables (in .env file):
# Flask environment (development|production)
FLASK_ENV=development
# Secret key for Flask sessions
SECRET_KEY=your-secret-key-here-change-in-production
# Maximum upload size in bytes (50MB for large cover/stego images)
MAX_CONTENT_LENGTH=52428800The capacity depends on the cover image size:
Capacity (bits) = width × height (of blue channel after transformation)
For example:
- 512×512 image: ~262,144 bits (~32 KB)
- 1024×1024 image: ~1,048,576 bits (~128 KB)
- 2048×2048 image: ~4,194,304 bits (~512 KB)
Remember: Payload is encrypted before embedding, so encrypted size may be larger than original.
- Key Management: Use strong, unique keys for each use case
- HTTPS: Use HTTPS in production to protect keys in transit
- Deterministic Seed: Seed is derived from key by default, ensuring reproducibility
- Header Validation: Magic number validation prevents accidental extraction
- Encryption: MLEA provides confidentiality even if stego image is detected
The test suite includes:
- Unit tests: Crypto, image codec, metrics, shuffle utilities
- Integration tests: Embed/extract roundtrips for text and images
- API tests: All endpoints with success and error cases
Run specific test categories:
# Test cryptography
pytest tests/test_crypto.py -v
# Test steganography
pytest tests/test_steganography.py -v
# Test API
pytest tests/test_api.py -vMeasures pixel-level differences between cover and stego images. Lower is better (0 = identical).
Measures image quality in dB. Higher is better (infinity = identical). Typically:
- > 40 dB: Excellent quality
- 30-40 dB: Good quality
- < 30 dB: Noticeable degradation
See LICENSE file for details.
Based on the image steganography algorithm described in the research on LSB embedding with block shuffling and multi-layer encryption.
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
For issues, questions, or contributions, please open an issue on the GitHub repository.