package security import ( "crypto/aes" "crypto/cipher" "crypto/rand" "encoding/base64" "io" "github.com/pkg/errors" ) type IEncryptor interface { EncryptChunk([]byte) []byte DecryptChunk([]byte) ([]byte, error) EncryptStringToBase64(string) string DecryptBase64ToString(string) (string, error) Close() } type Encryptor struct { key []byte iv []byte gcm_encryptor cipher.AEAD block_auth []byte } // NewEncryptor create new GCM - expects the key to be 32 bytes func NewEncryptor(encryption_key []byte, auth []byte, nonce []byte) (IEncryptor, []byte, error) { cypher, err := aes.NewCipher(encryption_key) if err != nil { return nil, nil, errors.WithStack(err) } gcm, err := cipher.NewGCM(cypher) if err != nil { return nil, nil, errors.WithStack(err) } if nonce != nil && len(nonce) != gcm.NonceSize() { return nil, nil, errors.Errorf("nonce is incorrect size %v", gcm.NonceSize()) } if nonce == nil { nonce = make([]byte, gcm.NonceSize()) _, err = io.ReadFull(rand.Reader, nonce) if err != nil { return nil, nil, errors.WithStack(err) } } return &Encryptor{key: encryption_key, iv: nonce, gcm_encryptor: gcm, block_auth: auth}, nonce, nil } // EncryptChunk takes a chunk and ecnrypts it using GCM func (s *Encryptor) EncryptChunk(chunk []byte) []byte { return s.gcm_encryptor.Seal(chunk[:0], s.iv, chunk, s.block_auth) } // DecryptChunk takes a chunk and decrypts it using GCM func (s *Encryptor) DecryptChunk(chunk []byte) ([]byte, error) { data, err := s.gcm_encryptor.Open(chunk[:0], s.iv, chunk, s.block_auth) return data, errors.WithStack(err) } // EncryptStringToBase64 takes a string as input and encrypts it and encodes it for transportability func (s *Encryptor) EncryptStringToBase64(input string) string { data := s.EncryptChunk([]byte(input)) return base64.RawStdEncoding.EncodeToString(data) } // DecryptStringToBase64 takes a string as input and decodes it and decrypts it from transport func (s *Encryptor) DecryptBase64ToString(input string) (string, error) { decoded, err := base64.RawStdEncoding.DecodeString(input) if err != nil { return "", errors.Wrapf(err, "request %s", input) } decrypted, err := s.DecryptChunk(decoded) if err != nil { return "", err } return string(decrypted), nil } func (s *Encryptor) Close() { s.gcm_encryptor = nil s.iv = nil s.key = nil }