我正在实现一个使用AES加密对字符串进行加密的程序,并且希望将“秘密密钥”保存在文件中,而不是在源代码中对其进行硬编码。
但是,这给我带来了一个问题,我该如何保护这个秘密密钥以防他人查看?
如果我要再次加密“ keyFile”,则必须再次处理相同的问题。
我该如何处理此类问题?
String keyFile = ...;
byte[] keyb = Files.readAllBytes(Paths.get(keyFile));
SecretKeySpec skey = new SecretKeySpec(keyb, "AES");
import java.util.Arrays;
import javax.crypto.*;
import javax.crypto.spec.*;
import java.security.*;
class Msc61 {
public static SecretKey generateKey() {
try {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
return kgen.generateKey();
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e.toString());
}
}
public static byte[] encrypt_cbc(SecretKey skey, String plaintext) {
/* Precond: skey is valid; otherwise IllegalStateException will be thrown. */
try {
byte[] ciphertext = null;
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
final int blockSize = cipher.getBlockSize();
byte[] initVector = new byte[blockSize];
(new SecureRandom()).nextBytes(initVector);
IvParameterSpec ivSpec = new IvParameterSpec(initVector);
cipher.init(Cipher.ENCRYPT_MODE, skey, ivSpec);
byte[] encoded = plaintext.getBytes(java.nio.charset.StandardCharsets.UTF_8);
ciphertext = new byte[initVector.length + cipher.getOutputSize(encoded.length)];
for (int i=0; i < initVector.length; i++) {
ciphertext[i] = initVector[i];
}
// Perform encryption
cipher.doFinal(encoded, 0, encoded.length, ciphertext, initVector.length);
return ciphertext;
} catch (NoSuchPaddingException | InvalidAlgorithmParameterException | ShortBufferException |
BadPaddingException | IllegalBlockSizeException | InvalidKeyException | NoSuchAlgorithmException e)
{
/* None of these exceptions should be possible if precond is met. */
throw new IllegalStateException(e.toString());
}
}
public static String decrypt_cbc(SecretKey skey, byte[] ciphertext)
throws BadPaddingException, IllegalBlockSizeException /* these indicate corrupt or malicious ciphertext */
{
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
final int blockSize = cipher.getBlockSize();
byte[] initVector = Arrays.copyOfRange(ciphertext, 0, blockSize);
IvParameterSpec ivSpec = new IvParameterSpec(initVector);
cipher.init(Cipher.DECRYPT_MODE, skey, ivSpec);
byte[] plaintext = cipher.doFinal(ciphertext, blockSize, ciphertext.length - blockSize);
return new String(plaintext);
} catch (NoSuchPaddingException | InvalidAlgorithmParameterException |
InvalidKeyException | NoSuchAlgorithmException e)
{
/* None of these exceptions should be possible if precond is met. */
throw new IllegalStateException(e.toString());
}
}
}
答案 0 :(得分:2)
您可以选择几种方法来实现。
如果您实际上需要加密密钥,则可以使用JCEKS密钥库以加密形式存储它。这个密钥库非常适合,或者更好的说,可以用来存储单个密钥。您可以在此DZone article中看到有关如何使用它的示例。它将为您提供有关幕后情况的解释,并为您提供有关此类密钥库的一些背景。
您还可以使用基于密码的加密算法直接加密您的密钥。 This stackoverflow question将为您提供实施此类解决方案的好例子。
如果仅要求按照注释中的指示加密和解密属性文件中的属性,则可以使用Jasypt。实际上,该库是几种加密解决方案的包装。
正如您在他们的documentation中所见,该库将为您提供some utilities的一般用例。
请考虑以下示例,该示例是从上面的链接获得的,非常适合您的需求。它们提供了一个属性文件:
datasource.driver=com.mysql.jdbc.Driver
datasource.url=jdbc:mysql://localhost/reportsdb
datasource.username=reportsUser
datasource.password=ENC(G6N718UuyPE5bHyWKyuLQSm02auQPUtm)
它们显示了如何读取此属性:
/*
* First, create (or ask some other component for) the adequate encryptor for
* decrypting the values in our .properties file.
*/
StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
encryptor.setPassword("jasypt"); // could be got from web, env variable...
encryptor.setAlgorithm("PBEWithHMACSHA512AndAES_256");
encryptor.setIvGenerator(new RandomIvGenerator());
/*
* Create our EncryptableProperties object and load it the usual way.
*/
Properties props = new EncryptableProperties(encryptor);
props.load(new FileInputStream("/path/to/my/configuration.properties"));
/*
* To get a non-encrypted value, we just get it with getProperty...
*/
String datasourceUsername = props.getProperty("datasource.username");
/*
* ...and to get an encrypted value, we do exactly the same. Decryption will
* be transparently performed behind the scenes.
*/
String datasourcePassword = props.getProperty("datasource.password");
// From now on, datasourcePassword equals "reports_passwd"...
如果您使用的是Spring,它还会给您great framework integration。
如您所见,独立于所采用的解决方案,您将始终需要一个可以真正保护密钥的密码。
向上述不同机制提供此密码的最佳方法是将其定义为环境变量:其想法是该信息仅对系统管理员和负责服务器维护的IT人员可见。软件。
另一方面,所有主要的云提供商(AWS,GCP,Azure等)将为您提供服务(前两个为KMS,Azure的Key Vault),这些服务可让您安全地加密和解密信息即使无法访问实际密钥:在这些情况下,使用这些服务可能也是更好的方法。
答案 1 :(得分:1)
简短的回答:如果您要保护自己的密钥不受逆向工程的影响,那么您将无能为力,一段时间之后,几乎每个安全系统都会被破解。但是您可以通过使用嵌套的加密算法和隐藏技术来使破解者感到困难。 您可以尝试:
最后,请记住,每个安全漏洞都可以破解,并且只能使它们变得更困难。
答案 2 :(得分:0)
我最好的答案是在运行时插入密钥。我的系统在Kubernetes上使用Spring-boot。因此,您可以使用Kube'Secrets保留密钥,并在通过Env启动应用程序时注入密钥。恶意演员通过某种方式 hack 来获取应用程序密钥的唯一方法。此外,微服务的优点在于您可以单独创建加密/解密微服务并使用健壮的语言和库。也许,Rust?
答案 3 :(得分:0)
您可以通过使用环境属性来解决此问题
例如;
secret.private.key.path=home-directory/secret/private.key
secret.public.key.path=home-directory/secret/public.key
您将创建2个公钥和私钥对,一个用于开发(任何与您合作(共享)的开发人员都可以访问它),另一个用于生产中,因此基于生产的私钥始终仅受授权保护人有生产的机会。
在代码System.getenv方法内部,该方法将检索该env变量
String privateKeyPath = System.getenv("secret.private.key.path")
String publicKeyPath = System.getenv("secret.public.key.path")
答案 4 :(得分:0)
正如其他人指出的那样,最好的选择是使用密钥容器存储。这假设应用程序的用户上下文是可信任的。如果不能,那么所有的选择都将落空,因为这些解决方案在信任用户上下文的基础上起作用。如果您不能信任应用程序的用户上下文,那么您将面临的问题不仅仅在于密钥库访问受到损害。因此,请全面考虑应用程序的安全性。以下解决方案解决了各种平台上的密钥存储问题。如果您担心以某种方式在用户上下文中运行其他应用程序,请访问滚动自己的应用程序。
密钥存储解决方案
对于Java:Java Keystore for Storing and Retrieving sensitive strings (Redhat)
对于Windows:Key Storage and Retrieval
对于MacOS:Storing Keys in the Secure Enclave
对于Android:Android Keystore System
对于NodeJS:keytar: A native Node module to get, add, replace, and delete passwords in system's keychain
滚动自己的
如果要自己滚动,可以通过实现服务来实现。这种方法有其优点和缺点,但是它为您提供了灵活性。
您将需要添加一种机制来对客户端(由服务器)和服务器(由客户端)进行身份验证,以防止中间人攻击。尽管Diffie-Hellman密钥交换可以在不安全的协议(例如纯TCP或HTTP)上运行,但是您可以通过在HTTPS和服务器身份验证下运行它来获得更高级别的安全性(因为HTTPS使用公共密钥证书)。