Digitale signaturer i Java
Jeg har lige annonceret det nye Lær foråret kursus med fokus på det grundlæggende i Spring 5 og Spring Boot 2:
>> KONTROLLER KURSEN1. Oversigt
I denne vejledning vil vi lære om Digital signaturmekanisme, og hvordan vi kan implementere den ved hjælp af Java Cryptography Architecture (JCA). Vi udforsker KeyPair, MessageDigest, Cipher, KeyStore, Certificate, og Underskrift JCA API'er.
Vi starter med at forstå, hvad der er Digital signatur, hvordan man genererer et nøglepar, og hvordan man certificerer den offentlige nøgle fra en certifikatmyndighed (CA). Derefter ser vi, hvordan vi implementerer Digital signatur ved hjælp af lavt niveau og højt niveau JCA API'er.
2. Hvad er digital signatur?
2.1. Definition af digital signatur
Digital signatur er en teknik til at sikre:
- Integritet: meddelelsen er ikke blevet ændret under forsendelse
- Ægthed: forfatteren af meddelelsen er virkelig den, de hævder at være
- Ikke-afvisning: forfatteren af meddelelsen kan ikke senere benægte, at de var kilden
2.2. Afsendelse af en besked med en digital signatur
Teknisk set -endigital signatur er den krypterede hash (fordøjelse, kontrolsum) af en meddelelse. Det betyder, at vi genererer en hash fra en besked og krypterer den med en privat nøgle i henhold til en valgt algoritme.
Meddelelsen, den krypterede hash, den tilsvarende offentlige nøgle og algoritmen sendes derefter alle. Dette er klassificeret som en besked med sin digitale signatur.
2.3. Modtagelse og kontrol af en digital signatur
For at kontrollere den digitale signatur genererer meddelelsesmodtageren en ny hash fra den modtagne besked, dekrypterer den modtagne krypterede hash ved hjælp af den offentlige nøgle og sammenligner dem. Hvis de stemmer overens, siges den digitale signatur at være verificeret.
Vi skal bemærke, at vi kun krypterer beskedens hash og ikke selve meddelelsen. Med andre ord forsøger Digital signatur ikke at holde beskeden hemmelig. Vores digitale signatur beviser kun, at beskeden ikke blev ændret under transport.
Når signaturen er bekræftet, er vi sikre på, at kun ejeren af den private nøgle kan være forfatteren af meddelelsen.
3. Digitalt certifikat og offentlig nøgleidentitet
Et certifikat er et dokument, der knytter en identitet til en given offentlig nøgle. Certifikater er underskrevet af en tredjepartsenhed kaldet en Certificate Authority (CA).
Vi ved, at hvis den hash, vi dekrypterer med den offentliggjorte offentlige nøgle, matcher den aktuelle hash, så underskrives meddelelsen. Men hvordan ved vi, at den offentlige nøgle virkelig kom fra den rigtige enhed? Dette løses ved brug af digitale certifikater.
Et digitalt certifikat indeholder en offentlig nøgle og er selv underskrevet af en anden enhed. Denne enheds signatur kan selv verificeres af en anden enhed og så videre. Vi ender med at have det, vi kalder en certifikatkæde. Hver topenhed certificerer den næste enheds offentlige nøgle. Den mest øverste enhed er selvsigneret, hvilket betyder, at hans offentlige nøgle er underskrevet af hans egen private nøgle.
X.509 er det mest anvendte certifikatformat, og det sendes enten som binært format (DER) eller tekstformat (PEM). JCA leverer allerede en implementering af dette via X509 Certifikat klasse.
4. KeyPair Management
Da Digital signatur bruger en privat og offentlig nøgle, bruger vi JCA-klasser PrivateKey og PublicKey til henholdsvis signering og kontrol af en besked.
4.1. Sådan får du et KeyPair
At oprette et nøglepar af en privat og offentlig nøgle, vi bruger Java nøgleværktøj.
Lad os generere et nøglepar ved hjælp af genkeypair kommando:
keytool -genkeypair -alias senderKeyPair -keyalg RSA -keysize 2048 \ -dname "CN = Baeldung" -validity 365 -storetype PKCS12 \ -keystore sender_keystore.p12 -storepass changeit
Dette skaber en privat nøgle og dens tilsvarende offentlige nøgle for os. Den offentlige nøgle pakkes ind i et X.509-selvsigneret certifikat, som igen indpakkes til et enkeltelementcertifikatkæde. Vi gemmer certifikatkæden og den private nøgle i Keystore-filen sender_keystore.p12, som vi kan behandle ved hjælp af KeyStore API.
Her har vi brugt PKCS12-nøglelagerformatet, da det er standard og anbefales over det Java-proprietære JKS-format. Vi skal også huske adgangskoden og aliaset, da vi bruger dem i næste afsnit, når vi indlæser Keystore-filen.
4.2. Indlæser den private nøgle til signering
For at underskrive en besked har vi brug for en forekomst af PrivateKey.
Bruger KeyStore API og den forrige Keystore-fil, sender_keystore.p12, vi kan få en PrivateKey objekt:
KeyStore keyStore = KeyStore.getInstance ("PKCS12"); keyStore.load (ny FileInputStream ("sender_keystore.p12"), "changeit"); PrivateKey privateKey = (PrivateKey) keyStore.getKey ("senderKeyPair", "changeit");
4.3. Udgivelse af den offentlige nøgle
Før vi kan offentliggøre den offentlige nøgle, skal vi først beslutte, om vi skal bruge et selvsigneret certifikat eller et CA-signeret certifikat.
Når du bruger et selvsigneret certifikat, behøver vi kun at eksportere det fra Keystore-filen. Vi kan gøre dette med eksportcert kommando:
keytool -exportcert -alias senderKeyPair -storetype PKCS12 \ -keystore sender_keystore.p12 -fil \ sender_certificate.cer -rfc -storepass changeit
Ellers, hvis vi skal arbejde med et CA-signeret certifikat, skal vi oprette en certifikatsigneringsanmodning (CSR). Vi gør dette med certreq kommando:
keytool -certreq -alias senderKeyPair -storetype PKCS12 \ -keystore sender_keystore.p12 -fil -rfc \ -storepass changeit> sender_certificate.csr
CSR-filen, sender_certificate.csr, sendes derefter til en certifikatmyndighed med det formål at underskrive. Når dette er gjort, modtager vi en underskrevet offentlig nøgle indpakket i et X.509-certifikat, enten i binært (DER) eller tekst (PEM) -format. Her har vi brugt rfc mulighed for et PEM-format.
Den offentlige nøgle, vi modtog fra CA, sender_certificate.cer, er nu underskrevet af en CA og kan gøres tilgængelig for klienter.
4.4. Indlæser en offentlig nøgle til verifikation
Efter at have adgang til den offentlige nøgle kan en modtager indlæse den i deres Keystore ved hjælp af importcert kommando:
keytool -importcert -alias receiverKeyPair -storetype PKCS12 \ -keystore receiver_keystore.p12 -fil \ sender_certificate.cer -rfc -storepass changeit
Og ved hjælp af KeyStore API som før, vi kan få en PublicKey eksempel:
KeyStore keyStore = KeyStore.getInstance ("PKCS12"); keyStore.load (ny FileInputStream ("receiver_keytore.p12"), "changeit"); Certifikatcertifikat = keyStore.getCertificate ("receiverKeyPair"); PublicKey publicKey = certifikat.getPublicKey ();
Nu hvor vi har en PrivateKey forekomst på afsendersiden og en forekomst af PublicKey på modtagersiden kan vi starte processen med at underskrive og verificere.
5. Digital signatur med MessageDigest og Kryptering Klasser
Som vi har set, er den digitale signatur baseret på hashing og kryptering.
Normalt bruger vi MessageDigest klasse med SHA eller MD5 til hashing og Kryptering klasse til kryptering.
Lad os nu begynde at implementere de digitale signaturmekanismer.
5.1. Generere en besked Hash
En besked kan være en streng, en fil eller andre data. Så lad os tage indholdet af en simpel fil:
byte [] messageBytes = Files.readAllBytes (Paths.get ("message.txt"));
Brug nu BeskedDigest, lad os bruge fordøje metode til at generere en hash:
MessageDigest md = MessageDigest.getInstance ("SHA-256"); byte [] messageHash = md.digest (messageBytes);
Her har vi brugt SHA-256-algoritmen, som er den mest almindelige. Andre alternativer er MD5, SHA-384 og SHA-512.
5.2. Kryptering af den genererede hash
For at kryptere en besked har vi brug for en algoritme og en privat nøgle. Her bruger vi RSA-algoritmen. DSA-algoritmen er en anden mulighed.
Lad os oprette en Kryptering instans og initialiser den til kryptering. Så ringer vi til doFinal () metode til at kryptere den tidligere hashede meddelelse:
Cipher cipher = Cipher.getInstance ("RSA"); cipher.init (Cipher.ENCRYPT_MODE, privateKey); byte [] digitalSignature = cipher.doFinal (messageHash);
Signaturen kan gemmes i en fil til senere afsendelse:
Files.write (Paths.get ("digital_signature_1"), digitalSignature);
På dette tidspunkt sendes beskeden, den digitale signatur, den offentlige nøgle og algoritmen, og modtageren kan bruge disse informationer til at verificere meddelelsens integritet.
5.3. Bekræftelse af signatur
Når vi modtager en besked, skal vi bekræfte dens signatur. For at gøre det dekrypterer vi den modtagne krypterede hash og sammenligner den med en hash, vi laver af den modtagne besked.
Lad os læse den modtagne digitale signatur:
byte [] encryptedMessageHash = Files.readAllBytes (Paths.get ("digital_signature_1"));
Til dekryptering opretter vi en Kryptering eksempel. Så kalder vi doFinal metode:
Cipher cipher = Cipher.getInstance ("RSA"); cipher.init (Cipher.DECRYPT_MODE, publicKey); byte [] decryptedMessageHash = cipher.doFinal (encryptedMessageHash);
Derefter genererer vi en ny meddelelseshash fra den modtagne besked:
byte [] messageBytes = Files.readAllBytes (Paths.get ("message.txt")); MessageDigest md = MessageDigest.getInstance ("SHA-256"); byte [] newMessageHash = md.digest (messageBytes);
Og til sidst kontrollerer vi, om den nyligt genererede meddelelseshash matcher den dekrypterede:
boolean isCorrect = Arrays.equals (decryptedMessageHash, newMessageHash);
I dette eksempel har vi brugt tekstfilen besked.txt for at simulere en besked, vi ønsker at sende, eller placeringen af kroppen for en besked, vi har modtaget. Normalt forventer vi at modtage vores besked sammen med signaturen.
6. Digital signatur ved hjælp af Underskrift Klasse
Indtil videre har vi brugt API'er på lavt niveau til at opbygge vores egen verifikationsproces for digital signatur. Dette hjælper os med at forstå, hvordan det fungerer, og giver os mulighed for at tilpasse det.
Imidlertid tilbyder JCA allerede en dedikeret API i form af Underskrift klasse.
6.1. Underskrift af en besked
For at starte processen med at underskrive opretter vi først en forekomst af Underskrift klasse. For at gøre det har vi brug for en signeringsalgoritme. Vi initialiserer derefter Underskrift med vores private nøgle:
Signatur signatur = Signature.getInstance ("SHA256withRSA"); signatur.initSign (privateKey);
Den signaturalgoritme, vi valgte, SHA256medRSA i dette eksempel, er en kombination af en hashingalgoritme og en krypteringsalgoritme. Andre alternativer inkluderer SHA1medRSA, SHA1medDSAog MD5medRSA, blandt andre.
Dernæst fortsætter vi med at underskrive meddelelsens byte-array:
byte [] messageBytes = Files.readAllBytes (Paths.get ("message.txt")); signatur.update (messageBytes); byte [] digitalSignature = signatur.sign ();
Vi kan gemme signaturen i en fil til senere transmission:
Files.write (Paths.get ("digital_signature_2"), digitalSignature);
6.2. Bekræftelse af signaturen
For at bekræfte den modtagne signatur opretter vi igen en Underskrift eksempel:
Signatur signatur = Signature.getInstance ("SHA256withRSA");
Dernæst initialiserer vi Underskrift objekt til verifikation ved at ringe til initVerify metode, der tager en offentlig nøgle:
signatur.initVerify (publicKey);
Derefter skal vi tilføje de modtagne meddelelsesbyte til signaturobjektet ved at påkalde opdatering metode:
byte [] messageBytes = Files.readAllBytes (Paths.get ("message.txt")); signatur.update (messageBytes);
Og endelig kan vi kontrollere signaturen ved at ringe til verificere metode:
boolsk isCorrect = signatur.verify (receivedSignature);
7. Konklusion
I denne artikel så vi først på, hvordan digital signatur fungerer, og hvordan man kan skabe tillid til et digitalt certifikat. Derefter implementerede vi en digital signatur ved hjælp af BeskedDigest,Kryptering, og Underskrift klasser fra Java Cryptography Architecture.
Vi så detaljeret, hvordan man signerer data ved hjælp af den private nøgle, og hvordan man verificerer signaturen ved hjælp af en offentlig nøgle.
Som altid er koden fra denne artikel tilgængelig på GitHub.
Java bund