首页 > 解决方案 > 如何保护我的 Java AES 加密密钥

问题描述

我正在实现一个程序来使用 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

标签: javaencryption

解决方案


你有几个选项可以做到这一点。

如果您确实需要加密您的密钥,您可以使用 JCEKS 密钥库以加密形式存储它。这个密钥库非常适合,或者更好地说,可以用于存储单个密钥。您可以在这篇 DZone文章中看到如何使用它的示例。它将向您解释幕后发生的事情,并为您提供有关这种密钥库的一些背景知识。

您还可以使用基于密码的加密算法直接加密您的密钥。这个 stackoverflow 问题将为您提供一个很好的例子来实现这种解决方案。

如果您的要求只是如您在评论中指出的那样加密和解密属性文件中的属性,则可以使用Jasypt。这个库实际上是几个加密解决方案的包装器。

正如您在他们的文档中看到的那样,该库将为您提供一些用于一般用例的实用程序。

考虑以下示例,从上面的链接获得,非常适合您的需求。他们提供了一个属性文件:

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,它还将为您提供出色的框架集成

如您所见,与采用的解决方案无关,您总是需要一个真正保护您的密钥的密码。

向上述不同机制提供此密码的最佳方法是将其定义为环境变量:其想法是此信息仅对系统管理员和负责维护服务器软件的 IT 人员可见。

另一方面,所有主要的云提供商(AWS、GCP、Azure 等)都会为您提供服务(前两个是 KMS,Azure 是 Key Vault),即使您没有访问实际密钥:在这些场景中,使用这些服务可能是更好的方法。


推荐阅读