天天看點

SpringBoot 配置檔案加密

作者:皮蛋solo粥哈

本章将對SpringBoot配置檔案中的資料加密做自定義開發. 在SpringBoot開發過程中配置檔案是明文存放在application.yml或者application.properties檔案中,這種配置方式會帶來一定的安全隐患,本章将對這個問題提出一個簡單的解決方案。

編碼

首先需要确定一個加密解密方式,本文采用RSA進行加密解密,首先編寫加密解密的代碼,注意RSA加密解密需要使用到公鑰和私鑰,公鑰私鑰的生成代碼如下:

public static void generateKey() throws NoSuchAlgorithmException {
   KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(EncryptionType.RSA);
   keyPairGen.initialize(1024, new SecureRandom());
   KeyPair keyPair = keyPairGen.generateKeyPair();
   RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();   // 得到私鑰
   RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();  // 得到公鑰
   String publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded()));
   String privateKeyString = new String(Base64.encodeBase64((privateKey.getEncoded())));
   System.out.println("目前生成的公鑰= " + publicKeyString);
   System.out.println("目前生成的私鑰= " + privateKeyString);
}           

加密代碼如下:

public static String encrypt(String str, String publicKey) throws NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException, NoSuchPaddingException, InvalidKeyException, InvalidKeySpecException {
                byte[] decoded = Base64.decodeBase64(publicKey);
                RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance(EncryptionType.RSA).generatePublic(new X509EncodedKeySpec(decoded));
                Cipher cipher = Cipher.getInstance(EncryptionType.RSA);
                cipher.init(Cipher.ENCRYPT_MODE, pubKey);
                return Base64.encodeBase64String(cipher.doFinal(str.getBytes(StandardCharsets.UTF_8)));
        }           

解密代碼如下:

public static String decrypt(String str, String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, BadPaddingException, IllegalBlockSizeException, InvalidKeyException {
   byte[] inputByte = Base64.decodeBase64(str.getBytes(StandardCharsets.UTF_8));
   byte[] decoded = Base64.decodeBase64(privateKey);
   RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance(EncryptionType.RSA).generatePrivate(new PKCS8EncodedKeySpec(decoded));
   Cipher cipher = Cipher.getInstance(EncryptionType.RSA);
   cipher.init(Cipher.DECRYPT_MODE, priKey);
   return new String(cipher.doFinal(inputByte));
}           

上述代碼為基本的加密解密工具,加下來需要在配置檔案中确定哪些配置是需要進行解密的,本例将采用自定義字首+字尾的方式進行比對,字首為PWD[,字尾為],如果在配置檔案中屬性值是以字首字尾包裹的那麼這個資料會被進行解密操作,

通過前文的加密工具可以先進行一次密碼加密加密原文為1234qwer,公鑰:

MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCBOkkkvjbOQ6UTCo8U4bRC/EcEtxz8haHg6lueM3NBbH3eIT7kfwQFOqj1h1qPGcQNeyn4vxzMWBAKzSQehjqVBL7/8GN7EZ7TEaUuWO+8qsuZnOdrztX7bNKACnks+SelmtbrbnFKUMAq2c2mS0o1V6iwyRxJYLGaHGXnz4KSkwIDAQAB           

私鑰:

MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAIE6SSS+Ns5DpRMKjxThtEL8RwS3HPyFoeDqW54zc0Fsfd4hPuR/BAU6qPWHWo8ZxA17Kfi/HMxYEArNJB6GOpUEvv/wY3sRntMRpS5Y77yqy5mc52vO1fts0oAKeSz5J6Wa1utucUpQwCrZzaZLSjVXqLDJHElgsZocZefPgpKTAgMBAAECgYAFaCDjTqoQWzgu4cQ2xXK7Ur7N7bNixVyOgn+u0MxDsnxZrN5qxP2wElI7Y5xgXF2diseoxqY3zn9tVEPsmwUcY73naoosx9V8oExgT/BUkZYIzj1ei08zOr984zl3dbFcxOCRvqywXj9FAAGx1mhmCzFCIauJg3aX0S9mt5/CwQJBAMYZsmMQ9owoXZuSclKVRfMHFpAPhQlcBM4xadhX0IRYATgNTxpESmcCoGWvyw3bvieNJyC9Njx6X4FJ2EZUzhECQQCm/2IM5MlsCwyKtME5RPFna2hSqYU80UzkNfDIyMokcU2JUI4Fhigog4ol0GFMiMBsHIjS+cJiAwNbIsq5rsJjAkA94yVBobkETFACHBwvBIdXxy0bUF3lcKPnrrQ8bCKuVbf7xNyjfhYoXD+zxNmQuMeNH6HLrpDVD/3qLCGuxyuhAkAiLPl/8gJWnhw+9qbkdXuB0rVS1WZy/9JgkblpHc5gjt9zTo0CDGaDhAftnSuMYiAe/+fwZTSmoj85k3ExdtZJAkEArJuG/NWY9HP4p7jtZX9rMokyB3517v7HQdJKBDIlOzseRC/roCvU8LQ/URDFBUqXCRgedxgW+0ZmKFf4xeawqw==           

加密結果為:

PWD[bMw8oqC/ma31JqF0DCuf5QWqSFRMigYw3fMBIIIfJ85vnmNnFbH9IcJfUHgbSmNHeITffToODwAygy4vKdzu6o1i1UQOd8w4nPKhnVJCLKqW5jmc3Yw+FkTIRBp63NJWzECVnRHqEK+bTxPMa1gfKql/2U45XxqeDSZOEXGeA+E=]           

得到這個資料後将其配置在application.properties檔案中,具體内容如下

server.port=8080
spring.application.name=test-app
spring.datasource.password=PWD[bMw8oqC/ma31JqF0DCuf5QWqSFRMigYw3fMBIIIfJ85vnmNnFbH9IcJfUHgbSmNHeITffToODwAygy4vKdzu6o1i1UQOd8w4nPKhnVJCLKqW5jmc3Yw+FkTIRBp63NJWzECVnRHqEK+bTxPMa1gfKql/2U45XxqeDSZOEXGeA+E=]           

接下來需要進一步解決的是如何将這個加密字元串進行解密,這裡需要使用BeanFactoryPostProcessor接口對環境變量進行修改,具體實作代碼如下:

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

   MutablePropertySources propertySources = environment.getPropertySources();

   for (PropertySource<?> propertySource : propertySources) {
      if (propertySource instanceof OriginTrackedMapPropertySource
      ) {
         OriginTrackedMapPropertySource om = (OriginTrackedMapPropertySource) propertySource;
         Map<String, Object> source = om.getSource();
         source.forEach((k, v) -> {
            String property = environment.getProperty(k);
            if (hasPreAndSuf(property)) {
               LOG.info("開始處理 k = [{}]", k);
               try {
                  String relay = splitPreAndSuf(property, this.prefix, this.suffix);
                  String decrypt = RSAEncrypt.decrypt(relay, getPrivateKey(environment));
                  source.put(k, decrypt);
               }
               catch (Exception e) {
                  LOG.error("e = ", e);
               }
            }
         });
      }
   }
}           

處理邏輯如下:

  1. 提取環境配置中的所有配置屬性
  2. 判斷配置屬性是否是OriginTrackedMapPropertySource類型,該類型的資料是在application.yaml中的内容
  3. 處理OriginTrackedMapPropertySource對象的value值,如果value包含自定義字首字尾則進行解密

在本例中對于公鑰私鑰以及字首字尾是允許自定義的,開發者隻需要在配置檔案中根據下面表格進行填寫即可

屬性名稱 屬性含義 預設值
encryption.prefix 字首 PWD[
encryption.suffix 字尾 ]
encryption.rsa.publicKey 公鑰
encryption.rsa.privateKey 私鑰

注意:**為了便捷操作開放了配置檔案形式的公鑰秘鑰的配置,這部配置設定置可以在測試環境中開發環境中進行使用。**如果需要在生産環境中使用請使用下面兩種方式:

方式一:在項目資源目錄resources檔案夾下建立hf_private_key檔案,向檔案中填寫如下内容

encryption.rsa.privateKey=MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAIE6SSS+Ns5DpRMKjxThtEL8RwS3HPyFoeDqW54zc0Fsfd4hPuR/BAU6qPWHWo8ZxA17Kfi/HMxYEArNJB6GOpUEvv/wY3sRntMRpS5Y77yqy5mc52vO1fts0oAKeSz5J6Wa1utucUpQwCrZzaZLSjVXqLDJHElgsZocZefPgpKTAgMBAAECgYAFaCDjTqoQWzgu4cQ2xXK7Ur7N7bNixVyOgn+u0MxDsnxZrN5qxP2wElI7Y5xgXF2diseoxqY3zn9tVEPsmwUcY73naoosx9V8oExgT/BUkZYIzj1ei08zOr984zl3dbFcxOCRvqywXj9FAAGx1mhmCzFCIauJg3aX0S9mt5/CwQJBAMYZsmMQ9owoXZuSclKVRfMHFpAPhQlcBM4xadhX0IRYATgNTxpESmcCoGWvyw3bvieNJyC9Njx6X4FJ2EZUzhECQQCm/2IM5MlsCwyKtME5RPFna2hSqYU80UzkNfDIyMokcU2JUI4Fhigog4ol0GFMiMBsHIjS+cJiAwNbIsq5rsJjAkA94yVBobkETFACHBwvBIdXxy0bUF3lcKPnrrQ8bCKuVbf7xNyjfhYoXD+zxNmQuMeNH6HLrpDVD/3qLCGuxyuhAkAiLPl/8gJWnhw+9qbkdXuB0rVS1WZy/9JgkblpHc5gjt9zTo0CDGaDhAftnSuMYiAe/+fwZTSmoj85k3ExdtZJAkEArJuG/NWY9HP4p7jtZX9rMokyB3517v7HQdJKBDIlOzseRC/roCvU8LQ/URDFBUqXCRgedxgW+0ZmKFf4xeawqw==           

需要将等于号後面的内容進行修改,替換為項目中的秘鑰

方式二:通過指令行進行傳遞

在啟動指令中添加

-Dencryption.rsa.privateKey=MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAIE6SSS+Ns5DpRMKjxThtEL8RwS3HPyFoeDqW54zc0Fsfd4hPuR/BAU6qPWHWo8ZxA17Kfi/HMxYEArNJB6GOpUEvv/wY3sRntMRpS5Y77yqy5mc52vO1fts0oAKeSz5J6Wa1utucUpQwCrZzaZLSjVXqLDJHElgsZocZefPgpKTAgMBAAECgYAFaCDjTqoQWzgu4cQ2xXK7Ur7N7bNixVyOgn+u0MxDsnxZrN5qxP2wElI7Y5xgXF2diseoxqY3zn9tVEPsmwUcY73naoosx9V8oExgT/BUkZYIzj1ei08zOr984zl3dbFcxOCRvqywXj9FAAGx1mhmCzFCIauJg3aX0S9mt5/CwQJBAMYZsmMQ9owoXZuSclKVRfMHFpAPhQlcBM4xadhX0IRYATgNTxpESmcCoGWvyw3bvieNJyC9Njx6X4FJ2EZUzhECQQCm/2IM5MlsCwyKtME5RPFna2hSqYU80UzkNfDIyMokcU2JUI4Fhigog4ol0GFMiMBsHIjS+cJiAwNbIsq5rsJjAkA94yVBobkETFACHBwvBIdXxy0bUF3lcKPnrrQ8bCKuVbf7xNyjfhYoXD+zxNmQuMeNH6HLrpDVD/3qLCGuxyuhAkAiLPl/8gJWnhw+9qbkdXuB0rVS1WZy/9JgkblpHc5gjt9zTo0CDGaDhAftnSuMYiAe/+fwZTSmoj85k3ExdtZJAkEArJuG/NWY9HP4p7jtZX9rMokyB3517v7HQdJKBDIlOzseRC/roCvU8LQ/URDFBUqXCRgedxgW+0ZmKFf4xeawqw==           

需要将等号後面的内容進行修改,替換為項目中的秘鑰

從筆者所經曆的角度來看指令行傳遞參數可能更加安全一些,因為檔案和指令行相比指令行需要在生産環境才可以看到,而檔案在倉庫中會存在。

接下來做一個測試,在啟動時輸出spring.datasource.password屬性檢視是否是加密前的資料,測試代碼如下:

@SpringBootApplication
public class App {

   @Value("${spring.datasource.password}")
   private String dataSourceProperties;


   public static void main(String[] args) {
      SpringApplication.run(App.class, args);
   }

   @Bean
   public ApplicationRunner runner() {
      return args -> {
         System.out.println(dataSourceProperties);
      };
   }
}           

啟動項目後可以看到控制台輸出

1234qwer           

繼續閱讀