--- title: KeyStoreに保存した秘密鍵を使って Spring Boot アプリに渡すパスワードを暗号化する tags: ["Java", "Spring", "Spring Boot"] categories: ["Programming", "Java", "org", "springframework", "boot"] date: 2014-07-16T00:24:31Z updated: 2014-07-17T05:38:05Z --- Spring Bootを使ってアプリを作ると、環境情報を外出しした上で、実行可能jarを作成し、 $ java -jar myapp.jar --barPassword=mypassword みたいに、実行時に外からプロパティを渡せる。便利。 でも、生パスワードはのせたくないですよね。 そこで、外からは暗号化したプロパティを渡し、Bean生成時に復号する方法を紹介します。 ### 秘密鍵生成 keytoolを使ってAES256の秘密鍵を生成します。 $ keytool -genseckey -storetype jceks -keyalg AES -keysize 256 -alias mykey -storepass changeme -keypass changeme このKeyStoreを使って暗号化・復号するサンプルコードは以下の通り。 import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.security.Key; import java.security.KeyStore; import java.util.Base64; public class Hoge { public static void main(String[] args) throws Exception { try (InputStream keystoreStream = Files.newInputStream(Paths.get(System.getProperty("user.home") + "/.keystore"))) { KeyStore keystore = KeyStore.getInstance("JCEKS"); keystore.load(keystoreStream, "changeme".toCharArray()); Key key = keystore.getKey("mykey", "changeme".toCharArray()); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); // 暗号化 cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec("0123456789012345".getBytes())); byte[] encrypted = cipher.doFinal("mypassword".getBytes(StandardCharsets.UTF_8)); String encryptedString = new String(Base64.getEncoder().encode(encrypted), StandardCharsets.UTF_8); System.out.println(encryptedString); // 復号 cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec("0123456789012345".getBytes())); byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(encryptedString.getBytes(StandardCharsets.UTF_8))); String decryptedString = new String(decrypted, StandardCharsets.UTF_8); System.out.println(decryptedString); } } } 実行結果 jYldzgRvHJwPWRBOvMC2Tg== mypassword "mypassword"という文字列を暗号化すると"jYldzgRvHJwPWRBOvMC2Tg=="になります。 ### 復号可能なJavaConfigの作成 JavaConfigで以下のように定義すればよいです。 package com.example; import com.example.App.Bar; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.security.Key; import java.security.KeyStore; import java.util.Base64; @Configuration public class AppConfig { @Value("${keyStore:#{systemProperties['user.home']}/.keystore}") String keyStore; @Value("${keyAlias}") String keyAlias; @Value("${keyStorePass}") char[] keyStorePass; @Value("${keyPass}") char[] keyPass; @Value("${initialVector:0123456789012345}") String initialVector; @Value("${barPassword}") String barPassword; private static final String ENCRYPTED_PREFIX = "encrypted:"; @Lazy // 復号するときだけ遅延評価されるように@Lazyをつける @Bean Key secretKey() throws Exception { try (InputStream keystoreStream = Files.newInputStream(Paths.get(keyStore))) { KeyStore keystore = KeyStore.getInstance("JCEKS"); keystore.load(keystoreStream, keyStorePass); return keystore.getKey(keyAlias, keyPass); } } String decrypt(String target) throws Exception { if (target != null && target.startsWith(ENCRYPTED_PREFIX)) { String encryptedString = target.substring(ENCRYPTED_PREFIX.length()); // 値が"encrypted:"から始まる場合は、それ以降を復号する Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, secretKey(), new IvParameterSpec(initialVector.getBytes())); byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(encryptedString.getBytes(StandardCharsets.UTF_8))); return new String(decrypted, StandardCharsets.UTF_8); } else { // 値が"encrypted:"から始まらない場合は、生の値をそのまま使う return target; } } @Bean Bar bar() throws Exception { Bar bar = new Bar(); bar.setPassword(decrypt(barPassword) /* 復号 */); return bar; } } src/main/resources/application.ymlに以下を設定しておきます。 keyAlias: mykey keyStorePass: changeme keyPass: changeme barPassword: password 開発中は開発用パスワードを生で使用すればよいです。 `SecretKey`の生成に`@Lazy`をつけているので、生パスワードを使っている限りはkeystoreへのアクセスはないです。開発中はこれで。 ### 本番実行 実行可能jarにビルドして、本番時は実行時に"encrypted:暗号化パスワード"を渡すとよいです。 $ java -jar myapp.jar --barPassword=encrypted:jYldzgRvHJwPWRBOvMC2Tg== 実行ディレクトリにapplication.ymlをおいて、オーバーライドしてもOK。 (keystoreのパスワードが生だったらあんまり意味ないか・・・?) * [Keystore Tutorial](http://www.javacodegeeks.com/2014/07/java-keystore-tutorial.html)