Kryptographie in Java

Hallo Habr! Ich präsentiere Ihnen die Übersetzung des Artikels "Java Cryptography" von Jakob Jenkov.


Diese Veröffentlichung ist eine Übersetzung des ersten Artikels über Java-Kryptographie aus einer Reihe von Artikeln für Anfänger, die die Grundlagen der Kryptographie in Java erlernen möchten.


Inhaltsverzeichnis:


  1. Java-Kryptographie
  2. Chiffre
  3. Messagedigest
  4. Mac
  5. Unterschrift
  6. Schlüsselpaar
  7. Schlüsselgenerator
  8. KeyPairGenerator
  9. Keystore
  10. Keytool
  11. Zertifikat
  12. CertificateFactory
  13. CertPath

Java-Kryptographie


Die Java Cryptography API bietet die Möglichkeit, Daten in Java zu verschlüsseln und zu entschlüsseln sowie Schlüssel, Signaturen und authentifizierte (authentifizierte) Nachrichten zu verwalten, kryptografische Hashes zu berechnen und vieles mehr.


In diesem Artikel werden die Grundlagen zur Verwendung der Java Cryptography API zum Ausführen verschiedener Aufgaben erläutert, für die eine sichere Verschlüsselung erforderlich ist.


Dieser Artikel erklärt nicht die Grundlagen der kryptografischen Theorie. Sie müssen diese Informationen woanders sehen.


Java Cryptography Extension


Die Java-Kryptographie-API wird von der sogenannten Java Cryptography Extension (JCE) bereitgestellt. JCE ist seit langem Teil der Java-Plattform. Ursprünglich wurde JCE aufgrund von Exportbeschränkungen für die Verschlüsselungstechnologie in den USA von Java getrennt. Daher waren die stärksten Verschlüsselungsalgorithmen nicht in der Standard-Java-Plattform enthalten. Diese robusteren Verschlüsselungsalgorithmen können angewendet werden, wenn sich Ihr Unternehmen in den USA befindet. In anderen Fällen müssen Sie jedoch schwächere Algorithmen verwenden oder Ihre eigenen Verschlüsselungsalgorithmen implementieren und diese mit JCE verbinden.


Seit 2017 wurden die Regeln für den Export von Verschlüsselungsalgorithmen in den USA erheblich gelockert. In den meisten Teilen der Welt können Sie internationale Verschlüsselungsstandards über Java JCE verwenden.


Java-Kryptografiearchitektur


Java Cryptography Architecture (JCA) ist der Name des internen Kryptographie-API-Designs in Java. JCA ist nach mehreren Kernklassen und Allzweckschnittstellen strukturiert. Die eigentliche Funktionalität dieser Schnittstellen wird von den Lieferanten bereitgestellt. Daher können Sie die Cipher-Klasse zum Ver- und Entschlüsseln einiger Daten verwenden. Die spezifische Implementierung der Verschlüsselung (Verschlüsselungsalgorithmus) hängt jedoch vom jeweiligen verwendeten Anbieter ab.


Sie können auch Ihre eigenen Anbieter implementieren und verbinden, müssen jedoch vorsichtig sein. Die korrekte Implementierung der Verschlüsselung ohne Sicherheitslücken ist schwierig! Wenn Sie nicht wissen, was Sie tun, ist es wahrscheinlich besser, den integrierten Java-Anbieter oder einen vertrauenswürdigen Anbieter wie Bouncy Castle zu verwenden.


Hauptklassen und Schnittstellen


Die Java Cryptography API besteht aus den folgenden Java-Paketen:


  • java.security
  • java.security.cert
  • java.security.spec
  • java.security.interfaces
  • javax.crypto
  • javax.crypto.spec
  • javax.crypto.interfaces

Die Hauptklassen und Schnittstellen dieser Pakete:


  • Anbieter
  • SecureRandom
  • Chiffre
  • Messagedigest
  • Unterschrift
  • Mac
  • AlgorithmParameters
  • AlgorithmParameterGenerator
  • Keyfactory
  • SecretKeyFactory
  • KeyPairGenerator
  • Schlüsselgenerator
  • Keyagreement
  • Keystore
  • CertificateFactory
  • CertPathBuilder
  • CertPathValidator
  • CertStore

Anbieter


Die Provider-Klasse (java.security.Provider) ist die zentrale Klasse in der Java-Krypto-API. Um die Java-Krypto-API verwenden zu können, müssen Sie einen Kryptografieanbieter installieren. Das Java SDK verfügt über einen eigenen Kryptografieanbieter. Sofern Sie den Kryptografieanbieter nicht explizit festlegen, wird der Standardanbieter verwendet. Dieser kryptografische Anbieter unterstützt jedoch möglicherweise nicht die Verschlüsselungsalgorithmen, die Sie verwenden möchten. Daher müssen Sie möglicherweise Ihren eigenen Kryptografieanbieter installieren.


Einer der beliebtesten Kryptografieanbieter für die Java-Krypto-API heißt Bouncy Castle. Hier ist ein Beispiel, in dem BouncyCastleProvider als kryptografischer Anbieter festgelegt ist:


import org.bouncycastle.jce.provider.BouncyCastleProvider; import java.security.Security; public class ProviderExample { public static void main(String[] args) { Security.addProvider(new BouncyCastleProvider()); } } 

Chiffre


Die Cipher-Klasse (javax.crypto.Cipher) repräsentiert einen kryptografischen Algorithmus. Eine Verschlüsselung kann sowohl zur Verschlüsselung als auch zur Entschlüsselung von Daten verwendet werden. Die Cipher-Klasse wird in den folgenden Abschnitten mit einer kurzen Beschreibung im Folgenden näher erläutert.


Erstellen einer Instanz der Verschlüsselungsklasse, die den AES-Verschlüsselungsalgorithmus für den internen Gebrauch verwendet:


 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 

Die Methode Cipher.getInstance (...) akzeptiert eine Zeichenfolge, die bestimmt, welcher Verschlüsselungsalgorithmus verwendet werden soll, sowie einige andere Algorithmusparameter.
Im obigen Beispiel:


  • AES - Verschlüsselungsalgorithmus
  • CBC ist ein Modus, in dem der AES-Algorithmus arbeiten kann.
  • Mit PKCS5Padding sollte der AES-Algorithmus die letzten Datenbytes für die Verschlüsselung verarbeiten. Was genau dies bedeutet, lesen Sie im gesamten Kryptografie-Handbuch und nicht in diesem Artikel.

Verschlüsselungsinitialisierung


Bevor Sie eine Verschlüsselungsinstanz verwenden, müssen Sie diese initialisieren. Die Verschlüsselungsinstanz wird durch Aufrufen der Methode init () initialisiert. Die Methode init () akzeptiert zwei Parameter:


  • Modus - Verschlüsselung / Entschlüsselung
  • Schlüssel

Der erste Parameter gibt den Betriebsmodus der Verschlüsselungsinstanz an: zum Ver- oder Entschlüsseln von Daten. Der zweite Parameter gibt an, mit welchem ​​Schlüssel Daten verschlüsselt oder entschlüsselt werden.


Ein Beispiel:


 byte[] keyBytes = new byte[]{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}; String algorithm = "RawBytes"; SecretKeySpec key = new SecretKeySpec(keyBytes, algorithm); cipher.init(Cipher.ENCRYPT_MODE, key); 

Bitte beachten Sie, dass die Methode zur Schlüsselerstellung in diesem Beispiel unsicher ist und in der Praxis nicht verwendet werden sollte. In diesem Artikel in den folgenden Abschnitten wird erläutert, wie Sie Schlüssel sicherer erstellen.


Um eine Verschlüsselungsinstanz zum Entschlüsseln von Daten zu initialisieren, müssen Sie Cipher.DECRYPT_MODE verwenden, zum Beispiel:


 cipher.init(Cipher.DECRYPT_MODE, key); 

Datenverschlüsselung oder -entschlüsselung


Nach dem Initialisieren der Verschlüsselung können Sie mit dem Ver- oder Entschlüsseln der Daten beginnen, indem Sie die Methoden update () oder doFinal () aufrufen . Die update () -Methode wird verwendet, wenn Sie ein Datenelement verschlüsseln oder entschlüsseln. Die Methode doFinal () wird aufgerufen, wenn Sie das letzte Datenelement verschlüsseln oder wenn der Datenblock, den Sie an doFinal () übergeben, ein einzelner Datensatz zur Verschlüsselung ist.


Ein Beispiel für die Datenverschlüsselung mit der Methode doFinal () :


 byte[] plainText = "abcdefghijklmnopqrstuvwxyz".getBytes("UTF-8"); byte[] cipherText = cipher.doFinal(plainText); 

Um die Daten zu entschlüsseln, müssen Sie den Chiffretext (Daten) an die Methode doFinal () oder doUpdate () übergeben .


Schlüssel


Zum Ver- oder Entschlüsseln von Daten benötigen Sie einen Schlüssel. Es gibt zwei Arten von Schlüsseln, je nachdem, welche Art von Verschlüsselungsalgorithmus verwendet wird:


  • Symmetrische Schlüssel
  • Asymmetrische Schlüssel

Symmetrische Schlüssel werden für symmetrische Verschlüsselungsalgorithmen verwendet. Symmetrische Verschlüsselungsalgorithmen verwenden denselben Schlüssel für die Ver- und Entschlüsselung.
Asymmetrische Schlüssel werden für asymmetrische Verschlüsselungsalgorithmen verwendet. Asymmetrische Verschlüsselungsalgorithmen verwenden einen Schlüssel zur Verschlüsselung und einen anderen zur Entschlüsselung. Verschlüsselungsalgorithmen mit öffentlichem und privatem Schlüssel sind Beispiele für asymmetrische Verschlüsselungsalgorithmen.


Irgendwie muss die Partei, die die Daten entschlüsseln muss, den Schlüssel kennen, der zum Entschlüsseln der Daten benötigt wird. Wenn der Entschlüsseler nicht an der Datenverschlüsselung beteiligt ist, müssen sich die beiden Parteien auf einen Schlüssel einigen oder einen Schlüssel austauschen. Dies wird als Schlüsselaustausch bezeichnet.


Schlüsselsicherheit


Schlüssel müssen schwer zu erraten sein, damit ein Angreifer einen Verschlüsselungsschlüssel nicht einfach abrufen kann. Im Beispiel aus dem vorherigen Abschnitt zur Cipher-Klasse wurde ein sehr einfacher, fest codierter Schlüssel verwendet. In der Praxis lohnt sich dies nicht. Wenn der Schlüssel der Parteien leicht zu erraten ist, kann ein Angreifer die verschlüsselten Daten leicht entschlüsseln und möglicherweise selbst gefälschte Nachrichten erstellen. Es ist wichtig, einen Schlüssel zu erstellen, der schwer zu erraten ist. Daher sollte der Schlüssel aus zufälligen Bytes bestehen. Je mehr zufällige Bytes vorhanden sind, desto schwieriger ist es zu erraten, da es mehr mögliche Kombinationen gibt.


Schlüsselgenerierung


Um zufällige Verschlüsselungsschlüssel zu generieren, können Sie die Java KeyGenerator-Klasse verwenden. KeyGenerator wird in den folgenden Kapiteln ausführlicher beschrieben. Hier ein kleines Beispiel für seine Verwendung:


 KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); SecureRandom secureRandom = new SecureRandom(); int keyBitSize = 256; keyGenerator.init(keyBitSize, secureRandom); SecretKey secretKey = keyGenerator.generateKey(); 

Die resultierende SecretKey-Instanz kann beispielsweise wie folgt an die Cipher.init () -Methode übergeben werden:


 cipher.init(Cipher.ENCRYPT_MODE, secretKey); 

Schlüsselpaargenerierung


Asymmetrische Verschlüsselungsalgorithmen verwenden ein Schlüsselpaar, das aus einem öffentlichen und einem privaten Schlüssel besteht, um Daten zu verschlüsseln und zu entschlüsseln. Um ein asymmetrisches Schlüsselpaar zu erstellen, können Sie KeyPairGenerator (java.security.KeyPairGenerator) verwenden. KeyPairGenerator wird in den folgenden Kapiteln ausführlicher beschrieben. Im Folgenden finden Sie ein einfaches Beispiel für die Verwendung von Java KeyPairGenerator:


 SecureRandom secureRandom = new SecureRandom(); KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA"); KeyPair keyPair = keyPairGenerator.generateKeyPair(); 

Schlüsselspeicher


Java KeyStore ist eine Datenbank, die Schlüssel enthalten kann. Java KeyStore wird durch die KeyStore-Klasse (java.security.KeyStore) dargestellt. Ein Schlüsselspeicher kann Schlüssel der folgenden Typen enthalten:


  • Private Schlüssel
  • Öffentliche Schlüssel und Zertifikate (Öffentliche Schlüssel + Zertifikate)
  • Geheime Schlüssel

Private und öffentliche Schlüssel werden bei der asymmetrischen Verschlüsselung verwendet. Dem öffentlichen Schlüssel kann ein Zertifikat zugeordnet sein. Ein Zertifikat ist ein Dokument, das die Identität einer Person, Organisation oder eines Geräts nachweist, die / das behauptet, einen öffentlichen Schlüssel zu besitzen.


Das Zertifikat wird in der Regel von der vertrauenden Partei als Nachweis digital signiert.
Private Schlüssel werden bei der symmetrischen Verschlüsselung verwendet. Die KeyStore-Klasse ist recht komplex, weshalb sie später in einem separaten Kapitel über Java KeyStore ausführlicher beschrieben wird.


Schlüsselverwaltungstool (Keytool)


Java Keytool ist ein Befehlszeilentool, das mit Java KeyStore-Dateien arbeiten kann. Keytool kann Schlüsselpaare in einer KeyStore-Datei generieren, Zertifikate exportieren und Zertifikate in KeyStore und einige andere Funktionen importieren. Keytool wird mit einer Java-Installation geliefert. Keytool wird später in einem separaten Kapitel über Java Keytool ausführlicher beschrieben.


Message Digest


Können Sie sicher sein, dass niemand die verschlüsselten Daten auf dem Weg zu Ihnen geändert hat, wenn Sie verschlüsselte Daten von der anderen Seite erhalten?


In der Regel besteht die Lösung darin, den Nachrichtenauszug aus den Daten zu berechnen, bevor sie verschlüsselt werden. Anschließend werden sowohl die Daten als auch der Nachrichtenauszug verschlüsselt und über das Netzwerk gesendet. Ein Nachrichtenauszug ist ein Hashwert, der basierend auf den Nachrichtendaten berechnet wird. Wenn mindestens ein Byte in den verschlüsselten Daten geändert wird, ändert sich auch der aus den Daten berechnete Nachrichtenauszug.


Wenn Sie verschlüsselte Daten empfangen, entschlüsseln Sie diese, berechnen den Nachrichtenauszug daraus und vergleichen den berechneten Nachrichtenauszug mit dem Auszug der zusammen mit den verschlüsselten Daten gesendeten Nachricht. Wenn die beiden Nachrichtenübersichten identisch sind, besteht eine hohe Wahrscheinlichkeit (jedoch nicht 100%), dass die Daten nicht geändert wurden.


Java MessageDigest (java.security.MessageDigest) kann zum Berechnen von Nachrichtenauszügen verwendet werden. Um eine Instanz von MessageDigest zu erstellen, wird die MessageDigest.getInstance () -Methode aufgerufen. Es gibt verschiedene Message Digest-Algorithmen. Sie müssen angeben, welchen Algorithmus Sie beim Erstellen der MessageDigest-Instanz verwenden möchten. Die Arbeit mit MessageDigest wird im Kapitel Java MessageDigest ausführlicher beschrieben.


Eine kurze Einführung in die MessageDigest-Klasse:


 MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); 

In diesem Beispiel wird eine Instanz von MessageDigest erstellt, die den internen kryptografischen Hashing-Algorithmus SHA-256 zum Berechnen von Nachrichtenauszügen verwendet.


Um den Message Digest einiger Daten zu berechnen, rufen Sie die update () - oder Digest () -Methode auf. Die update () -Methode kann mehrmals aufgerufen werden, und der Message Digest wird im Objekt aktualisiert. Wenn Sie alle Daten übergeben haben, die Sie in den Nachrichtenauszug aufnehmen möchten, rufen Sie Digest () auf und rufen die Zusammenfassung des Nachrichtenauszugs ab.


Ein Beispiel für das mehrmalige Aufrufen von update () , gefolgt von einem Aufruf von Digest () :


 MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); byte[] data1 = "0123456789".getBytes("UTF-8"); byte[] data2 = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8"); messageDigest.update(data1); messageDigest.update(data2); byte[] digest = messageDigest.digest(); 

Sie können Digest () auch einmal aufrufen und alle Daten übergeben, um den Digest der Nachricht zu berechnen. Ein Beispiel:


 MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); byte[] data1 = "0123456789".getBytes("UTF-8"); byte[] digest = messageDigest.digest(data1); 

Nachrichtenauthentifizierungscode (MAC)


Die Java Mac-Klasse wird verwendet, um aus einer Nachricht einen MAC (Message Authentication Code) zu erstellen. Der MAC ähnelt einem Message Digest, verwendet jedoch einen zusätzlichen Schlüssel, um den Message Digest zu verschlüsseln. Wenn Sie nur die Quelldaten und den Schlüssel haben, können Sie den MAC überprüfen. Daher ist ein MAC eine sicherere Methode, um einen Datenblock vor Änderungen zu schützen, als ein Message Digest. Die Mac-Klasse wird im Kapitel Java Mac ausführlicher beschrieben, gefolgt von einer kurzen Einführung.


Eine Java Mac-Instanz wird durch Aufrufen der Mac.getInstance () -Methode erstellt, wobei der Name des Algorithmus als Parameter übergeben wird. So sieht es aus:


 Mac mac = Mac.getInstance("HmacSHA256"); 

Bevor Sie einen MAC aus Daten erstellen, müssen Sie die Mac-Instanz mit dem Schlüssel initialisieren. Hier ist ein Beispiel für die Initialisierung einer Mac-Instanz mit einem Schlüssel:


 byte[] keyBytes = new byte[]{0,1,2,3,4,5,6,7,8 ,9,10,11,12,13,14,15}; String algorithm = "RawBytes"; SecretKeySpec key = new SecretKeySpec(keyBytes, algorithm); mac.init(key); 

Nach der Initialisierung der Mac-Instanz können Sie den MAC aus den Daten berechnen, indem Sie die Methoden update () und doFinal () aufrufen . Wenn Sie über alle Daten zur Berechnung des MAC verfügen, können Sie sofort die Methode doFinal () aufrufen . So sieht es aus:


 byte[] data = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8"); byte[] data2 = "0123456789".getBytes("UTF-8"); mac.update(data); mac.update(data2); byte[] macBytes = mac.doFinal(); 

Unterschrift


Die Signaturklasse (java.security.Signature) wird zum digitalen Signieren von Daten verwendet. Wenn Daten signiert werden, wird aus diesen Daten eine digitale Signatur erstellt. Somit wird die Signatur von den Daten getrennt.


Eine digitale Signatur wird erstellt, indem aus den Daten ein Message Digest (Hash) erstellt und dieser Message Digest mit dem privaten Schlüssel des Geräts, der Person oder Organisation verschlüsselt wird, die die Daten signieren müssen. Der Digest der verschlüsselten Nachricht wird als digitale Signatur bezeichnet.


Um eine Instanz von Signature zu erstellen, wird die Signature.getInstance (...) -Methode aufgerufen:


 Signature signature = Signature.getInstance("SHA256WithDSA"); 

Datensignatur


Um Daten zu signieren, müssen Sie die Signaturinstanz im Signaturmodus initialisieren, indem Sie die Methode initSign (...) aufrufen und den privaten Schlüssel zum Signieren der Daten übergeben. Ein Beispiel für die Initialisierung einer Signaturinstanz im Signaturmodus:


 signature.initSign(keyPair.getPrivate(), secureRandom); 

Nach dem Initialisieren der Signaturinstanz können die Daten damit signiert werden. Dazu rufen Sie die update () -Methode auf und übergeben die Signaturdaten als Parameter. Sie können die update () -Methode mehrmals aufrufen, um die Daten zum Erstellen der Signatur zu ergänzen. Nachdem alle Daten an die update () -Methode übergeben wurden, wird die sign () -Methode aufgerufen, um eine digitale Signatur zu erhalten. So sieht es aus:


 byte[] data = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8"); signature.update(data); byte[] digitalSignature = signature.sign(); 

Überprüfung der Unterschrift


Um die Signatur zu überprüfen, müssen Sie die Signaturinstanz im Überprüfungsmodus initialisieren, indem Sie die Methode initVerify (...) aufrufen und als Parameter den öffentlichen Schlüssel übergeben, der zum Überprüfen der Signatur verwendet wird. Ein Beispiel für die Initialisierung einer Signaturinstanz im Überprüfungsmodus sieht folgendermaßen aus:


 Signature signature = Signature.getInstance("SHA256WithDSA"); signature.initVerify(keyPair.getPublic()); 

Nach der Initialisierung im Überprüfungsmodus werden signierte Daten an die update () -Methode übertragen. Ein Aufruf der verify () -Methode gibt true oder false zurück, je nachdem, ob die Signatur überprüft werden kann oder nicht. Hier ist die Signaturüberprüfung:


 byte[] data2 = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8"); signature2.update(data2); boolean verified = signature2.verify(digitalSignature); 

Vollständiges Beispiel für Unterschrift und Überprüfung


 SecureRandom secureRandom = new SecureRandom(); KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA"); KeyPair keyPair = keyPairGenerator.generateKeyPair(); Signature signature = Signature.getInstance("SHA256WithDSA"); signature.initSign(keyPair.getPrivate(), secureRandom); byte[] data = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8"); signature.update(data); byte[] digitalSignature = signature.sign(); Signature signature2 = Signature.getInstance("SHA256WithDSA"); signature2.initVerify(keyPair.getPublic()); signature2.update(data); boolean verified = signature2.verify(digitalSignature); System.out.println("verified = " + verified); 

Source: https://habr.com/ru/post/de444764/


All Articles