Audio Steganography¶
Hide secret messages or binary data within audio signals so that casual listeners hear only the original sound, while decoders can extract the hidden payload.
Three methods¶
Method |
Capacity |
Audibility |
Robustness |
Requirement |
|---|---|---|---|---|
LSB |
~1 bit/sample (~16 KB / 3 s @ 44.1 kHz) |
Inaudible (≈ −90 dB) |
Fragile (destroyed by lossy compression, resampling) |
Any sample rate |
Frequency-band |
~2.6 kbit/s (~121 bytes / 3 s @ 44.1 kHz) |
Above most listeners’ hearing |
Moderate (survives mild noise) |
sample_rate ≥ 40 kHz |
Spectrogram text |
~1 bit/sample (same as LSB) |
Audible as buzzy tones; visually readable in spectrogram |
Fragile (same as LSB) |
Any sample rate |
LSB flips the least-significant bit of a 16-bit PCM representation — distortion ≈ −90 dB. Best for lossless pipelines (WAV, FLAC).
Frequency-band encodes data as brief BFSK tone bursts at 18.5 kHz (bit 0) or 19.5 kHz (bit 1). Choose this when light interference is expected.
Spectrogram text is a hybrid method that hides data via LSB encoding and renders the message as readable text in a spectrogram view. The message is rasterised with a built-in bitmap font, and sine waves at corresponding frequencies produce visible characters when viewed with a spectrogram analyser.
See also
- Spectrogram Text Art
Detailed guide on the spectrogram text art synthesis function.
- miniDSP C library — Audio Steganography
Upstream C library documentation with algorithm details and C-level examples.
Message structure¶
All three methods prepend a 32-bit little-endian header: bits 0–30 hold the byte count, bit 31 indicates payload type (0 = text, 1 = binary). This lets the decoder recover messages without prior knowledge of length.
Hiding text¶
import pyminidsp as md
host = md.sine_wave(44100, amplitude=0.8, freq=440.0, sample_rate=44100.0)
stego, n = md.steg_encode(host, "secret message",
sample_rate=44100.0, method=md.STEG_LSB)
print(f"Encoded {n} bytes")
Listen — compare the host signal and the stego outputs:
Original host (440 Hz sine):
LSB-encoded (sounds identical):
Frequency-band encoded (faint high-frequency tones):
Spectrogram text encoding works the same way — just pass
method=md.STEG_SPECTEXT:
stego_st, n = md.steg_encode(host, "HELLO",
sample_rate=44100.0, method=md.STEG_SPECTEXT)
print(f"Encoded {n} bytes (visible in spectrogram)")
Recovering text¶
recovered = md.steg_decode(stego, sample_rate=44100.0, method=md.STEG_LSB)
print(recovered) # "secret message"
# Recover from spectrogram-text encoded signal
recovered_st = md.steg_decode(stego_st, sample_rate=44100.0, method=md.STEG_SPECTEXT)
print(recovered_st) # "HELLO"
Binary data¶
data = b"\x00\x01\x02\xff\xfe\xfd"
stego, n = md.steg_encode_bytes(host, data, sample_rate=44100.0)
recovered = md.steg_decode_bytes(stego, sample_rate=44100.0)
assert recovered == data
Automatic detection¶
method, payload_type = md.steg_detect(stego, sample_rate=44100.0)
if method is not None:
names = {md.STEG_LSB: "LSB", md.STEG_FREQ_BAND: "Freq-band",
md.STEG_SPECTEXT: "Spectrogram-text"}
print(f"Method: {names[method]}")
print(f"Type: {'text' if payload_type == md.STEG_TYPE_TEXT else 'binary'}")
Capacity check¶
cap = md.steg_capacity(44100, sample_rate=44100.0, method=md.STEG_LSB)
print(f"Can hide up to {cap} bytes")
md.shutdown()