package crypto import ( "crypto/rand" "encoding/hex" "errors" "io" "golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/curve25519" "golang.org/x/crypto/argon2" ) const ( KeySize = 32 NonceSize = chacha20poly1305.NonceSizeX ) func GenerateKey() ([]byte, error) { key := make([]byte, KeySize) if _, err := rand.Read(key); err != nil { return nil, err } return key, nil } func GenerateNonce() ([]byte, error) { nonce := make([]byte, NonceSize) if _, err := rand.Read(nonce); err != nil { return nil, err } return nonce, nil } func DeriveKey(password string, salt []byte) []byte { return argon2.IDKey([]byte(password), salt, 3, 64*1024, 4, KeySize) } func Encrypt(plaintext []byte, key []byte) ([]byte, []byte, error) { aead, err := chacha20poly1305.NewX(key) if err != nil { return nil, nil, err } nonce := make([]byte, aead.NonceSize()) if _, err := io.ReadFull(rand.Reader, nonce); err != nil { return nil, nil, err } ciphertext := aead.Seal(nil, nonce, plaintext, nil) return ciphertext, nonce, nil } func Decrypt(ciphertext []byte, key []byte, nonce []byte) ([]byte, error) { aead, err := chacha20poly1305.NewX(key) if err != nil { return nil, err } if len(nonce) != aead.NonceSize() { return nil, errors.New("invalid nonce size") } plaintext, err := aead.Open(nil, nonce, ciphertext, nil) if err != nil { return nil, err } return plaintext, nil } func GenerateKeyPair() ([]byte, []byte, error) { priv := make([]byte, 32) if _, err := rand.Read(priv); err != nil { return nil, nil, err } priv[0] &= 248 priv[31] &= 127 priv[31] |= 64 pub, err := curve25519.X25519(priv, curve25519.Basepoint) if err != nil { return nil, nil, err } return priv, pub, nil } func ComputeSharedSecret(privateKey, publicKey []byte) ([]byte, error) { return curve25519.X25519(privateKey, publicKey) } func KeyToString(key []byte) string { return hex.EncodeToString(key) } func StringToKey(s string) ([]byte, error) { return hex.DecodeString(s) }