Javaを使ったBouncyCastleの紹介

1概要

BouncyCastle は、デフォルトのJava Cryptographic Extension(JCE)を補足するJavaライブラリです。

この紹介記事では、BouncyCastleを使用して暗号化や署名などの暗号化操作を実行する方法について説明します。

2 Mavenの設定

ライブラリを使い始める前に、 pom.xml ファイルに必要な依存関係を追加する必要があります。

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcpkix-jdk15on</artifactId>
    <version>1.58</version>
</dependency>

Maven Central Repository で、常に最新の依存関係バージョンを調べることができます。

3無制限強度管轄ポリシーファイルの設定

標準的なJavaインストールは、暗号機能の強度に関して制限されている。これは、ある値を超えるサイズを有する鍵の使用を禁止する方針によるものである。 AESの場合は128。

この制限を克服するには、** 無制限の強度の管轄ポリシーファイルを設定する必要があります。

そのためには、まずhttp://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html[link]に従ってパッケージをダウンロードする必要があります。

その後、zipファイルを選択したディレクトリに展開する必要があります。これには2つのjarファイルが含まれています。

  • local policy.jar__

  • US export policy.jar

最後に、 \ {JAVA HOME}/lib/security__フォルダーを探して、既存のポリシーファイルをここで抽出したものと置き換えます。

  • Java 9では、 crypto.policy プロパティを unlimited に設定するだけで、ポリシーファイルパッケージをダウンロードする必要がなくなりました。

Security.setProperty("crypto.policy", "unlimited");

完了したら、設定が正しく機能していることを確認する必要があります。

int maxKeySize = javax.crypto.Cipher.getMaxAllowedKeyLength("AES");
System.out.println("Max Key Size for AES : " + maxKeySize);

結果として:

Max Key Size for AES : 2147483647

getMaxAllowedKeyLength() メソッドによって返される最大キーサイズに基づいて、無制限強度ポリシーファイルが正しくインストールされたと安全に言えます。

戻り値が128の場合、コードを実行しているJVMにファイルをインストールしたことを確認する必要があります。

4暗号化操作

4.1. 証明書と秘密鍵の準備

暗号機能の実装に入る前に、まず証明書と秘密鍵を作成する必要があります。

テスト目的で、これらのリソースを使用できます。

(パスワード=“パスワード”)]

Baeldung.cer は、国際的なX.509公開鍵インフラストラクチャ標準を使用するデジタル証明書です。一方、 Baeldung.p12 は、パスワードで保護されたhttps://tools.ietf.org/html/rfc7292[PKCS12]秘密鍵を含む鍵ストアキー。

これらをJavaにロードする方法を見てみましょう。

Security.addProvider(new BouncyCastleProvider());
CertificateFactory certFactory= CertificateFactory
  .getInstance("X.509", "BC");

X509Certificate certificate = (X509Certificate) certFactory
  .generateCertificate(new FileInputStream("Baeldung.cer"));

char[]keystorePassword = "password".toCharArray();
char[]keyPassword = "password".toCharArray();

KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(new FileInputStream("Baeldung.p12"), keystorePassword);
PrivateKey key = (PrivateKey) keystore.getKey("baeldung", keyPassword);

まず、 addProvider() メソッドを動的に使用して BouncyCastleProvider をセキュリティプロバイダとして追加しました。

これは \ {JAVA HOME}/jre/lib/security/java.security__ファイルを編集して次の行を追加して静的に行うこともできます。

security.provider.N = org.bouncycastle.jce.provider.BouncyCastleProvider

プロバイダが正しくインストールされたら、 getInstance() メソッドを使用して CertificateFactory オブジェクトを作成しました。

getInstance() メソッドは2つの引数を取ります。証明書タイプは「X.509」、セキュリティプロバイダは「BC」です。

certFactory インスタンスは、その後、 generateCertificate() メソッドを介して X509Certificate オブジェクトを生成するために使用されます。

同様に、 load() メソッドが呼び出されるPKCS12 Keystoreオブジェクトを作成しました。

getKey() メソッドは、指定された別名に関連した秘密鍵を返します。

PKCS12キーストアには一連の秘密鍵が含まれており、各秘密鍵には特定のパスワードを設定できます。そのため、キーストアを開くにはグローバルパスワード、秘密鍵を取得するには特定のパスワードが必要です。

証明書と秘密鍵のペアは、主に非対称暗号操作で使用されます。

暗号化

復号化

  • サイン

  • 確認

4.2 CMS/PKCS7暗号化と復号化

非対称暗号化暗号方式では、通信ごとに公開証明書と秘密鍵が必要です。

受信者は証明書にバインドされており、すべての送信者間で公的に共有されています。

簡単に言うと、送信者はメッセージを暗号化するために受信者の証明書を必要とし、受信者はそれを解読することができるように関連する秘密鍵を必要とします。

暗号化証明書を使用して encryptData() 関数を実装する方法を見てみましょう。

public static byte[]encryptData(byte[]data,
  X509Certificate encryptionCertificate)
  throws CertificateEncodingException, CMSException, IOException {

    byte[]encryptedData = null;
    if (null != data && null != encryptionCertificate) {
        CMSEnvelopedDataGenerator cmsEnvelopedDataGenerator
          = new CMSEnvelopedDataGenerator();

        JceKeyTransRecipientInfoGenerator jceKey
          = new JceKeyTransRecipientInfoGenerator(encryptionCertificate);
        cmsEnvelopedDataGenerator.addRecipientInfoGenerator(transKeyGen);
        CMSTypedData msg = new CMSProcessableByteArray(data);
        OutputEncryptor encryptor
          = new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES128__CBC)
          .setProvider("BC").build();
        CMSEnvelopedData cmsEnvelopedData = cmsEnvelopedDataGenerator
          .generate(msg,encryptor);
        encryptedData = cmsEnvelopedData.getEncoded();
    }
    return encryptedData;
}

受信者の証明書を使用して JceKeyTransRecipientInfoGenerator オブジェクトを作成しました。

それから、新しい CMSEnvelopedDataGenerator オブジェクトを作成し、それに受信者情報ジェネレータを追加しました。

その後、 JceCMSContentEncryptorBuilder クラスを使用して、AES CBCアルゴリズムを使用して OutputEncrytor オブジェクトを作成しました。

暗号化機能は、後で暗号化されたメッセージをカプセル化する CMSEnvelopedData オブジェクトを生成するために使用されます。

最後に、エンベロープのエンコードされた表現がバイト配列として返されます。

それでは、 decryptData() メソッドの実装がどのようになるかを見てみましょう。

public static byte[]decryptData(
  byte[]encryptedData,
  PrivateKey decryptionKey)
  throws CMSException {

    byte[]decryptedData = null;
    if (null != encryptedData && null != decryptionKey) {
        CMSEnvelopedData envelopedData = new CMSEnvelopedData(encryptedData);

        Collection<RecipientInformation> recipients
          = envelopedData.getRecipientInfos().getRecipients();
        KeyTransRecipientInformation recipientInfo
          = (KeyTransRecipientInformation) recipients.iterator().next();
        JceKeyTransRecipient recipient
          = new JceKeyTransEnvelopedRecipient(decryptionKey);

        return recipientInfo.getContent(recipient);
    }
    return decryptedData;
}

最初に、暗号化されたデータバイト配列を使用して CMSEnvelopedData オブジェクトを初期化し、次に getRecipients() メソッドを使用してメッセージの受信予定者をすべて取得しました。

完了したら、受信者の秘密鍵に関連付けられた新しい JceKeyTransRecipient オブジェクトを作成しました。

recipientInfo インスタンスには復号化/カプセル化されたメッセージが含まれていますが、対応する受信者のキーがない限り、取得することはできません。

  • 最後に、引数として受信者キーを指定すると、 getContent() メソッドはこの受信者が関連付けられている EnvelopedData ** から抽出された生のバイト配列を返します。

すべてが正しく動作することを確認するための簡単なテストを書きましょう。

String secretMessage = "My password is 123456Seven";
System.out.println("Original Message : " + secretMessage);
byte[]stringToEncrypt = secretMessage.getBytes();
byte[]encryptedData = encryptData(stringToEncrypt, certificate);
System.out.println("Encrypted Message : " + new String(encryptedData));
byte[]rawData = decryptData(encryptedData, privateKey);
String decryptedMessage = new String(rawData);
System.out.println("Decrypted Message : " + decryptedMessage);

結果として:

Original Message : My password is 123456Seven
Encrypted Message : 0�** �H��...
Decrypted Message : My password is 123456Seven

4.2 CMS/PKCS7の署名と 検証

署名と検証は、データの信頼性を検証する暗号化操作です。

デジタル証明書を使用して秘密のメッセージに署名する方法を見てみましょう。

public static byte[]signData(
  byte[]data,
  X509Certificate signingCertificate,
  PrivateKey signingKey) throws Exception {

    byte[]signedMessage = null;
    List<X509Certificate> certList = new ArrayList<X509Certificate>();
    CMSTypedData cmsData= new CMSProcessableByteArray(data);
    certList.add(signingCertificate);
    Store certs = new JcaCertStore(certList);

    CMSSignedDataGenerator cmsGenerator = new CMSSignedDataGenerator();
    ContentSigner contentSigner
      = new JcaContentSignerBuilder("SHA256withRSA").build(signingKey);
    cmsGenerator.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(
      new JcaDigestCalculatorProviderBuilder().setProvider("BC")
      .build()).build(contentSigner, signingCertificate));
    cmsGenerator.addCertificates(certs);

    CMSSignedData cms = cmsGenerator.generate(cmsData, true);
    signedMessage = cms.getEncoded();
    return signedMessage;
}

まず、入力を CMSTypedData に埋め込み、次に新しい CMSSignedDataGenerator オブジェクトを作成しました。

署名アルゴリズムとして SHA256withRSA を使用し、署名キーを使用して新しい ContentSigner オブジェクトを作成しました。

その後、 ContentSigner インスタンスが署名証明書とともに使用され、 SigningInfoGenerator オブジェクトが作成されます。

SignerInfoGenerator と署名証明書を CMSSignedDataGenerator インスタンスに追加した後、最後に generate() メソッドを使用してCMS署名データオブジェクトを作成します。

データに署名する方法を見たので、署名したデータを確認する方法を見てみましょう。

public static boolean verifSignedData(byte[]signedData)
  throws Exception {

    X509Certificate signCert = null;
    ByteArrayInputStream inputStream
     = new ByteArrayInputStream(signedData);
    ASN1InputStream asnInputStream = new ASN1InputStream(inputStream);
    CMSSignedData cmsSignedData = new CMSSignedData(
      ContentInfo.getInstance(asnInputStream.readObject()));

    SignerInformationStore signers
      = cmsSignedData.getCertificates().getSignerInfos();
    SignerInformation signer = signers.getSigners().iterator().next();
    Collection<X509CertificateHolder> certCollection
      = certs.getMatches(signer.getSID());
    X509CertificateHolder certHolder = certCollection.iterator().next();

    return signer
      .verify(new JcaSimpleSignerInfoVerifierBuilder()
      .build(certHolder));
}

署名付きデータバイト配列に基づいて CMSSignedData オブジェクトを作成した後、 getSignerInfos() メソッドを使用して署名に関連付けられているすべての署名者を取得しました。

この例では1人の署名者のみを検証しましたが、一般的な使用のためには、 getSigners() メソッドによって返された署名者のコレクションを反復処理し、それぞれを個別にチェックすることが必須です。

最後に、 build() メソッドを使用して SignerInformationVerifier オブジェクトを作成し、それを verify() メソッドに渡しました。

特定のオブジェクトが署名者オブジェクトの署名を正常に検証できる場合、verify()メソッドは true を返します。

これが簡単な例です。

byte[]signedData = signData(rawData, certificate, privateKey);
Boolean check = verifSignData(signedData);
System.out.println(check);

結果として:

true

5結論

この記事では、BouncyCastleライブラリを使用して、暗号化や署名などの基本的な暗号化操作を実行する方法を発見しました。

現実の状況では、データを署名してから暗号化することがよくあります。そうすることで、受信者だけが秘密鍵を使用してデータを復号化し、デジタル署名に基づいてその信頼性を確認できます。

コードスニペットは、いつもどおりhttps://github.com/eugenp/tutorials/tree/master/libraries[over GitHub]にあります。