lsb-steganography
Version:
A coomand-line tool for encoding/decoding data via image.
91 lines (88 loc) • 3.1 kB
JavaScript
import cac from "cac";
import { readFile, writeFile } from "node:fs/promises";
import { Jimp } from "jimp";
//#region src/lib.ts
const MASK_0F = 15;
const MASK_F0 = 240;
const MASK_FF = 255;
async function encode(str, { input }) {
const textEncoder = new TextEncoder();
const buffer = textEncoder.encode(str);
let image;
let width = 0;
let height = 0;
async function getJimpInstance() {
if (typeof input === "string") return await Jimp.read(input);
if (input instanceof ArrayBuffer) return Jimp.fromBuffer(input);
}
if (input) {
const jimp = await getJimpInstance();
if (!jimp) throw new Error("Invalid input");
const size = jimp.width * jimp.height * 4;
const pixelApset = Math.sqrt(size / (buffer.length * 2));
width = Math.ceil(jimp.width / pixelApset);
height = Math.ceil(jimp.height / pixelApset);
image = jimp.resize({
w: width,
h: height
});
} else {
const Sqrt3Div2 = Math.sqrt(3 / 2);
width = Math.ceil(Math.sqrt(buffer.length) / Sqrt3Div2);
height = width;
image = new Jimp({
color: 4042322160,
width,
height
});
}
for (let i = 0; i < buffer.length; i++) {
image.bitmap.data[i * 2] = ((image.bitmap.data[i * 2] ?? MASK_FF) & MASK_F0) + (buffer[i] >> 4 & MASK_0F);
image.bitmap.data[i * 2 + 1] = ((image.bitmap.data[i * 2 + 1] ?? MASK_FF) & MASK_F0) + (buffer[i] & MASK_0F);
}
for (let i = 2 * buffer.length; i < width * height * 4; i++) image.bitmap.data[i] = (image.bitmap.data[i] ?? MASK_FF) & MASK_F0;
return {
data: image.bitmap.data,
async writeFile(path) {
return image.write(`${path}.png`);
}
};
}
async function decode(input) {
const textDecoder = new TextDecoder("utf-8");
const jimpInput = typeof input === "string" ? await Jimp.read(input) : await Jimp.fromBuffer(input);
const buffer = jimpInput.bitmap.data;
const array = new Uint8Array(buffer.length);
for (let i = 0; i < buffer.length; i += 2) array[i] = ((buffer[i] & MASK_0F) << 4) + (buffer[i + 1] & MASK_0F);
const data = textDecoder.decode(array);
return {
data,
writeFile(output) {
return writeFile(data, output);
}
};
}
//#endregion
//#region src/cli.ts
const cli = cac("lsb");
cli.command("encode [data]", "加密数据").option("-i, --input [input]", "源图片地址").option("-o, --output <output>", "加密图片保存地址").option("-f, --file [file]", "源数据文件地址").action(async (data, options) => {
try {
const { file, input, output } = options;
if (file) data = await readFile(file);
if (!data) throw new Error("加密数据不存在");
const { writeFile: writeFile$1 } = await encode(data, { input });
await writeFile$1(output);
} catch (error) {
console.log(error);
}
});
cli.command("decode <input>", "解密图片地址").option("-o, --output [output]", "源数据保存地址").option("--log", "仅打印,不保存源数据").action(async (input, options) => {
const { output, log } = options;
const { writeFile: writeFile$1, data } = await decode(input);
if (log) console.log(data);
else await writeFile$1(output);
});
cli.help();
cli.parse();
//#endregion