Encrypt connector sensitive data
Add database field level encryption level to sensitive data to reduce the risk in term of data leak. I dedice to have only one key for now in a near future I may move to one master key and one encryption key per organization to make rotation easiest. I don't use built-in pg_crypto function to have clear seperation and avoid any encryption key leak. Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
116
pkg/crypto/cipher/cipher.go
Normal file
116
pkg/crypto/cipher/cipher.go
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package cipher
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type (
|
||||
EncryptionKey [32]byte
|
||||
)
|
||||
|
||||
func NewEncryptionKey(key string) (EncryptionKey, error) {
|
||||
if len(key) != 32 {
|
||||
return EncryptionKey{}, fmt.Errorf("key must be 32 bytes for AES-256")
|
||||
}
|
||||
|
||||
var encryptionKey EncryptionKey
|
||||
copy(encryptionKey[:], key)
|
||||
|
||||
return encryptionKey, nil
|
||||
}
|
||||
|
||||
func (k EncryptionKey) Bytes() []byte {
|
||||
return k[:]
|
||||
}
|
||||
|
||||
func (k *EncryptionKey) UnmarshalJSON(data []byte) error {
|
||||
var key string
|
||||
if err := json.Unmarshal(data, &key); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return k.UnmarshalText([]byte(key))
|
||||
}
|
||||
|
||||
func (k *EncryptionKey) UnmarshalText(text []byte) error {
|
||||
decoded, err := base64.StdEncoding.DecodeString(string(text))
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot decode base64 key: %w", err)
|
||||
}
|
||||
|
||||
if len(decoded) != 32 {
|
||||
return fmt.Errorf("key must be 32 bytes for AES-256, got %d bytes after base64 decoding", len(decoded))
|
||||
}
|
||||
|
||||
copy(k[:], decoded)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k EncryptionKey) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(base64.StdEncoding.EncodeToString(k[:]))
|
||||
}
|
||||
|
||||
func Encrypt(data []byte, key EncryptionKey) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key.Bytes())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nonce := make([]byte, 12)
|
||||
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
aesgcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ciphertext := aesgcm.Seal(nonce, nonce, data, nil)
|
||||
|
||||
return ciphertext, nil
|
||||
}
|
||||
|
||||
func Decrypt(data []byte, key EncryptionKey) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key.Bytes())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
aesgcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(data) < 12 {
|
||||
return nil, fmt.Errorf("ciphertext too short")
|
||||
}
|
||||
nonce, ciphertext := data[:12], data[12:]
|
||||
|
||||
plaintext, err := aesgcm.Open(nil, nonce, ciphertext, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot decrypt: %w", err)
|
||||
}
|
||||
|
||||
return plaintext, nil
|
||||
}
|
||||
Reference in New Issue
Block a user