UNPKG

lsb-steganography

Version:

A coomand-line tool for encoding/decoding data via image.

91 lines (88 loc) 3.1 kB
#! /usr/bin/env node 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