or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

acme.mdencryption.mdhashing.mdindex.mdnacl.mdopenpgp.mdpublic-key.mdssh.mdutilities.md
tile.json

nacl.mddocs/

NaCl Cryptographic Library

The NaCl (pronounced "salt") packages provide high-level, easy-to-use cryptographic operations based on Daniel J. Bernstein's Networking and Cryptography library. These packages offer authenticated encryption, message authentication, and digital signatures with a focus on simplicity and security.

Package: golang.org/x/crypto/nacl/box

Public-key authenticated encryption using Curve25519, XSalsa20, and Poly1305. Box provides end-to-end encryption between two parties.

Constants

const (
    // Overhead is the number of bytes added by encryption.
    Overhead = secretbox.Overhead

    // AnonymousOverhead is the number of bytes added by anonymous encryption.
    AnonymousOverhead = Overhead + 32
)

Functions

// GenerateKey generates a new public/private key pair suitable for use with box.
func GenerateKey(rand io.Reader) (publicKey, privateKey *[32]byte, err error)

// Seal appends an encrypted and authenticated message to out.
// The nonce must be unique for each message with the same key pair.
// The message is encrypted and authenticated with Curve25519, XSalsa20, and Poly1305.
func Seal(out, message []byte, nonce *[24]byte, peersPublicKey, privateKey *[32]byte) []byte

// Open authenticates and decrypts a box produced by Seal.
// The message is only returned if authentication succeeds.
func Open(out, box []byte, nonce *[24]byte, peersPublicKey, privateKey *[32]byte) ([]byte, bool)

// Precompute calculates the shared key between peersPublicKey and privateKey
// and writes it to sharedKey. The shared key can be used with SealAfterPrecomputation
// and OpenAfterPrecomputation for improved performance when encrypting multiple messages.
func Precompute(sharedKey, peersPublicKey, privateKey *[32]byte)

// SealAfterPrecomputation encrypts a message using a precomputed shared key.
func SealAfterPrecomputation(out, message []byte, nonce *[24]byte, sharedKey *[32]byte) []byte

// OpenAfterPrecomputation decrypts a message using a precomputed shared key.
func OpenAfterPrecomputation(out, box []byte, nonce *[24]byte, sharedKey *[32]byte) ([]byte, bool)

// SealAnonymous encrypts a message to a recipient's public key.
// Unlike Seal, the sender's identity is not included in the message.
// The recipient must try each of their private keys to decrypt.
func SealAnonymous(out, message []byte, recipient *[32]byte, rand io.Reader) ([]byte, error)

// OpenAnonymous decrypts a message encrypted with SealAnonymous.
func OpenAnonymous(out, box []byte, publicKey, privateKey *[32]byte) (message []byte, ok bool)

Usage Examples

Basic Box Encryption

package main

import (
    "crypto/rand"
    "fmt"
    "golang.org/x/crypto/nacl/box"
)

func main() {
    // Generate key pairs for Alice and Bob
    alicePub, alicePriv, _ := box.GenerateKey(rand.Reader)
    bobPub, bobPriv, _ := box.GenerateKey(rand.Reader)

    // Alice encrypts a message to Bob
    message := []byte("Hello, Bob!")
    var nonce [24]byte
    rand.Read(nonce[:])

    encrypted := box.Seal(nil, message, &nonce, bobPub, alicePriv)
    fmt.Printf("Encrypted: %x\n", encrypted)

    // Bob decrypts the message from Alice
    decrypted, ok := box.Open(nil, encrypted, &nonce, alicePub, bobPriv)
    if !ok {
        panic("decryption failed")
    }
    fmt.Printf("Decrypted: %s\n", decrypted)
}

Precomputed Shared Key

package main

import (
    "crypto/rand"
    "fmt"
    "golang.org/x/crypto/nacl/box"
)

func main() {
    // Generate key pairs
    alicePub, alicePriv, _ := box.GenerateKey(rand.Reader)
    bobPub, bobPriv, _ := box.GenerateKey(rand.Reader)

    // Precompute shared keys (do this once)
    var aliceShared, bobShared [32]byte
    box.Precompute(&aliceShared, bobPub, alicePriv)
    box.Precompute(&bobShared, alicePub, bobPriv)

    // Alice encrypts multiple messages efficiently
    message1 := []byte("First message")
    message2 := []byte("Second message")

    var nonce1, nonce2 [24]byte
    rand.Read(nonce1[:])
    rand.Read(nonce2[:])

    encrypted1 := box.SealAfterPrecomputation(nil, message1, &nonce1, &aliceShared)
    encrypted2 := box.SealAfterPrecomputation(nil, message2, &nonce2, &aliceShared)

    // Bob decrypts efficiently
    decrypted1, _ := box.OpenAfterPrecomputation(nil, encrypted1, &nonce1, &bobShared)
    decrypted2, _ := box.OpenAfterPrecomputation(nil, encrypted2, &nonce2, &bobShared)

    fmt.Printf("Message 1: %s\n", decrypted1)
    fmt.Printf("Message 2: %s\n", decrypted2)
}

Anonymous Encryption

package main

import (
    "crypto/rand"
    "fmt"
    "golang.org/x/crypto/nacl/box"
)

func main() {
    // Generate Bob's key pair
    bobPub, bobPriv, _ := box.GenerateKey(rand.Reader)

    // Anonymous sender encrypts to Bob
    message := []byte("Anonymous message")
    encrypted, _ := box.SealAnonymous(nil, message, bobPub, rand.Reader)
    fmt.Printf("Encrypted: %x\n", encrypted)

    // Bob decrypts (doesn't know who sent it)
    decrypted, ok := box.OpenAnonymous(nil, encrypted, bobPub, bobPriv)
    if !ok {
        panic("decryption failed")
    }
    fmt.Printf("Decrypted: %s\n", decrypted)
}

Package: golang.org/x/crypto/nacl/secretbox

Secret-key authenticated encryption using XSalsa20 and Poly1305. Secretbox provides symmetric authenticated encryption.

Constants

const Overhead = poly1305.TagSize // 16 bytes

Functions

// Seal appends an encrypted and authenticated message to out.
// The nonce must be unique for each distinct message with the same key.
// The key must be 32 bytes.
func Seal(out, message []byte, nonce *[24]byte, key *[32]byte) []byte

// Open authenticates and decrypts a box produced by Seal.
// The message is only returned if authentication succeeds.
func Open(out, box []byte, nonce *[24]byte, key *[32]byte) ([]byte, bool)

Usage Example

package main

import (
    "crypto/rand"
    "fmt"
    "golang.org/x/crypto/nacl/secretbox"
)

func main() {
    // Generate a random key
    var key [32]byte
    rand.Read(key[:])

    // Encrypt a message
    message := []byte("Secret message")
    var nonce [24]byte
    rand.Read(nonce[:])

    encrypted := secretbox.Seal(nil, message, &nonce, &key)
    fmt.Printf("Encrypted: %x\n", encrypted)

    // Decrypt the message
    decrypted, ok := secretbox.Open(nil, encrypted, &nonce, &key)
    if !ok {
        panic("decryption failed")
    }
    fmt.Printf("Decrypted: %s\n", decrypted)

    // Tampered ciphertext fails authentication
    encrypted[0] ^= 1
    _, ok = secretbox.Open(nil, encrypted, &nonce, &key)
    fmt.Printf("Tampered decryption failed: %v\n", !ok)
}

Package: golang.org/x/crypto/nacl/auth

Message authentication using HMAC-SHA-512 truncated to 32 bytes.

Constants

const (
    Size    = 32 // Authenticator size in bytes
    KeySize = 32 // Key size in bytes
)

Functions

// Sum generates an authenticator for message m using key.
func Sum(m []byte, key *[KeySize]byte) *[Size]byte

// Verify reports whether digest is a valid authenticator for message m.
func Verify(digest []byte, m []byte, key *[KeySize]byte) bool

Usage Example

package main

import (
    "crypto/rand"
    "fmt"
    "golang.org/x/crypto/nacl/auth"
)

func main() {
    // Generate a random key
    var key [32]byte
    rand.Read(key[:])

    // Authenticate a message
    message := []byte("Message to authenticate")
    mac := auth.Sum(message, &key)
    fmt.Printf("MAC: %x\n", mac)

    // Verify the MAC
    valid := auth.Verify(mac[:], message, &key)
    fmt.Printf("Valid: %v\n", valid)

    // Wrong message fails verification
    wrongValid := auth.Verify(mac[:], []byte("Wrong message"), &key)
    fmt.Printf("Wrong valid: %v\n", wrongValid)

    // Tampered MAC fails verification
    mac[0] ^= 1
    tamperedValid := auth.Verify(mac[:], message, &key)
    fmt.Printf("Tampered valid: %v\n", tamperedValid)
}

Package: golang.org/x/crypto/nacl/sign

Message signing using Ed25519 signatures.

Constants

const Overhead = 64 // Signature overhead in bytes

Functions

// GenerateKey generates a new public/private key pair for signing.
func GenerateKey(rand io.Reader) (publicKey *[32]byte, privateKey *[64]byte, err error)

// Sign appends a signed message to out.
// The signed message includes both the signature and the message itself.
func Sign(out, message []byte, privateKey *[64]byte) []byte

// Open verifies a signed message and returns the message if the signature is valid.
// The message is only returned if the signature is valid.
func Open(out, signedMessage []byte, publicKey *[32]byte) ([]byte, bool)

Usage Example

package main

import (
    "crypto/rand"
    "fmt"
    "golang.org/x/crypto/nacl/sign"
)

func main() {
    // Generate key pair
    publicKey, privateKey, _ := sign.GenerateKey(rand.Reader)

    // Sign a message
    message := []byte("Important message")
    signedMessage := sign.Sign(nil, message, privateKey)
    fmt.Printf("Signed message length: %d bytes\n", len(signedMessage))
    fmt.Printf("Signature: %x...\n", signedMessage[:32])

    // Verify and extract message
    extractedMessage, ok := sign.Open(nil, signedMessage, publicKey)
    if !ok {
        panic("verification failed")
    }
    fmt.Printf("Extracted: %s\n", extractedMessage)

    // Invalid signature fails
    signedMessage[0] ^= 1
    _, ok = sign.Open(nil, signedMessage, publicKey)
    fmt.Printf("Tampered verification failed: %v\n", !ok)
}

Comparison and Use Cases

Box vs Secretbox

FeatureBox (nacl/box)Secretbox (nacl/secretbox)
EncryptionPublic-key (asymmetric)Secret-key (symmetric)
Key ExchangeBuilt-in (Curve25519)Manual key distribution
Sender AuthYes (sender's private key)No (shared secret only)
Use CasePeer-to-peer messagingEncrypted storage, VPN
Key Size32 bytes (public + private)32 bytes (shared secret)

Auth vs Sign

FeatureAuth (nacl/auth)Sign (nacl/sign)
TypeMAC (symmetric)Digital signature (asymmetric)
VerificationShared secret requiredPublic key only
Non-repudiationNoYes
Use CaseMessage integrityPublicly verifiable signatures
Output Size32 bytes64 bytes

Best Practices

Nonce Management

  1. Never reuse nonces with the same key
  2. Generation strategies:
    • Random (recommended): Use crypto/rand for each message
    • Counter: Increment for each message (requires state management)
    • Timestamp: Use high-resolution timestamp (risk of collisions)
  3. Nonce size: 24 bytes for box/secretbox, adequate for random generation

Key Management

  1. Key generation: Always use crypto/rand
  2. Key storage: Use OS keychain or hardware security module
  3. Key rotation: Implement periodic key rotation
  4. Derived keys: Use HKDF (from hashing.md) to derive multiple keys from a master key

Security Considerations

  1. Authentication: Box and secretbox provide authenticated encryption (AEAD)
  2. Forward secrecy: Box does not provide forward secrecy; use ephemeral keys
  3. Anonymous box: Provides no sender authentication
  4. Secretbox: Requires secure key distribution mechanism

Performance

  1. Precomputation: Use Precompute/SealAfterPrecomputation for multiple messages between same parties
  2. Secretbox: Faster than box for symmetric scenarios
  3. All NaCl operations: Highly optimized, suitable for high-throughput applications

Common Patterns

Secure Channel (with forward secrecy)

// Generate ephemeral key pairs for each session
alicePub, alicePriv, _ := box.GenerateKey(rand.Reader)
bobPub, bobPriv, _ := box.GenerateKey(rand.Reader)

// Exchange public keys
// ... send alicePub to Bob, receive bobPub from Bob

// Precompute for session
var shared [32]byte
box.Precompute(&shared, bobPub, alicePriv)

// Use shared key with secretbox for messages
// (more efficient than repeated box operations)

File Encryption

// Generate random key
var key [32]byte
rand.Read(key[:])

// Encrypt file contents
var nonce [24]byte
rand.Read(nonce[:])
encrypted := secretbox.Seal(nonce[:], fileData, &nonce, &key)

// Store encrypted with key encrypted by password-derived key
// (use argon2.IDKey from hashing.md to derive key from password)

Message Queue Authentication

// Producer and consumer share auth key
var authKey [32]byte
// ... distribute securely

// Producer creates authenticated message
message := []byte("queue message")
mac := auth.Sum(message, &authKey)
signedMessage := append(message, mac[:]...)

// Consumer verifies and processes
receivedMessage := signedMessage[:len(signedMessage)-32]
receivedMAC := signedMessage[len(signedMessage)-32:]
if auth.Verify(receivedMAC, receivedMessage, &authKey) {
    // Process message
}

Relationship to Other Packages

NaCl vs ChaCha20-Poly1305

  • NaCl secretbox: Uses XSalsa20-Poly1305 (24-byte nonce)
  • ChaCha20-Poly1305: Uses ChaCha20-Poly1305 (12 or 24-byte nonce)
  • ChaCha20-Poly1305 is more modern and standardized (RFC 8439)
  • NaCl provides simpler API with fixed-size arrays

NaCl box vs Raw Curve25519

  • NaCl box: Complete ECDH + encryption + authentication
  • Raw Curve25519: Only key exchange primitive
  • Box includes all necessary components for secure messaging

NaCl sign vs Ed25519

  • NaCl sign: Signature includes message (Sign/Open API)
  • Ed25519: Signature separate from message (Sign/Verify API)
  • Both use the same Ed25519 algorithm
  • NaCl sign is simpler but less flexible

Migration Notes

If migrating from NaCl to other packages:

  • secretbox → ChaCha20-Poly1305: More standardized, similar security
  • box → Standard library crypto/ecdh + ChaCha20-Poly1305: More flexible, lower-level
  • sign → Ed25519: Use standard library crypto/ed25519 for detached signatures
  • auth → HMAC: Use crypto/hmac with SHA-256 or SHA-512