Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: encrypt column fcm_service_account in push table #1291

Open
hvn2k1 opened this issue Oct 22, 2024 · 3 comments
Open

feat: encrypt column fcm_service_account in push table #1291

hvn2k1 opened this issue Oct 22, 2024 · 3 comments

Comments

@hvn2k1
Copy link
Contributor

hvn2k1 commented Oct 22, 2024

Describe the feature you'd like

  • When create Push API is requested, encrypt fcm_service_account before saving to database (column fcm_service_account from push table)
  • Create a function to decrypt whenever we needs to query fcm_service_account (e.g. get push before send notification via FCM)

Background

Another protection layer in case database got leaked then sensitive data, in this case, fcm service account key, were not exposed.

Alternative solutions

No response

@hvn2k1
Copy link
Contributor Author

hvn2k1 commented Oct 29, 2024

Solution:

  • Encrypt and decrypt using go-cipher https://pkg.go.dev/crypto/cipher
  • Secret key will be generated and set in environment variables with k8s secret so if the database is leak, the FCM key cannot be exposed
  • API and subcriber flow:

encryption

@cre8ivejp
Copy link
Member

Solution:

  • Encrypt and decrypt using go-cipher https://pkg.go.dev/crypto/cipher
  • Secret key will be generated and set in environment variables with k8s secret so if the database is leak, the FCM key cannot be exposed
  • API and subcriber flow:

encryption

How do you plan generating the secret key?

@hvn2k1
Copy link
Contributor Author

hvn2k1 commented Oct 29, 2024

I'll use random function in golang to generate key, below code is an example, from https://dev.to/breda/secret-key-encryption-with-go-using-aes-316d

package main

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"fmt"
)

var (
	// We're using a 32 byte long secret key.
	// This is probably something you generate first
	// then put into and environment variable.
	secretKey string
)

func generateSecretKey() string {
	key := make([]byte, 16)

	_, err := rand.Read(key)
	if err != nil {
		panic(err)
	}

	return fmt.Sprintf("%x", key)
}

func encrypt(plaintext string) string {
	aes, err := aes.NewCipher([]byte(secretKey))
	if err != nil {
		panic(err)
	}

	gcm, err := cipher.NewGCM(aes)
	if err != nil {
		panic(err)
	}

	// We need a 12-byte nonce for GCM (modifiable if you use cipher.NewGCMWithNonceSize())
	// A nonce should always be randomly generated for every encryption.
	nonce := make([]byte, gcm.NonceSize())
	_, err = rand.Read(nonce)
	if err != nil {
		panic(err)
	}

	// ciphertext here is actually nonce+ciphertext
	// So that when we decrypt, just knowing the nonce size
	// is enough to separate it from the ciphertext.
	ciphertext := gcm.Seal(nonce, nonce, []byte(plaintext), nil)

	return string(ciphertext)
}

func decrypt(ciphertext string) string {
	aes, err := aes.NewCipher([]byte(secretKey))
	if err != nil {
		panic(err)
	}

	gcm, err := cipher.NewGCM(aes)
	if err != nil {
		panic(err)
	}

	// Since we know the ciphertext is actually nonce+ciphertext
	// And len(nonce) == NonceSize(). We can separate the two.
	nonceSize := gcm.NonceSize()
	nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]

	plaintext, err := gcm.Open(nil, []byte(nonce), []byte(ciphertext), nil)
	if err != nil {
		panic(err)
	}

	return string(plaintext)
}

func main() {
	secretKey = generateSecretKey()
	fmt.Printf("Secret key: %s \n", secretKey)

	// This will successfully encrypt & decrypt
	ciphertext1 := encrypt("This is some sensitive information")
	fmt.Printf("Encrypted ciphertext 1: %x \n", ciphertext1)

	plaintext1 := decrypt(ciphertext1)
	fmt.Printf("Decrypted plaintext 1: %s \n", plaintext1)

	// This will successfully encrypt & decrypt as well.
	ciphertext2 := encrypt("Hello")
	fmt.Printf("Encrypted ciphertext 2: %x \n", ciphertext2)

	plaintext2 := decrypt(ciphertext2)
	fmt.Printf("Decrypted plaintext 2: %s \n", plaintext2)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: No status
Development

No branches or pull requests

2 participants