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() 提供的文档。
因此,您可以通过从已解析的密钥文件内容中传递字典来创建凭据证书。密钥文件内容可以来自加密的环境变量值。使用此凭据初始化应用程序。
我刚开始在我的 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() 提供的文档。
因此,您可以通过从已解析的密钥文件内容中传递字典来创建凭据证书。密钥文件内容可以来自加密的环境变量值。使用此凭据初始化应用程序。