go-nacl: the full NaCL API in Go, for talking to other languages
Pure Go implementation of the NaCL set of API's
At a glance
- What is it?
- kevinburke's nacl implements the complete NaCL API in Go rather than the subset that golang.org/x/crypto/nacl exposes, and it was written so that a message sealed in one language can be opened in another. The hard mathematics is not in this repository; it calls into x/crypto.
- Who is it for?
- go-nacl fits a Go service that must interoperate with NaCL implementations in other languages, or that needs one of the primitives x/crypto/nacl leaves out. It does not fit a project that only needs box or secretbox and would rather depend on the standard library ecosystem, and it does not fit a team that wants the newest ed25519 behaviour.
- 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 93 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
Why not just use x/crypto/nacl
The README's comparison with the implementation in golang.org/x/crypto/nacl is the clearest statement of the project's purpose. Compared with that implementation, this library offers all of the APIs present in NaCL, better compatibility with NaCL implementations written in other languages, and some utilities for generating and loading keys and nonces, and encrypting messages. The stated goal is twofold: to show how to implement the NaCL functions in pure Go, and to provide interoperability between messages encrypted, hashed or authenticated in other languages and available in Go. That interop goal is what shapes the design, and it is the reason the errata section exists. If you only need secretbox or box and you never intend to talk to another language, the standard library route is simpler and this library is not for you.
The hard maths is delegated, not reimplemented
The author is explicit about the boundary of the work. The implementation of the crypto_sign function uses the ref10 implementation of ed25519 from SUPERCOP, and the point of the who-am-I section is that none of the hard maths was written by hand: poly1305, XSalsa20 and curve25519 are all called into golang.org/x/crypto. The go.mod confirms it, with golang.org/x/crypto at 0.48.0 as a direct dependency alongside filippo.io/edwards25519, and only x/sys as an indirect one. Many of the functions are simple wrappers around standard library or x/crypto functionality, and the author states that other code was copied in directly with the appropriate LICENSE, and that if a function is longer than about five lines he did not write it himself. There are no dependencies outside the standard library and x/crypto, which is the kind of constraint that matters in a cryptography library.
Package names mirror the primitives, with the Go naming applied
The library is organised as a set of packages that mirror the NaCL primitives, with the crypto_ prefix removed from the names. The repository layout shows them all as directories: auth, box, onetimeauth, randombytes, scalarmult, secretbox, sign, stream, plus an internal directory, a cmd directory, and nacl.go at the root as the shared entry point. Some function names were changed to match Go conventions, which is the only visible departure from the NaCL naming. The grouping by primitive rather than by operation is a deliberate way to keep the mental model of the C library, which is the point of the project, since a developer porting between languages can search for the same primitive name in both.
Keys, nonces and a worked sealing example
The helpers for generating and loading keys and nonces are the part that saves you from writing the same boilerplate again. The README notes that NaCL standardises on 32 byte keys and 24 byte nonces everywhere, and that you can generate a key with openssl rand -hex 32. The example loads a key and seals a message with secretbox.
import "github.com/kevinburke/nacl"
import "github.com/kevinburke/nacl/secretbox"
func main() {
key, err := nacl.Load("6368616e676520746869732070617373776f726420746f206120736563726574")
if err != nil {
panic(err)
}
encrypted := secretbox.EasySeal([]byte("hello world"), key)
fmt.Println(base64.StdEncoding.EncodeToString(encrypted))
}The key is loaded from a hex string, EasySeal does the sealing with a generated nonce, and the result is printed as base64. The pairing with a NaCL implementation in another language is the point, since that other implementation has to be able to open it.
The errata, and why signing is the one to read carefully
The errata section is the most useful part of the README for anyone using the library in anger, and the signing differences are the significant ones. The implementation of crypto_sign uses the ref10 implementation of ed25519 from SUPERCOP, not the current implementation in NaCL, and the difference is that the entire 64 byte signature is prepended to the message, whereas in the current version of NaCL separate bits are prepended and appended. Compared with crypto/ed25519, this library's Sign returns the message along with the signature, and Verify expects the first 64 bytes of the message to be the signature, which the README says simplifies the API and matches the ref10 behaviour and other NaCL implementations. Sign also flips the argument order to Sign(message, privatekey) to match NaCL. The box and secretbox differences are smaller: Precompute returns the shared key rather than modifying the input, and Seal and Open call the onetimeauth package in this library rather than poly1305 directly.
A build with race detection, static analysis and benchmark comparison
The Makefile shows the engineering habits around the code, and they are the habits you would want in a cryptography library. The test target runs go test with trimpath across every package, and it is deliberately listed first so that a bare make runs the tests. There is a race-test target running the same suite with the race detector, a vet target, and a check target that installs and runs staticcheck. The release target depends on both check and race-test, which means a release cannot be made without the race detector and the linter passing. The bench target runs the benchmarks three times with a two second benchtime and pipes the output through benchstat for comparison, and there is a docs target that regenerates the godoc with a separate godocdoc tool. The release process itself uses a bump_version tool with a v tag prefix, applied to the minor version of nacl.go.
A maintained, if quiet, library
The project is a small one with a long tail of users, and the release history in the repository is not published as GitHub releases, so the tags are the version history. The licence is BSD-3-Clause, the repository is not archived, and the last push was on 2026-07-22. The module requires Go 1.25. The author also uses the who-am-I section to make an argument you should weigh rather than skip, that you probably should not trust random security code from the internet, and that he is reasonably confident this code is secure because he did not implement the hard maths himself, and because he ported over every test he could find from the C and C++ code and the associated RFCs and ensured the libraries passed them. That test-porting claim is the substantive one, and it is the reason this library can be considered alongside implementations in other languages despite the crypto itself living elsewhere.
Editorial conclusion
go-nacl fits a Go service that must interoperate with NaCL implementations in other languages, or that needs one of the primitives x/crypto/nacl leaves out. It does not fit a project that only needs box or secretbox and would rather depend on the standard library ecosystem, and it does not fit a team that wants the newest ed25519 behaviour. Verify first which primitives you actually need, since the standard library x/crypto path covers the common cases, and read the errata section before you use signing, because the signature layout differs from both NaCL's current behaviour and from crypto/ed25519.
Frequently asked questions
What does kevinburke's nacl library do?
It is a pure Go implementation of the APIs available in NaCL, offering all of the APIs present there rather than the subset in golang.org/x/crypto/nacl, with better compatibility with NaCL implementations in other languages, and helpers for generating and loading keys and nonces and for encrypting messages.
Does go-nacl implement its own cryptography?
No. The hard maths, poly1305, XSalsa20 and curve25519, is called into golang.org/x/crypto rather than written here, and the ed25519 signing uses the ref10 implementation from SUPERCOP. The go.mod has x/crypto as a direct dependency and nothing outside the standard library and x/crypto.
How does the signing in this library differ from NaCL and crypto/ed25519?
The entire 64 byte signature is prepended to the message, following the ref10 behaviour rather than the current NaCL layout. Compared with crypto/ed25519, Sign returns the message along with the signature, Verify expects the first 64 bytes of the message to be the signature, and the argument order is Sign(message, privatekey) to match NaCL.
How do I install and build the nacl library?
Run go get github.com/kevinburke/nacl, or clone the code into your GOPATH source directory. The Makefile provides test, race-test, vet, check with staticcheck, bench, docs and release targets, and the release target depends on both the check and race-test targets.
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/kevinburke-nacl)Was this useful?