如何保护我的Java AES加密密钥

时间:2019-03-13 14:39:07

标签: java encryption

我正在实现一个使用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());
        }
    }
}

参考:https://wiki.sei.cmu.edu/confluence/display/java/MSC61-J.+Do+not+use+insecure+or+weak+cryptographic+algorithms

5 个答案:

答案 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)

简短的回答:如果您要保护自己的密钥不受逆向工程的影响,那么您将无能为力,一段时间之后,几乎每个安全系统都会被破解。但是您可以通过使用嵌套的加密算法和隐藏技术来使破解者感到困难。 您可以尝试:

  1. 在运行时生成密钥。
  2. 将密钥拆分并存储在不同的位置。例如。服务器和设备。
  3. 2FA两因素身份验证。
  4. 使密钥过期后才能对其进行破解。
  5. 通过Obsecurity的安全性。

最后,请记住,每个安全漏洞都可以破解,并且只能使它们变得更困难。

答案 2 :(得分:0)

我最好的答案是在运行时插入密钥。我的系统在Kubernetes上使用Spring-boot。因此,您可以使用Kube'Secrets保留密钥,并在通过Env启动应用程序时注入密钥。恶意演员通过某种方式 hack 来获取应用程序密钥的唯一方法。此外,微服务的优点在于您可以单独创建加密/解密微服务并使用健壮的语言和库。也许,Rust?

答案 3 :(得分:0)

您可以通过使用环境属性来解决此问题

  • 让我们假设您有2种代码环境,其中1种用于开发,另一种用于生产
  • 现在,您需要创建一个将用于保存文件路径的环境变量,例如,如果您使用的是基于Linux的系统,则可以在bashrc或内部配置文件中设置此变量。

例如;

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

滚动自己的

如果要自己滚动,可以通过实现服务来实现。这种方法有其优点和缺点,但是它为您提供了灵活性。

  1. 您的服务将生成密钥
  2. 客户端和服务使用Diffie-Hellman密钥交换进行通信以交换共享机密。此共享机密充当临时密钥(即临时密钥),并且可以在单笔交易后丢弃。
  3. 该服务使用共享密钥对生成的密钥(密钥包装)进行加密,并将其发送给客户端
  4. 客户端解密包装的密钥以恢复秘密密钥
  5. Service将密钥存储在一个关联映射中,该映射映射客户端公共ID =>密钥(已加密)。使用另一个仅服务器知道的长寿命密钥(例如:其公共密钥证书的私有密钥)对密钥进行加密。如果要为每个客户端存储多个密钥,请对其进行相应的建模,即,关联映射将成为<客户端公共ID,密钥名称> => <秘密密钥(已加密)>

您将需要添加一种机制来对客户端(由服务器)和服务器(由客户端)进行身份验证,以防止中间人攻击。尽管Diffie-Hellman密钥交换可以在不安全的协议(例如纯TCP或HTTP)上运行,但是您可以通过在HTTPS和服务器身份验证下运行它来获得更高级别的安全性(因为HTTPS使用公共密钥证书)。