これは、なにをしたくて書いたもの?
このあたりを見ていて
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とだけ指定した場合に、どうなるんだろう?という疑問からいろいろ調べて
みました。
動作確認から、実際のソースコードまで追って確認できたので、いい勉強になったなぁと思います。