Einführung in BouncyCastle mit Java

1. Übersicht

BouncyCastle ist eine Java-Bibliothek, die die standardmäßige Java Cryptographic Extension (JCE) ergänzt.

In diesem Einführungsartikel zeigen wir, wie Sie mit BouncyCastle kryptografische Vorgänge wie Verschlüsselung und Signatur ausführen.

2. Maven-Konfiguration

Bevor wir mit der Bibliothek arbeiten, müssen wir die erforderlichen Abhängigkeiten zu unserer Datei pom.xml hinzufügen :

 org.bouncycastle bcpkix-jdk15on 1.58 

Beachten Sie, dass wir immer die neuesten Abhängigkeitsversionen im Maven Central Repository nachschlagen können.

3. Richten Sie Richtliniendateien mit unbegrenzter Stärke ein

Die Standard-Java-Installation ist hinsichtlich der Stärke für kryptografische Funktionen begrenzt. Dies liegt an Richtlinien, die die Verwendung eines Schlüssels mit einer Größe verbieten, die bestimmte Werte überschreitet, z. B. 128 für AES.

Um diese Einschränkung zu überwinden, müssen wir die Richtliniendateien mit unbegrenzter Stärke konfigurieren .

Dazu müssen wir zuerst das Paket herunterladen, indem wir diesem Link folgen. Anschließend müssen wir die komprimierte Datei in ein Verzeichnis unserer Wahl extrahieren, das zwei JAR-Dateien enthält:

  • local_policy.jar
  • US_export_policy.jar

Schließlich müssen wir nach dem Ordner {JAVA_HOME} / lib / security suchen und die vorhandenen Richtliniendateien durch die hier extrahierten ersetzen.

Beachten Sie, dass wir in Java 9 das Richtliniendateipaket nicht mehr herunterladen müssen. Es reicht aus , die Eigenschaft crypto.policy auf unbegrenzt zu setzen :

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

Sobald dies erledigt ist, müssen wir überprüfen, ob die Konfiguration korrekt funktioniert:

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

Als Ergebnis:

Max Key Size for AES : 2147483647

Basierend auf der maximalen Schlüsselgröße, die von der Methode getMaxAllowedKeyLength () zurückgegeben wird, können wir mit Sicherheit sagen, dass die Richtliniendateien mit unbegrenzter Stärke korrekt installiert wurden.

Wenn der zurückgegebene Wert gleich 128 ist, müssen wir sicherstellen, dass wir die Dateien in der JVM installiert haben, in der wir den Code ausführen.

4. Kryptografische Operationen

4.1. Zertifikat und privaten Schlüssel vorbereiten

Bevor wir mit der Implementierung kryptografischer Funktionen beginnen, müssen wir zunächst ein Zertifikat und einen privaten Schlüssel erstellen.

Zu Testzwecken können wir diese Ressourcen verwenden:

  • Baeldung.cer
  • Baeldung.p12 (Passwort = "Passwort")

Baeldung.cer ist ein digitales Zertifikat, das den internationalen Infrastrukturstandard für öffentliche Schlüssel X.509 verwendet, während Baeldung.p12 ein kennwortgeschützter PKCS12-Keystore ist, der einen privaten Schlüssel enthält.

Mal sehen, wie diese in Java geladen werden können:

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);

Zunächst haben wir den BouncyCastleProvider als Sicherheitsanbieter dynamisch mithilfe der addProvider () -Methode hinzugefügt .

Dies kann auch statisch erfolgen, indem Sie die Datei {JAVA_HOME} /jre/lib/security/java.security bearbeiten und diese Zeile hinzufügen:

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

Sobald der Anbieter ordnungsgemäß installiert ist, haben wir mit der Methode getInstance () ein CertificateFactory- Objekt erstellt .

Die Methode getInstance () akzeptiert zwei Argumente. den Zertifikatstyp "X.509" und den Sicherheitsanbieter "BC".

Die certFactory- Instanz wird anschließend verwendet, um ein X509Certificate- Objekt über die generateCertificate () -Methode zu generieren .

Auf die gleiche Weise haben wir ein PKCS12-Keystore-Objekt erstellt, für das die load () -Methode aufgerufen wird.

Die Methode getKey () gibt den privaten Schlüssel zurück, der einem bestimmten Alias ​​zugeordnet ist.

Beachten Sie, dass ein PKCS12-Keystore einen Satz privater Schlüssel enthält. Jeder private Schlüssel kann ein bestimmtes Kennwort haben. Aus diesem Grund benötigen wir ein globales Kennwort zum Öffnen des Keystores und ein bestimmtes Kennwort zum Abrufen des privaten Schlüssels.

Das Zertifikat und das private Schlüsselpaar werden hauptsächlich bei asymmetrischen kryptografischen Operationen verwendet:

  • Verschlüsselung
  • Entschlüsselung
  • Unterschrift
  • Überprüfung

4.2 CMS / PKCS7-Ver- und Entschlüsselung

Bei der asymmetrischen Verschlüsselungskryptografie erfordert jede Kommunikation ein öffentliches Zertifikat und einen privaten Schlüssel.

Der Empfänger ist an ein Zertifikat gebunden, das von allen Absendern öffentlich geteilt wird.

Einfach ausgedrückt, der Absender benötigt das Zertifikat des Empfängers, um eine Nachricht zu verschlüsseln, während der Empfänger den zugehörigen privaten Schlüssel benötigt, um sie entschlüsseln zu können.

Lassen Sie uns einen Blick darauf werfen, wie eine encryptData () - Funktion mithilfe eines Verschlüsselungszertifikats implementiert wird :

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; }

Wir haben ein JceKeyTransRecipientInfoGenerator- Objekt mit dem Zertifikat des Empfängers erstellt.

Then, we've created a new CMSEnvelopedDataGenerator object and added the recipient information generator into it.

After that, we've used the JceCMSContentEncryptorBuilder class to create an OutputEncrytor object, using the AES CBC algorithm.

The encryptor is used later to generate a CMSEnvelopedData object that encapsulates the encrypted message.

Finally, the encoded representation of the envelope is returned as a byte array.

Now, let's see what the implementation of the decryptData() method looks like:

public static byte[] decryptData( byte[] encryptedData, PrivateKey decryptionKey) throws CMSException { byte[] decryptedData = null; if (null != encryptedData && null != decryptionKey) { CMSEnvelopedData envelopedData = new CMSEnvelopedData(encryptedData); Collection recipients = envelopedData.getRecipientInfos().getRecipients(); KeyTransRecipientInformation recipientInfo = (KeyTransRecipientInformation) recipients.iterator().next(); JceKeyTransRecipient recipient = new JceKeyTransEnvelopedRecipient(decryptionKey); return recipientInfo.getContent(recipient); } return decryptedData; }

First, we've initialized a CMSEnvelopedData object using the encrypted data byte array, and then we've retrieved all the intended recipients of the message using the getRecipients() method.

Once done, we've created a new JceKeyTransRecipient object associated with the recipient's private key.

The recipientInfo instance contains the decrypted/encapsulated message, but we can't retrieve it unless we have the corresponding recipient's key.

Finally, given the recipient key as an argument, the getContent() method returns the raw byte array extracted from the EnvelopedData this recipient is associated with.

Let's write a simple test to make sure everything works exactly as it should:

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);

As a result:

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

4.3 CMS/PKCS7 Signature and Verification

Signature and verification are cryptographic operations that validate the authenticity of data.

Let's see how to sign a secret message using a digital certificate:

public static byte[] signData( byte[] data, X509Certificate signingCertificate, PrivateKey signingKey) throws Exception { byte[] signedMessage = null; List certList = new ArrayList(); 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; } 

First, we've embedded the input into a CMSTypedData, then, we've created a new CMSSignedDataGenerator object.

We've used SHA256withRSA as a signature algorithm, and our signing key to create a new ContentSigner object.

The contentSigner instance is used afterward, along with the signing certificate to create a SigningInfoGenerator object.

After adding the SignerInfoGenerator and the signing certificate to the CMSSignedDataGenerator instance, we finally use the generate() method to create a CMS signed-data object, which also carries a CMS signature.

Now that we've seen how to sign data, let's see how to verify signed data:

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 certCollection = certs.getMatches(signer.getSID()); X509CertificateHolder certHolder = certCollection.iterator().next(); return signer .verify(new JcaSimpleSignerInfoVerifierBuilder() .build(certHolder)); }

Again, we've created a CMSSignedData object based on our signed data byte array, then, we've retrieved all signers associated with the signatures using the getSignerInfos() method.

In this example, we've verified only one signer, but for generic use, it is mandatory to iterate over the collection of signers returned by the getSigners() method and check each one separately.

Schließlich haben wir ein SignerInformationVerifier- Objekt mit der build () -Methode erstellt und an die verify () -Methode übergeben.

Die Methode verify () gibt true zurück , wenn das angegebene Objekt die Signatur des Unterzeichnerobjekts erfolgreich überprüfen kann.

Hier ist ein einfaches Beispiel:

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

Als Ergebnis:

true

5. Schlussfolgerung

In diesem Artikel haben wir herausgefunden, wie die BouncyCastle-Bibliothek verwendet wird, um grundlegende kryptografische Vorgänge wie Verschlüsselung und Signatur auszuführen.

In einer realen Situation möchten wir unsere Daten häufig signieren und dann verschlüsseln. Auf diese Weise kann nur der Empfänger sie mit dem privaten Schlüssel entschlüsseln und ihre Authentizität anhand der digitalen Signatur überprüfen.

Die Codefragmente finden Sie wie immer auf GitHub.