Inleiding tot BouncyCastle met Java

1. Overzicht

BouncyCastle is een Java-bibliotheek die een aanvulling vormt op de standaard Java Cryptographic Extension (JCE).

In dit inleidende artikel laten we zien hoe u BouncyCastle kunt gebruiken om cryptografische bewerkingen uit te voeren, zoals codering en handtekening.

2. Maven-configuratie

Voordat we met de bibliotheek gaan werken, moeten we de vereiste afhankelijkheden toevoegen aan onze pom.xml het dossier:

 org.bouncycastle bcpkix-jdk15on 1.58 

Merk op dat we altijd de nieuwste versies van afhankelijkheden kunnen opzoeken in de Maven Central Repository.

3. Beleidsbestanden voor jurisdictie met onbeperkte kracht instellen

De standaard Java-installatie is beperkt in termen van kracht voor cryptografische functies, dit is te wijten aan beleid dat het gebruik van een sleutel met een grootte die bepaalde waarden overschrijdt, verbiedt. 128 voor AES.

Om deze beperking te overwinnen, moeten we dat doen configureer de bestanden met jurisdictiebeleid met onbeperkte sterkte.

Om dat te doen, moeten we eerst het pakket downloaden via deze link. Daarna moeten we het gecomprimeerde bestand uitpakken in een map naar keuze - die twee jar-bestanden bevat:

  • local_policy.jar
  • US_export_policy.jar

Ten slotte moeten we zoeken naar de {JAVA_HOME} / lib / security map en vervang de bestaande beleidsbestanden door degene die we hier hebben uitgepakt.

Let daar op in Java 9 hoeven we het pakket met beleidsbestanden niet langer te downloaden, het instellen van de crypto.policy eigendom aan onbeperkt is genoeg:

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

Als u klaar bent, moeten we controleren of de configuratie correct werkt:

int maxKeySize = javax.crypto.Cipher.getMaxAllowedKeyLength ("AES"); System.out.println ("Max. Sleutelgrootte voor AES:" + maxKeySize);

Als resultaat:

Max. Sleutelgrootte voor AES: 2147483647

Gebaseerd op de maximale sleutelgrootte die wordt geretourneerd door het getMaxAllowedKeyLength () methode, kunnen we gerust zeggen dat de beleidsbestanden voor onbeperkte sterkte correct zijn geïnstalleerd.

Als de geretourneerde waarde gelijk is aan 128, moeten we ervoor zorgen dat we de bestanden in de JVM hebben geïnstalleerd waar we de code uitvoeren.

4. Cryptografische bewerkingen

4.1. Certificaat en privésleutel voorbereiden

Voordat we ingaan op de implementatie van cryptografische functies, moeten we eerst een certificaat en een privésleutel maken.

Voor testdoeleinden kunnen we deze bronnen gebruiken:

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

Baeldung.cer is een digitaal certificaat dat gebruikmaakt van de internationale X.509 openbare-sleutelinfrastructuurstandaard, terwijl de Baeldung.p12 is een met een wachtwoord beveiligde PKCS12 Keystore die een privésleutel bevat.

Laten we eens kijken hoe deze in Java kunnen worden geladen:

Security.addProvider (nieuwe BouncyCastleProvider ()); CertificateFactory certFactory = CertificateFactory .getInstance ("X.509", "BC"); X509Certificate-certificaat = (X509Certificate) certFactory .generateCertificate (nieuwe FileInputStream ("Baeldung.cer")); char [] keystorePassword = "wachtwoord" .toCharArray (); char [] keyPassword = "wachtwoord" .toCharArray (); KeyStore keystore = KeyStore.getInstance ("PKCS12"); keystore.load (nieuwe FileInputStream ("Baeldung.p12"), keystorePassword); PrivateKey-sleutel = (PrivateKey) keystore.getKey ("baeldung", keyPassword);

Ten eerste hebben we het BouncyCastleProvider als beveiligingsprovider dynamisch gebruikmakend van de addProvider () methode.

Dit kan ook statisch worden gedaan door het {JAVA_HOME} /jre/lib/security/java.security bestand, en het toevoegen van deze regel:

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

Zodra de provider correct is geïnstalleerd, hebben we een CertificateFactory object met behulp van de getInstance () methode.

De getInstance () methode heeft twee argumenten; het certificaattype "X.509", en de beveiligingsprovider "BC".

De certFactory instantie wordt vervolgens gebruikt om een X509 Certificaat object, via de genererenCertificaat () methode.

Op dezelfde manier hebben we een PKCS12 Keystore-object gemaakt, waarop het laden() methode wordt genoemd.

De getKey () methode retourneert de persoonlijke sleutel die is gekoppeld aan een bepaalde alias.

Merk op dat een PKCS12 Keystore een set privésleutels bevat, elke privésleutel kan een specifiek wachtwoord hebben, daarom hebben we een globaal wachtwoord nodig om de Keystore te openen, en een specifiek wachtwoord om de privésleutel op te halen.

Het certificaat en het persoonlijke sleutelpaar worden voornamelijk gebruikt bij asymmetrische cryptografische bewerkingen:

  • Versleuteling
  • Decodering
  • Handtekening
  • Verificatie

4.2 CMS / PKCS7-codering en decodering

Bij asymmetrische encryptiecryptografie vereist elke communicatie een openbaar certificaat en een privésleutel.

De ontvanger is gebonden aan een certificaat, dat openbaar wordt gedeeld tussen alle afzenders.

Simpel gezegd, de afzender heeft het certificaat van de ontvanger nodig om een ​​bericht te versleutelen, terwijl de ontvanger de bijbehorende privésleutel nodig heeft om het te kunnen ontsleutelen.

Laten we eens kijken hoe we een encryptData () functie, met behulp van een versleutelingscertificaat:

openbare statische byte [] encryptData (byte [] gegevens, X509Certificate encryptionCertificate) genereert CertificateEncodingException, CMSException, IOException {byte [] encryptedData = null; if (null! = data && null! = encryptionCertificate) {CMSEnvelopedDataGenerator cmsEnvelopedDataGenerator = nieuwe CMSEnvelopedDataGenerator (); JceKeyTransRecipientInfoGenerator jceKey = nieuwe JceKeyTransRecipientInfoGenerator (encryptionCertificate); cmsEnvelopedDataGenerator.addRecipientInfoGenerator (transKeyGen); CMSTypedData msg = nieuwe CMSProcessableByteArray (data); OutputEncryptor encryptor = nieuwe JceCMSContentEncryptorBuilder (CMSAlgorithm.AES128_CBC) .setProvider ("BC"). Build (); CMSEnvelopedData cmsEnvelopedData = cmsEnvelopedDataGenerator .generate (msg, encryptor); encryptedData = cmsEnvelopedData.getEncoded (); } retourneer encryptedData; }

We hebben een JceKeyTransRecipientInfoGenerator object met behulp van het certificaat van de ontvanger.

Vervolgens hebben we een nieuw gemaakt CMSEnvelopedDataGenerator object en voegde de generator voor ontvangersinformatie eraan toe.

Daarna hebben we de JceCMSContentEncryptorBuilder class om een Uitgang Encrytor object, met behulp van het AES CBC-algoritme.

De encryptor wordt later gebruikt om een CMSEnvelopedData object dat het versleutelde bericht inkapselt.

Ten slotte wordt de gecodeerde weergave van de envelop geretourneerd als een byte-array.

Laten we nu eens kijken wat de implementatie van het decryptData () methode ziet eruit als:

openbare statische byte [] decryptData (byte [] encryptedData, PrivateKey decryptionKey) genereert CMSException {byte [] decryptedData = null; if (null! = encryptedData && null! = decryptionKey) {CMSEnvelopedData envelopedData = nieuwe CMSEnvelopedData (encryptedData); Ontvangers van collectie = envelopedData.getRecipientInfos (). GetRecipients (); KeyTransRecipientInformation recipientInfo = (KeyTransRecipientInformation) recipients.iterator (). Next (); JceKeyTransRecipient-ontvanger = nieuwe JceKeyTransEnvelopedRecipient (decryptionKey); return recipientInfo.getContent (ontvanger); } retourneer decryptedData; }

Ten eerste hebben we een CMSEnvelopedData object met behulp van de gecodeerde gegevensbyte-array, en vervolgens hebben we alle beoogde ontvangers van het bericht opgehaald met behulp van de getRecipients () methode.

Als u klaar bent, hebben we een nieuw gemaakt JceKeyTransRecipient object dat is gekoppeld aan de privésleutel van de ontvanger.

De ontvangerInfo instantie bevat het ontsleutelde / ingekapselde bericht, maar we kunnen het niet terughalen tenzij we de bijbehorende sleutel van de ontvanger hebben.

Ten slotte, gegeven de ontvangersleutel als argument, de Inhoud krijgen() methode retourneert de onbewerkte byte-array die uit de EnvelopedData deze ontvanger is geassocieerd met.

Laten we een eenvoudige test schrijven om er zeker van te zijn dat alles precies werkt zoals het zou moeten:

String secretMessage = "Mijn wachtwoord is 123456Seven"; System.out.println ("Origineel bericht:" + secretMessage); byte [] stringToEncrypt = secretMessage.getBytes (); byte [] encryptedData = encryptData (stringToEncrypt, certificaat); System.out.println ("Versleuteld bericht:" + nieuwe string (encryptedData)); byte [] rawData = decryptData (encryptedData, privateKey); String decryptedMessage = nieuwe String (rawData); System.out.println ("Decrypted Message:" + decryptedMessage);

Als resultaat:

Oorspronkelijk bericht: Mijn wachtwoord is 123456Seven Gecodeerd bericht: 0  *  H   ... Gedecodeerd bericht: Mijn wachtwoord is 123456Seven

4.3 CMS / PKCS7-handtekening en verificatie

Handtekening en verificatie zijn cryptografische bewerkingen die de authenticiteit van gegevens valideren.

Laten we eens kijken hoe we een geheim bericht kunnen ondertekenen met een digitaal certificaat:

openbare statische byte [] signData (byte [] gegevens, X509Certificate signingCertificate, PrivateKey signingKey) genereert Uitzondering {byte [] signedMessage = null; Lijst certList = nieuwe ArrayList (); CMSTypedData cmsData = nieuwe CMSProcessableByteArray (data); certList.add (signingCertificate); Store certs = nieuwe JcaCertStore (certList); CMSSignedDataGenerator cmsGenerator = nieuwe CMSSignedDataGenerator (); ContentSigner contentSigner = nieuwe JcaContentSignerBuilder ("SHA256withRSA"). Build (signingKey); cmsGenerator.addSignerInfoGenerator (nieuwe JcaSignerInfoGeneratorBuilder (nieuwe JcaDigestCalculatorProviderBuilder (). setProvider ("BC") .build ()). build (contentSigner, signingCertificate)); cmsGenerator.addCertificates (certs); CMSSignedData cms = cmsGenerator.generate (cmsData, true); ondertekendMessage = cms.getEncoded (); terug ondertekend bericht; } 

Ten eerste hebben we de invoer ingebed in een CMSTypedData, dan hebben we een nieuwe gemaakt CMSSignedDataGenerator voorwerp.

We hebben gebruikt SHA256withRSA als een ondertekeningsalgoritme en onze ondertekeningssleutel om een ​​nieuw ContentSigner voorwerp.

De contentSigner instantie wordt daarna gebruikt, samen met het ondertekeningscertificaat om een SigningInfoGenerator voorwerp.

Na het toevoegen van het SignerInfoGenerator en het ondertekeningscertificaat naar het CMSSignedDataGenerator we gebruiken bijvoorbeeld tenslotte de genereren () methode om een ​​CMS-object met ondertekende gegevens te maken, dat ook een CMS-handtekening draagt.

Nu we hebben gezien hoe we gegevens kunnen ondertekenen, gaan we kijken hoe we ondertekende gegevens kunnen verifiëren:

openbare statische boolean verifSignedData (byte [] signedData) genereert uitzondering {X509Certificate signCert = null; ByteArrayInputStream inputStream = nieuwe ByteArrayInputStream (ondertekendData); ASN1InputStream asnInputStream = nieuwe ASN1InputStream (inputStream); CMSSignedData cmsSignedData = nieuwe CMSSignedData (ContentInfo.getInstance (asnInputStream.readObject ())); SignerInformationStore-ondertekenaars = cmsSignedData.getCertificates (). GetSignerInfos (); SignerInformation signer = signers.getSigners (). Iterator (). Next (); Verzameling certCollection = certs.getMatches (signer.getSID ()); X509CertificateHolder certHolder = certCollection.iterator (). Next (); retourneer ondertekenaar .verify (nieuwe JcaSimpleSignerInfoVerifierBuilder () .build (certHolder)); }

Nogmaals, we hebben een CMSSignedData object op basis van onze ondertekende gegevensbyte-array, vervolgens hebben we alle ondertekenaars opgehaald die aan de handtekeningen zijn gekoppeld met behulp van de getSignerInfos () methode.

In dit voorbeeld hebben we slechts één ondertekenaar geverifieerd, maar voor algemeen gebruik is het verplicht om de verzameling ondertekenaars te herhalen die door de getSigners () methode en controleer elk afzonderlijk.

Ten slotte hebben we een SignerInformationVerifier object met behulp van de bouwen() methode en doorgegeven aan de verifiëren() methode.

De methode verify () retourneert waar of het opgegeven object de handtekening op het ondertekeningsobject met succes kan verifiëren.

Hier is een eenvoudig voorbeeld:

byte [] signedData = signData (rawData, certificaat, privateKey); Booleaanse controle = verifSignData (ondertekendData); System.out.println (check);

Als resultaat:

waar

5. Conclusie

In dit artikel hebben we ontdekt hoe we de BouncyCastle-bibliotheek kunnen gebruiken om elementaire cryptografische bewerkingen uit te voeren, zoals codering en handtekening.

In een echte situatie willen we onze gegevens vaak ondertekenen en vervolgens versleutelen, op die manier kan alleen de ontvanger deze ontsleutelen met de privésleutel en de authenticiteit controleren op basis van de digitale handtekening.

De codefragmenten zijn, zoals altijd, te vinden op GitHub.