Java AES Verschlüsselung und Entschlüsselung

Java Top

Ich habe gerade den neuen Learn Spring- Kurs angekündigt , der sich auf die Grundlagen von Spring 5 und Spring Boot 2 konzentriert:

>> Überprüfen Sie den Kurs

1. Übersicht

Die Blockverschlüsselung mit symmetrischen Schlüsseln spielt eine wichtige Rolle bei der Datenverschlüsselung. Dies bedeutet, dass für die Ver- und Entschlüsselung derselbe Schlüssel verwendet wird. Der Advanced Encryption Standard (AES) ist ein weit verbreiteter Verschlüsselungsalgorithmus mit symmetrischen Schlüsseln.

In diesem Tutorial erfahren Sie, wie Sie die AES-Verschlüsselung und -Entschlüsselung mithilfe der Java Cryptography Architecture (JCA) im JDK implementieren.

2. AES-Algorithmus

Der AES-Algorithmus ist eine iterative Blockverschlüsselung mit symmetrischen Schlüsseln, die kryptografische Schlüssel (geheime Schlüssel) mit 128, 192 und 256 Bit unterstützt, um Daten in Blöcken mit 128 Bit zu verschlüsseln und zu entschlüsseln . Die folgende Abbildung zeigt den übergeordneten AES-Algorithmus:

Wenn die zu verschlüsselnden Daten nicht die Blockgröße von 128 Bit erfüllen, müssen sie aufgefüllt werden. Beim Auffüllen wird der letzte Block auf 128 Bit aufgefüllt.

3. AES-Variationen

Der AES-Algorithmus verfügt über sechs Betriebsmodi:

  1. EZB (Elektronisches Codebuch)
  2. CBC (Cipher Block Chaining)
  3. CFB (Cipher FeedBack)
  4. OFB (Output FeedBack)
  5. CTR (Zähler)
  6. GCM (Galois / Counter Mode)

Die Betriebsart kann angewendet werden, um die Wirkung des Verschlüsselungsalgorithmus zu verstärken. Darüber hinaus kann die Betriebsart die Blockverschlüsselung in eine Stromverschlüsselung umwandeln. Jeder Modus hat seine Stärken und Schwächen. Lassen Sie uns einen kurzen Überblick geben.

3.1. EZB

Diese Betriebsart ist die einfachste von allen. Der Klartext ist in Blöcke mit einer Größe von 128 Bit unterteilt. Dann wird jeder Block mit demselben Schlüssel und Algorithmus verschlüsselt. Daher wird das gleiche Ergebnis für den gleichen Block erzeugt. Dies ist die Hauptschwäche dieses Modus und wird für die Verschlüsselung nicht empfohlen . Es erfordert Auffülldaten.

3.2. CBC

Um die EZB-Schwäche zu überwinden, verwendet der CBC-Modus einen Initialisierungsvektor (IV), um die Verschlüsselung zu erweitern. Zunächst verwendet CBC den Klartextblock xoder mit der IV. Dann verschlüsselt es das Ergebnis in den Chiffretextblock. Im nächsten Block wird das Verschlüsselungsergebnis verwendet, um mit dem Klartextblock bis zum letzten Block xor.

In diesem Modus kann die Verschlüsselung nicht parallelisiert werden, die Entschlüsselung kann jedoch parallelisiert werden. Es erfordert auch Auffülldaten.

3.3. CFB

Dieser Modus kann als Stream-Verschlüsselung verwendet werden. Zuerst verschlüsselt es die IV, dann xor es mit dem Klartextblock, um Chiffretext zu erhalten. Dann verschlüsselt CFB das Verschlüsselungsergebnis, um den Klartext zu xor. Es braucht eine IV.

In diesem Modus kann die Entschlüsselung parallelisiert werden, die Verschlüsselung kann jedoch nicht parallelisiert werden.

3.4. VON B

Dieser Modus kann als Stream-Verschlüsselung verwendet werden. Erstens verschlüsselt es die IV. Anschließend werden die Verschlüsselungsergebnisse verwendet, um den Klartext zu xorieren und Chiffretext zu erhalten.

Es erfordert keine Auffülldaten und wird vom verrauschten Block nicht beeinflusst.

3.5. CTR

In diesem Modus wird der Wert eines Zählers als IV verwendet. Es ist OFB sehr ähnlich, verwendet jedoch den Zähler, der jedes Mal anstelle der IV verschlüsselt wird.

Dieser Modus hat zwei Stärken, einschließlich der Parallelisierung von Verschlüsselung und Entschlüsselung, und das Rauschen in einem Block wirkt sich nicht auf andere Blöcke aus.

3.6. GCM

Dieser Modus ist eine Erweiterung des CTR-Modus. Das GCM hat erhebliche Aufmerksamkeit erhalten und wird vom NIST empfohlen. Das GCM-Modell gibt Chiffretext und ein Authentifizierungs-Tag aus. Der Hauptvorteil dieses Modus im Vergleich zu anderen Betriebsmodi des Algorithmus ist seine Effizienz.

In diesem Tutorial verwenden wir den AES / CBC / PKCS5Padding- Algorithmus, da er in vielen Projekten weit verbreitet ist.

3.7. Datengröße nach der Verschlüsselung

Wie bereits erwähnt, hat der AES eine Blockgröße von 128 Bit oder 16 Byte. Das AES ändert die Größe nicht und die Chiffretextgröße entspricht der Klartextgröße. Außerdem sollten wir im EZB- und CBC-Modus einen Auffüllalgorithmus wie PKCS 5 verwenden . Die Datengröße nach der Verschlüsselung beträgt also:

ciphertext_size (bytes) = cleartext_size + (16 - (cleartext_size % 16))

Zum Speichern von IV mit Chiffretext müssen 16 weitere Bytes hinzugefügt werden.

4. AES-Parameter

Im AES-Algorithmus benötigen wir drei Parameter: Eingabedaten, geheimer Schlüssel und IV. IV wird im EZB-Modus nicht verwendet.

4.1. Eingabedaten

Die Eingabedaten für das AES können auf Zeichenfolgen, Dateien, Objekten und Kennwörtern basieren.

4.2. Geheimer Schlüssel

Es gibt zwei Möglichkeiten, einen geheimen Schlüssel im AES zu generieren: Generieren aus einer Zufallszahl oder Ableiten aus einem bestimmten Passwort.

Beim ersten Ansatz sollte der geheime Schlüssel aus einem kryptografisch sicheren (Pseudo-) Zufallszahlengenerator wie der SecureRandom- Klasse generiert werden .

Zum Generieren eines geheimen Schlüssels können wir die KeyGenerator- Klasse verwenden. Definieren wir eine Methode zum Generieren des AES-Schlüssels mit der Größe von n (128, 192 und 256) Bits:

public static SecretKey generateKey(int n) throws NoSuchAlgorithmException { KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); keyGenerator.init(n); SecretKey key = keyGenerator.generateKey(); return key; }

Beim zweiten Ansatz kann der geheime AES-Schlüssel aus einem bestimmten Kennwort mithilfe einer kennwortbasierten Schlüsselableitungsfunktion wie PBKDF2 abgeleitet werden . Wir brauchen auch einen Salt-Wert, um ein Passwort in einen geheimen Schlüssel zu verwandeln. Das Salz ist auch ein zufälliger Wert.

We can use the SecretKeyFactory class with the PBKDF2WithHmacSHA256 algorithm for generating a key from a given password.

Let’s define a method for generating the AES key from a given password with 65,536 iterations and a key length of 256 bits:

public static SecretKey getKeyFromPassword(String password, String salt) throws NoSuchAlgorithmException, InvalidKeySpecException { SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); KeySpec spec = new PBEKeySpec(password.toCharArray(), salt.getBytes(), 65536, 256); SecretKey secret = new SecretKeySpec(factory.generateSecret(spec) .getEncoded(), "AES"); return secret; }

4.3. Initialization Vector (IV)

IV is a pseudo-random value and has the same size as the block that is encrypted. We can use the SecureRandom class to generate a random IV.

Let’s define a method for generating an IV:

public static IvParameterSpec generateIv() { byte[] iv = new byte[16]; new SecureRandom().nextBytes(iv); return new IvParameterSpec(iv); }

5. Encryption and Decryption

5.1. String

To implement input string encryption, we first need to generate the secret key and IV according to the previous section. As the next step, we create an instance from the Cipher class by using the getInstance() method.

Additionally, we configure a cipher instance using the init() method with a secret key, IV, and encryption mode. Finally, we encrypt the input string by invoking the doFinal() method. This method gets bytes of input and returns ciphertext in bytes:

public static String encrypt(String algorithm, String input, SecretKey key, IvParameterSpec iv) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException { Cipher cipher = Cipher.getInstance(algorithm); cipher.init(Cipher.ENCRYPT_MODE, key, iv); byte[] cipherText = cipher.doFinal(input.getBytes()); return Base64.getEncoder() .encodeToString(cipherText); }

For decrypting an input string, we can initialize our cipher using the DECRYPT_MODE to decrypt the content:

public static String decrypt(String algorithm, String cipherText, SecretKey key, IvParameterSpec iv) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException { Cipher cipher = Cipher.getInstance(algorithm); cipher.init(Cipher.DECRYPT_MODE, key, iv); byte[] plainText = cipher.doFinal(Base64.getDecoder() .decode(cipherText)); return new String(plainText); }

Let's write a test method for encrypting and decrypting a string input:

@Test void givenString_whenEncrypt_thenSuccess() throws NoSuchAlgorithmException, IllegalBlockSizeException, InvalidKeyException, BadPaddingException, InvalidAlgorithmParameterException, NoSuchPaddingException { String input = "baeldung"; SecretKey key = AESUtil.generateKey(128); IvParameterSpec ivParameterSpec = AESUtil.generateIv(); String algorithm = "AES/CBC/PKCS5Padding"; String cipherText = AESUtil.encrypt(algorithm, input, key, ivParameterSpec); String plainText = AESUtil.decrypt(algorithm, cipherText, key, ivParameterSpec); Assertions.assertEquals(input, plainText); }

5.2. File

Now let's encrypt a file using the AES algorithm. The steps are the same, but we need some IO classes to work with the files. Let's encrypt a text file:

public static void encryptFile(String algorithm, SecretKey key, IvParameterSpec iv, File inputFile, File outputFile) throws IOException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException { Cipher cipher = Cipher.getInstance(algorithm); cipher.init(Cipher.ENCRYPT_MODE, key, iv); FileInputStream inputStream = new FileInputStream(inputFile); FileOutputStream outputStream = new FileOutputStream(outputFile); byte[] buffer = new byte[64]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { byte[] output = cipher.update(buffer, 0, bytesRead); if (output != null) { outputStream.write(output); } } byte[] outputBytes = cipher.doFinal(); if (outputBytes != null) { outputStream.write(outputBytes); } inputStream.close(); outputStream.close(); }

Please note that trying to read the entire file – particularly if it is large – into memory is not recommended. Instead, we encrypt a buffer at a time.

For decrypting a file, we use similar steps and initialize our cipher using DECRYPT_MODE as we saw before.

Again, let's define a test method for encrypting and decrypting a text file. In this method, we read the baeldung.txt file from the test resource directory, encrypt it into a file called baeldung.encrypted, and then decrypt the file into a new file:

@Test void givenFile_whenEncrypt_thenSuccess() throws NoSuchAlgorithmException, IOException, IllegalBlockSizeException, InvalidKeyException, BadPaddingException, InvalidAlgorithmParameterException, NoSuchPaddingException { SecretKey key = AESUtil.generateKey(128); String algorithm = "AES/CBC/PKCS5Padding"; IvParameterSpec ivParameterSpec = AESUtil.generateIv(); Resource resource = new ClassPathResource("inputFile/baeldung.txt"); File inputFile = resource.getFile(); File encryptedFile = new File("classpath:baeldung.encrypted"); File decryptedFile = new File("document.decrypted"); AESUtil.encryptFile(algorithm, key, ivParameterSpec, inputFile, encryptedFile); AESUtil.decryptFile( algorithm, key, ivParameterSpec, encryptedFile, decryptedFile); assertThat(inputFile).hasSameTextualContentAs(decryptedFile); }

5.3. Password-Based

We can do the AES encryption and decryption using the secret key that is derived from a given password.

For generating a secret key, we use the getKeyFromPassword() method. The encryption and decryption steps are the same as those shown in the string input section. We can then use the instantiated cipher and the provided secret key to perform the encryption.

Let's write a test method:

@Test void givenPassword_whenEncrypt_thenSuccess() throws InvalidKeySpecException, NoSuchAlgorithmException, IllegalBlockSizeException, InvalidKeyException, BadPaddingException, InvalidAlgorithmParameterException, NoSuchPaddingException { String plainText = "www.baeldung.com"; String password = "baeldung"; String salt = "12345678"; IvParameterSpec ivParameterSpec = AESUtil.generateIv(); SecretKey key = AESUtil.getKeyFromPassword(password,salt); String cipherText = AESUtil.encryptPasswordBased(plainText, key, ivParameterSpec); String decryptedCipherText = AESUtil.decryptPasswordBased( cipherText, key, ivParameterSpec); Assertions.assertEquals(plainText, decryptedCipherText); }

5.4. Object

For encrypting a Java object, we need to use the SealedObject class. The object should be Serializable. Let's begin by defining a Student class:

public class Student implements Serializable { private String name; private int age; // standard setters and getters } 

Next, let's encrypt the Student object :

public static SealedObject encryptObject(String algorithm, Serializable object, SecretKey key, IvParameterSpec iv) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IOException, IllegalBlockSizeException { Cipher cipher = Cipher.getInstance(algorithm); cipher.init(Cipher.ENCRYPT_MODE, key, iv); SealedObject sealedObject = new SealedObject(object, cipher); return sealedObject; }

The encrypted object can later be decrypted using the correct cipher:

public static Serializable decryptObject(String algorithm, SealedObject sealedObject, SecretKey key, IvParameterSpec iv) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, ClassNotFoundException, BadPaddingException, IllegalBlockSizeException, IOException { Cipher cipher = Cipher.getInstance(algorithm); cipher.init(Cipher.DECRYPT_MODE, key, iv); Serializable unsealObject = (Serializable) sealedObject.getObject(cipher); return unsealObject; }

Let's write a test case:

@Test void givenObject_whenEncrypt_thenSuccess() throws NoSuchAlgorithmException, IllegalBlockSizeException, InvalidKeyException, InvalidAlgorithmParameterException, NoSuchPaddingException, IOException, BadPaddingException, ClassNotFoundException { Student student = new Student("Baeldung", 20); SecretKey key = AESUtil.generateKey(128); IvParameterSpec ivParameterSpec = AESUtil.generateIv(); String algorithm = "AES/CBC/PKCS5Padding"; SealedObject sealedObject = AESUtil.encryptObject( algorithm, student, key, ivParameterSpec); Student object = (Student) AESUtil.decryptObject( algorithm, sealedObject, key, ivParameterSpec); assertThat(student).isEqualToComparingFieldByField(object); }

6. Conclusion

Zusammenfassend haben wir gelernt, wie Eingabedaten wie Zeichenfolgen, Dateien, Objekte und kennwortbasierte Daten mithilfe des AES-Algorithmus in Java verschlüsselt und entschlüsselt werden. Darüber hinaus haben wir die AES-Variationen und die Datengröße nach der Verschlüsselung erläutert.

Wie immer ist der vollständige Quellcode des Artikels auf GitHub verfügbar.

Java unten

Ich habe gerade den neuen Learn Spring- Kurs angekündigt , der sich auf die Grundlagen von Spring 5 und Spring Boot 2 konzentriert:

>> Überprüfen Sie den Kurs