springframeworkでプロパティの暗号化
springでデータベースの接続設定を定義するときに、つぎのような感じで接続情報をプロパティファイルに記述することがしばしばあります。
# db connection properties jdbc.driverClassName=org.mariadb.jdbc.Driver jdbc.url=jdbc:mariadb://ipaddress:port/service jdbc.username=username jdbc.password=password
使用するだけであれば、これでなんの問題もありません。しかしセキュリティ的にプロパティの一部を暗号化したいことがあります。そんなときは、PropertySourcesPlaceholderConfigurerを使うとプロパティの暗号化を実現できます。
具体的には、以下のように、PropertySourcesPlaceholderConfigurerクラスを継承して、EncryptedPropertySourcePlaceholderConfigurerクラスを作成し、doProcessPropertiesメソッドをオーバライドします。このときdoProcessPropertiesメソッドでは、親クラスの同メソッドを呼び出して第2パラメータとして、複合化を実装したStringValueResolverの実装クラス(ここではEncryptedValueResolver)のインスタンスを指定します。
public class EncryptedPropertySourcePlaceholderConfigurer extends PropertySourcesPlaceholderConfigurer { protected void doProcessProperties(ConfigurableListableBeanFactory beanFactory, StringValueResolver valueResolver) { super.doProcessProperties(beanFactory, new EncryptedValueResolver(valueResolver)); } }
次に、EncryptedValueResolverクラスでは、doProcessPropertiesメソッドで渡されるvalueResolverを引数とするコンストラクタの定義と、複合化の実体であるresolveStringValueメソッドを定義しています。
resolveStringValueメソッドでは、プロパティファイルから読み込んだ値をvalueに設定し、valueの先頭が「Encrypted:」であれば複合化するという実装としています。
import org.springframework.util.StringValueResolver; public class EncryptedValueResolver implements StringValueResolver { private final StringValueResolver valueResolver; EncryptedValueResolver(StringValueResolver valueResolver) { this.valueResolver = valueResolver; } @Override public String resolveStringValue(String propertyValue) { String value = "" + valueResolver.resolveStringValue(propertyValue); if( value.startsWith("Encrypted:") ) { value = value.substring(10); value = CryptUtil.decrypt(value); } return value; } }
ただこれだけでは、プロパティファイルを読み込む際に、EncryptedPropertySourcePlaceholderConfigurerクラスが有効にならないので、applicationContext.xmlを次のように書き換えます。
<!-- application.properties読み込み <context:property-placeholder location="WEB-INF/application.properties" />--> <!-- 暗号化対応のためcontext:property-placeholderの代わりに以下を定義 --> <bean class="jp.hogehoge.example.spring.resolver.EncryptedValueResolver"> <property name="locations" value="WEB-INF/application.properties" /> </bean>
あとはプロパティファイルの設定値を暗号化したものに「Encrypted:」を付与すればOKです。以下の例では、jdbc.usernameとjdbc.passwordを暗号化しています。
# db connection properties jdbc.driverClassName=org.mariadb.jdbc.Driver jdbc.url=jdbc:mariadb://ipaddress:port/service jdbc.username=Encrypted:6zCFxbJoqI5lnJeiM3s4mA== jdbc.password=Encrypted:VPBgo1cXhHVlnJeiM3s4mA==
おまけ
今回使用したCryptUtilクラスの実装は以下のとおり。privateKeyは適当に変えて使用する必要があります。
public class CryptUtil { private static final String privateKey = "private crypt key"; public static String encrypt(String key, String text) { SecretKeySpec sksSpec = new SecretKeySpec(key.getBytes(), "Blowfish"); try { Cipher cipher = Cipher.getInstance("Blowfish"); cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, sksSpec); return Base64.getEncoder().encodeToString(cipher.doFinal(text.getBytes())); } catch( Throwable t ) { throw new SystemException(t); } } public static String decrypt(String key, String encrypted) { SecretKeySpec sksSpec = new SecretKeySpec(key.getBytes(), "Blowfish"); try { Cipher cipher = Cipher.getInstance("Blowfish"); cipher.init(Cipher.DECRYPT_MODE, sksSpec); return new String(cipher.doFinal(Base64.getDecoder().decode(encrypted))); } catch( Throwable t ) { throw new SystemException(t); } } public static String encrypt(String text) { return CryptUtil.encrypt(privateKey, text); } public static String decrypt(String encrypted) { return CryptUtil.decrypt(privateKey, encrypted); } }
※場合によっては、java.security.InvalidKeyException: Illegal key size or default parametersのような例外が発生することがありますので、その時は以下のサイトを参考に対策をしてください。
http://d.hatena.ne.jp/nakamiri/20111121/1321902479d.hatena.ne.jp