Uvod u BouncyCastle s Javom

1. Pregled

BouncyCastle je Java knjižnica koja nadopunjuje zadano Java Cryptographic Extension (JCE).

U ovom uvodnom članku pokazat ćemo kako koristiti BouncyCastle za izvođenje kriptografskih operacija, poput šifriranja i potpisa.

2. Konfiguracija Maven

Prije nego počnemo raditi s knjižnicom, moramo dodati potrebne ovisnosti u našu pom.xml datoteka:

 org.bouncycastle bcpkix-jdk15on 1.58 

Imajte na umu da u Maven Central Repository uvijek možemo potražiti najnovije verzije ovisnosti.

3. Postavite datoteke s pravilima o nadležnosti za neograničenu snagu

Standardna instalacija Jave ograničena je u pogledu snage kriptografskih funkcija, to je zbog pravila koja zabranjuju upotrebu ključa veličine koja premašuje određene vrijednosti, npr. 128 za AES.

Da bismo prevladali ovo ograničenje, moramo konfigurirati datoteke politika o nadležnosti neograničene snage.

Da bismo to učinili, prvo moramo preuzeti paket slijedeći ovu vezu. Nakon toga moramo stvoriti zip datoteku u direktorij po našem izboru - koji sadrži dvije jar datoteke:

  • local_policy.jar
  • US_export_policy.jar

Napokon, moramo potražiti {JAVA_HOME} / lib / sigurnost mapu i zamijenite postojeće datoteke s pravilima onima koje smo ovdje izvukli.

Imajte na umu da u Javi 9 više ne moramo preuzimati paket datoteka s pravilima, postavljanje kripto.politika svojstvo da neograničen dovoljno je:

Security.setProperty ("crypto.policy", "neograničeno");

Kada završimo, moramo provjeriti radi li konfiguracija ispravno:

int maxKeySize = javax.crypto.Cipher.getMaxAllowedKeyLength ("AES"); System.out.println ("Maksimalna veličina ključa za AES:" + maxKeySize);

Kao rezultat:

Maksimalna veličina ključa za AES: 2147483647

Na temelju maksimalne veličine ključa koju je vratio getMaxAllowedKeyLength () metodom, možemo sa sigurnošću reći da su datoteke s pravilima neograničene snage ispravno instalirane.

Ako je vraćena vrijednost jednaka 128, moramo biti sigurni da smo datoteke instalirali u JVM gdje pokrećemo kôd.

4. Kriptografske operacije

4.1. Priprema certifikata i privatnog ključa

Prije nego što uskočimo u implementaciju kriptografskih funkcija, prvo moramo stvoriti certifikat i privatni ključ.

U svrhu ispitivanja možemo koristiti ove resurse:

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

Baeldung.cer je digitalni certifikat koji koristi međunarodni X.509 infrastrukturni javni ključ, dok je Baeldung.p12 je zaštićena lozinkom PKCS12 pohrana ključeva koja sadrži privatni ključ.

Pogledajmo kako se ovi mogu učitati u Javi:

Security.addProvider (novi BouncyCastleProvider ()); CertificateFactory certFactory = CertificateFactory .getInstance ("X.509", "BC"); Potvrda X509Certificate = (X509Certificate) certFactory .generateCertificate (novi FileInputStream ("Baeldung.cer")); char [] keystorePassword = "lozinka" .toCharArray (); char [] keyPassword = "lozinka" .toCharArray (); KeyStore keystore = KeyStore.getInstance ("PKCS12"); keystore.load (novi FileInputStream ("Baeldung.p12"), keystorePassword); Ključ PrivateKey = (PrivateKey) keystore.getKey ("baeldung", keyPassword);

Prvo smo dodali BouncyCastleProvider kao pružatelj osiguranja koji dinamički koristi addProvider () metoda.

To se također može učiniti statički uređivanjem {JAVA_HOME} /jre/lib/security/java.security datoteku i dodajući ovaj redak:

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

Nakon što je davatelj usluga pravilno instaliran, stvorili smo CertifikatTvornica objekt pomoću getInstance () metoda.

The getInstance () metoda uzima dva argumenta; tip certifikata “X.509”, a davatelj zaštite “BC”.

The certFactory instanca se naknadno koristi za generiranje X509 Potvrda objekt, putem generirajCertificate () metoda.

Na isti smo način kreirali objekt PKCS12 Keystore na kojem je opterećenje() metoda se naziva.

The getKey () metoda vraća privatni ključ povezan s danim aliasom.

Imajte na umu da pohrana ključeva PKCS12 sadrži skup privatnih ključeva, svaki privatni ključ može imati određenu lozinku, zato nam je potrebna globalna lozinka za otvaranje trgovine ključeva i posebna za dohvaćanje privatnog ključa.

Potvrda i par privatnih ključeva uglavnom se koriste u asimetričnim kriptografskim operacijama:

  • Šifriranje
  • Dešifriranje
  • Potpis
  • Verifikacija

4.2 CMS / PKCS7 šifriranje i dešifriranje

U asimetričnoj kriptografiji za šifriranje, svaka komunikacija zahtijeva javni certifikat i privatni ključ.

Primatelj je vezan za certifikat koji se javno dijeli između svih pošiljatelja.

Jednostavno rečeno, pošiljatelju je potreban primateljev certifikat za šifriranje poruke, dok primatelju treba pridruženi privatni ključ da bi ga mogao dešifrirati.

Pogledajmo kako implementirati encryptData () funkcija, koristeći potvrdu šifriranja:

javni statički bajt [] encryptData (bajt [] podaci, X509Certificate encryptionCertificate) baca CertificateEncodingException, CMSException, IOException {byte [] encryptedData = null; if (null! = data && null! = encryptionCertificate) {CMSEnvelopedDataGenerator cmsEnvelopedDataGenerator = novi CMSEnvelopedDataGenerator (); JceKeyTransRecipientInfoGenerator jceKey = novi JceKeyTransRecipientInfoGenerator (encryptionCertificate); cmsEnvelopedDataGenerator.addRecipientInfoGenerator (transKeyGen); CMSTypedData msg = novi CMSProcessableByteArray (podaci); OutputEncryptor encryptor = novi JceCMSContentEncryptorBuilder (CMSAlgorithm.AES128_CBC) .setProvider ("BC"). Build (); CMSEnvelopedData cmsEnvelopedData = cmsEnvelopedDataGenerator .generate (msg, enkriptor); encryptedData = cmsEnvelopedData.getEncoded (); } vratiti encryptedData; }

Stvorili smo JceKeyTransRecipientInfoGenerator objekt koji koristi potvrdu primatelja.

Zatim smo stvorili novi CMSEnvelopedDataGenerator objekt i u njega je dodao generator podataka o primatelju.

Nakon toga koristili smo JceCMSContentEncryptorBuilder razred za stvaranje OutputEncrytor objekt, koristeći AES CBC algoritam.

Enkriptor se koristi kasnije za generiranje CMSEnvelopedData objekt koji enkapsulira šifriranu poruku.

Konačno, kodirani prikaz omotnice vraća se kao bajtni niz.

Sada, da vidimo kakva je provedba decryptData () metoda izgleda ovako:

javni statički bajt [] decryptData (bajt [] encryptedData, PrivateKey decryptionKey) baca CMSException {byte [] decryptedData = null; if (null! = encryptedData && null! = decryptionKey) {CMSEnvelopedData envelopedData = novi CMSEnvelopedData (encryptedData); Primatelji zbirke = envelopedData.getRecipientInfos (). GetRecipients (); KeyTransRecipientInformation receiverInfo = (KeyTransRecipientInformation) primatelji.iterator (). Next (); Primatelj JceKeyTransRecipient = novi JceKeyTransEnvelopedRecipient (decryptionKey); vratiti primateljInfo.getContent (primatelj); } vratiti decryptedData; }

Prvo smo inicijalizirali a CMSEnvelopedData objekt koristeći niz bajtova šifriranih podataka, a zatim smo dohvatili sve predviđene primatelje poruke pomoću getRecipients () metoda.

Kad završimo, stvorili smo novi Primatelj JceKeyTrans objekt povezan s privatnim ključem primatelja.

The primateljInfo instanca sadrži dešifriranu / inkapsuliranu poruku, ali ne možemo je dohvatiti ako nemamo odgovarajući ključ primatelja.

Konačno, s obzirom na ključ primatelja kao argument, getContent () metoda vraća sirovi bajtni niz izvađen iz EnvelopedData ovaj je primatelj povezan sa.

Napišimo jednostavan test kako bismo bili sigurni da sve funkcionira točno onako kako bi trebalo:

String secretMessage = "Moja lozinka je 123456Seven"; System.out.println ("Izvorna poruka:" + secretMessage); bajt [] stringToEncrypt = secretMessage.getBytes (); bajt [] encryptedData = encryptData (stringToEncrypt, certifikat); System.out.println ("Šifrirana poruka:" + novi niz (šifrirani podaci)); bajt [] rawData = decryptData (šifrirani podaci, privateKey); String decryptedMessage = novi String (rawData); System.out.println ("Dešifrirana poruka:" + dešifrirana poruka);

Kao rezultat:

Izvorna poruka: Moja lozinka je 123456Sedam šifriranih poruka: 0  *  H   ... Dešifrirana poruka: Moja lozinka je 123456Seven

4.3 Potpis i provjera CMS / PKCS7

Potpis i provjera kriptografske su operacije kojima se provjerava autentičnost podataka.

Pogledajmo kako potpisati tajnu poruku pomoću digitalnog certifikata:

javni statički bajt [] signData (podaci bajta [], X509Certificate signatureCertificate, PrivateKey signKey) baca iznimku {byte [] signedMessage = null; Popis certList = novi ArrayList (); CMSTypedData cmsData = novi CMSProcessableByteArray (podaci); certList.add (signatureCertificate); Pohraniti certifikate = novi JcaCertStore (certList); CMSSignedDataGenerator cmsGenerator = novi CMSSignedDataGenerator (); ContentSigner contentSigner = novi JcaContentSignerBuilder ("SHA256withRSA"). Build (signatureKey); cmsGenerator.addSignerInfoGenerator (novi JcaSignerInfoGeneratorBuilder (novi JcaDigestCalculatorProviderBuilder (). setProvider ("BC") .build ()). build (contentSigner, signatureCertificate)); cmsGenerator.addCertificates (certs); CMSSignedData cms = cmsGenerator.generate (cmsData, true); signMessage = cms.getEncoded (); vrati potpisanu poruku; } 

Prvo smo ugradili unos u CMSTypedData, onda smo stvorili novi CMSSignedDataGenerator objekt.

Koristili smo SHA256s RSA kao algoritam za potpis i naš ključ za potpisivanje za stvaranje novog ContentSigner objekt.

The contentSigner instanca koristi se nakon toga, zajedno s certifikatom o potpisivanju za stvaranje a SigningInfoGenerator objekt.

Nakon dodavanja SignerInfoGenerator i potvrdu o potpisivanju CMSSignedDataGenerator primjerice, napokon koristimo generirati() metoda za stvaranje CMS objekta s potpisanim podacima, koji također nosi CMS potpis.

Sad kad smo vidjeli kako potpisati podatke, pogledajmo kako provjeriti potpisane podatke:

javna statička logička provjeraSignedData (bajt [] signedData) baca iznimku {X509Certificate signCert = null; ByteArrayInputStream inputStream = novi ByteArrayInputStream (signedData); ASN1InputStream asnInputStream = novi ASN1InputStream (inputStream); CMSSignedData cmsSignedData = novi CMSSignedData (ContentInfo.getInstance (asnInputStream.readObject ())); SignerInformationStore potpisnici = cmsSignedData.getCertificates (). GetSignerInfos (); SignerInformation potpisnik = signers.getSigners (). Iterator (). Next (); Zbirka certCollection = certs.getMatches (signer.getSID ()); X509CertificateHolder certHolder = certCollection.iterator (). Next (); vrati potpisnik .verify (novi JcaSimpleSignerInfoVerifierBuilder () .build (certHolder)); }

Opet smo stvorili CMSSignedData objekt na temelju našeg potpisanog niza bajtova podataka, dohvatili smo sve potpisnike povezane s potpisima pomoću getSignerInfos () metoda.

U ovom smo primjeru potvrdili samo jednog potpisnika, ali za generičku upotrebu obvezno je izvršiti iteraciju preko zbirke potpisnika koje je vratio getSigners () metodu i provjerite svaku zasebno.

Konačno, stvorili smo SignerInformationVerifier objekt pomoću izgraditi() metodu i proslijedio je provjeri () metoda.

Vraća se metoda verify () pravi ako zadani objekt može uspješno provjeriti potpis na objektu potpisnika.

Evo jednostavnog primjera:

bajt [] signedData = signData (rawData, certifikat, privateKey); Logička provjera = verifSignData (signedData); System.out.println (provjera);

Kao rezultat:

pravi

5. Zaključak

U ovom smo članku otkrili kako koristiti knjižnicu BouncyCastle za obavljanje osnovnih kriptografskih operacija, poput šifriranja i potpisa.

U stvarnoj situaciji često želimo potpisati, a zatim šifrirati svoje podatke, tako da ih samo primatelj može dešifrirati pomoću privatnog ključa i provjeriti njihovu autentičnost na temelju digitalnog potpisa.

Isječke koda možete pronaći, kao i uvijek, na GitHubu.