1. 概要

このチュートリアルでは、デジタル署名メカニズムと、Java暗号化アーキテクチャ(JCA)を使用してそれを実装する方法について学習します。 KeyPair、MessageDigest、Cipher、KeyStore、Certificate、および Signature JCAAPIについて説明します。

まず、デジタル署名とは何か、キーペアを生成する方法、および認証局(CA)から公開キーを認証する方法を理解することから始めます。 その後、低レベルおよび高レベルのJCAAPIを使用してデジタル署名を実装する方法を説明します。

2. デジタル署名とは何ですか?

2.1. デジタル署名の定義

デジタル署名は、次のことを保証するための手法です。

  • 整合性:メッセージは転送中に変更されていません
  • 信憑性:メッセージの作成者は、実際には彼らが主張する人物です
  • 否認防止:メッセージの作成者は、後で自分が発信元であることを否定できません

2.2. デジタル署名付きのメッセージの送信

技術的に言えば、 a デジタル署名はメッセージの暗号化されたハッシュ(ダイジェスト、チェックサム)です。 つまり、メッセージからハッシュを生成し、選択したアルゴリズムに従って秘密鍵で暗号化します。

次に、メッセージ、暗号化されたハッシュ、対応する公開鍵、およびアルゴリズムがすべて送信されます。 これは、デジタル署名付きのメッセージとして分類されます。

2.3. デジタル署名の受信と確認

デジタル署名を確認するために、メッセージ受信者は受信したメッセージから新しいハッシュを生成し、公開鍵を使用して受信した暗号化されたハッシュを復号化し、それらを比較します。 それらが一致する場合、デジタル署名は検証されたと言われます。

メッセージ自体ではなく、メッセージハッシュのみを暗号化することに注意してください。つまり、デジタル署名はメッセージを秘密にしようとはしません。 私たちのデジタル署名は、メッセージが転送中に変更されていないことを証明するだけです。

署名が検証されると、秘密鍵の所有者のみがメッセージの作成者になることができると確信しています

3. デジタル証明書と公開鍵ID

証明書は、IDを特定の公開鍵に関連付けるドキュメントです。証明書は、認証局(CA)と呼ばれるサードパーティのエンティティによって署名されます。

公開された公開鍵で復号化したハッシュが実際のハッシュと一致する場合、メッセージは署名されていることがわかっています。 しかし、公開鍵が実際に適切なエンティティからのものであることをどうやって知ることができますか? これは、デジタル証明書を使用することで解決されます。

デジタル証明書には公開鍵が含まれており、それ自体が別のエンティティによって署名されています。そのエンティティの署名自体は、別のエンティティなどによって検証できます。 最終的には、証明書チェーンと呼ばれるものができあがります。 各上位エンティティは、次のエンティティの公開鍵を認証します。 最もトップレベルのエンティティは自己署名です。つまり、彼の公開鍵は彼自身の秘密鍵によって署名されています。

X.509は最も使用されている証明書形式であり、バイナリ形式(DER)またはテキスト形式(PEM)のいずれかで出荷されます。 JCAは、X509Certificateクラスを介してこれの実装をすでに提供しています。

4. KeyPair管理

デジタル署名は秘密鍵と公開鍵を使用するため、メッセージの署名と確認には、それぞれJCAクラスPrivateKeyPublicKeyを使用します。

4.1. KeyPairを取得する

秘密鍵と公開鍵の鍵ペアを作成するには、Java keytoolを使用します。

genkeypairコマンドを使用してキーペアを生成してみましょう。

keytool -genkeypair -alias senderKeyPair -keyalg RSA -keysize 2048 \
  -dname "CN=Baeldung" -validity 365 -storetype PKCS12 \
  -keystore sender_keystore.p12 -storepass changeit

これにより、秘密鍵とそれに対応する公開鍵が作成されます。 公開鍵はX.509自己署名証明書にラップされ、X.509自己署名証明書は単一要素の証明書チェーンにラップされます。 証明書チェーンと秘密鍵をキーストアファイルsender_keystore.p12に保存します。このファイルは、 KeyStoreAPIを使用して処理できます。

ここでは、PKCS12キーストア形式を使用しました。これは、Java独自のJKS形式よりも標準で推奨されているためです。 また、パスワードとエイリアスは、次のサブセクションでキーストアファイルをロードするときに使用するため、覚えておく必要があります。

4.2. 署名用の秘密鍵のロード

メッセージに署名するには、PrivateKey。のインスタンスが必要です。

KeyStore APIと、以前のKeystoreファイル sender_keystore.p12、を使用して、PrivateKeyオブジェクトを取得できます。

KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new FileInputStream("sender_keystore.p12"), "changeit");
PrivateKey privateKey = 
  (PrivateKey) keyStore.getKey("senderKeyPair", "changeit");

4.3. 公開鍵の公開

公開鍵を公開する前に、まず自己署名証明書とCA署名証明書のどちらを使用するかを決定する必要があります。

自己署名証明書を使用する場合は、キーストアファイルからエクスポートするだけで済みます。これは、exportcertコマンドで実行できます。

keytool -exportcert -alias senderKeyPair -storetype PKCS12 \
  -keystore sender_keystore.p12 -file \
  sender_certificate.cer -rfc -storepass changeit

それ以外の場合、 CA署名付き証明書を使用する場合は、証明書署名要求(CSR)を作成する必要があります。 これは、certreqコマンドを使用して行います。

keytool -certreq -alias senderKeyPair -storetype PKCS12 \
  -keystore sender_keystore.p12 -file -rfc \
  -storepass changeit > sender_certificate.csr

次に、CSRファイル sender_certificate.csr、が、署名の目的で認証局に送信されます。 これが行われると、バイナリ(DER)またはテキスト(PEM)形式のいずれかでX.509証明書にラップされた署名付き公開鍵を受け取ります。 ここでは、PEM形式にrfcオプションを使用しました。

CAから受け取った公開鍵sender_certificate.cer、は、CAによって署名され、クライアントが利用できるようになりました。

4.4. 検証のための公開鍵のロード

公開鍵にアクセスできる受信者は、importcertコマンドを使用して公開鍵をキーストアにロードできます。

keytool -importcert -alias receiverKeyPair -storetype PKCS12 \
  -keystore receiver_keystore.p12 -file \
  sender_certificate.cer -rfc -storepass changeit

また、以前と同様に KeyStore APIを使用すると、PublicKeyインスタンスを取得できます。

KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new FileInputStream("receiver_keytore.p12"), "changeit");
Certificate certificate = keyStore.getCertificate("receiverKeyPair");
PublicKey publicKey = certificate.getPublicKey();

送信側にPrivateKeyインスタンスがあり、受信側に PublicKey のインスタンスがあるので、署名と検証のプロセスを開始できます。

5. MessageDigestおよびCipherクラスを使用したデジタル署名

これまで見てきたように、デジタル署名はハッシュと暗号化に基づいています。

通常、MessageDigestクラスとSHAまたはMD5をハッシュに使用し、Cipherクラスを暗号化に使用します。

それでは、デジタル署名メカニズムの実装を始めましょう。

5.1. メッセージハッシュの生成

メッセージには、文字列、ファイル、またはその他のデータを使用できます。 それでは、簡単なファイルの内容を見てみましょう。

byte[] messageBytes = Files.readAllBytes(Paths.get("message.txt"));

ここで、 MessageDigest、を使用して、digestメソッドを使用してハッシュを生成しましょう。

MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] messageHash = md.digest(messageBytes);

ここでは、最も一般的に使用されているSHA-256アルゴリズムを使用しました。 他の選択肢は、MD5、SHA-384、およびSHA-512です。

5.2. 生成されたハッシュの暗号化

メッセージを暗号化するには、アルゴリズムと秘密鍵が必要です。 ここでは、RSAアルゴリズムを使用します。 DSAアルゴリズムは別のオプションです。

Cipher インスタンスを作成し、暗号化のために初期化してみましょう。 次に、 doFinal()メソッドを呼び出して、以前にハッシュされたメッセージを暗号化します。

Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
byte[] digitalSignature = cipher.doFinal(messageHash);

署名は、後で送信するためにファイルに保存できます。

Files.write(Paths.get("digital_signature_1"), digitalSignature);

この時点で、メッセージ、デジタル署名、公開鍵、およびアルゴリズムがすべて送信され、受信者はこれらの情報を使用してメッセージの整合性を検証できます。

5.3. 署名の確認

メッセージを受信したら、その署名を確認する必要があります。 そのために、受信した暗号化されたハッシュを復号化し、受信したメッセージから作成したハッシュと比較します。

受信したデジタル署名を読みましょう。

byte[] encryptedMessageHash = 
  Files.readAllBytes(Paths.get("digital_signature_1"));

復号化のために、Cipherインスタンスを作成します。 次に、doFinalメソッドを呼び出します。

Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, publicKey);
byte[] decryptedMessageHash = cipher.doFinal(encryptedMessageHash);

次に、受信したメッセージから新しいメッセージハッシュを生成します。

byte[] messageBytes = Files.readAllBytes(Paths.get("message.txt"));

MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] newMessageHash = md.digest(messageBytes);

そして最後に、新しく生成されたメッセージハッシュが復号化されたものと一致するかどうかを確認します。

boolean isCorrect = Arrays.equals(decryptedMessageHash, newMessageHash);

この例では、テキストファイル message.txt を使用して、送信するメッセージ、または受信したメッセージの本文の場所をシミュレートしました。 通常、署名と一緒にメッセージを受信することを期待します。

6. 署名クラスを使用したデジタル署名

これまで、低レベルのAPIを使用して、独自のデジタル署名検証プロセスを構築してきました。 これは、それがどのように機能するかを理解するのに役立ち、カスタマイズすることができます。

ただし、JCAはすでにSignatureクラスの形式で専用のAPIを提供しています。

6.1. メッセージに署名する

署名のプロセスを開始するには、最初にSignatureクラスのインスタンスを作成します。 そのためには、署名アルゴリズムが必要です。 次に、Signatureを秘密鍵で初期化します。

Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);

選択した署名アルゴリズムSHA256withRSAこの例ではは、ハッシュアルゴリズムと暗号化アルゴリズムを組み合わせたものです。 他の選択肢には、 SHA1withRSA SHA1withDSA 、およびMD5withRSAなどがあります。

次に、メッセージのバイト配列に署名します。

byte[] messageBytes = Files.readAllBytes(Paths.get("message.txt"));

signature.update(messageBytes);
byte[] digitalSignature = signature.sign();

後で送信するために、署名をファイルに保存できます。

Files.write(Paths.get("digital_signature_2"), digitalSignature);

6.2. 署名の確認

受信した署名を確認するために、Signatureインスタンスを再度作成します。

Signature signature = Signature.getInstance("SHA256withRSA");

次に、公開鍵を取得する initVerify メソッドを呼び出して、検証のためにSignatureオブジェクトを初期化します。

signature.initVerify(publicKey);

次に、 update メソッドを呼び出して、受信したメッセージバイトを署名オブジェクトに追加する必要があります。

byte[] messageBytes = Files.readAllBytes(Paths.get("message.txt"));

signature.update(messageBytes);

そして最後に、verifyメソッドを呼び出すことで署名を確認できます。

boolean isCorrect = signature.verify(receivedSignature);

7. 結論

この記事では、最初にデジタル署名がどのように機能するか、およびデジタル証明書の信頼を確立する方法について説明しました。 次に、Java暗号化アーキテクチャの MessageDigest、 Cipher、、およびSignatureクラスを使用してデジタル署名を実装しました。

秘密鍵を使用してデータに署名する方法と、公開鍵を使用して署名を検証する方法について詳しく説明しました。

いつものように、この記事のコードはGitHubから入手できます。