J. Hesters
J. Hesters

Reputation: 14768

Firebase Cloud Messaging + Django: How to securely store the service account's private key?

I just started implementing FCM into my Django backend.

The problem I encountered is the following.

In the docs you are told to generate a private key JSON file and securely store it. Usually I store my keys in an os.env variable. But this is not possible, since this is a whole file and not just a value. Also on the same page, the doc tells you how to get a request token:

def _get_access_token():
  """Retrieve a valid access token that can be used to authorize requests.

  :return: Access token.
  """
  credentials = ServiceAccountCredentials.from_json_keyfile_name(
      'service-account.json', SCOPES)
  access_token_info = credentials.get_access_token()
  return access_token_info.access_token

As you can see here, the library needs direct access to the file.

So my question is? How do I securely store this? I'm currently hosting on heroku, so I need this in my version control system.

Upvotes: 0

Views: 1184

Answers (4)

Akshay Lohi
Akshay Lohi

Reputation: 71

please read the below documentation provided for firebase_admin.credentials.Certificate().

fd

So, you can create a credential Certificate by passing a dict from the parsed key file contents. key file content can come from an encrypted environment variable value. Use this credential to initialize the app.

Upvotes: 1

thefolenangel
thefolenangel

Reputation: 1060

this is Based on what ivanspenchev suggested in his post.

Flow is : read json data -> encrypt with a key - > decrypt with a key. I am not using his suggested encrypt algo, because I dont like it.

But the basic idea is, that you have a class "EncryptEngine" that does two things, encrypts a string, and decrypts a string.

public class EncryptEngine
{

Cipher ecipher;
Cipher dcipher;
// 8-byte Salt
byte[] salt = {
    (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32,
    (byte) 0x56, (byte) 0x35, (byte) 0xE3, (byte) 0x03
};
// Iteration count
int iterationCount = 19;

public EncryptEngine() {

}

/**
 *
 * @param secretKey Key used to encrypt data
 * @param plainText Text input to be encrypted
 * @return Returns encrypted text
 * @throws java.security.NoSuchAlgorithmException
 * @throws java.security.spec.InvalidKeySpecException
 * @throws javax.crypto.NoSuchPaddingException
 * @throws java.security.InvalidKeyException
 * @throws java.security.InvalidAlgorithmParameterException
 * @throws java.io.UnsupportedEncodingException
 * @throws javax.crypto.IllegalBlockSizeException
 * @throws javax.crypto.BadPaddingException
 *
 */
public String encrypt(String secretKey, String plainText)
        throws NoSuchAlgorithmException,
        InvalidKeySpecException,
        NoSuchPaddingException,
        InvalidKeyException,
        InvalidAlgorithmParameterException,
        UnsupportedEncodingException,
        IllegalBlockSizeException,
        BadPaddingException {
    //Key generation for enc and desc
    KeySpec keySpec = new PBEKeySpec(secretKey.toCharArray(), salt, iterationCount);
    SecretKey key = SecretKeyFactory.getInstance("PBEWithMD5AndDES").generateSecret(keySpec);
    // Prepare the parameter to the ciphers
    AlgorithmParameterSpec paramSpec = new PBEParameterSpec(salt, iterationCount);

    //Enc process
    ecipher = Cipher.getInstance(key.getAlgorithm());
    ecipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
    String charSet = "UTF-8";
    byte[] in = plainText.getBytes(charSet);
    byte[] out = ecipher.doFinal(in);
    String encStr = new String(Base64.getEncoder().encode(out));
    return encStr;
}

/**
 * @param secretKey Key used to decrypt data
 * @param encryptedText encrypted text input to decrypt
 * @return Returns plain text after decryption
 * @throws java.security.NoSuchAlgorithmException
 * @throws java.security.spec.InvalidKeySpecException
 * @throws javax.crypto.NoSuchPaddingException
 * @throws java.security.InvalidKeyException
 * @throws java.security.InvalidAlgorithmParameterException
 * @throws java.io.UnsupportedEncodingException
 * @throws javax.crypto.IllegalBlockSizeException
 * @throws javax.crypto.BadPaddingException
 */
public String decrypt(String secretKey, String encryptedText)
        throws NoSuchAlgorithmException,
        InvalidKeySpecException,
        NoSuchPaddingException,
        InvalidKeyException,
        InvalidAlgorithmParameterException,
        UnsupportedEncodingException,
        IllegalBlockSizeException,
        BadPaddingException,
        IOException {
    //Key generation for enc and desc
    KeySpec keySpec = new PBEKeySpec(secretKey.toCharArray(), salt, iterationCount);
    SecretKey key = SecretKeyFactory.getInstance("PBEWithMD5AndDES").generateSecret(keySpec);
    // Prepare the parameter to the ciphers
    AlgorithmParameterSpec paramSpec = new PBEParameterSpec(salt, iterationCount);
    //Decryption process; same key will be used for decr
    dcipher = Cipher.getInstance(key.getAlgorithm());
    dcipher.init(Cipher.DECRYPT_MODE, key, paramSpec);
    byte[] enc = Base64.getDecoder().decode(encryptedText);
    byte[] utf8 = dcipher.doFinal(enc);
    String charSet = "UTF-8";
    String plainStr = new String(utf8, charSet);
    return plainStr;
}    
public static void main(String[] args) throws Exception {
    EncryptEngine cryptoUtil=new EncryptEngine();
    String key="ezeon8547";   
    JsonFactory f = new JsonFactory();
    JsonParser jp = 
    f.createJsonParser(/MyDocuments/Repo/Myproject/service-account.json);
    String plain;
  while (jp.nextToken() == JsonToken.START_OBJECT)) {
         plain += jp.toString();
       }
    String enc=cryptoUtil.encrypt(key, plain);
    System.out.println("Original text: "+plain);
    System.out.println("Encrypted text: "+enc);
    String plainAfter=cryptoUtil.decrypt(key, enc);
    System.out.println("Original text after decryption: "+plainAfter);
 }
}

Upvotes: 2

ivanspenchev
ivanspenchev

Reputation: 46

The way we do it:

Encrypt the json string / Store in Resource file Decryption key, stored as Env variable.

When creating the credentials decrypt the encrypted string.

Upvotes: 3

Frank van Puffelen
Frank van Puffelen

Reputation: 598740

Most teams keep the JSON file out of version control and add to the environment manually.

While not exactly the same as keeping the values in an environment variable, it has similar security. A developer with only access to version control can't access the key, and a developer cannot accidentally run on their own system with the production keys. But on the other hand: someone who has access to the production server, can get the key in both cases.

Upvotes: 1

Related Questions