Files
grafana/pkg/registry/apis/secret/encryption/cipher/provider/cipher_aesgcm.go
T
Dana Axinte 4d8678c7f2 SecretsManager: Add base encryption manager (#107562)
Co-authored-by: Michael Mandrus <michael.mandrus@grafana.com>
Co-authored-by: Matheus Macabu <macabu@users.noreply.github.com>
2025-07-03 11:29:14 +01:00

122 lines
2.7 KiB
Go

package provider
import (
"context"
"crypto/aes"
cpr "crypto/cipher"
"crypto/rand"
"io"
"github.com/grafana/grafana/pkg/registry/apis/secret/encryption/cipher"
)
const (
gcmSaltLength = 8
AesGcm = "aes-gcm"
)
var (
_ cipher.Encrypter = (*aesGcmCipher)(nil)
_ cipher.Decrypter = (*aesGcmCipher)(nil)
)
type aesGcmCipher struct {
// randReader is used to generate random bytes for the nonce.
// This allows us to change out the entropy source for testing.
randReader io.Reader
}
func NewAesGcmCipher() aesGcmCipher {
return aesGcmCipher{
randReader: rand.Reader,
}
}
func (c aesGcmCipher) Encrypt(_ context.Context, payload []byte, secret string) ([]byte, error) {
salt, err := c.readEntropy(gcmSaltLength)
if err != nil {
return nil, err
}
key, err := aes256CipherKey(secret, salt)
if err != nil {
return nil, err
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cpr.NewGCM(block)
if err != nil {
return nil, err
}
nonce, err := c.readEntropy(gcm.NonceSize())
if err != nil {
return nil, err
}
ciphertext := gcm.Seal(nil, nonce, payload, nil)
// Salt Nonce Encrypted
// | | Payload
// | | |
// | +---------v-------------+ |
// +-->SSSSSSSNNNNNNNEEEEEEEEE<--+
// +-----------------------+
prefix := append(salt, nonce...)
ciphertext = append(prefix, ciphertext...)
return ciphertext, nil
}
func (c aesGcmCipher) Decrypt(_ context.Context, payload []byte, secret string) ([]byte, error) {
// The input payload looks like:
// Salt Nonce Encrypted
// | | Payload
// | | |
// | +---------v-------------+ |
// +-->SSSSSSSNNNNNNNEEEEEEEEE<--+
// +-----------------------+
if len(payload) < gcmSaltLength {
// If we don't return here, we'd panic.
return nil, ErrPayloadTooShort
}
salt, payload := payload[:gcmSaltLength], payload[gcmSaltLength:]
// Can't get nonce until we get a size from the AEAD interface.
key, err := aes256CipherKey(secret, salt)
if err != nil {
return nil, err
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cpr.NewGCM(block)
if err != nil {
return nil, err
}
if len(payload) < gcm.NonceSize() {
// If we don't return here, we'd panic.
return nil, ErrPayloadTooShort
}
nonce, payload := payload[:gcm.NonceSize()], payload[gcm.NonceSize():]
return gcm.Open(nil, nonce, payload, nil)
}
func (c aesGcmCipher) readEntropy(n int) ([]byte, error) {
entropy := make([]byte, n)
if _, err := io.ReadFull(c.randReader, entropy); err != nil {
return nil, err
}
return entropy, nil
}