saltpack: a modern crypto messaging format, and the reasoning behind it
a modern crypto messaging format
At a glance
- What is it?
- saltpack is Keybase's messaging format, a chunked and authenticated binary encoding built on NaCl, designed to fix five specific weaknesses of existing formats: unauthenticated output, non-repudiable signatures, reorderable chunks, visible sender and recipient keys and undetectable truncation. The README is short because the specification does the arguing.
- Who is it for?
- Adopt saltpack if you are designing a messaging format and want a specification that has already made the hard trade-offs, particularly repudiable authentication and a printable encoding that survives being pasted into a chat window. Do not adopt it as a replacement for an existing standard inside an ecosystem, since nothing else in your stack will read saltpack, and do not treat it as a transport, since it defines the message format and leaves delivery to you.
- Can I use it commercially?
- Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 8 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A format, with the cryptography left to NaCl
saltpack is described as a streamlined, modern solution designed with simplicity in mind, and the README says directly that it is easy to implement and integrate, that the project has made few crypto decisions, and that almost all of the heavy lifting is left to the NaCl library in golang.org/x/crypto/nacl.
That last point is the design philosophy in one sentence. A message format has to make choices that a crypto library cannot make for it, such as what a message looks like on the wire and how a reader knows it has read the whole thing. The cryptographic primitives, the key agreement, the secret key encryption, the signatures, are all NaCl's job, and NaCl's design decision is that you do not get to choose the algorithm.
What you get is therefore a small set of format decisions, made once, documented in a specification, with an implementation in Go. The repository is the reference implementation, the specification is at saltpack.org, and the encryption format version 2 document is linked for the details.
The framing matters for evaluation. This is a format and a library, not a protocol, not a client and not a key management system. It says nothing about how you get keys, how you find recipients or how a message travels. It is the layer that decides what a message is.
Five properties the format was built to guarantee
The README states that the project has taken pains to address many of the shortcomings of current message formats, and then lists five of them. This list is the argument of the project, and it is worth reading closely because each item is a real failure mode of deployed formats.
First, only authenticated data is output. Everything the format emits has been authenticated, so a reader never has to ask whether the part of the message they are looking at is covered by the integrity check.
Second, repudiable authentication is used wherever possible. In the strict sense, repudiable means a signature can be verified by anyone but does not prove who signed it, which sounds weaker and is in fact the opposite in the settings saltpack targets, since it avoids the need for a trusted third party to keep the certificate. Where that is not possible, the format falls back to something stronger.
Third, chunks cannot be reordered or combined with other transmissions. Fourth, the public keys of senders and recipients can be hidden. Fifth, message truncation is detectable.
The last one is the most under-appreciated. A format where a truncated message looks like a short message is a format that can be used to make a conversation look different from what it was, and the fact that saltpack treats detection of truncation as a format guarantee rather than an application concern is a sign of how seriously the design takes the problem.
One megabyte chunks over NaCl, encoded as MessagePack
Mechanically, saltpack is a binary message format encoded using MessagePack, and messages are broken up into reasonable chunks, 1 MB, over which regular NaCl operations are performed.
Chunking is what makes the format streamable and what makes the reorder and combine guarantees possible. A chunk is a self-contained unit with its own authentication, so a receiver can verify each one as it arrives, and the sequence binding in the format is what prevents an attacker from taking chunk seven of one message and dropping it into another.
The MessagePack encoding is a pragmatic choice rather than a cryptographic one. It is a compact binary serialisation with a self-describing schema, and it is well specified, so an implementer in a language other than Go can produce and consume the same bytes.
The Go module is minimal in the way a format library should be. The go.mod for the module github.com/keybase/saltpack requires keybase/go-codec, stretchr/testify, golang.org/x/crypto at 0.46.0 and golang.org/x/sync at 0.19.0, and requires Go 1.26.0 with a toolchain directive for go1.27.1. So the actual dependency surface is one crypto library, one codec, one concurrency library and a test framework, which is what you want from something that will be reimplemented in a dozen languages.
The armor: base62 with period markers, because chat apps mangle text
The saltpack ASCII output looks a lot like PGP's, and the README shows both side by side, a saltpack signed message and a PGP message, so the difference in appearance is visible at a glance.
The mechanism is described as reducing the characters to base62 plus some period markers, and the period markers only at the ends of words. The reason given is that PGP messages often get mangled by different apps, websites and smart text processors.
The beginning of an armoured saltpack signed message looks like this, with the armour header carrying the version and the payload following in short base62 groups:
BEGIN SALTPACK SIGNED MESSAGE. kXR7VktZdyH7rvq v5wcIkHbs7XwHpb
nPtLcF6vE5yY63t aHF62jiEC1zHGqD inx5YqK0nf5W9Lp TvUmM2zBwxgd3Nw
kvzZ96W7ZfDdTVg F5Y99c2l5EsCy1I xVNl0nY1TP25vsX 2cRXXPUrM2UKtWqThat is a real problem, and it is a good example of format design being driven by deployment rather than by theory. A PGP message in its armour uses line breaks, headers like a Comment line, a checksum and base64 with its own line lengths, and a web page that reflows text or a messaging app that inserts its own punctuation can corrupt it. Base62 with periodic word boundaries removes the line structure entirely, so the payload survives being reflowed, quoted, or pasted into something that inserts links.
The README is clear that the appearance is not the point. Of course, saltpack can output binary too, and either way it is what is inside the format that matters, with the specification linked for the details. The armour exists for the cases where binary is not an option, and the implementation in the repository reflects that, with separate files for the classic armour and the base62 variant, including separate encrypt, decrypt, sign, verify and signcrypt paths for each.
The file names are the specification
The repository layout is unusually readable because the files are named after the operations of the format.
There is a set of core operations at the top level: encrypt.go, decrypt.go, sign.go, sign_stream.go, signcrypt_seal.go and signcrypt_open.go for the combined sign and encrypt operation. On top of that sit the pieces that make it a format rather than a primitive: packets.go for the packet layer, frame.go for framing, chunk_reader.go and punctuated_reader.go for reading chunked input, msgpack.go for the encoding, nonce.go for nonce handling, key.go for key handling, rand.go for randomness, and errors.go.
The armour has its own cluster, armor.go and armor62.go, with the base62 variant split into armor62_encrypt.go, armor62_decrypt.go, armor62_sign.go, armor62_verify.go and armor62_signcrypt.go, and three of those, encrypt, sign and signcrypt, have matching test files. There are also directories that are unusual for a crypto library: encoding/ for the serialisation, specs/ for the specification documents, basic/ for a simpler or lower-level API, and a doc.go for the package documentation.
The test files are worth noticing too. There are architecture-specific tests, signcrypt_seal_amd64_test.go and signcrypt_seal_i386_test.go, which suggests the implementation has hand-tuned paths for particular architectures, and a tweakable_encryptor_test.go and tweakable_signer_test.go, which suggests the test suite exercises the implementation with substituted primitives as well as the real ones. For a format that other people will reimplement, that is the right shape of test suite to have.
Where saltpack is the wrong choice
The first wrong-tool case is interop with an existing ecosystem. saltpack is a new format, so a message in it is readable only by software that implements saltpack. If your counterpart is a PGP user, a Signal user or anything with a standard client, the format buys you nothing and costs you every conversion.
The second is a project that needs key management rather than a format. saltpack deliberately leaves the hard cryptographic lifting to NaCl and says nothing about how you obtain, store, rotate or revoke the keys of senders and recipients. The hiding of sender and recipient public keys, one of the five properties, is a property of the format, and it means the surrounding system has to decide how a recipient learns which key to use, which is where the actual work of a messaging system lives.
The third is a team that wants audited, standardised cryptography rather than a specification. saltpack is a project specification with a reference implementation, and the README points to the spec at saltpack.org for the details. A team that has to argue compliance will find more comfort in a format that has been through a standards process, whatever the engineering merits of saltpack.
There is also the practical point that issues are not tracked here. The README says to post issues to the keybase issues tracker, and it means this repository is not where a specification ambiguity gets resolved.
BSD-3-Clause, a 2026 commit, and no release tags
The project is licensed BSD-3-Clause, with the LICENSE file at the repository root, which is a permissive grant with the attribution clause and a disclaimer of warranty.
The repository is not archived, and the last push was on 2026-10-15, days before this review, so the work continues. There are no GitHub releases, which for a format library is a little surprising but consistent with a project whose distribution channel is the Go module and whose specification is the document.
The go.mod shows the module is being maintained against current toolchains, Go 1.26.0 with a toolchain directive of go1.27.1, and the dependency on golang.org/x/crypto is current rather than pinned to an old revision, which matters for a library that leans on NaCl for its primitives.
The tooling around it is modern as well, with a .golangci.yml for linting, a .pre-commit-config.yaml for the pre-commit hooks, and a CI workflow with a build badge.
The most interesting piece of the repository is not the code but the pairing of the two. A format that documents the five properties it guarantees, and an implementation that is structured as a direct reading of the format, is a good model for anyone designing a wire format of their own, and it is a legible one to read even if you never send a saltpack message.
Editorial conclusion
Adopt saltpack if you are designing a messaging format and want a specification that has already made the hard trade-offs, particularly repudiable authentication and a printable encoding that survives being pasted into a chat window. Do not adopt it as a replacement for an existing standard inside an ecosystem, since nothing else in your stack will read saltpack, and do not treat it as a transport, since it defines the message format and leaves delivery to you. Before you build, read the encryption format specification at saltpack.org, and note that issues are handled on the keybase issues tracker rather than in this repository, and that the module has no GitHub releases, so pin a commit if you need reproducibility.
Frequently asked questions
What is saltpack and what does it fix?
saltpack is Keybase's modern crypto messaging format. The README lists the shortcomings it addresses: only authenticated data is output, repudiable authentication is used wherever possible, chunks cannot be reordered or combined with other transmissions, sender and recipient public keys can be hidden, and message truncation is detectable.
How is a saltpack message encoded?
It is a binary format encoded with MessagePack, with the message broken into 1 MB chunks over which NaCl operations from golang.org/x/crypto/nacl are performed. The crypto primitives are NaCl's, and the project says it has made few crypto decisions of its own.
What is the difference between saltpack armor and PGP armor?
The saltpack ASCII output reduces the characters to base62 with period markers only at the ends of words, because PGP messages often get mangled by apps, websites and smart text processors. Saltpack can also output binary, and the README stresses that what is inside the format is what matters.
How do I add saltpack to a Go project?
The module path is github.com/keybase/saltpack, and the go.mod requires Go 1.26.0 with a toolchain directive for go1.27.1. The dependency surface is small: keybase/go-codec, stretchr/testify, golang.org/x/crypto and golang.org/x/sync.
What licence is saltpack released under?
BSD-3-Clause, with the LICENSE file at the repository root. There are no GitHub releases, and the last push was on 2026-10-15. Issues are posted to the keybase issues tracker rather than to this repository.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/keybase-saltpack)Was this useful?