これは、なにをしたくて書いたもの?
このあたりを見ていて
digest = MessageDigest.getInstance("SHA");
MessageDigest
を使う時は、今まで具体的なアルゴリズム名を指定して使ったことしかなったので、こういう指定の時に
実体としてはどうなるのかな?と思いまして。
あと、調べていたらCipher
についてもこんな指定ができるようです。
Cipher cipher = Cipher.getInstance("AES");
こちらもモードとかパディングは指定するものだと思っていたので、こういう指定だとどういう構成になるのかをちゃんと
知らないなぁと。
簡単にjshell
でアルゴリズム名を見ても、SHA
やAES
としかわかりません。
jshell> java.security.MessageDigest.getInstance("SHA").getAlgorithm() $1 ==> "SHA" jshell> javax.crypto.Cipher.getInstance("AES").getAlgorithm() $2 ==> "AES"
いったいあなた方は何者なのでしょう?
というわけで、このような使い方をした場合のMessageDigest
やCipher
がどのような構成になるかを見てみたいと思います。
MessageDigestとSHA
MessageDigest
のJavadocを見てみましょう。
MessageDigest (Java SE 11 & JDK 11 )
MessageDigest
で使えるアルゴリズムはこちらです。
Javaセキュリティ標準アルゴリズム名 / MessageDigestアルゴリズム
Javadocにも、こちらのドキュメントにもSHA
という名前は登場しません。
JCAやJDKプロバイダのドキュメントを見ても、書いていません。
Java暗号化アーキテクチャ(JCA)リファレンス・ガイド / MessageDigestクラス
さて、これはなんなのでしょう?
CipherとAES
続いて、Cipher
のJavadocを見てみます。こちらは書いてありました。アルゴリズムだけでも指定可能です。
変換は、次の書式で記述されます。
・"algorithm/mode/padding"または ・"algorithm"
後者の場合、モードおよびパディング方式には、プロバイダ固有のデフォルト値が使用されます。
ただ、デフォルト値がなにかはJavadocからはわかりません。Javaセキュリティ標準アルゴリズム名を見てもわかりません。
Javaセキュリティ標準アルゴリズム名 / Cipherアルゴリズム名
JCAやJDKプロバイダのドキュメントを見ると、答えが書いてあります。
Java暗号化アーキテクチャ(JCA)リファレンス・ガイド / Cipherクラス
アルゴリズム、モードおよびパディングを完全に指定した変換を使用することをお薦めします。そうしないと、プロバイダはデフォルトを使用します。たとえば、SunJCEプロバイダとSunPKCS11プロバイダは、多くの対称暗号でECBをデフォルト・モードとして、PKCS5Paddingをデフォルト・パディングとして使用します。
Java暗号化アーキテクチャ(JCA)リファレンス・ガイド / Cipherオブジェクトの作成
javax.crypto.Cipher.getInstance(String transformation)ファクトリ・メソッドは、algorithm/mode/padding形式の変換を使用してCipherオブジェクトを生成します。モード/パディングを省略すると、SunJCEプロバイダとSunPKCS11プロバイダは、多くの対称暗号でECBをデフォルト・モードとして、PKCS5Paddingをデフォルト・パディングとして使用します。
つまり、AES
と指定した場合はAES/ECB/PKCS5Padding
となるようです。
こう見ると、ドキュメントに記載のある通りちゃんとモードとパディングを指定するのが正解のようですね(ECB
を選ばない)。
MessageDigest
の方も含めて、実際の動作を確認してみましょう。
環境
今回の環境は、こちら。
$ java --version openjdk 11.0.9.1 2020-11-04 OpenJDK Runtime Environment (build 11.0.9.1+1-Ubuntu-0ubuntu1.20.04) OpenJDK 64-Bit Server VM (build 11.0.9.1+1-Ubuntu-0ubuntu1.20.04, mixed mode, sharing)
MessageDigestでSHAのアルゴリズムをひととおり使う
では、最初はMessageDigest
から確認してみましょう。
こんなプログラムを用意。
MessageDigestSample.java
import java.nio.charset.StandardCharsets; import java.security.MessageDigest; public class MessageDigestSample { public static void main(String... args) throws Exception { String targetValue = "Hello World"; printDigest(MessageDigest.getInstance("SHA"), targetValue); printDigest(MessageDigest.getInstance("SHA-1"), targetValue); printDigest(MessageDigest.getInstance("SHA-224"), targetValue); printDigest(MessageDigest.getInstance("SHA-256"), targetValue); printDigest(MessageDigest.getInstance("SHA-384"), targetValue); printDigest(MessageDigest.getInstance("SHA-512/224"), targetValue); printDigest(MessageDigest.getInstance("SHA-512/256"), targetValue); } private static void printDigest(MessageDigest md, String targetValue) throws Exception { md.update(targetValue.getBytes(StandardCharsets.UTF_8)); byte[] digest = md.digest(); StringBuilder builder = new StringBuilder(); for (byte b : digest) { builder.append(Integer.toHexString(0xff & b)); } String digestAsString = builder.toString(); System.out.printf("Input: %s, Algorithm: %s, Digest: %s%n", targetValue, md.getAlgorithm(), digestAsString); } }
同じ値に対して、Javaセキュリティ標準アルゴリズム名 / MessageDigestアルゴリズムに記載されているすべてのアルゴリズムに加え、SHA
を使っています。
実行。
$ java MessageDigestSample.java Input: Hello World, Algorithm: SHA, Digest: a4d55a8d778e522fab701977c5d840bbc486d0 Input: Hello World, Algorithm: SHA-1, Digest: a4d55a8d778e522fab701977c5d840bbc486d0 Input: Hello World, Algorithm: SHA-224, Digest: c489faffdb0105d991a461e668e276685401b2eab1ef4372795047 Input: Hello World, Algorithm: SHA-256, Digest: a591a6d4bf420404a11733cfb7b190d62c65bfbcda32b57b277d9ad9f146e Input: Hello World, Algorithm: SHA-384, Digest: 99514329186b2f6ae4a1329e7ee6c610a729636335174ac6b740f928396fcc83d0e93863a7c3d9f86beee782f4f3f Input: Hello World, Algorithm: SHA-512/224, Digest: feca4195c80a571ae782f96bcef9ab81bdf182509a6844f32c4c17 Input: Hello World, Algorithm: SHA-512/256, Digest: ff2018851481c25bfc2e5d0c1e1fa57dac2a237a1a96192f99a10da47aa5442
これを見ると、どうやらSHA
はSHA-1
と同じようです。
他に確認する方法はないでしょうか。ちょっと、Provider#Service
を出力してみましょう。
Security (Java SE 11 & JDK 11 )
Provider (Java SE 11 & JDK 11 )
Provider.Service (Java SE 11 & JDK 11 )
こんなプログラムを用意。
PrintSecurityProviders.java
import java.security.Provider; import java.security.Security; public class PrintSecurityProviders { public static void main(String... args) { for (Provider provider : Security.getProviders()) { for (Provider.Service service : provider.getServices()) { System.out.printf("Provider: %s, class: %s%n", provider.getName(), provider.getClass()); System.out.println(service); } } } }
実行して、MessageDigest.SHA
が含まれている内容を探してみます。
$ java PrintSecurityProviders.java | grep MessageDigest.SHA -A 3 -B 1 Provider: SUN, class: class sun.security.provider.Sun SUN: MessageDigest.SHA3-224 -> sun.security.provider.SHA3$SHA224 aliases: [2.16.840.1.101.3.4.2.7, OID.2.16.840.1.101.3.4.2.7] attributes: {ImplementedIn=Software} -- Provider: SUN, class: class sun.security.provider.Sun SUN: MessageDigest.SHA3-384 -> sun.security.provider.SHA3$SHA384 aliases: [2.16.840.1.101.3.4.2.9, OID.2.16.840.1.101.3.4.2.9] attributes: {ImplementedIn=Software} Provider: SUN, class: class sun.security.provider.Sun SUN: MessageDigest.SHA3-256 -> sun.security.provider.SHA3$SHA256 aliases: [2.16.840.1.101.3.4.2.8, OID.2.16.840.1.101.3.4.2.8] attributes: {ImplementedIn=Software} -- Provider: SUN, class: class sun.security.provider.Sun SUN: MessageDigest.SHA-512 -> sun.security.provider.SHA5$SHA512 aliases: [2.16.840.1.101.3.4.2.3, OID.2.16.840.1.101.3.4.2.3] attributes: {ImplementedIn=Software} -- Provider: SUN, class: class sun.security.provider.Sun SUN: MessageDigest.SHA -> sun.security.provider.SHA aliases: [1.3.14.3.2.26, SHA-1, SHA1, OID.1.3.14.3.2.26] attributes: {ImplementedIn=Software} -- Provider: SUN, class: class sun.security.provider.Sun SUN: MessageDigest.SHA-512/256 -> sun.security.provider.SHA5$SHA512_256 aliases: [2.16.840.1.101.3.4.2.6, OID.2.16.840.1.101.3.4.2.6] attributes: {ImplementedIn=Software} -- Provider: SUN, class: class sun.security.provider.Sun SUN: MessageDigest.SHA3-512 -> sun.security.provider.SHA3$SHA512 aliases: [2.16.840.1.101.3.4.2.10, OID.2.16.840.1.101.3.4.2.10] attributes: {ImplementedIn=Software} Provider: SUN, class: class sun.security.provider.Sun SUN: MessageDigest.SHA-384 -> sun.security.provider.SHA5$SHA384 aliases: [2.16.840.1.101.3.4.2.2, OID.2.16.840.1.101.3.4.2.2] attributes: {ImplementedIn=Software} Provider: SUN, class: class sun.security.provider.Sun SUN: MessageDigest.SHA-256 -> sun.security.provider.SHA2$SHA256 aliases: [2.16.840.1.101.3.4.2.1, OID.2.16.840.1.101.3.4.2.1] attributes: {ImplementedIn=Software} -- Provider: SUN, class: class sun.security.provider.Sun SUN: MessageDigest.SHA-512/224 -> sun.security.provider.SHA5$SHA512_224 aliases: [2.16.840.1.101.3.4.2.5, OID.2.16.840.1.101.3.4.2.5] attributes: {ImplementedIn=Software} Provider: SUN, class: class sun.security.provider.Sun SUN: MessageDigest.SHA-224 -> sun.security.provider.SHA2$SHA224 aliases: [2.16.840.1.101.3.4.2.4, OID.2.16.840.1.101.3.4.2.4] attributes: {ImplementedIn=Software}
ここですね。
Provider: SUN, class: class sun.security.provider.Sun SUN: MessageDigest.SHA -> sun.security.provider.SHA aliases: [1.3.14.3.2.26, SHA-1, SHA1, OID.1.3.14.3.2.26] attributes: {ImplementedIn=Software}
SHA
のエイリアスとして、SHA-1
、SHA1
が登録されているようです。
ソースコードでいくと、ここですね。
というわけで、SHA
はSHA-1
であることが、動作確認としてもプロバイダー定義としても、ソースコードとしても
確認できました。
CipherでAESのモード、パディングをひととおり使う
続いて、Cipher
のAES
も確認してみましょう。
こういうプログラムを用意。
CipherSample.java
CipherSample.java import java.nio.charset.StandardCharsets; import java.security.SecureRandom; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; public class CipherSample { public static void main(String... args) throws Exception { String targetValue = "Hello World!!!!!"; KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); keyGenerator.init(128); SecretKey key = keyGenerator.generateKey(); SecureRandom random = new SecureRandom(); byte[] ivBytes = new byte[16]; random.nextBytes(ivBytes); IvParameterSpec iv = new IvParameterSpec(ivBytes); printEncrypted(Cipher.getInstance("AES"), key, null, targetValue); printEncrypted(Cipher.getInstance("AES/ECB/NoPadding"), key, null, targetValue); printEncrypted(Cipher.getInstance("AES/ECB/PKCS5Padding"), key, null, targetValue); printEncrypted(Cipher.getInstance("AES/CBC/NoPadding"), key, iv, targetValue); printEncrypted(Cipher.getInstance("AES/CBC/PKCS5Padding"), key, iv, targetValue); } private static void printEncrypted(Cipher cipher, SecretKey key, IvParameterSpec iv, String targetValue) throws Exception { cipher.init(Cipher.ENCRYPT_MODE, key, iv); byte[] encrypted = cipher.doFinal(targetValue.getBytes(StandardCharsets.UTF_8)); StringBuilder builder = new StringBuilder(); for (byte b : encrypted) { builder.append(Integer.toHexString(0xff & b)); } String encryptedAsString = builder.toString(); System.out.printf("Input: %s, Algorithm: %s, Encrypted: %s%n", targetValue, cipher.getAlgorithm(), encryptedAsString); } }
Javadocに記載されているAES
のモード、パディングをGCM
を除いてすべて使い、同じ値を暗号化します。
鍵はランダムで生成しますが、各アルゴリズム、モード、パディングで同じものを使います。IVは、IVを要求するパターンだけ
ランダムで生成して同じものを渡します(IVが不要なECB
にIVを渡すと例外がスローされます)。
実行してみます。
※鍵やIVをランダムに作っているので、実行するごとに結果が変わります
$ java CipherSample.java Input: Hello World!!!!!, Algorithm: AES, Encrypted: c8b93e63c474e11fdd5019f693c3fcdaae4491828f5f7b6d8648514dbe8a7 Input: Hello World!!!!!, Algorithm: AES/ECB/NoPadding, Encrypted: c8b93e63c474e11fdd5019f693c3fc Input: Hello World!!!!!, Algorithm: AES/ECB/PKCS5Padding, Encrypted: c8b93e63c474e11fdd5019f693c3fcdaae4491828f5f7b6d8648514dbe8a7 Input: Hello World!!!!!, Algorithm: AES/CBC/NoPadding, Encrypted: daaaad8cfe7191c28b74fbe29d4896f1 Input: Hello World!!!!!, Algorithm: AES/CBC/PKCS5Padding, Encrypted: daaaad8cfe7191c28b74fbe29d4896f1e399f94d81e37ce4d79df132ed39a3f
JCAやJDKプロバイダのドキュメントに書かれていた通り、AES
とAES/ECB/PKCS5Padding
は同じのようです。
利用できるProvider#Service
のうち、Cipher.AES
に関するものを出力してみましょう。
$ java PrintSecurityProviders.java | grep Cipher.AES -A 3 -B 1 Provider: SunJCE, class: class com.sun.crypto.provider.SunJCE SunJCE: Cipher.AES_192/CBC/NoPadding -> com.sun.crypto.provider.AESCipher$AES192_CBC_NoPadding aliases: [2.16.840.1.101.3.4.1.22, OID.2.16.840.1.101.3.4.1.22] attributes: {SupportedKeyFormats=RAW} Provider: SunJCE, class: class com.sun.crypto.provider.SunJCE SunJCE: Cipher.AES_192/OFB/NoPadding -> com.sun.crypto.provider.AESCipher$AES192_OFB_NoPadding aliases: [2.16.840.1.101.3.4.1.23, OID.2.16.840.1.101.3.4.1.23] attributes: {SupportedKeyFormats=RAW} -- Provider: SunJCE, class: class com.sun.crypto.provider.SunJCE SunJCE: Cipher.AES_192/CFB/NoPadding -> com.sun.crypto.provider.AESCipher$AES192_CFB_NoPadding aliases: [2.16.840.1.101.3.4.1.24, OID.2.16.840.1.101.3.4.1.24] attributes: {SupportedKeyFormats=RAW} -- Provider: SunJCE, class: class com.sun.crypto.provider.SunJCE SunJCE: Cipher.AESWrap_192 -> com.sun.crypto.provider.AESWrapCipher$AES192 aliases: [2.16.840.1.101.3.4.1.25, OID.2.16.840.1.101.3.4.1.25] attributes: {SupportedKeyFormats=RAW, SupportedModes=ECB, SupportedPaddings=NOPADDING} -- Provider: SunJCE, class: class com.sun.crypto.provider.SunJCE SunJCE: Cipher.AES_192/ECB/NoPadding -> com.sun.crypto.provider.AESCipher$AES192_ECB_NoPadding aliases: [2.16.840.1.101.3.4.1.21, OID.2.16.840.1.101.3.4.1.21] attributes: {SupportedKeyFormats=RAW} -- Provider: SunJCE, class: class com.sun.crypto.provider.SunJCE SunJCE: Cipher.AES_192/GCM/NoPadding -> com.sun.crypto.provider.AESCipher$AES192_GCM_NoPadding aliases: [2.16.840.1.101.3.4.1.26, OID.2.16.840.1.101.3.4.1.26] attributes: {SupportedKeyFormats=RAW} -- Provider: SunJCE, class: class com.sun.crypto.provider.SunJCE SunJCE: Cipher.AES_128/ECB/NoPadding -> com.sun.crypto.provider.AESCipher$AES128_ECB_NoPadding aliases: [2.16.840.1.101.3.4.1.1, OID.2.16.840.1.101.3.4.1.1] attributes: {SupportedKeyFormats=RAW} -- Provider: SunJCE, class: class com.sun.crypto.provider.SunJCE SunJCE: Cipher.AES_128/OFB/NoPadding -> com.sun.crypto.provider.AESCipher$AES128_OFB_NoPadding aliases: [2.16.840.1.101.3.4.1.3, OID.2.16.840.1.101.3.4.1.3] attributes: {SupportedKeyFormats=RAW} -- Provider: SunJCE, class: class com.sun.crypto.provider.SunJCE SunJCE: Cipher.AES_128/CBC/NoPadding -> com.sun.crypto.provider.AESCipher$AES128_CBC_NoPadding aliases: [2.16.840.1.101.3.4.1.2, OID.2.16.840.1.101.3.4.1.2] attributes: {SupportedKeyFormats=RAW} Provider: SunJCE, class: class com.sun.crypto.provider.SunJCE SunJCE: Cipher.AESWrap_128 -> com.sun.crypto.provider.AESWrapCipher$AES128 aliases: [2.16.840.1.101.3.4.1.5, OID.2.16.840.1.101.3.4.1.5] attributes: {SupportedKeyFormats=RAW, SupportedModes=ECB, SupportedPaddings=NOPADDING} Provider: SunJCE, class: class com.sun.crypto.provider.SunJCE SunJCE: Cipher.AES_128/CFB/NoPadding -> com.sun.crypto.provider.AESCipher$AES128_CFB_NoPadding aliases: [2.16.840.1.101.3.4.1.4, OID.2.16.840.1.101.3.4.1.4] attributes: {SupportedKeyFormats=RAW} -- Provider: SunJCE, class: class com.sun.crypto.provider.SunJCE SunJCE: Cipher.AES_128/GCM/NoPadding -> com.sun.crypto.provider.AESCipher$AES128_GCM_NoPadding aliases: [2.16.840.1.101.3.4.1.6, OID.2.16.840.1.101.3.4.1.6] attributes: {SupportedKeyFormats=RAW} -- Provider: SunJCE, class: class com.sun.crypto.provider.SunJCE SunJCE: Cipher.AES_256/GCM/NoPadding -> com.sun.crypto.provider.AESCipher$AES256_GCM_NoPadding aliases: [2.16.840.1.101.3.4.1.46, OID.2.16.840.1.101.3.4.1.46] attributes: {SupportedKeyFormats=RAW} -- Provider: SunJCE, class: class com.sun.crypto.provider.SunJCE SunJCE: Cipher.AES_256/CFB/NoPadding -> com.sun.crypto.provider.AESCipher$AES256_CFB_NoPadding aliases: [2.16.840.1.101.3.4.1.44, OID.2.16.840.1.101.3.4.1.44] attributes: {SupportedKeyFormats=RAW} Provider: SunJCE, class: class com.sun.crypto.provider.SunJCE SunJCE: Cipher.AESWrap_256 -> com.sun.crypto.provider.AESWrapCipher$AES256 aliases: [2.16.840.1.101.3.4.1.45, OID.2.16.840.1.101.3.4.1.45] attributes: {SupportedKeyFormats=RAW, SupportedModes=ECB, SupportedPaddings=NOPADDING} -- Provider: SunJCE, class: class com.sun.crypto.provider.SunJCE SunJCE: Cipher.AES_256/ECB/NoPadding -> com.sun.crypto.provider.AESCipher$AES256_ECB_NoPadding aliases: [2.16.840.1.101.3.4.1.41, OID.2.16.840.1.101.3.4.1.41] attributes: {SupportedKeyFormats=RAW} Provider: SunJCE, class: class com.sun.crypto.provider.SunJCE SunJCE: Cipher.AES_256/CBC/NoPadding -> com.sun.crypto.provider.AESCipher$AES256_CBC_NoPadding aliases: [2.16.840.1.101.3.4.1.42, OID.2.16.840.1.101.3.4.1.42] attributes: {SupportedKeyFormats=RAW} -- Provider: SunJCE, class: class com.sun.crypto.provider.SunJCE SunJCE: Cipher.AES_256/OFB/NoPadding -> com.sun.crypto.provider.AESCipher$AES256_OFB_NoPadding aliases: [2.16.840.1.101.3.4.1.43, OID.2.16.840.1.101.3.4.1.43] attributes: {SupportedKeyFormats=RAW} -- Provider: SunJCE, class: class com.sun.crypto.provider.SunJCE SunJCE: Cipher.AES -> com.sun.crypto.provider.AESCipher$General aliases: [Rijndael] attributes: {SupportedKeyFormats=RAW, SupportedModes=ECB|CBC|PCBC|CTR|CTS|CFB|OFB|CFB8|CFB16|CFB24|CFB32|CFB40|CFB48|CFB56|CFB64|OFB8|OFB16|OFB24|OFB32|OFB40|OFB48|OFB56|OFB64|GCM|CFB72|CFB80|CFB88|CFB96|CFB104|CFB112|CFB120|CFB128|OFB72|OFB80|OFB88|OFB96|OFB104|OFB112|OFB120|OFB128, SupportedPaddings=NOPADDING|PKCS5PADDING|ISO10126PADDING} -- Provider: SunJCE, class: class com.sun.crypto.provider.SunJCE SunJCE: Cipher.AESWrap -> com.sun.crypto.provider.AESWrapCipher$General attributes: {SupportedKeyFormats=RAW, SupportedModes=ECB, SupportedPaddings=NOPADDING} Provider: SunJCE, class: class com.sun.crypto.provider.SunJCE
最後の1行は余計でした…。こちらはSHA
の時のようなエイリアスはないですね。
むしろ、Rijndael
ってなってます。
Provider: SunJCE, class: class com.sun.crypto.provider.SunJCE SunJCE: Cipher.AES -> com.sun.crypto.provider.AESCipher$General aliases: [Rijndael] attributes: {SupportedKeyFormats=RAW, SupportedModes=ECB|CBC|PCBC|CTR|CTS|CFB|OFB|CFB8|CFB16|CFB24|CFB32|CFB40|CFB48|CFB56|CFB64|OFB8|OFB16|OFB24|OFB32|OFB40|OFB48|OFB56|OFB64|GCM|CFB72|CFB80|CFB88|CFB96|CFB104|CFB112|CFB120|CFB128|OFB72|OFB80|OFB88|OFB96|OFB104|OFB112|OFB120|OFB128, SupportedPaddings=NOPADDING|PKCS5PADDING|ISO10126PADDING}
確かに。
jshell> javax.crypto.Cipher.getInstance("Rijndael").getAlgorithm() $1 ==> "Rijndael"
なので、Rijndael/CBC/PKCS5Padding
といった指定も可能だったりします。
ソースコード上も確認してみましょう。
Cipher.AES_128/ECB/NoPadding
などの、パディングなしの場合は固有にクラスがありますね。
そうでない場合は、こちらのようです。
キーサイズは-1
です。-1
は制限なし、を表すようですが…。
で、肝心のデフォルト値はここを見るとわかります。
モードはECB
ですね。
パディングは、PKCS5Padding
です。
というわけで、動作でもソースコード上でも確認できました、と。
まとめ
MessageDigest
にSHA
とか、Cipher
にAES
とだけ指定した場合に、どうなるんだろう?という疑問からいろいろ調べて
みました。
動作確認から、実際のソースコードまで追って確認できたので、いい勉強になったなぁと思います。