package internal import ( "crypto/cipher" "crypto/rand" "fmt" "golang.org/x/crypto/argon2" "golang.org/x/crypto/chacha20poly1305" ) const ( SaltSize = 32 KeySize = uint32(32) // KeySize is 32 bytes (256 bits). KeyTime = uint32(5) KeyMemory = uint32(1024 * 64) // KeyMemory in KiB. here, 64 MiB. KeyThreads = uint8(4) ) // NewCipher creates a cipher using XChaCha20-Poly1305 // https://pkg.go.dev/golang.org/x/crypto/chacha20poly1305 // A salt is required to derive the key from a password using argon func NewCipher(password string, salt []byte) (cipher.AEAD, error) { key := argon2.IDKey([]byte(password), salt, KeyTime, KeyMemory, KeyThreads, KeySize) return chacha20poly1305.NewX(key) } // Encrypt to encrypt a plaintext with a password // Returns a byte slice with the generated salt, nonce and the ciphertext func Encrypt(plaintext []byte, password string) (result []byte, err error) { salt := make([]byte, SaltSize) if n, err := rand.Read(salt); err != nil || n != SaltSize { return nil, err } aead, err := NewCipher(password, salt) if err != nil { return nil, err } result = append(result, salt...) nonce := make([]byte, aead.NonceSize()) if m, err := rand.Read(nonce); err != nil || m != aead.NonceSize() { return nil, err } result = append(result, nonce...) ciphertext := aead.Seal(nil, nonce, plaintext, nil) result = append(result, ciphertext...) return result, nil } // Decrypt to decrypt a ciphertext with a password // Returns the plaintext func Decrypt(ciphertext []byte, password string) ([]byte, error) { if len(ciphertext) < SaltSize { return nil, fmt.Errorf("ciphertext is too short: cannot read salt") } salt := ciphertext[:SaltSize] aead, err := NewCipher(password, salt) if err != nil { return nil, err } if len(ciphertext) < SaltSize+aead.NonceSize() { return nil, fmt.Errorf("ciphertext is too short: cannot read nonce") } nonce := ciphertext[SaltSize : SaltSize+aead.NonceSize()] ciphertext = ciphertext[SaltSize+aead.NonceSize():] return aead.Open(nil, nonce, ciphertext, nil) }