Go ChaCha20-Poly1305 Code Example (Online Runner)

Go ChaCha20-Poly1305 examples with key/nonce/AAD encoding and ciphertext+tag handling.

Online calculator: use the site ChaCha20-Poly1305 text tool.

Note: This snippet requires locally installed dependencies and will not run in the online runner. Run it locally with: go mod init chacha-demo && go get golang.org/x/crypto/chacha20poly1305 && go run chacha20_poly1305_basic.go

Calculation method

ChaCha20-Poly1305 uses a 32-byte key, 12-byte nonce, optional AAD, and outputs ciphertext plus a 16-byte tag. The tool concatenates ciphertext + tag; this helper matches that format.

Implementation notes

  • Package: golang.org/x/crypto/chacha20poly1305.
  • Implementation: Seal returns ciphertext+tag, matching the UI output.
  • Notes: never reuse a nonce with the same key; AAD must match on decrypt.

Text encryption example

go
package main

import (
	"encoding/hex"
	"fmt"
	"strings"

	"golang.org/x/crypto/chacha20poly1305"
)

func chachaEncryptHex(plaintext string, keyHex string, nonceHex string) (string, error) {
	key, err := hex.DecodeString(keyHex)
	if err != nil {
		return "", err
	}
	nonce, err := hex.DecodeString(nonceHex)
	if err != nil {
		return "", err
	}
	aead, err := chacha20poly1305.New(key)
	if err != nil {
		return "", err
	}
	out := aead.Seal(nil, nonce, []byte(plaintext), nil)
	return hex.EncodeToString(out), nil
}

func main() {
	key := strings.Repeat("00", 32)
	nonce := strings.Repeat("01", 12)
	value, err := chachaEncryptHex("hello", key, nonce)
	if err != nil {
		panic(err)
	}
	fmt.Println(value)
}

File encryption example

go
package main

import (
	"encoding/hex"
	"fmt"
	"os"
	"strings"

	"golang.org/x/crypto/chacha20poly1305"
)

func chachaFile(path string, keyHex string, nonceHex string) (string, error) {
	key, err := hex.DecodeString(keyHex)
	if err != nil {
		return "", err
	}
	nonce, err := hex.DecodeString(nonceHex)
	if err != nil {
		return "", err
	}
	aead, err := chacha20poly1305.New(key)
	if err != nil {
		return "", err
	}
	data, err := os.ReadFile(path)
	if err != nil {
		return "", err
	}
	out := aead.Seal(nil, nonce, data, nil)
	return hex.EncodeToString(out), nil
}

func main() {
	file, err := os.CreateTemp("", "chacha-example-*.bin")
	if err != nil {
		panic(err)
	}
	defer os.Remove(file.Name())
	file.WriteString("hello")
	file.Close()

	key := strings.Repeat("00", 32)
	nonce := strings.Repeat("01", 12)
	value, err := chachaFile(file.Name(), key, nonce)
	if err != nil {
		panic(err)
	}
	fmt.Println(value)
}

Complete script (implementation + tests)

go
package main

import (
	"encoding/base64"
	"encoding/hex"
	"errors"
	"fmt"
	"strings"

	"golang.org/x/crypto/chacha20poly1305"
)

type KeyEncoding string

type CipherEncoding string

const (
	EncUTF8 KeyEncoding = "utf8"
	EncHex  KeyEncoding = "hex"
)

const (
	CipherHex    CipherEncoding = "hex"
	CipherBase64 CipherEncoding = "base64"
)

func decodeValue(value string, encoding KeyEncoding) ([]byte, error) {
	if encoding == EncHex {
		return hex.DecodeString(value)
	}
	return []byte(value), nil
}

func normalizeKey(key []byte) []byte {
	out := make([]byte, chacha20poly1305.KeySize)
	copy(out, key)
	return out
}

func encodeCiphertext(data []byte, encoding CipherEncoding) string {
	if encoding == CipherHex {
		return hex.EncodeToString(data)
	}
	return base64.StdEncoding.EncodeToString(data)
}

func decodeCiphertext(value string, encoding CipherEncoding) ([]byte, error) {
	if encoding == CipherHex {
		return hex.DecodeString(value)
	}
	return base64.StdEncoding.DecodeString(value)
}

func chachaEncrypt(plaintext []byte, key, nonce string, keyEnc, nonceEnc KeyEncoding, aad string, aadEnc KeyEncoding, outEnc CipherEncoding) (string, error) {
	keyBytes, err := decodeValue(key, keyEnc)
	if err != nil {
		return "", err
	}
	nonceBytes, err := decodeValue(nonce, nonceEnc)
	if err != nil {
		return "", err
	}
	if len(nonceBytes) != chacha20poly1305.NonceSize {
		return "", errors.New("nonce must be 12 bytes")
	}
	aead, err := chacha20poly1305.New(normalizeKey(keyBytes))
	if err != nil {
		return "", err
	}
	var aadBytes []byte
	if aad != "" {
		aadBytes, err = decodeValue(aad, aadEnc)
		if err != nil {
			return "", err
		}
	}
	ciphertext := aead.Seal(nil, nonceBytes, plaintext, aadBytes)
	return encodeCiphertext(ciphertext, outEnc), nil
}

func chachaDecrypt(combined, key, nonce string, keyEnc, nonceEnc KeyEncoding, aad string, aadEnc KeyEncoding, inEnc CipherEncoding) ([]byte, error) {
	keyBytes, err := decodeValue(key, keyEnc)
	if err != nil {
		return nil, err
	}
	nonceBytes, err := decodeValue(nonce, nonceEnc)
	if err != nil {
		return nil, err
	}
	if len(nonceBytes) != chacha20poly1305.NonceSize {
		return nil, errors.New("nonce must be 12 bytes")
	}
	aead, err := chacha20poly1305.New(normalizeKey(keyBytes))
	if err != nil {
		return nil, err
	}
	data, err := decodeCiphertext(combined, inEnc)
	if err != nil {
		return nil, err
	}
	var aadBytes []byte
	if aad != "" {
		aadBytes, err = decodeValue(aad, aadEnc)
		if err != nil {
			return nil, err
		}
	}
	return aead.Open(nil, nonceBytes, data, aadBytes)
}

func main() {
	key := strings.Repeat("00", 32)
	nonce := strings.Repeat("01", 12)

	ciphertext, err := chachaEncrypt([]byte("hello"), key, nonce, EncHex, EncHex, "context", EncUTF8, CipherHex)
	if err != nil {
		panic(err)
	}
	fmt.Println("cipher=", ciphertext)

	plain, err := chachaDecrypt(ciphertext, key, nonce, EncHex, EncHex, "context", EncUTF8, CipherHex)
	if err != nil {
		panic(err)
	}
	fmt.Println("plain=", string(plain))
}