Introduktion til BouncyCastle med Java

1. Oversigt

BouncyCastle er et Java-bibliotek, der supplerer standard Java Cryptographic Extension (JCE).

I denne indledende artikel skal vi vise, hvordan man bruger BouncyCastle til at udføre kryptografiske operationer, såsom kryptering og signatur.

2. Maven-konfiguration

Før vi begynder at arbejde med biblioteket, skal vi tilføje de krævede afhængigheder til vores pom.xml fil:

 org.bouncycastle bcpkix-jdk15on 1.58 

Bemærk, at vi altid kan slå de nyeste afhængighedsversioner op i Maven Central Repository.

3. Opsæt ubegrænsede styringspolitiske filer

Standard Java-installationen er begrænset med hensyn til styrke for kryptografiske funktioner, dette skyldes politikker, der forbyder brugen af ​​en nøgle med en størrelse, der overstiger visse værdier, f.eks. 128 til AES.

For at overvinde denne begrænsning er vi nødt til det konfigurer de ubegrænsede styringspolitiske filer.

For at gøre det skal vi først downloade pakken ved at følge dette link. Bagefter er vi nødt til at udpakke den zip-fil til en mappe efter eget valg - som indeholder to jar-filer:

  • local_policy.jar
  • US_export_policy.jar

Endelig skal vi kigge efter {JAVA_HOME} / lib / sikkerhed mappe og udskift de eksisterende politikfiler med dem, vi har hentet her.

Noter det i Java 9 behøver vi ikke længere downloade pakken med politikfiler, indstilling af crypto.policy ejendom til ubegrænset er nok:

Security.setProperty ("crypto.policy", "ubegrænset");

Når det er gjort, skal vi kontrollere, at konfigurationen fungerer korrekt:

int maxKeySize = javax.crypto.Cipher.getMaxAllowedKeyLength ("AES"); System.out.println ("Maks. Nøglestørrelse for AES:" + maxKeySize);

Som resultat:

Maks nøglestørrelse for AES: 2147483647

Baseret på den maksimale nøglestørrelse, der returneres af getMaxAllowedKeyLength () metode, kan vi med sikkerhed sige, at de ubegrænsede styrkepolitiske filer er installeret korrekt.

Hvis den returnerede værdi er lig med 128, skal vi sikre os, at vi har installeret filerne i JVM, hvor vi kører koden.

4. Kryptografiske operationer

4.1. Forberedelse af certifikat og privat nøgle

Før vi hopper ind i implementeringen af ​​kryptografiske funktioner, skal vi først oprette et certifikat og en privat nøgle.

Til testformål kan vi bruge disse ressourcer:

  • Baeldung.cer
  • Baeldung.p12 (password = “password”)

Baeldung.cer er et digitalt certifikat, der bruger den internationale X.509-infrastruktur for offentlig nøgleinfrastruktur, mens Baeldung.p12 er en adgangskodebeskyttet PKCS12 Keystore, der indeholder en privat nøgle.

Lad os se, hvordan disse kan indlæses i Java:

Security.addProvider (ny BouncyCastleProvider ()); CertificateFactory certFactory = CertificateFactory .getInstance ("X.509", "BC"); X509Certificate certifikat = (X509Certificate) certFactory .generateCertificate (nyt FileInputStream ("Baeldung.cer")); char [] keystorePassword = "adgangskode" .toCharArray (); char [] keyPassword = "password" .toCharArray (); KeyStore keystore = KeyStore.getInstance ("PKCS12"); keystore.load (ny FileInputStream ("Baeldung.p12"), keystorePassword); PrivateKey-nøgle = (PrivateKey) keystore.getKey ("baeldung", keyPassword);

Først har vi tilføjet BouncyCastleProvider som en sikkerhedsudbyder, der dynamisk bruger addProvider () metode.

Dette kan også gøres statisk ved at redigere {JAVA_HOME} /jre/lib/security/java.security fil og tilføje denne linje:

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

Når udbyderen er korrekt installeret, har vi oprettet en CertificateFactory objekt ved hjælp af getInstance () metode.

Det getInstance () metoden tager to argumenter; certifikattypen “X.509” og sikkerhedsudbyderen “BC”.

Det certFabrik instans bruges efterfølgende til at generere en X509 Certifikat objekt, via generereCertifikat () metode.

På samme måde har vi oprettet et PKCS12 Keystore-objekt, hvorpå belastning() metode kaldes.

Det getKey () metode returnerer den private nøgle, der er knyttet til et givet alias.

Bemærk, at en PKCS12 Keystore indeholder et sæt private nøgler, hver privat nøgle kan have en bestemt adgangskode, det er derfor, vi har brug for en global adgangskode for at åbne Keystore og en bestemt nøgle til at hente den private nøgle.

Certifikatet og det private nøglepar bruges primært til asymmetriske kryptografiske operationer:

  • Kryptering
  • Dekryptering
  • Underskrift
  • Verifikation

4.2 CMS / PKCS7-kryptering og dekryptering

I asymmetrisk krypteringskryptering kræver hver kommunikation et offentligt certifikat og en privat nøgle.

Modtageren er bundet til et certifikat, der deles offentligt mellem alle afsendere.

Kort sagt, afsenderen har brug for modtagerens certifikat for at kryptere en besked, mens modtageren har brug for den tilknyttede private nøgle for at kunne dekryptere den.

Lad os se på, hvordan man implementerer en encryptData () funktion ved hjælp af et krypteringscertifikat:

offentlig statisk byte [] encryptData (byte [] data, X509Certificate encryptionCertificate) kaster CertificateEncodingException, CMSException, IOException {byte [] encryptedData = null; hvis (null! = data && null! = encryptionCertificate) {CMSEnvelopedDataGenerator cmsEnvelopedDataGenerator = ny CMSEnvelopedDataGenerator (); JceKeyTransRecipientInfoGenerator jceKey = ny JceKeyTransRecipientInfoGenerator (encryptionCertificate); cmsEnvelopedDataGenerator.addRecipientInfoGenerator (transKeyGen); CMSTypedData msg = ny CMSProcessableByteArray (data); OutputEncryptor encryptor = ny JceCMSContentEncryptorBuilder (CMSAlgorithm.AES128_CBC) .setProvider ("BC"). Build (); CMSEnvelopedData cmsEnvelopedData = cmsEnvelopedDataGenerator .generate (msg, encryptor); encryptedData = cmsEnvelopedData.getEncoded (); } returner encryptedData; }

Vi har oprettet en JceKeyTransRecipientInfoGenerator objekt ved hjælp af modtagerens certifikat.

Så har vi oprettet en ny CMSEnvelopedDataGenerator objekt og tilføjede modtagerinformationsgeneratoren til den.

Derefter har vi brugt JceCMSContentEncryptorBuilder klasse for at skabe en OutputEncrytor objekt ved hjælp af AES CBC-algoritmen.

Krypteren bruges senere til at generere en CMSEnvelopedData objekt, der indkapsler den krypterede meddelelse.

Endelig returneres den kodede repræsentation af konvolutten som et byte-array.

Lad os nu se, hvad implementeringen af decryptData () metoden ligner:

offentlig statisk byte [] decryptData (byte [] encryptedData, PrivateKey decryptionKey) kaster CMSException {byte [] decryptedData = null; if (null! = encryptedData && null! = decryptionKey) {CMSEnvelopedData envelopedData = new CMSEnvelopedData (encryptedData); Samlingsmodtagere = envelopedData.getRecipientInfos (). GetRecipients (); KeyTransRecipientInformation recipientInfo = (KeyTransRecipientInformation) modtagere.iterator (). Næste (); JceKeyTransRecipient modtager = ny JceKeyTransEnvelopedRecipient (dekrypteringKey); returner recipientInfo.getContent (modtager); } returnere dekrypterede data; }

For det første har vi initialiseret en CMSEnvelopedData objekt ved hjælp af det krypterede data-byte-array, og så har vi hentet alle de tilsigtede modtagere af meddelelsen ved hjælp af getRecipients () metode.

Når det er gjort, har vi oprettet et nyt JceKeyTransModtager objekt tilknyttet modtagerens private nøgle.

Det recipientInfo forekomst indeholder den dekrypterede / indkapslede besked, men vi kan ikke hente den, medmindre vi har den tilsvarende modtagers nøgle.

Endelig givet modtagernøglen som et argument, getContent () metoden returnerer det rå byte-array, der er ekstraheret fra EnvelopedData denne modtager er tilknyttet.

Lad os skrive en simpel test for at sikre, at alt fungerer nøjagtigt som det skal:

String secretMessage = "Min adgangskode er 123456Seven"; System.out.println ("Original besked:" + secretMessage); byte [] stringToEncrypt = secretMessage.getBytes (); byte [] encryptedData = encryptData (stringToEncrypt, certifikat); System.out.println ("Krypteret meddelelse:" + ny streng (krypteret data)); byte [] rawData = decryptData (encryptedData, privateKey); String decryptedMessage = ny streng (rawData); System.out.println ("Dekrypteret meddelelse:" + decryptedMessage);

Som resultat:

Oprindelig besked: Min adgangskode er 123456 Seven Krypteret besked: 0  *  H   ... Dekrypteret besked: Min adgangskode er 123456Seven

4.3 CMS / PKCS7 signatur og verifikation

Underskrift og verifikation er kryptografiske operationer, der validerer ægtheden af ​​data.

Lad os se, hvordan man underskriver en hemmelig besked ved hjælp af et digitalt certifikat:

offentlig statisk byte [] signData (byte [] -data, X509Certificate-signeringCertificate, PrivateKey-signeringKey) kaster Undtagelse {byte [] signeretMessage = null; Liste certList = ny ArrayList (); CMSTypedData cmsData = ny CMSProcessableByteArray (data); certList.add (signeringscertifikat); Store certs = ny JcaCertStore (certList); CMSSignedDataGenerator cmsGenerator = ny CMSSignedDataGenerator (); ContentSigner contentSigner = ny JcaContentSignerBuilder ("SHA256withRSA"). Build (signeringKey); cmsGenerator.addSignerInfoGenerator (ny JcaSignerInfoGeneratorBuilder (ny JcaDigestCalculatorProviderBuilder (). setProvider ("BC") .build ()). build (contentSigner, signering Certifikat)); cmsGenerator.addCertificates (certs); CMSSignedData cms = cmsGenerator.generate (cmsData, sand); signeretMessage = cms.getEncoded (); return signMessage; } 

For det første har vi integreret input i en CMSTypedData, så har vi oprettet en ny CMSSignedDataGenerator objekt.

Vi har brugt SHA256medRSA som en signaturalgoritme, og vores signaturnøgle til at oprette en ny ContentSigner objekt.

Det contentSigner forekomst bruges bagefter sammen med underskriftscertifikatet til at oprette en SigningInfoGenerator objekt.

Efter tilføjelse af SignerInfoGenerator og underskriftsbevis til CMSSignedDataGenerator for eksempel bruger vi endelig frembringe() metode til at oprette et CMS-signeret dataobjekt, som også bærer en CMS-signatur.

Nu hvor vi har set, hvordan vi underskriver data, lad os se, hvordan vi bekræfter underskrevne data:

offentlig statisk boolsk verifSignedData (byte [] signeretData) kaster undtagelse {X509Certificate signCert = null; ByteArrayInputStream inputStream = ny ByteArrayInputStream (signeret data); ASN1InputStream asnInputStream = ny ASN1InputStream (inputStream); CMSSignedData cmsSignedData = ny CMSSignedData (ContentInfo.getInstance (asnInputStream.readObject ())); SignerInformationStore signers = cmsSignedData.getCertificates (). GetSignerInfos (); SignerInformation signer = signers.getSigners (). Iterator (). Næste (); Samling certCollection = certs.getMatches (signer.getSID ()); X509CertificateHolder certHolder = certCollection.iterator (). Næste (); return signer .verify (ny JcaSimpleSignerInfoVerifierBuilder () .build (certHolder)); }

Igen har vi oprettet en CMSSignedData objekt baseret på vores underskrevne data byte array, så har vi hentet alle underskrivere, der er knyttet til signaturerne ved hjælp af getSignerInfos () metode.

I dette eksempel har vi kun verificeret en underskriver, men til generisk brug er det obligatorisk at gentage over samlingen af ​​underskrivere, der returneres af getSigners () metode og kontrollere hver enkelt separat.

Endelig har vi oprettet en SignerInformationVerifier objekt ved hjælp af bygge () metode og videregivet den til verificere() metode.

Metoden verificere () vender tilbage rigtigt hvis det givne objekt med succes kan bekræfte signaturen på underskriverobjektet.

Her er et simpelt eksempel:

byte [] signeretData = signData (rawData, certifikat, privateKey); Boolsk kontrol = verifSignData (signeretData); System.out.println (afkryds);

Som resultat:

rigtigt

5. Konklusion

I denne artikel har vi opdaget, hvordan vi bruger BouncyCastle-biblioteket til at udføre grundlæggende kryptografiske operationer, såsom kryptering og signatur.

I en situation i den virkelige verden ønsker vi ofte at underskrive og derefter kryptere vores data, på den måde er kun modtageren i stand til at dekryptere dem ved hjælp af den private nøgle og kontrollere dens ægthed baseret på den digitale signatur.

Kodestykkerne findes som altid over på GitHub.