Documentation
¶
Overview ¶
Package mist is an asymmetric-key audio steganography library.
A sender embeds a payload using only the recipient's X25519 public key. Only the matching private key can recover it. The payload lives in the audio itself — not container metadata — so a digital recording of a live stream is enough to extract the message.
Output is Ogg Vorbis, where the bits go into quantized residues, or any lossless format the installed FFmpeg can write (FLAC, WAV, ALAC, WavPack and the rest), where they go into PCM sample LSBs. Formats lists them; WithFormat and WithCodec choose one. Carriers are whatever FFmpeg can decode, whatever the output is.
Hybrid encryption follows the age / NaCl box shape: an ephemeral X25519 key agreement derives a ChaCha20-Poly1305 key. Each stego frame is sealed independently so repeating a payload across a live stream does not produce a correlatable ciphertext. Unused capacity is filled with CSPRNG bytes at the same embedding density as a real message, so presence and absence have the same statistical footprint.
Codec I/O goes through libav (libavformat, libavcodec, libavutil) via cgo. Which codecs qualify is libav's answer, not a list kept here: a format is usable when FFmpeg reports an encoder for it and marks it lossless.
Index ¶
- Constants
- Variables
- func GenerateKeyPair() (pub, priv []byte, err error)
- func GenerateSigningKeyPair() (pub, priv []byte, err error)
- type Analysis
- type AnalyzeOptions
- type Basis
- type Capacity
- type Catcher
- type CatcherOption
- type Emitter
- func (e *Emitter) Embed(ctx context.Context, source string, payload Payload) (io.ReadCloser, error)
- func (e *Emitter) EmbedFile(ctx context.Context, carrier *os.File, payload Payload) (io.ReadCloser, error)
- func (e *Emitter) EmbedReader(ctx context.Context, carrier io.Reader, payload Payload) (io.ReadCloser, error)
- type EmitterOption
- type Format
- type Payload
- type PayloadType
- type Progress
- type ProgressFunc
- type ProgressStage
- type Result
- type Scope
- type Source
- type Stage
- type StageStatus
- type Suspicion
Constants ¶
const ( PublicKeySize = 32 PrivateKeySize = 32 )
X25519 key sizes in bytes.
const ( SigningPublicKeySize = 32 SigningPrivateKeySize = 64 SignatureSize = 64 )
Ed25519 sizes for optional sender authentication.
const ( NonceSize = 12 TagSize = 16 )
ChaCha20-Poly1305 sizes in bytes.
const DefaultFormat = "ogg"
DefaultFormat is the target Embed writes when none is named.
const EnvelopeOverhead = PublicKeySize + LengthSize + NonceSize + TagSize
EnvelopeOverhead is the number of bytes added by hybrid encryption before embedding: ephemeral public key + masked length + nonce + Poly1305 tag.
const FrameDuration = 8 * time.Second
FrameDuration is the fixed stego-frame length. Embed loops the payload across consecutive frames of this duration; Listen uses the same value to recover frame phase. It is a protocol constant, not a per-call option.
const LengthSize = 4
LengthSize is the masked ciphertext-length field that lets a receiver read exactly the right number of bytes instead of searching for the end.
const MaxLR = steganalysis.MaxLR
MaxLR is the most one stage's likelihood ratio can say either way: a Stage's LR lies in [1/MaxLR, MaxLR].
const Version byte = 1
Version is the payload framing version written inside the ciphertext.
Variables ¶
var ( ErrInvalidKey = errors.New("mist: invalid key") ErrInvalidPayload = errors.New("mist: invalid payload") ErrInvalidSource = errors.New("mist: invalid source") ErrUnsupportedCodec = errors.New("mist: unsupported codec") ErrCarrier = errors.New("mist: unreadable carrier") ErrClosed = errors.New("mist: already closed") // ErrNoCapacity means no frame in the carrier had room for the // envelope, so nothing was embedded. Quiet or tonal audio yields too // few usable residues; a richer or longer carrier fixes it. ErrNoCapacity = errors.New("mist: carrier has no frame with room for the payload") )
Sentinel errors returned by the public API. Per-frame decrypt failures inside Listen are not surfaced — they are indistinguishable from frames that carry no payload and must stay silent.
Functions ¶
func GenerateKeyPair ¶
GenerateKeyPair creates a new X25519 keypair for a recipient. pub is shared freely with senders; priv is kept secret by the recipient.
func GenerateSigningKeyPair ¶
GenerateSigningKeyPair creates an Ed25519 keypair for optional sender authentication. Pass the private key to NewEmitter via WithSenderAuth.
Types ¶
type Analysis ¶ added in v1.1.0
type Analysis struct {
Source Source
Stages []Stage
Probability float64
Basis Basis
Calibration string
Suspicion Suspicion
}
Analysis is how a warden reads one file. Probability is the chance of a Mist message, from the calibrated stages. Suspicion is the strongest sign of data hidden by any other tool, from the uncalibrated ones.
func Analyze ¶ added in v1.1.0
Analyze runs every stage of the harness one file supports and reports the probability that it carries a Mist message. Blind, that probability comes from the detectors read against a calibration run, and stays near 0.5 wherever the harness found them at chance. With opts.Reference, the file is also diffed against Mist's own clean encode of that original, which is decisive when the two are the same recording.
type AnalyzeOptions ¶ added in v1.1.0
AnalyzeOptions configures Analyze. Reference is the original carrier, when the caller has it. FFmpeg and FFprobe name the binaries the fingerprint stage runs, found on PATH when empty. Progress, when set, sees each stage as it finishes.
type Capacity ¶ added in v0.2.0
type Capacity struct {
Source Source
Format Format
// Frames is the number of stego frames the carrier splits into.
Frames int
// Usable is how many of those frames have room to contribute to
// TotalCapacity — the protocol envelope plus at least one byte, paid
// once by whichever usable frame Embed writes first.
Usable int
// FrameCapacity is the largest plaintext, in bytes, a single unspanned
// frame could carry — what Embed uses when the payload fits in one
// frame outright.
FrameCapacity int
// TotalCapacity is the largest plaintext, in bytes, Embed could carry
// overall by spanning the payload across every usable frame in turn
// when it does not fit in just one.
TotalCapacity int
}
Capacity reports how much of a carrier Embed could use for a given target format, without producing any output.
func EstimateCapacity ¶ added in v0.2.0
EstimateCapacity decodes source the way Embed would for the target picked by formatName/codecName (LookupFormat's rules) and reports how large a text payload Embed could carry into it, without writing anything out. It costs what Embed costs: the carrier still has to be decoded and, for a Vorbis target, re-encoded, because capacity depends on what the target encoder actually produces, not on the source.
type Catcher ¶
type Catcher struct {
// contains filtered or unexported fields
}
Catcher extracts payloads with a recipient private key. Construct it once with NewCatcher and share it; methods do not mutate it.
func NewCatcher ¶
func NewCatcher(privKey []byte, opts ...CatcherOption) (*Catcher, error)
NewCatcher returns an immutable Catcher for privKey. The key and any option-held secrets are copied; later writes to the caller's slices are not observed.
func (*Catcher) Extract ¶
Extract scans source synchronously and returns every frame that decrypts and authenticates. It stops at EOF or when ctx is cancelled. source stays owned by the caller.
func (*Catcher) Listen ¶
Listen opens source (a local file path, file:// URL, or http(s):// URL), continuously scans for stego frames, and sends each successfully decrypted payload to the returned channel.
The returned channel is closed when either:
- the source reaches EOF (finite file or stream ended by the server), or
- ctx is cancelled.
The caller distinguishes the two after the channel closes:
- ctx.Err() == nil → source reached EOF naturally
- ctx.Err() != nil → context was cancelled by the caller
The error return covers only immediate setup failures (invalid source, unreadable file, malformed key). Per-frame decrypt failures are silently skipped — they are indistinguishable from frames carrying no payload and must not surface as errors.
type CatcherOption ¶
type CatcherOption func(*Catcher)
CatcherOption configures NewCatcher.
func WithBackoff ¶
func WithBackoff(backoff time.Duration) CatcherOption
WithBackoff configures the pause between read retries.
func WithCatchProgress ¶ added in v1.2.0
func WithCatchProgress(f ProgressFunc) CatcherOption
WithCatchProgress reports each frame scanned and each chunk of a spanned payload assembled to f. Frames that fail to decrypt are reported exactly like frames that carry nothing.
func WithLogger ¶
func WithLogger(log *slog.Logger) CatcherOption
WithLogger directs diagnostics — such as joining a stream mid-frame — to log. Failed decrypts are never logged: they are indistinguishable from frames carrying no payload. Discarded by default.
func WithMaxRetries ¶
func WithMaxRetries(retries int) CatcherOption
WithMaxRetries configures how many consecutive read failures a live source may produce before Listen gives up and closes the channel.
func WithPartial ¶ added in v1.2.0
func WithPartial() CatcherOption
WithPartial makes a spanned payload that stops arriving part way through a Result with Partial set, holding the bytes that did arrive, instead of nothing. Off by default.
type Emitter ¶
type Emitter struct {
// contains filtered or unexported fields
}
Emitter encrypts a payload for a recipient public key and embeds it into a carrier. Construct it once with NewEmitter and share it. Methods do not mutate the Emitter.
func NewEmitter ¶
func NewEmitter(recipientPubKey []byte, opts ...EmitterOption) (*Emitter, error)
NewEmitter returns an Emitter for recipientPubKey. The key and any option-held secrets are copied; later writes to the caller's slices are not observed. An output format this FFmpeg cannot write, or a lossy one other than Vorbis, is ErrUnsupportedCodec here rather than after a carrier has been decoded.
func (*Emitter) Embed ¶
Embed opens source (a local file path, file:// URL, or http(s):// URL) as the carrier, encrypts payload for the recipient, and returns a new stego stream in the Emitter's output format.
The payload is looped across fixed-duration stego frames, each re-encrypted with a fresh ephemeral X25519 key, for the full duration of the carrier. Suitable for both finite files and wrapping a live encoder output — the returned reader produces output as fast as the carrier does.
func (*Emitter) EmbedFile ¶
func (e *Emitter) EmbedFile(ctx context.Context, carrier *os.File, payload Payload) (io.ReadCloser, error)
EmbedFile is Embed over an *os.File carrier. The file stays owned by the caller.
func (*Emitter) EmbedReader ¶
func (e *Emitter) EmbedReader(ctx context.Context, carrier io.Reader, payload Payload) (io.ReadCloser, error)
EmbedReader is Embed over an already-open carrier (pipe, HTTP body, in-memory buffer). The caller retains ownership of carrier and must close it if it is an io.Closer.
type EmitterOption ¶
type EmitterOption func(*Emitter)
EmitterOption configures NewEmitter.
func WithCodec ¶ added in v0.2.0
func WithCodec(name string) EmitterOption
WithCodec overrides the encoder the container would default to, the way ffmpeg's -c:a does — an m4a holds AAC unless told to hold ALAC.
func WithEmitProgress ¶ added in v1.2.0
func WithEmitProgress(f ProgressFunc) EmitterOption
WithEmitProgress reports each frame Embed writes to f.
func WithFormat ¶ added in v0.2.0
func WithFormat(name string) EmitterOption
WithFormat selects the output target the way ffmpeg's -f does: a container short name ("flac", "wav", "ogg") or the output path whose extension names one ("song.flac"). Ogg Vorbis by default.
func WithSenderAuth ¶
func WithSenderAuth(senderPrivKey []byte) EmitterOption
WithSenderAuth attaches an Ed25519 signature over the plaintext, itself encrypted before embedding. Off by default — an unauthenticated ciphertext is shorter and one step closer to looking like nothing.
type Format ¶ added in v0.2.0
Format is an output target Embed can write: a container, the encoder inside it, and the extension a file of that kind normally carries.
func Formats ¶ added in v0.2.0
func Formats() []Format
Formats lists every output target this FFmpeg build can write, sorted by container name. A container and its default encoder are reachable by either name, so the same target is reported once.
func LookupFormat ¶ added in v0.2.0
LookupFormat resolves an output target the way ffmpeg's command line does: name is an output path whose extension names a container ("song.flac"), a container short name ("flac"), or an encoder name ("alac"); codec, when not empty, overrides the encoder the container would default to. An empty name means DefaultFormat.
Ogg Vorbis and every lossless codec the installed FFmpeg can encode are supported. Any other lossy codec is ErrUnsupportedCodec: Mist can only hide bits it can still find afterwards, and a lossy re-encode it does not own the bitstream of destroys them.
type Payload ¶
type Payload struct {
Type PayloadType
Data []byte
}
Payload is the unit that Emitter encrypts and Catcher emits. A PayloadFile carries its name in Data; build and read it with File and FileName. For every other type Data is the content itself.
func Audio ¶ added in v1.2.0
Audio returns an audio payload. Data is the encoded audio as it is on disk.
func File ¶ added in v1.2.0
File returns a file payload that remembers name. The name travels inside Data as a big-endian u16 length and the name bytes, ahead of the content.
type PayloadType ¶
type PayloadType byte
PayloadType identifies the kind of data inside a Payload. The type byte is encrypted with the payload, so a warden who isolates the embedded bytes still cannot read it without the private key.
const ( PayloadText PayloadType = 0x01 PayloadImage PayloadType = 0x02 PayloadAudio PayloadType = 0x03 PayloadFile PayloadType = 0x04 )
Payload types. The values are part of the wire format.
type Progress ¶ added in v1.2.0
type Progress struct {
Stage ProgressStage
Done int
Total int
}
Progress counts frames for ProgressEmbed and ProgressScan and message bytes for ProgressAssemble. Total is 0 when it is not known, as on a live stream.
type ProgressFunc ¶ added in v1.2.0
type ProgressFunc func(Progress)
ProgressFunc receives Progress reports on the goroutine doing the work, so it must return quickly.
type ProgressStage ¶ added in v1.2.0
type ProgressStage int
ProgressStage names the part of the work a Progress report describes.
const ( ProgressEmbed ProgressStage = iota + 1 ProgressScan ProgressAssemble )
Stages reported by an Emitter (ProgressEmbed) and a Catcher (ProgressScan, ProgressAssemble).
type Result ¶
type Result struct {
Payload Payload
FrameIdx int64 // ordinal of the frame within this Listen session (0-based)
Partial bool // only the start of a spanned payload arrived; see WithPartial
Total int // declared Data length of a Partial payload
}
Result is emitted by Catcher whenever a stego frame decrypts and authenticates successfully.
type Source ¶ added in v0.2.0
type Source struct {
Codec string // libav's display name for the source codec
Container string // demuxer's name for the source container
SampleRate int
Channels int
Bitrate int64 // bits per second as the source reports it, 0 if it does not
Duration time.Duration
Lossless bool // whether libav's codec descriptor marks the source lossless
}
Source describes the carrier Embed would decode, straight from what the installed libav reports about it — nothing here is Mist's own analysis.
type Stage ¶ added in v1.1.0
type Stage struct {
Name string
Scope Scope
Status StageStatus
Score float64
LR float64
AUC float64
Note string
Suspicion Suspicion
Findings []string
Informational bool
}
Stage is one step of an Analysis. For a ScopeMist stage, LR is the likelihood ratio against the calibration, above 1 favouring a message, and 0 when the stage is not evidence; AUC is how well the stage separated clean from stego files in the calibration run. A ScopeAny stage instead has a Suspicion and the Findings behind it. An Informational stage describes the file for context and is never evidence either way.
type StageStatus ¶ added in v1.1.0
type StageStatus int
StageStatus says whether a stage of an Analysis ran.
const ( // StageRan means the stage scored the file. StageRan StageStatus = iota // StageSkipped means the stage could run here but did not, because a // tool or a reference it needs is missing. StageSkipped // StageNotRun means the stage needs what a single-file analysis does // not have: a key, or the original carrier. StageNotRun )
type Suspicion ¶ added in v1.1.0
type Suspicion int
Suspicion is how strongly an uncalibrated check points at hidden data.
const ( // SuspicionNone means nothing out of the ordinary. SuspicionNone Suspicion = iota // SuspicionLow is unusual but common in honest files. SuspicionLow // SuspicionMedium is rare in honest files. SuspicionMedium // SuspicionHigh is data no player reads, stored where encoders write none. SuspicionHigh )
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
mist
command
Command mist hides a text message or a file inside an audio file and recovers it again with the matching private key.
|
Command mist hides a text message or a file inside an audio file and recovers it again with the matching private key. |
|
Package example contains Godoc examples for github.com/iSerganov/mist.
|
Package example contains Godoc examples for github.com/iSerganov/mist. |
|
internal
|
|
|
av
Package av is the cgo boundary to libavformat, libavcodec, libavutil and libswresample.
|
Package av is the cgo boundary to libavformat, libavcodec, libavutil and libswresample. |
|
codec
Package codec is the boundary between PCM and a compressed bitstream.
|
Package codec is the boundary between PCM and a compressed bitstream. |
|
codec/vorbis
Package vorbis is the Phase 1 codec: PCM ↔ Ogg Vorbis via package av, plus a bitstream residue parser used by stego.
|
Package vorbis is the Phase 1 codec: PCM ↔ Ogg Vorbis via package av, plus a bitstream residue parser used by stego. |
|
crypto
Package crypto is the hybrid box: X25519, HKDF, ChaCha20-Poly1305, Ed25519.
|
Package crypto is the hybrid box: X25519, HKDF, ChaCha20-Poly1305, Ed25519. |
|
dsp
Package dsp holds signal-processing primitives shared by the analysis packages.
|
Package dsp holds signal-processing primitives shared by the analysis packages. |
|
forensics
Package forensics looks for data any steganography tool may have hidden in an audio file outside its samples: bytes past the end of the audio, chunks and tags no player reads, padding that is not empty, files appended to cover art, and the MP3 frame fields known to carry covert bits.
|
Package forensics looks for data any steganography tool may have hidden in an audio file outside its samples: bytes past the end of the audio, chunks and tags no player reads, padding that is not empty, files appended to cover art, and the MP3 frame fields known to carry covert bits. |
|
frame
Package frame models fixed-duration stego frames.
|
Package frame models fixed-duration stego frames. |
|
quality
Package quality measures how far a processed signal has drifted from its reference: signal-to-distortion ratios on planar float PCM, and perceptual scores from external tools.
|
Package quality measures how far a processed signal has drifted from its reference: signal-to-distortion ratios on planar float PCM, and perceptual scores from external tools. |
|
steganalysis
Package steganalysis scores streams of quantized values for traces of LSB embedding and measures how well those scores separate two populations.
|
Package steganalysis scores streams of quantized values for traces of LSB embedding and measures how well those scores separate two populations. |
|
stego
Package stego embeds and extracts bit strings in quantized Vorbis residues.
|
Package stego embeds and extracts bit strings in quantized Vorbis residues. |
|
trace
Package trace reads what an audio file shows without looking at its samples: container identity, length, how it ends, and the page or STREAMINFO facts a metadata warden can read.
|
Package trace reads what an audio file shows without looking at its samples: container identity, length, how it ends, and the page or STREAMINFO facts a metadata warden can read. |
|
wire
Package wire is the inner payload framing and the outer hybrid-box envelope.
|
Package wire is the inner payload framing and the outer hybrid-box envelope. |