Use when implementing Least Significant Bit (LSB) steganography - allows hiding images or text within other images by encoding data in the least significant bits of pixel color channels
Complete implementation guide for Least Significant Bit (LSB) steganography - a technique for hiding images or text within other images by encoding data in the least significant bits of pixel color channels. The hidden data is imperceptible to the human eye while remaining fully recoverable.
Core Capabilities:
LSB steganography works by replacing the least significant bits of pixel color channels with data bits:
Key Concepts:
width × height × 3 × n_bits bits can be stored in an imageExample:
Carrier Image: 1000×1000 pixels
n_bits: 2
Capacity: 1000 × 1000 × 3 × 2 = 6,000,000 bits = 750 KB
Browser-Native Implementation: This implementation uses only native browser APIs - no Node.js or external packages required for the core functionality.
Required Browser APIs:
HTMLCanvasElement, CanvasRenderingContext2D)File, Blob, URL.createObjectURL)Optional dependencies:
react and react-dom for React hooks and componentsBrowser Support:
Note: This implementation is designed for browser environments. For Node.js server-side use, you would need to adapt the code to use the canvas npm package, but the default implementation is 100% browser-native.
// lib/lsbSteganography.ts
const MAX_COLOR_VALUE = 256;
const MAX_BIT_VALUE = 8;
/**
* Removes the n least significant bits from a color value.
* Used to clear space for embedding data.
*
* @param value - Color channel value (0-255)
* @param n - Number of bits to remove (1-8)
* @returns Value with n least significant bits set to 0
*/
export function removeNLeastSignificantBits(value: number, n: number): number {
value = value >> n;
return value << n;
}
How it works:
removeNLeastSignificantBits(107, 2) → 104 (binary: 01101000)/**
* Extracts the n least significant bits from a color value.
* Used to retrieve embedded data.
*
* @param value - Color channel value (0-255)
* @param n - Number of bits to extract (1-8)
* @returns The n least significant bits (0 to 2^n - 1)
*/
export function getNLeastSignificantBits(value: number, n: number): number {
value = value << (MAX_BIT_VALUE - n);
value = value % MAX_COLOR_VALUE;
return value >> (MAX_BIT_VALUE - n);
}
How it works:
getNLeastSignificantBits(107, 2) → 3 (binary: 11)/**
* Extracts the n most significant bits from a color value.
* Used to get the important bits from source images.
*
* @param value - Color channel value (0-255)
* @param n - Number of bits to extract (1-8)
* @returns The n most significant bits (0 to 2^n - 1)
*/
export function getNMostSignificantBits(value: number, n: number): number {
return value >> (MAX_BIT_VALUE - n);
}
How it works:
getNMostSignificantBits(107, 2) → 1 (binary: 01)/**
* Shifts n bits to fill an 8-bit value.
* Used when reconstructing color values from extracted bits.
*
* @param value - Bit value (0 to 2^n - 1)
* @param n - Original number of bits
* @returns Value shifted to 8-bit position
*/
export function shiftNBitsTo8(value: number, n: number): number {
return value << (MAX_BIT_VALUE - n);
}
How it works:
shiftNBitsTo8(3, 2) → 192 (binary: 11000000)/**
* Loads an image file and returns a canvas element.
*
* @param file - Image file (File or Blob)
* @returns Promise resolving to HTMLCanvasElement
*/
export async function loadImageToCanvas(file: File | Blob): Promise<HTMLCanvasElement> {
return new Promise((resolve, reject) => {
const img = new Image();
const url = URL.createObjectURL(file);
img.onload = () => {
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
if (!ctx) {
reject(new Error('Failed to get canvas context'));
return;
}
ctx.drawImage(img, 0, 0);
URL.revokeObjectURL(url);
resolve(canvas);
};
img.onerror = () => {
URL.revokeObjectURL(url);
reject(new Error('Failed to load image'));
};
img.src = url;
});
}
/**
* Extracts pixel data from a canvas as RGB tuples.
*
* @param canvas - Canvas element
* @returns Array of [r, g, b] tuples
*/
export function getImageData(canvas: HTMLCanvasElement): [number, number, number][] {
const ctx = canvas.getContext('2d');
if (!ctx) {
throw new Error('Failed to get canvas context');
}
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
const pixels: [number, number, number][] = [];
for (let i = 0; i < data.length; i += 4) {
pixels.push([data[i], data[i + 1], data[i + 2]]);
}
return pixels;
}
/**
* Creates a canvas from pixel data.
*
* @param data - Array of [r, g, b] tuples
* @param width - Image width
* @param height - Image height
* @returns Canvas element
*/
export function createImageFromData(
data: [number, number, number][],
width: number,
height: number
): HTMLCanvasElement {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
if (!ctx) {
throw new Error('Failed to get canvas context');
}
const imageData = ctx.createImageData(width, height);
const pixels = imageData.data;
for (let i = 0; i < data.length; i++) {
const [r, g, b] = data[i];
const idx = i * 4;
pixels[idx] = r;
pixels[idx + 1] = g;
pixels[idx + 2] = b;
pixels[idx + 3] = 255; // Alpha
}
ctx.putImageData(imageData, 0, 0);
return canvas;
}
/**
* Encodes a source image into a carrier image using LSB steganography.
*
* @param imageToHide - Canvas of image to hide
* @param imageToHideIn - Canvas of carrier image
* @param nBits - Number of least significant bits to use (1-8)
* @returns Canvas with encoded image
*/
export async function lsbEncode(
imageToHide: HTMLCanvasElement,
imageToHideIn: HTMLCanvasElement,
nBits: number
): Promise<HTMLCanvasElement> {
const width = imageToHide.width;
const height = imageToHide.height;
// Check if carrier image is large enough
if (imageToHideIn.width < width || imageToHideIn.height < height) {
throw new Error('Carrier image must be at least as large as image to hide');
}
const hidePixels = getImageData(imageToHide);
const hideInPixels = getImageData(imageToHideIn);
const encodedPixels: [number, number, number][] = [];
for (let i = 0; i < hidePixels.length; i++) {
const [rHide, gHide, bHide] = hidePixels[i];
const [rHideIn, gHideIn, bHideIn] = hideInPixels[i];
// Get most significant bits from image to hide
const rHideMSB = getNMostSignificantBits(rHide, nBits);
const gHideMSB = getNMostSignificantBits(gHide, nBits);
const bHideMSB = getNMostSignificantBits(bHide, nBits);
// Remove least significant bits from carrier image
const rHideInLSB = removeNLeastSignificantBits(rHideIn, nBits);
const gHideInLSB = removeNLeastSignificantBits(gHideIn, nBits);
const bHideInLSB = removeNLeastSignificantBits(bHideIn, nBits);
// Combine: carrier image (with cleared LSB) + hidden image (MSB)
encodedPixels.push([
rHideMSB + rHideInLSB,
gHideMSB + gHideInLSB,
bHideMSB + bHideInLSB
]);
}
return createImageFromData(encodedPixels, width, height);
}
Key points:
/**
* Decodes a hidden image from an encoded image.
*
* @param encodedCanvas - Canvas with encoded image
* @param nBits - Number of least significant bits used (1-8)
* @returns Canvas with decoded hidden image
*/
export function lsbDecode(
encodedCanvas: HTMLCanvasElement,
nBits: number
): HTMLCanvasElement {
const width = encodedCanvas.width;
const height = encodedCanvas.height;
const encodedPixels = getImageData(encodedCanvas);
const decodedPixels: [number, number, number][] = [];
for (const [rEncoded, gEncoded, bEncoded] of encodedPixels) {
// Extract least significant bits
const rLSB = getNLeastSignificantBits(rEncoded, nBits);
const gLSB = getNLeastSignificantBits(gEncoded, nBits);
const bLSB = getNLeastSignificantBits(bEncoded, nBits);
// Shift bits to 8-bit position to reconstruct color
const r = shiftNBitsTo8(rLSB, nBits);
const g = shiftNBitsTo8(gLSB, nBits);
const b = shiftNBitsTo8(bLSB, nBits);
decodedPixels.push([r, g, b]);
}
return createImageFromData(decodedPixels, width, height);
}
/**
* Converts text to binary representation.
*
* @param text - Text to convert
* @returns Binary string (padded to multiple of 8)
*/
export function textToBinary(text: string): string {
const bytes = new TextEncoder().encode(text);
let binary = '';
for (const byte of bytes) {
binary += byte.toString(2).padStart(8, '0');
}
return binary;
}
How it works:
TextEncoder to convert string to UTF-8 bytes/**
* Converts binary representation back to text.
*
* @param binary - Binary string (multiple of 8)
* @returns Decoded text
*/
export function binaryToText(binary: string): string {
const bytes: number[] = [];
for (let i = 0; i < binary.length; i += 8) {
const byte = parseInt(binary.substr(i, 8), 2);
bytes.push(byte);
}
return new TextDecoder().decode(new Uint8Array(bytes));
}
/**
* Encodes text into an image using LSB steganography.
*
* @param canvas - Canvas of carrier image
* @param text - Text to hide
* @param nBits - Number of least significant bits to use (1-8)
* @returns Canvas with encoded text
*/
export function encodeTextInImage(
canvas: HTMLCanvasElement,
text: string,
nBits: number = 2
): HTMLCanvasElement {
const width = canvas.width;
const height = canvas.height;
const pixels = getImageData(canvas);
// Convert text to binary
const binaryText = textToBinary(text);
// Add 32-bit length prefix (to know how much to decode)
const lengthPrefix = binaryText.length.toString(2).padStart(32, '0');
const fullBinary = lengthPrefix + binaryText;
// Check capacity
const maxBits = width * height * 3 * nBits;
if (fullBinary.length > maxBits) {
throw new Error(
`Text is too long. Maximum ${maxBits} bits can be hidden (${Math.floor(maxBits / 8)} bytes).`
);
}
// Encode binary into image
let binaryIndex = 0;
const encodedPixels: [number, number, number][] = [];
for (let i = 0; i < pixels.length; i++) {
const [r, g, b] = pixels[i];
let rModified = r;
let gModified = g;
let bModified = b;
// Red channel
if (binaryIndex < fullBinary.length) {
const bits = fullBinary.substr(binaryIndex, nBits);
const bitValue = parseInt(bits.padEnd(nBits, '0'), 2);
rModified = (r & ~((1 << nBits) - 1)) | bitValue;
binaryIndex += nBits;
}
// Green channel
if (binaryIndex < fullBinary.length) {
const bits = fullBinary.substr(binaryIndex, nBits);
const bitValue = parseInt(bits.padEnd(nBits, '0'), 2);
gModified = (g & ~((1 << nBits) - 1)) | bitValue;
binaryIndex += nBits;
}
// Blue channel
if (binaryIndex < fullBinary.length) {
const bits = fullBinary.substr(binaryIndex, nBits);
const bitValue = parseInt(bits.padEnd(nBits, '0'), 2);
bModified = (b & ~((1 << nBits) - 1)) | bitValue;
binaryIndex += nBits;
}
encodedPixels.push([rModified, gModified, bModified]);
if (binaryIndex >= fullBinary.length) {
// Fill remaining pixels with original values
for (let j = i + 1; j < pixels.length; j++) {
encodedPixels.push(pixels[j]);
}
break;
}
}
return createImageFromData(encodedPixels, width, height);
}
Key points:
width × height × 3 × nBits bits/**
* Decodes hidden text from an image.
*
* @param canvas - Canvas with encoded text
* @param nBits - Number of least significant bits used (1-8)
* @returns Decoded text
*/
export function decodeTextFromImage(
canvas: HTMLCanvasElement,
nBits: number = 2
): string {
const pixels = getImageData(canvas);
let binaryText = '';
let bitCount = 0;
let textLength: number | null = null;
for (const [r, g, b] of pixels) {
// Extract bits from each channel
const rBits = r & ((1 << nBits) - 1);
binaryText += rBits.toString(2).padStart(nBits, '0');
bitCount += nBits;
// Read length prefix (first 32 bits)
if (textLength === null && bitCount >= 32) {
textLength = parseInt(binaryText.substr(0, 32), 2);
binaryText = binaryText.substr(32);
bitCount -= 32;
}
if (textLength !== null && bitCount >= textLength) {
break;
}
const gBits = g & ((1 << nBits) - 1);
binaryText += gBits.toString(2).padStart(nBits, '0');
bitCount += nBits;
if (textLength !== null && bitCount >= textLength) {
break;
}
const bBits = b & ((1 << nBits) - 1);
binaryText += bBits.toString(2).padStart(nBits, '0');
bitCount += nBits;
if (textLength !== null && bitCount >= textLength) {
break;
}
}
// Trim to exact length and convert to text
if (textLength === null) {
throw new Error('Failed to decode text length');
}
const textBinary = binaryText.substr(0, textLength);
return binaryToText(textBinary);
}
/**
* Resizes an image while maintaining aspect ratio and adds padding to match target size.
*
* @param canvas - Source canvas
* @param targetWidth - Target width
* @param targetHeight - Target height
* @param backgroundColor - Background color for padding (default: white)
* @returns Resized and padded canvas
*/
export function resizeAndPadImage(
canvas: HTMLCanvasElement,
targetWidth: number,
targetHeight: number,
backgroundColor: string = '#FFFFFF'
): HTMLCanvasElement {
// Calculate scaling to fit within target size while maintaining aspect ratio
const scale = Math.min(targetWidth / canvas.width, targetHeight / canvas.height);
const scaledWidth = Math.floor(canvas.width * scale);
const scaledHeight = Math.floor(canvas.height * scale);
// Create temporary canvas for resized image
const tempCanvas = document.createElement('canvas');
tempCanvas.width = scaledWidth;
tempCanvas.height = scaledHeight;
const tempCtx = tempCanvas.getContext('2d');
if (!tempCtx) {
throw new Error('Failed to get canvas context');
}
// Draw resized image
tempCtx.drawImage(canvas, 0, 0, scaledWidth, scaledHeight);
// Create final canvas with target size
const finalCanvas = document.createElement('canvas');
finalCanvas.width = targetWidth;
finalCanvas.height = targetHeight;
const finalCtx = finalCanvas.getContext('2d');
if (!finalCtx) {
throw new Error('Failed to get canvas context');
}
// Fill with background color
finalCtx.fillStyle = backgroundColor;
finalCtx.fillRect(0, 0, targetWidth, targetHeight);
// Center the resized image
const pasteX = (targetWidth - scaledWidth) / 2;
const pasteY = (targetHeight - scaledHeight) / 2;
finalCtx.drawImage(tempCanvas, pasteX, pasteY);
return finalCanvas;
}
/**
* Converts a canvas to a Blob.
*
* @param canvas - Canvas element
* @param mimeType - MIME type (default: 'image/png')
* @param quality - Quality for JPEG (0-1, default: 0.92)
* @returns Promise resolving to Blob
*/
export function canvasToBlob(
canvas: HTMLCanvasElement,
mimeType: string = 'image/png',
quality: number = 0.92
): Promise<Blob> {
return new Promise((resolve, reject) => {
canvas.toBlob(
(blob) => {
if (blob) {
resolve(blob);
} else {
reject(new Error('Failed to convert canvas to blob'));
}
},
mimeType,
quality
);
});
}
/**
* Calculates the maximum text capacity for an image.
*
* @param width - Image width
* @param height - Image height
* @param nBits - Number of bits per channel (1-8)
* @returns Maximum capacity in bytes
*/
export function calculateTextCapacity(
width: number,
height: number,
nBits: number
): number {
// Subtract 32 bits for length prefix
const totalBits = width * height * 3 * nBits - 32;
return Math.floor(totalBits / 8);
}
// hooks/useLSBEncode.ts
import { useState } from 'react';
import {
loadImageToCanvas,
lsbEncode,
resizeAndPadImage,
canvasToBlob
} from '@/lib/lsbSteganography';
export function useLSBEncode() {
const [isEncoding, setIsEncoding] = useState(false);
const [error, setError] = useState<string | null>(null);
const encode = async (
imageToHide: File,
carrierImage: File,
nBits: number = 2
): Promise<Blob | null> => {
setIsEncoding(true);
setError(null);
try {
// Load images
const hideCanvas = await loadImageToCanvas(imageToHide);
const carrierCanvas = await loadImageToCanvas(carrierImage);
// Resize and pad image to hide to match carrier
const resizedHideCanvas = resizeAndPadImage(
hideCanvas,
carrierCanvas.width,
carrierCanvas.height
);
// Encode
const encodedCanvas = await lsbEncode(resizedHideCanvas, carrierCanvas, nBits);
// Convert to blob
const blob = await canvasToBlob(encodedCanvas);
return blob;
} catch (err) {
const message = err instanceof Error ? err.message : 'Encoding failed';
setError(message);
return null;
} finally {
setIsEncoding(false);
}
};
return { encode, isEncoding, error };
}
// hooks/useLSBDecode.ts
import { useState } from 'react';
import { loadImageToCanvas, lsbDecode, canvasToBlob } from '@/lib/lsbSteganography';
export function useLSBDecode() {
const [isDecoding, setIsDecoding] = useState(false);
const [error, setError] = useState<string | null>(null);
const decode = async (
encodedImage: File,
nBits: number = 2
): Promise<Blob | null> => {
setIsDecoding(true);
setError(null);
try {
const canvas = await loadImageToCanvas(encodedImage);
const decodedCanvas = lsbDecode(canvas, nBits);
const blob = await canvasToBlob(decodedCanvas);
return blob;
} catch (err) {
const message = err instanceof Error ? err.message : 'Decoding failed';
setError(message);
return null;
} finally {
setIsDecoding(false);
}
};
return { decode, isDecoding, error };
}
// hooks/useTextEncode.ts
import { useState } from 'react';
import {
loadImageToCanvas,
encodeTextInImage,
calculateTextCapacity,
canvasToBlob
} from '@/lib/lsbSteganography';
export function useTextEncode() {
const [isEncoding, setIsEncoding] = useState(false);
const [error, setError] = useState<string | null>(null);
const encode = async (
carrierImage: File,
text: string,
nBits: number = 2
): Promise<Blob | null> => {
setIsEncoding(true);
setError(null);
try {
const canvas = await loadImageToCanvas(carrierImage);
// Check capacity
const capacity = calculateTextCapacity(canvas.width, canvas.height, nBits);
if (text.length > capacity) {
throw new Error(
`Text is too long. Maximum ${capacity} characters can be encoded.`
);
}
const encodedCanvas = encodeTextInImage(canvas, text, nBits);
const blob = await canvasToBlob(encodedCanvas);
return blob;
} catch (err) {
const message = err instanceof Error ? err.message : 'Encoding failed';
setError(message);
return null;
} finally {
setIsEncoding(false);
}
};
return { encode, isEncoding, error };
}
// hooks/useTextDecode.ts
import { useState } from 'react';
import { loadImageToCanvas, decodeTextFromImage } from '@/lib/lsbSteganography';
export function useTextDecode() {
const [isDecoding, setIsDecoding] = useState(false);
const [error, setError] = useState<string | null>(null);
const decode = async (
encodedImage: File,
nBits: number = 2
): Promise<string | null> => {
setIsDecoding(true);
setError(null);
try {
const canvas = await loadImageToCanvas(encodedImage);
const text = decodeTextFromImage(canvas, nBits);
return text;
} catch (err) {
const message = err instanceof Error ? err.message : 'Decoding failed';
setError(message);
return null;
} finally {
setIsDecoding(false);
}
};
return { decode, isDecoding, error };
}
// components/LSBImageEncoder.tsx
import { useState } from 'react';
import { useLSBEncode } from '@/hooks/useLSBEncode';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Alert, AlertDescription } from '@/components/ui/alert';
export function LSBImageEncoder() {
const [imageToHide, setImageToHide] = useState<File | null>(null);
const [carrierImage, setCarrierImage] = useState<File | null>(null);
const [nBits, setNBits] = useState(2);
const [encodedImageUrl, setEncodedImageUrl] = useState<string | null>(null);
const { encode, isEncoding, error } = useLSBEncode();
const handleEncode = async () => {
if (!imageToHide || !carrierImage) return;
const blob = await encode(imageToHide, carrierImage, nBits);
if (blob) {
setEncodedImageUrl(URL.createObjectURL(blob));
}
};
const handleDownload = () => {
if (encodedImageUrl) {
const a = document.createElement('a');
a.href = encodedImageUrl;
a.download = 'encoded-image.png';
a.click();
}
};
return (
<Card>
<CardHeader>
<CardTitle>LSB Image Encoder</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label>Image to Hide</Label>
<Input
type="file"
accept="image/*"
onChange={(e) => setImageToHide(e.target.files?.[0] || null)}
/>
</div>
<div className="space-y-2">
<Label>Carrier Image</Label>
<Input
type="file"
accept="image/*"
onChange={(e) => setCarrierImage(e.target.files?.[0] || null)}
/>
</div>
<div className="space-y-2">
<Label>Bits per Channel (1-8)</Label>
<Input
type="number"
min="1"
max="8"
value={nBits}
onChange={(e) => setNBits(parseInt(e.target.value) || 2)}
/>
</div>
{error && (
<Alert variant="destructive">
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
<Button onClick={handleEncode} disabled={isEncoding || !imageToHide || !carrierImage}>
{isEncoding ? 'Encoding...' : 'Encode Image'}
</Button>
{encodedImageUrl && (
<div className="space-y-2">
<img src={encodedImageUrl} alt="Encoded" className="max-w-full" />
<Button onClick={handleDownload}>Download Encoded Image</Button>
</div>
)}
</CardContent>
</Card>
);
}
// components/LSBTextEncoder.tsx
import { useState } from 'react';
import { useTextEncode } from '@/hooks/useTextEncode';
import { calculateTextCapacity } from '@/lib/lsbSteganography';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Alert, AlertDescription } from '@/components/ui/alert';
export function LSBTextEncoder() {
const [carrierImage, setCarrierImage] = useState<File | null>(null);
const [text, setText] = useState('');
const [nBits, setNBits] = useState(2);
const [encodedImageUrl, setEncodedImageUrl] = useState<string | null>(null);
const [capacity, setCapacity] = useState<number | null>(null);
const { encode, isEncoding, error } = useTextEncode();
const handleImageLoad = async (file: File) => {
const img = new Image();
const url = URL.createObjectURL(file);
img.onload = () => {
const cap = calculateTextCapacity(img.width, img.height, nBits);
setCapacity(cap);
URL.revokeObjectURL(url);
};
img.src = url;
};
const handleEncode = async () => {
if (!carrierImage) return;
const blob = await encode(carrierImage, text, nBits);
if (blob) {
setEncodedImageUrl(URL.createObjectURL(blob));
}
};
return (
<Card>
<CardHeader>
<CardTitle>LSB Text Encoder</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label>Carrier Image</Label>
<Input
type="file"
accept="image/*"
onChange={(e) => {
const file = e.target.files?.[0];
setCarrierImage(file);
if (file) handleImageLoad(file);
}}
/>
{capacity !== null && (
<p className="text-sm text-muted-foreground">
Capacity: {capacity} characters
</p>
)}
</div>
<div className="space-y-2">
<Label>Text to Hide</Label>
<Textarea
value={text}
onChange={(e) => setText(e.target.value)}
rows={5}
/>
<p className="text-sm text-muted-foreground">
{text.length} / {capacity || '?'} characters
</p>
</div>
<div className="space-y-2">
<Label>Bits per Channel (1-8)</Label>
<Input
type="number"
min="1"
max="8"
value={nBits}
onChange={(e) => setNBits(parseInt(e.target.value) || 2)}
/>
</div>
{error && (
<Alert variant="destructive">
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
<Button onClick={handleEncode} disabled={isEncoding || !carrierImage || !text}>
{isEncoding ? 'Encoding...' : 'Encode Text'}
</Button>
{encodedImageUrl && (
<div className="space-y-2">
<img src={encodedImageUrl} alt="Encoded" className="max-w-full" />
<Button onClick={() => {
const a = document.createElement('a');
a.href = encodedImageUrl;
a.download = 'encoded-text.png';
a.click();
}}>Download Encoded Image</Button>
</div>
)}
</CardContent>
</Card>
);
}
import { loadImageToCanvas, lsbEncode, lsbDecode, canvasToBlob } from '@/lib/lsbSteganography';
// Encode
const hideCanvas = await loadImageToCanvas(hideImageFile);
const carrierCanvas = await loadImageToCanvas(carrierImageFile);
const encodedCanvas = await lsbEncode(hideCanvas, carrierCanvas, 2);
const encodedBlob = await canvasToBlob(encodedCanvas);
// Decode
const encodedCanvas2 = await loadImageToCanvas(encodedBlob);
const decodedCanvas = lsbDecode(encodedCanvas2, 2);
const decodedBlob = await canvasToBlob(decodedCanvas);
import { loadImageToCanvas, encodeTextInImage, decodeTextFromImage } from '@/lib/lsbSteganography';
// Encode text
const carrierCanvas = await loadImageToCanvas(carrierImageFile);
const encodedCanvas = encodeTextInImage(carrierCanvas, 'Secret message!', 2);
// Decode text
const decodedText = decodeTextFromImage(encodedCanvas, 2);
console.log(decodedText); // "Secret message!"
import { LSBImageEncoder } from '@/components/LSBImageEncoder';
import { LSBTextEncoder } from '@/components/LSBTextEncoder';
function SteganographyPage() {
return (
<div className="space-y-6">
<LSBImageEncoder />
<LSBTextEncoder />
</div>
);
}
// Image too small
if (carrierCanvas.width < hideCanvas.width || carrierCanvas.height < hideCanvas.height) {
throw new Error('Carrier image must be at least as large as image to hide');
}
// Text too long
const capacity = calculateTextCapacity(width, height, nBits);
if (text.length > capacity) {
throw new Error(`Text exceeds capacity of ${capacity} characters`);
}
// Invalid n_bits
if (nBits < 1 || nBits > 8) {
throw new Error('n_bits must be between 1 and 8');
}
export function validateNBits(nBits: number): void {
if (!Number.isInteger(nBits) || nBits < 1 || nBits > 8) {
throw new Error('n_bits must be an integer between 1 and 8');
}
}
export function validateImageSize(
width: number,
height: number,
minWidth: number = 1,
minHeight: number = 1
): void {
if (width < minWidth || height < minHeight) {
throw new Error(`Image must be at least ${minWidth}×${minHeight} pixels`);
}
}
import { describe, it, expect } from 'vitest';
import {
removeNLeastSignificantBits,
getNLeastSignificantBits,
getNMostSignificantBits,
textToBinary,
binaryToText
} from '@/lib/lsbSteganography';
describe('LSB Steganography', () => {
it('removes least significant bits correctly', () => {
expect(removeNLeastSignificantBits(107, 2)).toBe(104);
});
it('extracts least significant bits correctly', () => {
expect(getNLeastSignificantBits(107, 2)).toBe(3);
});
it('converts text to binary and back', () => {
const text = 'Hello, World!';
const binary = textToBinary(text);
const decoded = binaryToText(binary);
expect(decoded).toBe(text);
});
});
import { describe, it, expect } from 'vitest';
import {
loadImageToCanvas,
encodeTextInImage,
decodeTextFromImage
} from '@/lib/lsbSteganography';
describe('Text Encoding/Decoding', () => {
it('encodes and decodes text correctly', async () => {
// Create test image
const canvas = document.createElement('canvas');
canvas.width = 100;
canvas.height = 100;
const ctx = canvas.getContext('2d')!;
ctx.fillStyle = '#FF0000';
ctx.fillRect(0, 0, 100, 100);
// Encode
const text = 'Test message';
const encoded = encodeTextInImage(canvas, text, 2);
// Decode
const decoded = decodeTextFromImage(encoded, 2);
expect(decoded).toBe(text);
});
});
LSB steganography provides a powerful way to hide images or text within other images. Key points:
The implementation is production-ready and can be integrated into any web application that needs steganographic capabilities.