another machine Steganography GitHub

Stega64 encodes and decodes messages in image pixels.

Stega64.encode({ source: HTMLImageElement | HTMLCanvasElement; messages: string[]; encoding: Stega64Encoding; encodeMetadata: boolean; minWidth?: number; minHeight?: number; borderWidth?: number; }): HTMLCanvasElement

const source = await loadImageFromImageUrl({
  url: "./example.jpg" 
});
Stega64.encode({
  source,
  encoding: "",
  encodeMetadata: ,
  messages: [""],
  minWidth: ,
  minHeight: ,
  borderWidth: ,
  aspectRatio: 
})

Stega64.decode({ source: HTMLImageElement | HTMLCanvasElement; encoding: Stega64Encoding; borderWidth?: number; }): String

const result = Stega64.decode({ source });

StegaCassette encodes and decodes audio in image pixels.

StegaCassette.encode({ source?: HTMLImageElement | HTMLCanvasElement; sources?: (HTMLImageElement | HTMLCanvasElement)[]; audioBuffers: Float32Array[]; sampleRate: number; bitDepth: StegaCassetteBitDepth; encoding: StegaCassetteEncoding; encodeMetadata?: boolean; aspectRatio?: number; borderWidth?: number; }): HTMLCanvasElement | HTMLCanvasElement[]

const audioBuffers = await loadAudioBuffersFromAudioUrl({
  url: "./example.mp3",
  audioContext,
  channels: ,
  sampleRate: ,
});
const result = StegaCassette.encode({
  source,
  audioBuffers,
  sampleRate: ,
  bitDepth: ,
  encoding: "",
  encodeMetadata: ,
  aspectRatio: ,
  borderWidth: 
});

StegaCassette.decode({ source?: HTMLImageElement | HTMLCanvasElement; sources?: (HTMLImageElement | HTMLCanvasElement)[]; bitDepth: StegaCassetteBitDepth; channels: StegaCassetteChannels; encoding: StegaCassetteEncoding; borderWidth?: number; }): Float32Array[]

const metadata = StegaMetadata.decode({ source }) || {};
...

const audioBuffers = StegaCassette.decode({
  source,
  bitDepth: metadata.bitDepth || ,
  channels: metadata.channels || ,
  encoding: metadata.encoding || "",
});
const audio = await playDecodedAudioBuffers({
  audioBuffers,
  audioContext,
  sampleRate: metadata.sampleRate || ,
});

Splitting audio across multiple images

StegaCassette can split audio data across multiple images using the sources parameter for encoding and decoding. To properly play back the audio, all images must be provided in the right order. They can still be heard when provided partially (will sound sped up depending on how many samples are missing) or in the wrong order (will sound distored).

const result = StegaCassette.encode({
  ,
  audioBuffers,
  sampleRate: ,
  bitDepth: ,
  encoding: "",
});

const audioBuffers = StegaCassette.decode({
  ,
  bitDepth: ,
  channels: ,
  encoding: "",
});

Obfuscating the data

StegaCassette encodes audio with a key parameter to apply permutation-based encryption, making the audio unplayable without the correct key. Keys can be strings or StegaMetadataString images. You can use Stega64 to create these.

const result = StegaCassette.encode({
  source,
  audioBuffers,
  sampleRate: ,
  bitDepth: ,
  encoding: "",
  key: 
});

const audioBuffers = StegaCassette.decode({
  source,
  bitDepth: ,
  channels: ,
  encoding: "",
  key: 
});

Encoding audio waveforms in image pixels.

This is a demonstration of how waveforms are stored inside of pixels using StegaCassette.

Source waveform (drag to move the playhead)
64 sample waveform | 1.3ms of audio
24-bit | 64 colors with red, green, and blue channels separated above | 1 sample per pixel
64 sample waveform as a 8×8px image
Source waveform encoded into image

StegaMetadata can be optionally encoded inside of stega images.

StegaMetadata.encode({ source: HTMLCanvasElement; metadata: StegaMetadata }): HTMLCanvasElement

StegaMetadata.decode({ source: HTMLCanvasElement; }): StegaMetadata | null

enum StegaContentType StegaContentType {
  AUDIO = 0,
  STRING = 1,
  ROBUST = 2,
  MUSIC = 3,
  BINARY = 4,
  KEY = 5,
}

type StegaMetadata =
  | StegaMetadataAudio
  | StegaMetadataString
  | StegaMetadataRobust
  | StegaMetadataMusic
  | StegaMetadataBinary
  | StegaMetadataKey;

interface StegaMetadataAudio {
  type: StegaContentType.AUDIO;
  sampleRate: number;
  bitDepth: StegaCassetteBitDepth;
  channels: StegaCassetteChannels;
  encoding: StegaCassetteEncoding;
  borderWidth: number;
}

export interface StegaMetadataMusic extends Omit<StegaMetadataAudio, "type"> {
  type: StegaContentType.MUSIC;
  bpm: number;
  semitones: number;
}

interface StegaMetadataString {
  type: StegaContentType.STRING;
  messageCount: number;
  encoding: Stega64Encoding;
  borderWidth: number;
}

interface StegaMetadataRobust {
  type: StegaContentType.ROBUST;
  redundancyLevel: number;
  messageCount: number;
  blockSize: 2 | 4;
  encoding: "hex" | "base16";
}

interface StegaMetadataBinary {
  type: StegaContentType.BINARY;
  dataLength: number;
  borderWidth: number;
  sessionId: number; // 0 = keyless, non-zero = requires matching key
}

interface StegaMetadataKey {
  type: StegaContentType.KEY;
  borderWidth: number;
  sessionId: number;
}

View StegaMetadata for an image.

Choose an image to see its metadata here

StegaBinary encodes arbitrary binary data into images.

StegaBinary.encode({ source: HTMLImageElement | HTMLCanvasElement; data: Uint8Array; mimeType: string; borderWidth?: number; aspectRatio?: number; key?: boolean | HTMLImageElement | HTMLCanvasElement; sessionId?: number; }): EncodeResult

const fileBytes; // 0 bytes from "select a file"

const { key, encoded, sessionId } = StegaBinary.encode({
  source,
  data: fileBytes,
  mimeType: "application/octet-stream",
  borderWidth: 1,
  aspectRatio: undefined,
  key:  // source image is also the key
});
Source Image
Key Image
Encoded Image

StegaBinary.decode({ encoded: HTMLImageElement | HTMLCanvasElement; key?: HTMLImageElement | HTMLCanvasElement; }): { mimeType: string; data: Uint8Array; }

// Decode with key (for keyed or custom key)
const { mimeType, data } = StegaBinary.decode({ encoded, key });

// Decode keyless (sessionId = 0)
const { mimeType, data } = StegaBinary.decode({ encoded });

// Download decoded binary data as a file
downloadBytes({ data, mimeType, fileName: "decoded.bin" });
Decoded output will appear here

Animate steganographic image

new StegaAnimator({ resolution: number; source: HTMLImageElement | HTMLCanvasElement; fadeAmount?: number; rotationMode?: "2d" | "3d"; shape?: "circle" | "square" | "implicit"; })

const animator = new StegaAnimator({
  source,
  resolution: ,
  fadeAmount: ,
  rotationMode: "",
  shape: ""
});
document.body.appendChild(animator.canvas);
await animator.animate({
  from: { rotation: Math.PI, scale: 0.0, x: 0.5, y: 0.5, },
  to: { rotation: Math.PI * 4, scale: 0.5, x: 0.5, y: 0.5, },
  rate: 0.005,
});
const killLoop = animator.animationLoop([
  {
    from: { rotation: 0, scale: 0.5, x: 0.5, y: 0.5, },
    to: { rotation: Math.PI * 1, scale: 0.6, x: 0.5, y: 0.5, },
    rate: ,
  },
  {
    from: { rotation: Math.PI * 1, scale: 0.6, x: 0.5, y: 0.5, },
    to: { rotation: Math.PI * 2, scale: 0.5, x: 0.5, y: 0.5, },
    rate: ,
  },
]);
killLoop();

Load an audio buffer from a url string

async loadAudioBuffersFromAudioUrl({ url: string; audioContext: AudioContext; channels: StegaCassetteChannels; sampleRate?: number; }): Promise<Float32Array[]>

const audioContext = new AudioContext();
const audioBuffer = await loadAudioBuffersFromAudioUrl({
  url: "./example.mp3",
  audioContext,
  sampleRate: audioContext.sampleRate,
});

Load an image from a url string

async loadImageFromImageUrl({ url: string }): Promise<HTMLImageElement>

const audioBuffer = await loadImageFromImageUrl({
  url: "./example.jpg"
});

Play decoded audio buffers

async playDecodedAudioBuffers({ audioBuffers: Float32Array[]; audioContext: AudioContext; sampleRate?: number; }): Promise<AudioBufferSourceNode>

const source = await playDecodedAudioBuffers({
  audioBuffers,
  audioContext,
  sampleRate: audioContext.sampleRate,
});
source.stop();

Turn an HTML element into a file drop area

createDropReader({ element: HTMLElement; onSuccess: (element: HTMLImageElement | HTMLAudioElement) => void; onFailure?: (message: string) => void; onDragEnter?: () => void; onDragLeave?: () => void; onDrop?: () => void; types?: (AudioType | ImageType)[]; }): void

const element = document.body;
createDropReader({
  element,
  onSuccess: (image) => element.appendChild(image),
  onFailure: (message) => console.error(message),
  onDragEnter: () => element.classList.add("droppable"),
  onDragLeave: () => element.classList.remove("droppable"),
  onDrop: () => element.classList.remove("droppable"),
  types: ["image/*"]
});

Turn an HTML input into a file input

createFileReader({ element: HTMLInputElement; onSuccess: (element: HTMLImageElement | HTMLAudioElement) => void; onBinarySuccess?: (result: { data: Uint8Array; mimeType: string; fileName: string }) => void; onFailure?: (message: string) => void; types?: (AudioType | ImageType | VideoType | "*/*")[]; }): void

const element = document.createElement("input");
createFileReader({
  element,
  onSuccess: (image) => document.body.appendChild(image),
  onFailure: (message) => console.error(message),
  types: ["image/*"]
});

// Or for binary data (any file type)
createFileReader({
  element,
  onBinarySuccess: ({ data, mimeType, fileName }) => {
    console.log(`Loaded ${fileName} (${mimeType}): ${data.length} bytes`);
  },
  types: ["*/*"]
});

Read a file as binary bytes

readFileAsBytes({ file: File }): Promise<{ data: Uint8Array; mimeType: string; fileName: string }>

const input = document.createElement("input");
input.type = "file";
input.onchange = async () => {
  const file = input.files[0];
  const { data, mimeType, fileName } = await readFileAsBytes({ file });
  console.log(`Read ${fileName} (${mimeType}): ${data.length} bytes`);
};

Convert binary data to a Blob URL

bytesToBlobUrl({ data: Uint8Array; mimeType: string }): string

const url = bytesToBlobUrl({ data: myBytes, mimeType: "image/png" });
const img = document.createElement("img");
img.src = url;
document.body.appendChild(img);

// Remember to revoke when done
URL.revokeObjectURL(url);

Download binary data as a file

downloadBytes({ data: Uint8Array; mimeType: string; fileName: string }): void

downloadBytes({
  data: myBytes,
  mimeType: "application/pdf",
  fileName: "document.pdf"
});