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 CentralRepositoryで常に最新の依存関係バージョンを検索できることに注意してください。

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

標準のJavaインストールは、暗号化機能の強度の点で制限されています。これは、特定の値を超えるサイズのキーの使用を禁止するポリシーによるものです。 AESの場合は128。

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

そのためには、まずこのリンクをたどってパッケージをダウンロードする必要があります。 その後、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 は、パスワードで保護された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オブジェクトを生成します。

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

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 クラスを使用して、AESCBCアルゴリズムを使用してOutputEncrytorオブジェクトを作成しました。

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

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

次に、 decodeData()メソッドの実装がどのようになるかを見てみましょう。

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オブジェクトを作成しました。

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

最後に、引数として受信者キーを指定すると、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.3. 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 インスタンスが署名証明書とともに使用され、SignatureInfoGeneratorオブジェクトが作成されます。

SignerInfoGeneratorと署名証明書をCMSSignedDataGeneratorインスタンスに追加した後、最後に generate()メソッドを使用してCMS署名付きデータオブジェクトを作成します。 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ライブラリを使用して、暗号化や署名などの基本的な暗号化操作を実行する方法を発見しました。

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

コードスニペットは、いつものように、GitHubにあります。