VeilVoice: irreversible voice de-identification

VeilVoice destroys the biometric voiceprint of a speaker, meaning pitch, formants, timbre, micro-timing and the melody of an accent, so that neither software nor a human can re-identify them or reconstruct the original voice, while the words stay clean and transcribable.

No network code in the dependency graph CI fails the build if an HTTP client appears No unsafe code, in any of the 13 crates 1659 tests, and 20 more suites for the website Reproducible builds: each target built twice, and compared Releases signed; the fingerprint is published everywhere Measured phase is discarded every frame, and never stored Every speaker is mapped onto one canonical voice Many-to-one, so there is no inverse to compute Modulation seeded by ChaCha20; the seed never leaves the process A forward-secure ratchet, every two seconds Recordings are encrypted at rest by default X25519 + ML-KEM-768: hybrid, post-quantum XChaCha20-Poly1305, and Argon2id The app lock is a verifier, not disk encryption Tamper detection detects; it does not prevent Secure erase is unreliable on flash, and the docs say so Accent removal is partial, and that limit is documented Metadata stripped: tags, EXIF, GPS No telemetry. No accounts. No automatic update check Ten platforms, from one reproducible build The artwork is generated by a script you can read Every crate and every source file has a generated page 196 defects found and fixed across thirty-three audit rounds A complete edition of this site that runs no scripts Free software, GPL-3.0-or-later

WHAT HAPPENS TO YOUR RECORDING

What goes in is a file; what comes out is a file. There is no account, no upload and no queue: the whole of it happens on the machine you are sitting at, and CI fails the build if a network client appears anywhere in the dependency graph.

What this picture does not show is the limit. The voiceprint goes; what you said stays, because the output is meant to be listened to and transcribed. If the words themselves identify you: a name, a place, a story only you could tell. VeilVoice has not touched that and does not claim to. Segmental accent cues, which phonemes you actually produced, survive for the same reason. See security and cryptography for the full scope.

WHAT IT ACTUALLY DOES

Anonymise a recording

wav, mp3, flac, ogg, m4a in, a clean WAV out, with metadata stripped. Roughly 90× faster than real time.

Scramble a microphone live

Route the veiled voice into a virtual audio cable and every application on the machine, whether calls, streams or recorders, receives it instead of you.

Encrypt at rest, by default

Every recording is sealed as it is written, using an X25519 + ML-KEM-768 hybrid, so one captured today is not readable by a quantum adversary tomorrow. Turning that off makes you read why first.

Lock the app

A separate password gates the desktop app, rate limited and Argon2id-derived. It stops someone who picks up your unlocked computer. It is not tamper-proof, and the unlock screen says so.

Strip metadata

Audio tags, image EXIF and GPS. A de-identified voice is worthless if the file still says who recorded it, where, and on what.

Work as a Rust library

Every crate is a normal dependency. The engine is allocation-free and safe to call from inside an audio callback.

Transcribe without giving up your voice

Speech-to-text needs the words, not the voiceprint. Anonymise first and the service gets speech it can transcribe and a voice belonging to nobody.

See what is listening

Which applications are holding your microphone or camera, right now, with an alert the moment one starts. De-identifying a call achieves little if a second program is recording the raw microphone beside it. macOS exposes no interface for this, so nothing is reported there rather than something guessed.

Detect tampering with its own files

A signed manifest of what VeilVoice should be, and a check that reports what changed. Where the system's own auditing allows it, it names the program responsible, and says plainly when it cannot see, rather than implying nothing happened.

Erase a recording

Overwrite and unlink, with an honest account of what that is worth. On flash storage the controller may have written the data somewhere the filesystem can no longer reach, so this is not a guarantee and is not described as one.

Verify a download without GnuPG

veilvoice verify is part of the program you downloaded, and the desktop application's Verify tab runs the same code. The signing key is compiled in, so it checks the signature over the hash list and then the file on a machine with no GnuPG and no network. It distinguishes a download being intact from a build being reproducible, because those are different claims.

Honest scope. “Fill the spectrogram with noise” and “stay understandable” are mutually exclusive, because noise that covers the voice covers the words. VeilVoice targets the achievable goal: irreversible speaker de-identification with intelligibility preserved on purpose. If the message must also be secret, encrypt it; that is a separate problem with a separate answer.

The same applies to accent. Its melody and colour do not survive. What no signal-level transform can change is which phonemes you produced, and at that level the accent and the words are the same thing, so a strong regional accent may still be audible.

DOWNLOAD

Latest release: see GitHub. Every binary is built twice in separate directories and verified byte-identical before it ships.

Files in the latest release

Always verify before you run it. A download can be corrupted in transit or replaced entirely. Two independent checks are published with every release: a SHA-256 for each file, and an OpenPGP signature over that hash list. The in-browser verifier does the first one for you.

SO YOU HAVE DOWNLOADED IT, NOW WHAT

There are two programs in the archive: veilvoice-gui, the desktop app, and veilvoice, the command line. They share one engine, so anything one can do the other can. Nothing installs a service, writes to a registry, or phones home. Delete the folder and it is gone.

  1. Give it a recording

    wav, mp3, flac, ogg, m4a and friends. Open the desktop app on the anonymise file tab and choose one, or point the command line at it. Roughly 90× faster than real time, so an hour of audio takes well under a minute.

    veilvoice anonymise interview.mp3 -o clean.wav
  2. The voiceprint is destroyed, the words are kept

    Each frame's measured phase is thrown away and resynthesised, and pitch register, vocal-tract length and spectral tilt are each collapsed onto one canonical value, so a whole population of speakers lands on the same output and there is nothing left to invert. What comes out is understandable, transcribable, and belongs to nobody.

    The same energy, and no shared structure. Left, a voice as it was recorded; right, what is left after the phase relationship that identified the speaker has been destroyed. The words survive the journey; the speaker does not.

  3. It is encrypted before it reaches the disk

    The result is sealed into a .veil container as it is written, so -o clean.wav produces clean.wav.veil. The WAV is built in memory and encrypted there, so the plaintext never exists on disk, not even for a moment, because a file that is written and then deleted cannot be reliably taken back on flash storage.

    veilvoice decrypt clean.wav.veil -o clean.wav   # when you want it back
  4. Or scramble your microphone as you speak

    The Studio tab routes your veiled voice into a virtual audio cable. Every application on the machine, whether a call, a stream or a recorder, then receives that instead of you, with no per-app setup. The same tab keeps a take of it, sealed into a vault, when you ask for one.

  5. Check nothing else is listening

    De-identifying your voice on a call achieves little if a second program is recording the raw microphone at the same time. The monitor tab names what is holding your microphone and camera and warns the moment something starts.

  6. Lock the app behind you

    Set a password on the lock tab and VeilVoice will not open without it. The lock button in the header locks it immediately and clears the session passphrase with it.

The two passwords, and why there are two

The app lock

Decides whether VeilVoice opens at all. Argon2id verifier, rate limited, three attempts free and then a doubling wait.

The recording passphrase

Encrypts the files it writes. Argon2id at 256 MiB, or seal to a post-quantum hybrid public key instead.

They are deliberately different secrets. If one password did both, then opening the app would be the same act as unsealing everything it had ever written, which is the opposite of what a lock is for. VeilVoice keeps the two derivations domain-separated, so typing the same passphrase in both places still does not produce two copies of one value. Use two anyway: one guess that opens both defeats the point regardless of the maths.

The app lock is not tamper-proof, and cannot be. A program running on your computer has nowhere to hide a secret from that computer: anyone who can write to your files can delete the lock, and anyone holding the disk can attack the stored password hash offline. It protects against casual access, meaning the person who sits down at your unlocked session, which is a real and common threat, and is exactly what the unlock screen says it is for. If someone taking your disk is the threat, encrypt the whole volume.

WHAT IT LOOKS LIKE

Every picture here is of the current build. The window captures are taken by a script that drives the release build and photographs each tab, and the terminal drawings are generated from the command output committed beside them, so a picture that disagrees with the program fails the build rather than sitting here saying something untrue.

Every one of these is below in the demonstration too, one at a time and larger. Go there to watch the command line type itself out first.

Anonymise a file
Anonymise a file One recording in, a voice nobody owns out. Encrypted at rest by default.
Group mode
Group mode Several people in one recording, a name and a colour each, a different voice each.
Recording Studio
Recording Studio A microphone, scrambled as it runs, into a virtual cable anything else can hear, and a take kept straight into a locked vault if you want one. The vault opens with both passphrases at once, and with neither on its own.
Recording Browser
Recording Browser What is in the vault, listed without opening any of it. Play one out of locked memory, take it out as a page or a video, or fill the folder with decoys so which vault is yours stops being visible.
Monitor
Monitor Which programs are using the microphone and the camera, and what this cannot see.
Lock
Lock The app lock, and a plain account of what it is and is not worth.
Verify a download
Verify a download Drop the download, the SHA256SUMS and its signature on the window. Nothing is fetched, and the key is compiled in.
Settings
Settings Nine palettes, your own if you write one, motion, and which tabs are shown.
Install
Install Offered only to a portable copy. An installed one does not show this tab.
About
About Versions, scope, and the update check that happens when you press it.

The command line

Everything the window does, and some things it does not. These are drawings rather than photographs: they follow your palette, and the text in them can be selected and searched.

veilvoice --help
veilvoice --help
veilvoice conversation --help
veilvoice conversation --help
veilvoice anonymise --help
veilvoice anonymise --help
veilvoice conversation render --help
veilvoice conversation render --help
veilvoice conversation preview --help
veilvoice conversation preview --help
veilvoice companions --help
veilvoice companions --help

WHAT IT SOUNDS LIKE, IN ONE PICTURE

The bars marked “normal voice” are in step with one another. That shared phase relationship is most of what makes a voice recognisable as yours: the precise waveform, the micro-timing, the way your vocal tract shapes every harmonic.

The bars marked “anonymised voice” carry the same energy and none of the relationship. The list between them is what actually happens, in order, and it is six steps rather than one:

  1. Framed. The audio is cut into overlapping frames of 1024 samples and each one is turned into frequencies. Everything below happens per frame, which at 48 kHz is about 187 times a second.
  2. Phase measured. A frame has a magnitude, which is how much of each frequency there is, and a phase, which is how those frequencies line up in time. The second one is a great deal of what makes a voice recognisably yours.
  3. Phase discarded. Not scrambled, not encrypted, not hidden: thrown away and replaced, and never written anywhere. That is the step with no inverse. Nothing is left from which the original timing could be recovered, by us or by anybody else.
  4. Pitch and formants moved. Every speaker is mapped onto one canonical register and one vocal-tract scale. Many voices in, one set of characteristics out, which is the other reason there is nothing to undo: several different people arrive at the same place.
  5. Modulation seed rolled. How far things move is drawn from a cryptographic random stream, and that stream rolls forward at an interval drawn fresh at every launch. Audio from before a roll is sealed off behind it, so there is no fixed period to observe.
  6. Words left alone. None of it touches which words were said.

The words survive all of it. The output is meant to be listened to, shared, understood and transcribed. That is the whole design: a scrambler you cannot understand protects nobody, because nobody uses it.

THE COMMAND LINE, TYPED OUT

Five sessions, recorded from the real programs on a real terminal, prompts and passphrases included. Pick one and it types itself out. The only invented thing is the speed: a session that arrives all at once is a paste rather than a session.

THE COMMAND LINE, ONE JOB AT A TIME

Each of these is a real command. Every one is checked against this build's own --help when the page is generated, so a command here that no longer exists stops the build rather than teaching you something that fails.

EVERY SCREEN, AND WHAT IT IS FOR

Photographs of the real window, one per tab. Pick a tab and its picture is below. These are captures of the program running, not drawings of it, and the build re-takes them so a screen that changes cannot leave a stale picture here.

VERIFY A DOWNLOAD

Drop the file you downloaded here. It is hashed locally, in your browser, using the built-in WebCrypto API, so there is no upload and no server that could receive it. Read js/verify.js; that file is the whole implementation.

click or drop a release archive here
no file hashed yet

The stronger check: the signature

A hash proves the file matches a list. The signature proves the list came from the maintainer. Browsers cannot verify OpenPGP, so this part runs on your machine:

gpg --import veilvoice-signing-key.asc
gpg --verify SHA256SUMS.asc SHA256SUMS
sha256sum -c SHA256SUMS --ignore-missing

Signing key fingerprint. Check that gpg --verify names this exact key, not merely “a good signature”:

8101 FB3B B28D 02FB 239E  0CDF 9CC1 C7E7 A9B5 833A

download the public key. The user ID is exactly tilas01, with no e-mail address attached.

Or let it do all of that for you

veilvoice verify is built into the program itself: it ships in every release because veilvoice does, and it needs no installer and no separate download. Open a terminal in the folder you downloaded to and run it, or open the desktop application's verify tab and drop the archive on the window. One press does all four steps:

  1. the signature over SHA256SUMS;
  2. the archive, against SHA256SUMS;
  3. CONTENTS.sha256, against SHA256SUMS;
  4. every file you extracted, against CONTENTS.sha256, and it names anything in that folder the release never published.

Step 4 is the one worth having. A hash over the archive tells you the zip is genuine; this tells you the program you are about to run is. Releases before v0.1.15 carry no contents list and are checked as far as step 2, which it says at the time.

If GnuPG is on your machine it is used as well: the key is added to your keyring, gpg --verify is run, and what GnuPG said is shown. The signature is then checked by two independent implementations. The commands above are still printed for you to run yourself, because a program telling you a download is genuine came out of that download, only you typing them makes the answer independent of it.

Every one of those commands, written out with what each answer proves, sits with the release you are downloading: the whole check in one line, the signature over the hash list on its own, one file against that list, one file against a hash with no list at all, the desktop application's three slots, and the build that answers the harder question. Every command there is checked against the program's own help when the page is generated.

SECURITY, IN FULL

Why the transform cannot be undone

Three independent mechanisms, each individually lossy. Reversing the output means defeating all three.

MechanismWhat it destroys
Phase discardEvery frame's measured phase is thrown away and a synthetic one generated. Phase encodes the exact waveform and the speaker's micro-timing. It is never stored, and infinitely many waveforms share any given magnitude spectrogram.
Many-to-one normalisationPitch register, vocal-tract length and long-term spectral tilt are each collapsed onto a single canonical value. A whole population of speakers maps to the same output, so there is nothing to invert.
CSPRNG modulationThe residual transform changes every frame from a ChaCha20 stream whose seed comes from the OS CSPRNG, lives only in page-locked RAM, and is zeroized on drop. There is no fixed transform to undo.
Rolling seedEvery two seconds by default the stream draws a fresh seed from its own output and restarts. ChaCha20 does not run backwards, so each roll permanently seals off the audio before it: a long recording is a chain of short streams, not one. Configurable, and inaudible: parameters glide across a roll and phase offsets ease over half a second.

At-rest encryption

LayerPrimitiveWhy
Password → keyArgon2id (RFC 9106)Memory-hard, so GPU and ASIC cracking gains little. Cost parameters travel with the file so old files still open.
Public-keyX25519 + ML-KEM-768 hybridAn attacker must break both. Guards against harvest-now-decrypt-later: a recording stored today may be attacked decades from now.
PayloadXChaCha20-Poly1305192-bit random nonces remove the counter-management failure mode entirely.
HeaderAuthenticated as associated dataAn attacker cannot downgrade the stored KDF cost to make cracking cheap, because tampering makes decryption fail.
Keys in memoryPage-locked, zeroized, constant-timeKeys stay out of the swap file and are wiped on drop. Comparison leaks no timing.

Stated plainly: page-locking keeps keys off disk, not away from an attacker who can already read this process's memory, and hibernation writes RAM to disk wholesale and defeats it. A passphrase still sitting in a text field has not reached that protection yet, which is why it is wiped the moment it is used.

Recordings are sealed in memory and written once. An encrypted recording never exists on disk in the clear, because a plaintext file that is written and then deleted cannot be reliably taken back on flash storage.

The app lock, and exactly what it is worth

VeilVoice can sit behind a password of its own, separate from the one that encrypts recordings, so that opening the app is not the same act as unsealing everything it has written.

What it isWhat that means
An Argon2id verifier, not a keyA password hash is stored and compared in constant time. It encrypts nothing, because there is nothing local it could usefully encrypt.
Rate limited, and the limit persistsThree attempts are free; then the wait doubles from 5 s to a 15-minute cap. The count is written to disk after every attempt, so killing the app does not hand an attacker a fresh budget.
Domain separatedType the same passphrase in both places and you still do not end up with two copies of one value.

Not tamper-proof, and it cannot be. A local application has nowhere to hide a secret from the machine it runs on. Anyone who can write to your files can delete the lock; anyone holding the disk can edit the attempt counter, move the clock, or attack the stored hash offline. This protects against casual access, meaning the person who sits down at your unlocked session. If the disk is the threat, the answers are full-volume encryption and the at-rest encryption above, not this.

Libre, and what that buys you

  • GPL-3.0-or-later. You may use, study, modify and redistribute it; derivatives stay free under the same terms.
  • No unsafe anywhere. Every crate carries #![forbid(unsafe_code)], including the page-locking path. Whole classes of memory-corruption bugs are impossible by construction.
  • Offline by construction. No telemetry and no accounts, and CI fails the build if an HTTP client so much as enters the dependency graph. One thing reaches the network and only when you press it: the desktop app's check for updates button, which runs then and at no other time, sends nothing about you, and downloads nothing. It borrows your system's own transfer tool, exactly as the release verifier does.
  • Reproducible. Pinned toolchain, committed lockfile, path-remapped builds. Rebuild a release and confirm it matches, byte for byte.
  • Artwork generated from source. Every icon and the banner come out of a readable script, not a committed binary blob.
  • This website too. No CDN, no web fonts, no analytics, no cookies. The only third-party request is the optional repository panel, and it is a button you press.

Audited by tilas01, the author, who wrote and reviewed it. Be clear about what that is worth: a maintainer audit catches what the author can see, and no external firm or independent researcher has reviewed this code. The cryptography uses standard, well-reviewed primitives rather than anything invented here, and the de-identification argument is verifiable by reading two source files. Until an independent review exists, the source is the strongest verification available to you.

THE REPOSITORY, LIVE

This panel fetches from api.github.com, which learns your IP address. GitHub already serves this page, so for most visitors that changes nothing, but it is your call, so nothing loads until you ask.

…stars
…forks
…open issues
GPL-3.0licence

The README renders here once loaded.