Java AES-codering en decodering

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

Het symmetrische sleutelblokcijfer speelt een belangrijke rol bij gegevensversleuteling. Het betekent dat dezelfde sleutel wordt gebruikt voor zowel codering als decodering. De Advanced Encryption Standard (AES) is een veelgebruikt versleutelingsalgoritme met symmetrische sleutels.

In deze zelfstudie zien we hoe u AES-codering en decodering implementeert met behulp van de Java Cryptography Architecture (JCA) binnen de JDK.

2. AES-algoritme

Het AES-algoritme is een iteratief, symmetrisch sleutelblokcijfer dat ondersteunt cryptografische sleutels (geheime sleutels) van 128, 192 en 256 bits om gegevens te coderen en decoderen in blokken van 128 bits. De onderstaande afbeelding toont het AES-algoritme op hoog niveau:

Als de te versleutelen gegevens niet voldoen aan de vereiste blokgrootte van 128 bits, moet deze worden opgevuld. Padding is een proces waarbij het laatste blok wordt opgevuld tot 128 bits.

3. AES-variaties

Het AES-algoritme heeft zes werkingsmodi:

  1. ECB (elektronisch codeboek)
  2. CBC (Cipher Block Chaining)
  3. CFB (Cipher FeedBack)
  4. OFB (Output FeedBack)
  5. CTR (teller)
  6. GCM (Galois / Counter-modus)

De werkingsmodus kan worden toegepast om het effect van het versleutelingsalgoritme te versterken. Bovendien kan de werkingsmodus het blokcijfer omzetten in een stroomcijfer. Elke modus heeft zijn kracht en zwakte. Laten we een korte bespreking geven.

3.1. ECB

Deze manier van werken is de eenvoudigste van allemaal. De leesbare tekst is verdeeld in blokken met een grootte van 128 bits. Vervolgens wordt elk blok versleuteld met dezelfde sleutel en hetzelfde algoritme. Daarom levert het hetzelfde resultaat op voor hetzelfde blok. Dit is de belangrijkste zwakte van deze modus en het wordt niet aanbevolen voor codering. Het vereist opvulgegevens.

3.2. CBC

Om de zwakte van de ECB te verhelpen, gebruikt de CBC-modus een initialisatievector (IV) om de versleuteling te vergroten. Ten eerste gebruikt CBC het platte tekstblok xor met de IV. Vervolgens versleutelt het het resultaat naar het gecodeerde tekstblok. In het volgende blok gebruikt het het versleutelingsresultaat om te xor met het platte tekstblok tot het laatste blok.

In deze modus kan codering niet worden geparallelliseerd, maar decodering kan wel worden geparallelliseerd. Het vereist ook opvulgegevens.

3.3. CFB

Deze modus kan worden gebruikt als een stroomcijfer. Ten eerste versleutelt het de IV, dan zal het xor met het leesbare tekstblok om cijfertekst te krijgen. Vervolgens versleutelt CFB het versleutelingsresultaat naar xof de leesbare tekst. Het heeft een infuus nodig.

In deze modus kan de decodering worden geparallelliseerd, maar de codering niet.

3.4. OFB

Deze modus kan worden gebruikt als een stroomcijfer. Ten eerste versleutelt het de IV. Vervolgens gebruikt het de coderingsresultaten om de platte tekst te xor- ren om cijfertekst op te halen.

Het vereist geen opvulgegevens en wordt niet beïnvloed door het lawaaierige blok.

3.5. CTR

Deze modus gebruikt de waarde van een teller als een IV. Het lijkt erg op OFB, maar het gebruikt de teller om elke keer te worden gecodeerd in plaats van de IV.

Deze modus heeft twee sterke punten, waaronder parallellisatie met codering / decodering, en ruis in één blok heeft geen invloed op andere blokken.

3.6. GCM

Deze modus is een uitbreiding van de CTR-modus. De GCM heeft veel aandacht gekregen en wordt aanbevolen door NIST. Het GCM-model voert cijfertekst en een authenticatietag uit. Het belangrijkste voordeel van deze modus, vergeleken met andere bedieningsmodi van het algoritme, is de efficiëntie.

In deze tutorial gebruiken we de AES / CBC / PKCS5Padding algoritme omdat het in veel projecten veel wordt gebruikt.

3.7. Grootte van gegevens na versleuteling

Zoals eerder vermeld, heeft de AES een blokgrootte van 128 bits of 16 bytes. De AES verandert de grootte niet en de cijfertekstgrootte is gelijk aan de gewone tekstgrootte. In de modi ECB en CBC moeten we ook een opvulalgoritme 'likes' gebruiken PKCS 5. De grootte van de gegevens na versleuteling is dus:

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

Voor het opslaan van IV met cijfertekst, moeten we 16 extra bytes toevoegen.

4. AES-parameters

In het AES-algoritme hebben we drie parameters nodig: invoergegevens, geheime sleutel en IV. IV wordt niet gebruikt in de ECB-modus.

4.1. Invoergegevens

De invoergegevens voor de AES kunnen op tekenreeksen, bestanden, objecten en wachtwoorden zijn gebaseerd.

4.2. Geheime sleutel

Er zijn twee manieren om een ​​geheime sleutel in het AES te genereren: genereren op basis van een willekeurig nummer of afleiden van een bepaald wachtwoord.

In de eerste benadering moet de geheime sleutel worden gegenereerd vanuit een cryptografisch beveiligde (pseudo) willekeurige nummergenerator zoals de SecureRandom klasse.

Voor het genereren van een geheime sleutel kunnen we de KeyGenerator klasse. Laten we een methode definiëren voor het genereren van de AES-sleutel met de grootte van n (128, 192 en 256) bits:

openbare statische SecretKey generationKey (int n) gooit NoSuchAlgorithmException {KeyGenerator keyGenerator = KeyGenerator.getInstance ("AES"); keyGenerator.init (n); SecretKey-sleutel = keyGenerator.generateKey (); return-toets; }

In de tweede benadering kan de geheime AES-sleutel worden afgeleid van een bepaald wachtwoord met behulp van een op een wachtwoord gebaseerde sleutelafleidingsfunctie zoals PBKDF2. We hebben ook een salt-waarde nodig om een ​​wachtwoord in een geheime sleutel te veranderen. Het zout is ook een willekeurige waarde.

We kunnen de SecretKeyFactory klasse met de PBKDF2MetHmacSHA256 algoritme voor het genereren van een sleutel op basis van een bepaald wachtwoord.

Laten we een methode definiëren voor het genereren van de AES-sleutel van een bepaald wachtwoord met 65.536 iteraties en een sleutellengte van 256 bits:

openbare statische SecretKey getKeyFromPassword (String-wachtwoord, String-salt) gooit NoSuchAlgorithmException, InvalidKeySpecException {SecretKeyFactory factory = SecretKeyFactory.getInstance ("PBKDF2WithHmacSHA256"); KeySpec spec = nieuwe PBEKeySpec (password.toCharArray (), salt.getBytes (), 65536, 256); SecretKey secret = nieuwe SecretKeySpec (factory.generateSecret (spec) .getEncoded (), "AES"); geheim teruggeven; }

4.3. Initialisatie Vector (IV)

IV is een pseudo-willekeurige waarde en heeft dezelfde grootte als het blok dat is versleuteld. We kunnen de SecureRandom class om een ​​willekeurige IV te genereren.

Laten we een methode definiëren voor het genereren van een IV:

openbare statische IvParameterSpec generationIv () {byte [] iv = nieuwe byte [16]; nieuwe SecureRandom (). nextBytes (iv); retourneer nieuwe IvParameterSpec (iv); }

5. Versleuteling en ontsleuteling

5.1. Draad

Om encryptie van de invoertekenreeks te implementeren, moeten we eerst de geheime sleutel en IV genereren volgens de vorige sectie. Als volgende stap maken we een instantie van het Cijfer klasse met behulp van de getInstance () methode.

Bovendien configureren we een versleutelingsinstantie met behulp van de in het() methode met een geheime sleutel, IV en coderingsmodus. Ten slotte versleutelen we de invoertekenreeks door de doFinal () methode. Deze methode haalt bytes aan invoer op en retourneert cijfertekst in bytes:

openbare statische String-codering (String-algoritme, String-invoer, SecretKey-sleutel, IvParameterSpec iv) gooit NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {Cipher cipher = Cipher.getInstance; cipher.init (Cipher.ENCRYPT_MODE, key, iv); byte [] cipherText = cipher.doFinal (input.getBytes ()); retourneer Base64.getEncoder () .encodeToString (cipherText); }

Voor het decoderen van een invoertekenreeks kunnen we onze codering initialiseren met de DECRYPT_MODE om de inhoud te ontsleutelen:

openbare statische String-decodering (String-algoritme, String cipherText, SecretKey-sleutel, IvParameterSpec iv) gooit NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {Cipherget cipher = Cipher. cipher.init (Cipher.DECRYPT_MODE, key, iv); byte [] plainText = cipher.doFinal (Base64.getDecoder () .decode (cipherText)); retourneer nieuwe String (plainText); }

Laten we een testmethode schrijven voor het versleutelen en ontsleutelen van een stringinvoer:

@Test ongeldig gegevenString_whenEncrypt_thenSuccess () gooit NoSuchAlgorithmException, IllegalBlockSizeException, InvalidKeyException, BadPaddingException, InvalidAlgorithmParameterException, NoSuchPaddingException {String input = "baeldung"; SecretKey-sleutel = AESUtil.generateKey (128); IvParameterSpec ivParameterSpec = AESUtil.generateIv (); String algoritme = "AES / CBC / PKCS5Padding"; String cipherText = AESUtil.encrypt (algoritme, invoer, sleutel, ivParameterSpec); String plainText = AESUtil.decrypt (algoritme, cipherText, sleutel, ivParameterSpec); Assertions.assertEquals (input, plainText); }

5.2. het dossier

Laten we nu een bestand versleutelen met het AES-algoritme. De stappen zijn hetzelfde, maar we hebben er een paar nodig IO klassen om met de bestanden te werken. Laten we een tekstbestand versleutelen:

public static void encryptFile (String-algoritme, SecretKey-sleutel, IvParameterSpec iv, File inputFile, File outputFile) gooit IOException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPadding Cipher = Cijfer cipher.init (Cipher.ENCRYPT_MODE, key, iv); FileInputStream inputStream = nieuwe FileInputStream (inputFile); FileOutputStream outputStream = nieuwe FileOutputStream (outputFile); byte [] buffer = nieuwe 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 (); }

Houd er rekening mee dat het niet wordt aanbevolen om het hele bestand - vooral als het groot is - in het geheugen te lezen. In plaats daarvan versleutelen we een buffer per keer.

Voor het decoderen van een bestand gebruiken we vergelijkbare stappen en initialiseren we onze codering met DECRYPT_MODE zoals we eerder zagen.

Laten we nogmaals een testmethode definiëren voor het versleutelen en ontsleutelen van een tekstbestand. Bij deze methode lezen we de baeldung.txt bestand uit de testresource-map, codeer het dan in een bestand met de naam baeldung.versleuteld, en decodeer het bestand vervolgens naar een nieuw bestand:

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

5.3. Op wachtwoord gebaseerd

We kunnen de AES-codering en decodering uitvoeren met behulp van de geheime sleutel die is afgeleid van een bepaald wachtwoord.

Voor het genereren van een geheime sleutel gebruiken we de getKeyFromPassword () methode. De coderings- en decoderingsstappen zijn dezelfde als die worden getoond in de sectie tekenreeksinvoer. We kunnen dan het geïnstantieerde cijfer en de verstrekte geheime sleutel gebruiken om de codering uit te voeren.

Laten we een testmethode schrijven:

@Test ongeldig gegevenPassword_whenEncrypt_thenSuccess () gooit InvalidKeySpecException, NoSuchAlgorithmException, IllegalBlockSizeException, InvalidKeyException, BadPaddingException, InvalidAlgorithmParameterException, NoSuchPaddingException "{StringbaTungException ="; String wachtwoord = "baeldung"; String salt = "12345678"; IvParameterSpec ivParameterSpec = AESUtil.generateIv (); SecretKey-sleutel = AESUtil.getKeyFromPassword (wachtwoord, salt); String cipherText = AESUtil.encryptPasswordBased (plainText, key, ivParameterSpec); String decryptedCipherText = AESUtil.decryptPasswordBased (cipherText, sleutel, ivParameterSpec); Assertions.assertEquals (plainText, decryptedCipherText); }

5.4. Voorwerp

Voor het versleutelen van een Java-object hebben we de VerzegeldObject klasse. Het object zou moeten zijn Serialiseerbaar. Laten we beginnen met het definiëren van een Leerling klasse:

openbare klasse Student implementeert Serializable {private String-naam; privé int leeftijd; // standaard setters en getters} 

Laten we vervolgens het Leerling voorwerp :

openbare statische SealedObject encryptObject (String-algoritme, Serializable object, SecretKey-sleutel, IvParameterSpec iv) gooit NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IOException, IllegalBlockgetInstipherance {CiphermlockSizeException (algoritme) cipher.init (Cipher.ENCRYPT_MODE, key, iv); SealedObject sealObject = nieuw SealedObject (object, cijfer); retour verzegeldObject; }

Het gecodeerde object kan later worden gedecodeerd met de juiste codering:

public statisch Serializable decryptObject (String-algoritme, SealedObject sealObject, SecretKey-sleutel, IvParameterSpec iv) gooit NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, ClassNotFoundException, CipherPaddingException, CipherPaddingException cipher.init (Cipher.DECRYPT_MODE, key, iv); Serialiseerbaar unsealObject = (Serialiseerbaar) sealedObject.getObject (cijfer); retourneer unsealObject; }

Laten we een testcase schrijven:

@Test void givenObject_whenEncrypt_thenSuccess () gooit NoSuchAlgorithmException, IllegalBlockSizeException, InvalidKeyException, InvalidAlgorithmParameterException, NoSuchPaddingException, IOException, BadPaddingException, ClassNotFoundungException, ClassNotFoundungException, ClassNotFoundungException, {" SecretKey-sleutel = AESUtil.generateKey (128); IvParameterSpec ivParameterSpec = AESUtil.generateIv (); String algoritme = "AES / CBC / PKCS5Padding"; SealedObject sealObject = AESUtil.encryptObject (algoritme, student, sleutel, ivParameterSpec); Student-object = (Student) AESUtil.decryptObject (algoritme, sealedObject, sleutel, ivParameterSpec); assertThat (student) .isEqualToComparingFieldByField (object); }

6. Conclusie

Samenvattend hebben we geleerd hoe we invoergegevens zoals strings, bestanden, objecten en op wachtwoorden gebaseerde gegevens kunnen versleutelen en ontsleutelen met behulp van het AES-algoritme in Java. Bovendien hebben we de AES-variaties en de grootte van gegevens na versleuteling besproken.

Zoals altijd is de volledige broncode van het 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