Digitale handtekeningen in Java

Java Top

Ik heb zojuist het nieuwe aangekondigd Leer de lente natuurlijk, gericht op de basisprincipes van Spring 5 en Spring Boot 2:

>> BEKIJK DE CURSUS

1. Overzicht

In deze zelfstudie gaan we leren over de Mechanisme voor digitale handtekeningen en hoe we het kunnen implementeren met behulp van de Java Cryptography Architecture (JCA). We zullen de KeyPair, MessageDigest, Cipher, KeyStore, Certificaat, en Handtekening JCA-API's.

We beginnen met te begrijpen wat digitale handtekening is, hoe u een sleutelpaar genereert en hoe u de openbare sleutel van een certificeringsinstantie (CA) certificeert. Daarna zullen we zien hoe we digitale handtekening kunnen implementeren met behulp van de JCA-API's op laag en hoog niveau.

2. Wat is digitale handtekening?

2.1. Definitie van digitale handtekening

Digitale handtekening is een techniek om ervoor te zorgen dat:

  • Integriteit: het bericht is tijdens de verzending niet gewijzigd
  • Authenticiteit: de auteur van het bericht is echt wie ze beweren te zijn
  • Onweerlegbaarheid: de auteur van het bericht kan later niet ontkennen dat zij de bron waren

2.2. Een bericht verzenden met een digitale handtekening

Technisch sprekend, eendigitale handtekening is de versleutelde hash (samenvatting, checksum) van een bericht. Dat betekent dat we een hash uit een bericht genereren en deze versleutelen met een privésleutel volgens een gekozen algoritme.

Het bericht, de gecodeerde hash, de bijbehorende openbare sleutel en het algoritme worden allemaal verzonden. Dit is geclassificeerd als een bericht met zijn digitale handtekening.

2.3. Een digitale handtekening ontvangen en controleren

Om de digitale handtekening te controleren, genereert de berichtontvanger een nieuwe hash uit het ontvangen bericht, decodeert de ontvangen gecodeerde hash met behulp van de openbare sleutel en vergelijkt deze. Als ze overeenkomen, is de digitale handtekening geverifieerd.

We moeten er rekening mee houden dat we alleen de berichthash versleutelen, en niet het bericht zelf. Met andere woorden, digitale handtekening probeert het bericht niet geheim te houden. Onze digitale handtekening bewijst alleen dat het bericht tijdens de verzending niet is gewijzigd.

Wanneer de handtekening is geverifieerd, weten we zeker dat alleen de eigenaar van de privésleutel de auteur van het bericht kan zijn.

3. Digitaal certificaat en identiteit met openbare sleutel

Een certificaat is een document dat een identiteit aan een bepaalde openbare sleutel koppelt. Certificaten worden ondertekend door een derde partij, een certificeringsinstantie (CA) genaamd.

We weten dat als de hash die we decoderen met de gepubliceerde openbare sleutel overeenkomt met de werkelijke hash, het bericht wordt ondertekend. Hoe weten we echter dat de openbare sleutel echt van de juiste entiteit kwam? Dit wordt opgelost door het gebruik van digitale certificaten.

Een digitaal certificaat bevat een openbare sleutel en is zelf ondertekend door een andere entiteit. De handtekening van die entiteit kan zelf worden geverifieerd door een andere entiteit enzovoort. We hebben uiteindelijk wat we een certificaatketen noemen. Elke topentiteit certificeert de openbare sleutel van de volgende entiteit. De entiteit op het hoogste niveau is zelfondertekend, wat betekent dat zijn openbare sleutel is ondertekend met zijn eigen privésleutel.

De X.509 is het meest gebruikte certificaatformaat en wordt verzonden als binair formaat (DER) of tekstformaat (PEM). JCA zorgt hiervoor al voor een implementatie via het X509 Certificaat klasse.

4. KeyPair-beheer

Omdat digitale handtekening een privé- en openbare sleutel gebruikt, gebruiken we de JCA-klassen Prive sleutel en Publieke sleutel voor het ondertekenen en controleren van een bericht.

4.1. Een KeyPair verkrijgen

Om een ​​sleutelpaar van een persoonlijke en openbare sleutel te maken, we zullen de Java gebruiken belangrijk hulpmiddel.

Laten we een sleutelpaar genereren met de genkeypair opdracht:

keytool -genkeypair -alias senderKeyPair -keyalg RSA -keysize 2048 \ -dname "CN = Baeldung" -validiteit 365 -storetype PKCS12 \ -keystore sender_keystore.p12 -storepass changeit

Dit creëert een privésleutel en de bijbehorende openbare sleutel voor ons. De openbare sleutel is verpakt in een zelfondertekend X.509-certificaat dat op zijn beurt is verpakt in een certificaatketen met één element. We slaan de certificaatketen en de privésleutel op in het Keystore-bestand sender_keystore.p12, die we kunnen verwerken met behulp van de KeyStore API.

Hier hebben we het PKCS12-sleutelarchiefformaat gebruikt, aangezien dit de standaard is en wordt aanbevolen boven het Java-gepatenteerde JKS-formaat. We moeten ook het wachtwoord en de alias onthouden, omdat we ze in de volgende subsectie zullen gebruiken bij het laden van het Keystore-bestand.

4.2. Laden van de privésleutel voor ondertekening

Om een ​​bericht te ondertekenen, hebben we een instantie van de Prive sleutel.

De ... gebruiken KeyStore API en het vorige Keystore-bestand, sender_keystore.p12, we kunnen een Prive sleutel voorwerp:

KeyStore keyStore = KeyStore.getInstance ("PKCS12"); keyStore.load (nieuwe FileInputStream ("sender_keystore.p12"), "changeit"); PrivateKey privateKey = (PrivateKey) keyStore.getKey ("senderKeyPair", "changeit");

4.3. Publiceren van de openbare sleutel

Voordat we de openbare sleutel kunnen publiceren, moeten we eerst beslissen of we een zelfondertekend certificaat of een door een CA ondertekend certificaat gaan gebruiken.

Als u een zelfondertekend certificaat gebruikt, hoeven we dit alleen uit het Keystore-bestand te exporteren. We kunnen dit doen met de exportcert opdracht:

keytool -exportcert -alias senderKeyPair -storetype PKCS12 \ -keystore sender_keystore.p12 -file \ sender_certificate.cer -rfc -storepass changeit

Anders, als we gaan werken met een CA-ondertekend certificaat, dan moeten we een Certificate Signing Request (CSR) aanmaken. Dit doen we met de certreq opdracht:

keytool -certreq -alias senderKeyPair -storetype PKCS12 \ -keystore sender_keystore.p12 -file -rfc \ -storepass changeit> sender_certificate.csr

Het CSR-bestand, sender_certificate.csr, wordt vervolgens ter ondertekening naar een certificeringsinstantie gestuurd. Wanneer dit is gebeurd, ontvangen we een ondertekende openbare sleutel verpakt in een X.509-certificaat, hetzij in binair (DER) of tekst (PEM) formaat. Hier hebben we de rfc optie voor een PEM-formaat.

De openbare sleutel die we van de CA hebben ontvangen, sender_certificate.cer, is nu ondertekend door een CA en kan beschikbaar worden gemaakt voor klanten.

4.4. Een openbare sleutel laden voor verificatie

Als een ontvanger toegang heeft tot de openbare sleutel, kan deze deze in zijn Keystore laden met behulp van de importcert opdracht:

keytool -importcert -alias receiverKeyPair -storetype PKCS12 \ -keystore receiver_keystore.p12 -file \ sender_certificate.cer -rfc -storepass changeit

En met behulp van de KeyStore API zoals eerder, kunnen we een Publieke sleutel voorbeeld:

KeyStore keyStore = KeyStore.getInstance ("PKCS12"); keyStore.load (nieuwe FileInputStream ("receiver_keytore.p12"), "changeit"); Certificaatcertificaat = keyStore.getCertificate ("receiverKeyPair"); PublicKey publicKey = certificate.getPublicKey ();

Nu we een Prive sleutel instantie aan de afzenderzijde, en een instantie van de Publieke sleutel aan de kant van de ontvanger kunnen we het proces van ondertekening en verificatie starten.

5. Digitale handtekening met MessageDigest en Cijfer Klassen

Zoals we hebben gezien, is de digitale handtekening gebaseerd op hashing en codering.

Meestal gebruiken we de MessageDigest klasse met SHA of MD5 voor hashing en de Cijfer class voor versleuteling.

Laten we nu beginnen met het implementeren van de mechanismen voor digitale handtekeningen.

5.1. Een berichthash genereren

Een bericht kan een tekenreeks, een bestand of andere gegevens zijn. Laten we dus de inhoud van een eenvoudig bestand nemen:

byte [] messageBytes = Files.readAllBytes (Paths.get ("message.txt"));

Nu met behulp van MessageDigest, laten we de verteren methode om een ​​hash te genereren:

MessageDigest md = MessageDigest.getInstance ("SHA-256"); byte [] messageHash = md.digest (messageBytes);

Hier hebben we het SHA-256-algoritme gebruikt, dat het meest wordt gebruikt. Andere alternatieven zijn MD5, SHA-384 en SHA-512.

5.2. De gegenereerde hash versleutelen

Om een ​​bericht te versleutelen, hebben we een algoritme en een privésleutel nodig. Hier gebruiken we het RSA-algoritme. Het DSA-algoritme is een andere optie.

Laten we een Cijfer instantie en initialiseer het voor versleuteling. Dan bellen we de doFinal () methode om het eerder gehashte bericht te versleutelen:

Cipher cipher = Cipher.getInstance ("RSA"); cipher.init (Cipher.ENCRYPT_MODE, privateKey); byte [] digitalSignature = cipher.doFinal (messageHash);

De handtekening kan worden opgeslagen in een bestand om het later te verzenden:

Files.write (Paths.get ("digital_signature_1"), digitalSignature);

Op dit punt worden het bericht, de digitale handtekening, de openbare sleutel en het algoritme allemaal verzonden en kan de ontvanger deze stukjes informatie gebruiken om de integriteit van het bericht te verifiëren.

5.3. Handtekening verifiëren

Als we een bericht ontvangen, moeten we de handtekening ervan verifiëren. Om dit te doen, decoderen we de ontvangen gecodeerde hash en vergelijken we deze met een hash die we van het ontvangen bericht maken.

Laten we de ontvangen digitale handtekening lezen:

byte [] encryptedMessageHash = Files.readAllBytes (Paths.get ("digital_signature_1"));

Voor decodering maken we een Cijfer voorbeeld. Dan noemen we de doFinal methode:

Cipher cipher = Cipher.getInstance ("RSA"); cipher.init (Cipher.DECRYPT_MODE, publicKey); byte [] decryptedMessageHash = cipher.doFinal (encryptedMessageHash);

Vervolgens genereren we een nieuwe berichthash van het ontvangen bericht:

byte [] messageBytes = Files.readAllBytes (Paths.get ("message.txt")); MessageDigest md = MessageDigest.getInstance ("SHA-256"); byte [] newMessageHash = md.digest (messageBytes);

En tot slot controleren we of de nieuw gegenereerde berichthash overeenkomt met de gedecodeerde hash:

boolean isCorrect = Arrays.equals (decryptedMessageHash, newMessageHash);

In dit voorbeeld hebben we het tekstbestand gebruikt bericht.txt om een ​​bericht te simuleren dat we willen verzenden, of de locatie van de hoofdtekst van een bericht dat we hebben ontvangen. Normaal gesproken verwachten we ons bericht naast de handtekening te ontvangen.

6. Digitale handtekening met behulp van de Handtekening Klasse

Tot nu toe hebben we de low-level API's gebruikt om ons eigen verificatieproces voor digitale handtekeningen te bouwen. Dit helpt ons te begrijpen hoe het werkt en stelt ons in staat het aan te passen.

JCA biedt echter al een speciale API in de vorm van de Handtekening klasse.

6.1. Een bericht ondertekenen

Om het ondertekeningsproces te starten, maken we eerst een instantie van het Handtekening klasse. Om dat te doen, hebben we een ondertekeningsalgoritme nodig. Vervolgens initialiseren we het Handtekening met onze privésleutel:

Handtekening handtekening = Signature.getInstance ("SHA256withRSA"); signature.initSign (privateKey);

Het ondertekeningsalgoritme dat we hebben gekozen, SHA256withRSA in dit voorbeeld, is een combinatie van een hash-algoritme en een versleutelingsalgoritme. Andere alternatieven zijn onder meer SHA1withRSA, SHA1withDSA, en MD5withRSA, onder andere.

Vervolgens gaan we verder met het ondertekenen van de byte-array van het bericht:

byte [] messageBytes = Files.readAllBytes (Paths.get ("message.txt")); signature.update (messageBytes); byte [] digitalSignature = signature.sign ();

We kunnen de handtekening opslaan in een bestand voor latere verzending:

Files.write (Paths.get ("digital_signature_2"), digitalSignature);

6.2. De handtekening verifiëren

Om de ontvangen handtekening te verifiëren, maken we opnieuw een Handtekening voorbeeld:

Handtekening handtekening = Signature.getInstance ("SHA256withRSA");

Vervolgens initialiseren we het Handtekening object ter verificatie door het initVerify methode, waarvoor een openbare sleutel nodig is:

signature.initVerify (publicKey);

Vervolgens moeten we de ontvangen berichtbytes toevoegen aan het handtekeningobject door de bijwerken methode:

byte [] messageBytes = Files.readAllBytes (Paths.get ("message.txt")); signature.update (messageBytes);

En tot slot kunnen we de handtekening controleren door het verifiëren methode:

boolean isCorrect = signature.verify (ontvangenSignatuur);

7. Conclusie

In dit artikel hebben we eerst bekeken hoe digitale handtekening werkt en hoe u vertrouwen kunt wekken voor een digitaal certificaat. Vervolgens hebben we een digitale handtekening geïmplementeerd met behulp van de MessageDigest,Cijfer, en Handtekening klassen uit de Java Cryptography Architecture.

We hebben in detail gezien hoe u gegevens kunt ondertekenen met de privésleutel en hoe u de handtekening kunt verifiëren met een openbare sleutel.

Zoals altijd is de code uit dit artikel beschikbaar op GitHub.

Java onderkant

Ik heb zojuist het nieuwe aangekondigd Leer de lente natuurlijk, gericht op de basisprincipes van Spring 5 en Spring Boot 2:

>> BEKIJK DE CURSUS