shelpngcrypt
v0.1.0
Published
Encrypt UTF-8 text into PNG metadata with optional LSB redundancy.
Maintainers
Readme
ShelPngCrypt
Encrypt UTF-8 text into PNG files with a shared passphrase. The library stores the encrypted payload in PNG metadata and can optionally duplicate the same payload into pixel LSBs for redundancy.
Features
- Encrypt plain text into an existing PNG or a generated blank PNG
- Shared-passphrase encryption with
PBKDF2-SHA256 + AES-256-GCM - Dual-channel embedding:
- PNG
tEXtmetadata - RGBA pixel LSB redundancy
- PNG
- Recover text from PNG using the same passphrase
- Works with
Uint8Arrayfor both Node.js and browser usage - Includes a CLI
Security Model
This package is a text-encryption carrier, not an advanced anti-forensics steganography system.
- Confidentiality depends on the passphrase strength
- Metadata may be stripped by platforms that rewrite PNG files
- LSB data may be destroyed by recompression, redraw, screenshotting, or format conversion
- The package does not try to hide the fact that data may exist in the image
Install
npm install shelpngcryptFor local development:
npm install
npm run build
npm testLibrary Usage
Encrypt text into a PNG
import { encryptTextToPng } from "shelpngcrypt";
import { readFile, writeFile } from "node:fs/promises";
const cover = new Uint8Array(await readFile("cover.png"));
const result = await encryptTextToPng("hello from ShelPngCrypt", {
passphrase: "shared-secret",
inputPng: cover
});
await writeFile("secret.png", result.png);
console.log(result.channel);
console.log(result.capacity);Auto-generate a carrier PNG
import { encryptTextToPng } from "shelpngcrypt";
import { writeFile } from "node:fs/promises";
const result = await encryptTextToPng("text without an input PNG", {
passphrase: "shared-secret"
});
await writeFile("generated-secret.png", result.png);Decrypt text from a PNG
import { decryptTextFromPng } from "shelpngcrypt";
import { readFile } from "node:fs/promises";
const png = new Uint8Array(await readFile("secret.png"));
const plaintext = await decryptTextFromPng(png, "shared-secret");
console.log(plaintext);Inspect whether a PNG contains data
import { inspectPng } from "shelpngcrypt";
import { readFile } from "node:fs/promises";
const png = new Uint8Array(await readFile("secret.png"));
const result = await inspectPng(png);
console.log(result);Browser Usage
The public API uses Uint8Array, so browser usage can stay close to the Node version:
import { encryptTextToPng, decryptTextFromPng } from "shelpngcrypt";
const file = input.files?.[0];
const inputBytes = file ? new Uint8Array(await file.arrayBuffer()) : undefined;
const encrypted = await encryptTextToPng("browser secret", {
passphrase: "shared-secret",
inputPng: inputBytes
});
const blob = new Blob([encrypted.png], { type: "image/png" });
const url = URL.createObjectURL(blob);
image.src = url;
const plaintext = await decryptTextFromPng(encrypted.png, "shared-secret");
console.log(plaintext);Example Demo
A browser demo is included in example/index.html.
Run it locally:
npm install
npm run example:devBuild the static demo:
npm run example:buildWhat it does:
- Upload an optional carrier PNG
- Enter text and a shared passphrase
- Generate and download an encrypted PNG
- Inspect a PNG for embedded payloads
- Decrypt text from a PNG in the browser
API
encryptTextToPng(plaintext, options)
type EncryptOptions = {
passphrase: string;
inputPng?: Uint8Array;
width?: number;
height?: number;
redundant?: boolean;
};
type EncryptResult = {
png: Uint8Array;
channel: "metadata" | "metadata+lsb";
capacity?: { bytesTotal: number; bytesUsed: number };
};Behavior:
- If
inputPngis provided, data is embedded into that PNG - If
inputPngis omitted:widthandheightare used if both exist- otherwise the library generates the smallest square PNG it can
redundantdefaults totrue- When
redundantistrue, insufficient LSB capacity throws an error
decryptTextFromPng(png, passphrase)
- Reads metadata first
- Falls back to LSB payload if metadata is missing or unreadable
- Throws
口令错误或图片已损坏when decryption or recovery fails
inspectPng(png)
Returns:
{
hasMetadataPayload: boolean;
hasStegoPayload: boolean;
recoverable: boolean;
version?: number;
}CLI
After building:
npm run build
node dist/cli.js --helpEncrypt using an existing PNG
node dist/cli.js encrypt --in cover.png --out secret.png --text "hello" --passphrase "shared-secret"Encrypt without an existing PNG
node dist/cli.js encrypt --out secret.png --text "hello" --passphrase "shared-secret" --width 512 --height 512Metadata only
node dist/cli.js encrypt --out secret.png --text "hello" --passphrase "shared-secret" --metadata-onlyDecrypt
node dist/cli.js decrypt --in secret.png --passphrase "shared-secret"Inspect
node dist/cli.js inspect --in secret.pngProject Layout
src/
cli.ts
crypto.ts
index.ts
payload.ts
png-metadata.ts
png-stego.ts
example/
index.html
main.js
styles.css
test/
index.test.tsDevelopment
npm install
npm run check
npm run build
npm testLimitations
- PNG only
- UTF-8 text only
- No streaming mode for large payloads
- No resistance guarantee against image transformation pipelines
- Current metadata storage uses
tEXt, not encrypted PNG-standard ancillary compression chunks
Next Recommended Work
- Add browser demo page
- Add README badges and package publishing metadata
- Add payload size guidance and better CLI output
- Add E2E tests against real-world PNG files
- Add optional metadata-only vs redundancy mode examples in docs
See the full implementation guide in docs/IMPLEMENTATION_GUIDE.md.
