Co-authored-by: Claude <developers@lethean.io> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
60 lines
1.9 KiB
Go
60 lines
1.9 KiB
Go
// Package chachapoly provides XChaCha20-Poly1305 authenticated encryption.
|
|
//
|
|
// Encrypt prepends a random nonce to the ciphertext; Decrypt extracts it.
|
|
// The key must be 32 bytes (256 bits).
|
|
//
|
|
// Ported from Enchantrix (github.com/Snider/Enchantrix/pkg/crypt/std/chachapoly).
|
|
package chachapoly
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"fmt"
|
|
"io"
|
|
|
|
"golang.org/x/crypto/chacha20poly1305"
|
|
)
|
|
|
|
// Encrypt encrypts plaintext using XChaCha20-Poly1305.
|
|
// The key must be exactly 32 bytes. A random 24-byte nonce is generated
|
|
// and prepended to the returned ciphertext.
|
|
func Encrypt(plaintext, key []byte) ([]byte, error) {
|
|
aead, err := chacha20poly1305.NewX(key)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("chachapoly: failed to create AEAD: %w", err)
|
|
}
|
|
|
|
nonce := make([]byte, aead.NonceSize(), aead.NonceSize()+len(plaintext)+aead.Overhead())
|
|
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
|
return nil, fmt.Errorf("chachapoly: failed to generate nonce: %w", err)
|
|
}
|
|
|
|
return aead.Seal(nonce, nonce, plaintext, nil), nil
|
|
}
|
|
|
|
// Decrypt decrypts ciphertext produced by Encrypt using XChaCha20-Poly1305.
|
|
// The key must be exactly 32 bytes. The nonce is extracted from the first
|
|
// 24 bytes of the ciphertext.
|
|
func Decrypt(ciphertext, key []byte) ([]byte, error) {
|
|
aead, err := chacha20poly1305.NewX(key)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("chachapoly: failed to create AEAD: %w", err)
|
|
}
|
|
|
|
minLen := aead.NonceSize() + aead.Overhead()
|
|
if len(ciphertext) < minLen {
|
|
return nil, fmt.Errorf("chachapoly: ciphertext too short: got %d bytes, need at least %d bytes", len(ciphertext), minLen)
|
|
}
|
|
|
|
nonce, ciphertext := ciphertext[:aead.NonceSize()], ciphertext[aead.NonceSize():]
|
|
|
|
decrypted, err := aead.Open(nil, nonce, ciphertext, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("chachapoly: decryption failed: %w", err)
|
|
}
|
|
|
|
if len(decrypted) == 0 {
|
|
return []byte{}, nil
|
|
}
|
|
|
|
return decrypted, nil
|
|
}
|