Firebase Cloud Messaging + Django:如何安全地存储服务帐户的私钥?

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

我刚开始在我的 Django 后端实现 FCM。

我遇到的问题如下

the docs 中,您被告知生成私钥 JSON 文件并安全地存储它。 通常我将密钥存储在 os.env 变量中。但这是不可能的,因为这是一个完整的文件,而不仅仅是一个值。同样在同一页面上,文档告诉您如何获取请求令牌:

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

如您所见,库需要直接访问该文件。

所以我的问题是?我如何安全地存储它?我目前在 heroku 上托管,所以我的版本控制系统中需要它。

大多数团队将 JSON 文件置于版本控制之外并手动添加到环境中。

虽然与将值保存在环境变量中不完全相同,但它具有相似的安全性。只能访问版本控制的开发人员无法访问密钥,并且开发人员不能意外 运行 在他们自己的系统上使用生产密钥。但另一方面:有权访问生产服务器的人在这两种情况下都可以获得密钥。

我们的做法:

加密json字符串/存储在资源文件中 解密密钥,存储为 Env 变量。

创建凭据时解密加密字符串。

这是基于 ivanspenchev 在他的 post 中的建议。

流程是:读取 json 数据 -> 使用密钥加密 -> 使用密钥解密。我没有使用他建议的加密算法,因为我不喜欢它。

但基本思想是,您有一个 class "EncryptEngine" 做两件事,加密字符串和解密字符串。

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);
 }
}

请阅读以下为 firebase_admin.credentials.Certificate() 提供的文档。

因此,您可以通过从已解析的密钥文件内容中传递字典来创建凭据证书。密钥文件内容可以来自加密的环境变量值。使用此凭据初始化应用程序。