概要
仕事でデータの暗号化/復号化を前提とした案件の対応方法を検討することがあったので備忘も兼ねて試したことを書いてみる。
参考情報
英語だがspring.ioのドキュメントが参考になる
spring.io - Encryptorsのドキュメント
日本語のだとTERASOLUNAのドキュメントあたりが読みやすい
6. TERASOLUNA Server Framework for Java (5.x)によるセキュリティ対策 >> 6.9. 暗号化
結論(いきなり)
結論から言うと以下の様な感じ(あくまで個人的な見解)
メソッド | 内容 | 暗号利用モード | 暗号化強度 |
---|---|---|---|
noOpText | 暗号化しない。実装のMock等として利用するもの。 | なし | 論外 |
queryableText | 「標準の」パスワードベースの暗号化を使用する。不変の値を生成する。 | CBC | 低 |
text | 「標準の」パスワードベースの暗号化を使用する。可変の値を生成する。 | CBC | 中 |
delux | 「より強力な」パスワードベースの暗号化を使用する。可変の値を生成する。 | GCM | 高 |
検証
以下のようなサンプルを作成し、実際に動かしてみる。
public class EncryptorTest {
private static String secret = "1234";
private static String salt = "5678";
public static void main(String[] args) throws IOException {
String text = "Hello";
TextEncryptor encryptor1 = Encryptors.noOpText();
String noOpTextEncryptText = encryptor1.encrypt(text);
String noOpTextDecryptText = encryptor1.decrypt(noOpTextEncryptText);
TextEncryptor encryptor2 = Encryptors.queryableText(secret, salt);
String queryableTextEncryptText = encryptor2.encrypt(text);
String queryableTextDecryptText = encryptor2.decrypt(queryableTextEncryptText);
TextEncryptor encryptor3 = Encryptors.text(secret, salt);
String textEncryptText = encryptor3.encrypt(text);
String textDecryptText = encryptor3.decrypt(textEncryptText);
TextEncryptor encryptor4 = Encryptors.delux(secret, salt);
String deluxEncryptText = encryptor4.encrypt(text);
String deluxDecryptText = encryptor4.decrypt(deluxEncryptText);
System.out.println("noOpText() :");
System.out.println(" 暗号化文字列 : " + noOpTextEncryptText);
System.out.println(" 復号化確認 : " + noOpTextDecryptText);
System.out.println("");
System.out.println("queryableText() : ");
System.out.println(" 暗号化文字列 : " + queryableTextEncryptText);
System.out.println(" 復号化確認 : " + queryableTextDecryptText);
System.out.println("");
System.out.println("text() : ");
System.out.println(" 暗号化文字列 : " + textEncryptText);
System.out.println(" 復号化確認 : " + textDecryptText);
System.out.println("");
System.out.println("delux() : ");
System.out.println(" 暗号化文字列 : " + deluxEncryptText);
System.out.println(" 復号化確認 : " + deluxDecryptText);
}
noOpText() :
暗号化文字列 : Hello
復号化確認 : Hello
queryableText() :
暗号化文字列 : 524110739525b3f266a0064b4229a77d
復号化確認 : Hello
text() :
暗号化文字列 : ced1eb40b88e691f502d548ed17752bff82592da917804ea66ea1a49bfa77033
復号化確認 : Hello
delux() :
暗号化文字列 : 61e62f473d0cb228305081b927a3225d487a749b87af62071ad60dc6b27703410de45f7fbf
復号化確認 : Hello
暗号化の文字長が異なるがいずれのケースも暗号化からの復号化が出来ていることが分かる。
どういう違いがあるのか確認するため、ここで各メソッドのロジックを見てみる。
※ 暗号化処理の専門家ではないのでアリゴリズム云々は語れないのでご容赦いただきたい
noOpText
Encryptors.noOpText()でインスタンスを作成した場合、
以下のロジックが呼び出される。
private static final class NoOpTextEncryptor implements TextEncryptor {
public String encrypt(String text) {
return text;
}
public String decrypt(String encryptedText) {
return encryptedText;
}
}
論外と評したが何もしていない。
果たして何か使い道はあるのか…
queryableText
続いてqueryableText
public static TextEncryptor queryableText(CharSequence password, CharSequence salt) {
return new HexEncodingTextEncryptor(new AesBytesEncryptor(password.toString(),
salt));
}
public AesBytesEncryptor(String password, CharSequence salt) {
this(password, salt, null); // ← ここの引数がnull
}
public AesBytesEncryptor(String password, CharSequence salt,
BytesKeyGenerator ivGenerator) {
this(password, salt, ivGenerator, CipherAlgorithm.CBC);
}
public AesBytesEncryptor(String password, CharSequence salt,
BytesKeyGenerator ivGenerator, CipherAlgorithm alg) {
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), Hex.decode(salt),
1024, 256);
SecretKey secretKey = newSecretKey("PBKDF2WithHmacSHA1", keySpec);
this.secretKey = new SecretKeySpec(secretKey.getEncoded(), "AES");
this.alg = alg;
this.encryptor = alg.createCipher();
this.decryptor = alg.createCipher();
this.ivGenerator = ivGenerator != null ? ivGenerator : alg.defaultIvGenerator();
}
text
queryableTextのコメントは置いといてtextを
public static TextEncryptor text(CharSequence password, CharSequence salt) {
return new HexEncodingTextEncryptor(standard(password, salt));
}
public static BytesEncryptor standard(CharSequence password, CharSequence salt) {
return new AesBytesEncryptor(password.toString(), salt,
KeyGenerators.secureRandom(16)); // ← ここの引数が乱数
}
public AesBytesEncryptor(String password, CharSequence salt,
BytesKeyGenerator ivGenerator) {
this(password, salt, ivGenerator, CipherAlgorithm.CBC);
}
public AesBytesEncryptor(String password, CharSequence salt,
BytesKeyGenerator ivGenerator, CipherAlgorithm alg) {
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), Hex.decode(salt),
1024, 256);
SecretKey secretKey = newSecretKey("PBKDF2WithHmacSHA1", keySpec);
this.secretKey = new SecretKeySpec(secretKey.getEncoded(), "AES");
this.alg = alg;
this.encryptor = alg.createCipher();
this.decryptor = alg.createCipher();
this.ivGenerator = ivGenerator != null ? ivGenerator : alg.defaultIvGenerator();
}
queryableTextとtextの違いは
BytesKeyGeneratorをnullで渡すか、**KeyGenerators.secureRandom(16)**で渡すかの違いである。
queryableTextはnullで渡しているので毎回、同じ暗号化文字列を生成しており、
textは**KeyGenerators.secureRandom(16)**を渡しているので毎回違う乱数が生成され、結果、毎回違う暗号化文字列が生成される。
delux
そして最後にdelux
public static TextEncryptor delux(CharSequence password, CharSequence salt) {
return new HexEncodingTextEncryptor(stronger(password, salt));
}
public static BytesEncryptor stronger(CharSequence password, CharSequence salt) {
return new AesBytesEncryptor(password.toString(), salt,
KeyGenerators.secureRandom(16), CipherAlgorithm.GCM);
}
public AesBytesEncryptor(String password, CharSequence salt,
BytesKeyGenerator ivGenerator, CipherAlgorithm alg) {
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), Hex.decode(salt),
1024, 256);
SecretKey secretKey = newSecretKey("PBKDF2WithHmacSHA1", keySpec);
this.secretKey = new SecretKeySpec(secretKey.getEncoded(), "AES");
this.alg = alg;
this.encryptor = alg.createCipher();
this.decryptor = alg.createCipher();
this.ivGenerator = ivGenerator != null ? ivGenerator : alg.defaultIvGenerator();
}
textとdeluxの違いは
CipherAlgorithmにCBCを指定するかGCMを指定するかの違いである。
GCM (Galois/Counter Mode) は並列処理が可能でCBCより処理効率が優れていると一般的にいわれており、
CBCより高度な暗号化を実施しているにも関わらず、処理時間は大差無い。
事実として100文字の文字列を暗号化→復号化を10,000回繰り返す処理を書いて数回サンプリングして
textとdeluxで比較してみたが、処理時間は殆ど変わらなかった。(誤差1秒未満)
所感
特段の理由がなければdeluxを利用した方が良いと感じた。
(この時代でデータ長が長くなるからディスク容量が…なんて言っているシステムは少ないだろうし)