Anfangsbytes nach Java AES / CBC-Entschlüsselung falsch

116

Was ist los mit dem folgenden Beispiel?

Das Problem ist, dass der erste Teil der entschlüsselten Zeichenfolge Unsinn ist. Der Rest ist jedoch in Ordnung, ich verstehe ...

Result: eB6OgeS��i are you? Have a nice day.
@Test
public void testEncrypt() {
  try {
    String s = "Hello there. How are you? Have a nice day.";

    // Generate key
    KeyGenerator kgen = KeyGenerator.getInstance("AES");
    kgen.init(128);
    SecretKey aesKey = kgen.generateKey();

    // Encrypt cipher
    Cipher encryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    encryptCipher.init(Cipher.ENCRYPT_MODE, aesKey);

    // Encrypt
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, encryptCipher);
    cipherOutputStream.write(s.getBytes());
    cipherOutputStream.flush();
    cipherOutputStream.close();
    byte[] encryptedBytes = outputStream.toByteArray();

    // Decrypt cipher
    Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
    decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);

    // Decrypt
    outputStream = new ByteArrayOutputStream();
    ByteArrayInputStream inStream = new ByteArrayInputStream(encryptedBytes);
    CipherInputStream cipherInputStream = new CipherInputStream(inStream, decryptCipher);
    byte[] buf = new byte[1024];
    int bytesRead;
    while ((bytesRead = cipherInputStream.read(buf)) >= 0) {
        outputStream.write(buf, 0, bytesRead);
    }

    System.out.println("Result: " + new String(outputStream.toByteArray()));

  } 
  catch (Exception ex) {
    ex.printStackTrace();
  }
}
TedTrippin
quelle
48
VERWENDEN SIE KEINE ANTWORT AUF DIESE FRAGE IN ERNSTEN PROJEKTEN! Alle in dieser Frage bereitgestellten Beispiele sind anfällig für Padding Orakel und weisen insgesamt eine sehr schlechte Kryptografienutzung auf. Sie werden in Ihrem Projekt eine schwerwiegende Sicherheitslücke in Bezug auf Kryptografie einführen, indem Sie eines der folgenden Snippets verwenden.
HoLyVieR
16
@HoLyVieR, In Bezug auf die folgenden Anführungszeichen: "Sie sollten keine eigene Kryptografiebibliothek entwickeln" und "Verwenden Sie eine von Ihrem Framework bereitgestellte API auf hoher Ebene". Niemand hier entwickelt eine eigene Kryptographie-Bibliothek. Wir verwenden einfach die bereits vorhandene API auf hoher Ebene, die das Java-Framework bereitstellt. Sie, mein Herr, sind wild ungenau.
k170
10
@ MaartenBodewes, Nur weil Sie beide zustimmen, heißt das nicht, dass Sie beide richtig sind. Gute Entwickler kennen den Unterschied zwischen dem Umschließen einer API auf hoher Ebene und dem Umschreiben einer API auf niedriger Ebene. Gute Leser werden feststellen, dass das OP nach einem "einfachen Java AES-Verschlüsselungs- / Entschlüsselungsbeispiel" gefragt hat, und genau das hat er bekommen . Ich bin auch nicht mit den anderen Antworten einverstanden, weshalb ich eine eigene Antwort gepostet habe. Vielleicht solltet ihr es gleich versuchen und uns alle mit eurem Fachwissen aufklären.
k170
6
@HoLyVieR Das ist wirklich das Absurdeste, was ich je auf SO gelesen habe! Wer bist du, um den Menschen zu sagen, was sie entwickeln können und was nicht?
TedTrippin
14
Ich sehe immer noch keine Beispiele @HoLyVieR. Sehen wir uns einige an oder Verweise auf Bibliotheken? Überhaupt nicht konstruktiv.
Danieljimenez

Antworten:

245

Viele Leute, einschließlich ich, haben viele Probleme damit, dass dies funktioniert, weil einige Informationen fehlen, wie das Vergessen, in Base64 zu konvertieren, Initialisierungsvektoren, Zeichensätze usw. Ich dachte daran, einen voll funktionsfähigen Code zu erstellen.

Ich hoffe, dies ist für Sie alle nützlich: Zum Kompilieren benötigen Sie eine zusätzliche Apache Commons Codec-JAR-Datei, die hier verfügbar ist: http://commons.apache.org/proper/commons-codec/download_codec.cgi

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

public class Encryptor {
    public static String encrypt(String key, String initVector, String value) {
        try {
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);

            byte[] encrypted = cipher.doFinal(value.getBytes());
            System.out.println("encrypted string: "
                    + Base64.encodeBase64String(encrypted));

            return Base64.encodeBase64String(encrypted);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static String decrypt(String key, String initVector, String encrypted) {
        try {
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);

            byte[] original = cipher.doFinal(Base64.decodeBase64(encrypted));

            return new String(original);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static void main(String[] args) {
        String key = "Bar12345Bar12345"; // 128 bit key
        String initVector = "RandomInitVector"; // 16 bytes IV

        System.out.println(decrypt(key, initVector,
                encrypt(key, initVector, "Hello World")));
    }
}
Chand Priyankara
quelle
47
Wenn Sie nicht von der Apache Commons Codec-Bibliothek eines Drittanbieters abhängig sein möchten, können Sie JDKs javax.xml.bind.DatatypeConverter verwenden , um die Base64-Codierung / -Decodierung durchzuführen: System.out.println("encrypted string:" + DatatypeConverter.printBase64Binary(encrypted)); byte[] original = cipher.doFinal(DatatypeConverter.parseBase64Binary(encrypted));
curd0
8
Verwenden Sie eine konstante IV?!
vianna77
36
Java 8 hat bereits Base64-Tools: java.util.Base64.getDecoder () und java.util.Base64.getEncoder ()
Hristo Stoyanov
11
Die IV muss nicht geheim sein, aber für den CBC-Modus unvorhersehbar (und für die Klickrate eindeutig). Es kann zusammen mit dem Chiffretext gesendet werden. Ein üblicher Weg, dies zu tun, besteht darin, dem Chiffretext die IV voranzustellen und ihn vor der Entschlüsselung abzuschneiden. Es sollte generiert werden durchSecureRandom
Artjom B.
6
Ein Passwort ist kein Schlüssel. Eine IV sollte zufällig sein.
Maarten Bodewes
40

Hier ist eine Lösung ohne Apache Commons Codec‚s Base64:

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

public class AdvancedEncryptionStandard
{
    private byte[] key;

    private static final String ALGORITHM = "AES";

    public AdvancedEncryptionStandard(byte[] key)
    {
        this.key = key;
    }

    /**
     * Encrypts the given plain text
     *
     * @param plainText The plain text to encrypt
     */
    public byte[] encrypt(byte[] plainText) throws Exception
    {
        SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);

        return cipher.doFinal(plainText);
    }

    /**
     * Decrypts the given byte array
     *
     * @param cipherText The data to decrypt
     */
    public byte[] decrypt(byte[] cipherText) throws Exception
    {
        SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, secretKey);

        return cipher.doFinal(cipherText);
    }
}

Anwendungsbeispiel:

byte[] encryptionKey = "MZygpewJsCpRrfOr".getBytes(StandardCharsets.UTF_8);
byte[] plainText = "Hello world!".getBytes(StandardCharsets.UTF_8);
AdvancedEncryptionStandard advancedEncryptionStandard = new AdvancedEncryptionStandard(
        encryptionKey);
byte[] cipherText = advancedEncryptionStandard.encrypt(plainText);
byte[] decryptedCipherText = advancedEncryptionStandard.decrypt(cipherText);

System.out.println(new String(plainText));
System.out.println(new String(cipherText));
System.out.println(new String(decryptedCipherText));

Drucke:

Hello world!
դ;��LA+�ߙb*
Hello world!
BullyWiiPlaza
quelle
5
Dies ist ein perfekt funktionierendes Beispiel, genau wie bei @ chandpriyankara. Aber warum eine Signatur definieren encrypt(String)und nicht encrypt(byte[] )? Die Verschlüsselung (auch die Entschlüsselung) ist ein bytebasierter Prozess (AES ist es sowieso). Die Verschlüsselung verwendet Bytes als Eingabe und gibt Bytes aus, ebenso die Entschlüsselung (Beispiel: Das CipherObjekt tut dies). Ein besonderer Anwendungsfall kann nun darin bestehen, verschlüsselte Bytes aus einem String zu haben oder als String gesendet zu werden (base64-MIME-Anhang für eine Mail ...). Dies ist jedoch ein Problem beim Codieren von Bytes, für das Hunderte von Bytes vorhanden sind Lösungen, die völlig unabhängig von AES / Verschlüsselung sind.
GPI
3
@GPI: Ja, aber ich finde es nützlicher, Stringsda ich in 95% der Fälle damit arbeite und Sie trotzdem konvertieren.
BullyWiiPlaza
9
Nein, dies entspricht nicht dem Code von chandpriyankara! Ihr Code verwendet die EZB, die im Allgemeinen unsicher ist und nicht gewünscht wird. Sollte CBC explizit angeben. Wenn CBC angegeben wird, wird Ihr Code beschädigt.
Dan
Perfekt funktionsfähig, absolut unsicher und mit sehr schlechten Programmierpraktiken. Die Klasse ist schlecht benannt. Die Schlüsselgröße wird nicht im Voraus überprüft. Am wichtigsten ist jedoch, dass der Code den unsicheren EZB-Modus verwendet und das Problem in der ursprünglichen Frage verbirgt . Schließlich wird keine Zeichenkodierung angegeben, was bedeutet, dass die Dekodierung in Text auf anderen Plattformen möglicherweise fehlschlägt.
Maarten Bodewes
24

Sieht für mich so aus, als würden Sie mit Ihrem Initialisierungsvektor (IV) nicht richtig umgehen. Es ist lange her, dass ich das letzte Mal über AES, IVs und Blockverkettung gelesen habe, aber über Ihre Linie

IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());

scheint nicht in Ordnung zu sein. Im Fall von AES können Sie sich den Initialisierungsvektor als den "Anfangszustand" einer Verschlüsselungsinstanz vorstellen, und dieser Zustand ist eine Information, die Sie nicht von Ihrem Schlüssel erhalten können, sondern von der tatsächlichen Berechnung der Verschlüsselungsverschlüsselung. (Man könnte argumentieren, dass wenn die IV aus dem Schlüssel extrahiert werden könnte, dies keinen Nutzen hätte, da der Schlüssel der Verschlüsselungsinstanz bereits während ihrer Init-Phase übergeben wird.)

Daher sollten Sie die IV am Ende Ihrer Verschlüsselung als Byte [] von der Verschlüsselungsinstanz abrufen

  cipherOutputStream.close();
  byte[] iv = encryptCipher.getIV();

und Sie sollten Ihr CipherIn DECRYPT_MODEmit diesem Byte [] initialisieren :

  IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);

Dann sollte Ihre Entschlüsselung in Ordnung sein. Hoffe das hilft.

GPI
quelle
Danke, dass du einem Neuling geholfen hast. Ich habe dieses Beispiel aus anderen Posts gepflastert. Ich nehme nicht an, dass Sie wissen, wie Sie die Notwendigkeit einer Infusion vermeiden können? Ich habe andere AES-Beispiele gesehen, aber nicht ausprobiert, die sie nicht verwenden.
TedTrippin
Ignoriere das, ich habe die Antwort gefunden! Ich muss AES / ECB / PKCS5Padding verwenden.
TedTrippin
20
Meistens möchten Sie die EZB nicht verwenden. Google einfach warum.
João Fernandes
2
@Mushy: stimmte zu, dass es besser ist, eine IV aus einer vertrauenswürdigen Zufallsquelle auszuwählen und explizit festzulegen, als nur die Cihper-Instanz eine auswählen zu lassen. Andererseits behebt diese Antwort das ursprüngliche Problem der Verwirrung des Initialisierungsvektors für den Schlüssel. Deshalb wurde es zunächst positiv bewertet. Jetzt ist dieser Beitrag eher zu einer Anlaufstelle für Beispielcodes geworden, und die Leute hier haben ein großartiges Beispiel gemacht - genau neben dem, worum es bei der ursprünglichen Frage ging.
GPI
3
@ GPI Upvoted. Die anderen "großartigen Beispiele" sind nicht so großartig, und sie sprechen die Frage überhaupt nicht an. Stattdessen scheint dies der Ort für Neulinge gewesen zu sein, um kryptografische Proben blind zu kopieren, ohne zu verstehen, dass möglicherweise Sicherheitsprobleme vorliegen - und wie immer auch.
Maarten Bodewes
17

Die IV, die Sie zur Entschlüsselung verwenden, ist falsch. Ersetzen Sie diesen Code

//Decrypt cipher
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);

Mit diesem Code

//Decrypt cipher
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(encryptCipher.getIV());
decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);

Und das sollte Ihr Problem lösen.


Unten finden Sie ein Beispiel für eine einfache AES-Klasse in Java. Ich empfehle nicht, diese Klasse in Produktionsumgebungen zu verwenden, da sie möglicherweise nicht alle spezifischen Anforderungen Ihrer Anwendung berücksichtigt.

import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class AES 
{
    public static byte[] encrypt(final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
    {       
        return AES.transform(Cipher.ENCRYPT_MODE, keyBytes, ivBytes, messageBytes);
    }

    public static byte[] decrypt(final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
    {       
        return AES.transform(Cipher.DECRYPT_MODE, keyBytes, ivBytes, messageBytes);
    }

    private static byte[] transform(final int mode, final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
    {
        final SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
        final IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
        byte[] transformedBytes = null;

        try
        {
            final Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");

            cipher.init(mode, keySpec, ivSpec);

            transformedBytes = cipher.doFinal(messageBytes);
        }        
        catch (NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e) 
        {
            e.printStackTrace();
        }
        return transformedBytes;
    }

    public static void main(final String[] args) throws InvalidKeyException, InvalidAlgorithmParameterException
    {
        //Retrieved from a protected local file.
        //Do not hard-code and do not version control.
        final String base64Key = "ABEiM0RVZneImaq7zN3u/w==";

        //Retrieved from a protected database.
        //Do not hard-code and do not version control.
        final String shadowEntry = "AAECAwQFBgcICQoLDA0ODw==:ZtrkahwcMzTu7e/WuJ3AZmF09DE=";

        //Extract the iv and the ciphertext from the shadow entry.
        final String[] shadowData = shadowEntry.split(":");        
        final String base64Iv = shadowData[0];
        final String base64Ciphertext = shadowData[1];

        //Convert to raw bytes.
        final byte[] keyBytes = Base64.getDecoder().decode(base64Key);
        final byte[] ivBytes = Base64.getDecoder().decode(base64Iv);
        final byte[] encryptedBytes = Base64.getDecoder().decode(base64Ciphertext);

        //Decrypt data and do something with it.
        final byte[] decryptedBytes = AES.decrypt(keyBytes, ivBytes, encryptedBytes);

        //Use non-blocking SecureRandom implementation for the new IV.
        final SecureRandom secureRandom = new SecureRandom();

        //Generate a new IV.
        secureRandom.nextBytes(ivBytes);

        //At this point instead of printing to the screen, 
        //one should replace the old shadow entry with the new one.
        System.out.println("Old Shadow Entry      = " + shadowEntry);
        System.out.println("Decrytped Shadow Data = " + new String(decryptedBytes, StandardCharsets.UTF_8));
        System.out.println("New Shadow Entry      = " + Base64.getEncoder().encodeToString(ivBytes) + ":" + Base64.getEncoder().encodeToString(AES.encrypt(keyBytes, ivBytes, decryptedBytes)));
    }
}

Beachten Sie, dass AES nichts mit Codierung zu tun hat, weshalb ich mich dafür entschieden habe, es separat und ohne die Notwendigkeit von Bibliotheken von Drittanbietern zu behandeln.

k170
quelle
Zunächst einmal haben Sie die ursprüngliche Frage nicht beantwortet. Zweitens, warum beantworten Sie eine bereits beantwortete, gut akzeptierte Frage? Ich dachte, der Schutz sollte diesen Spam stoppen.
TedTrippin
14
Wie die akzeptierte Antwort habe ich mich entschieden, Ihre Frage anhand eines Beispiels zu beantworten. Ich habe einen voll funktionsfähigen Code bereitgestellt, der Ihnen unter anderem zeigt, wie Sie mit dem Initialisierungsvektor richtig umgehen. Bei Ihrer zweiten Frage war ich der Meinung, dass eine aktualisierte Antwort erforderlich ist, da der Apache-Codec nicht mehr erforderlich ist. Also nein, das ist kein Spam. Hör auf zu trippin.
k170
7
Eine IV hat einen bestimmten Zweck, nämlich den Chiffretext zu randomisieren und semantische Sicherheit zu bieten. Wenn Sie dasselbe Schlüssel + IV-Paar verwenden, können Angreifer feststellen, ob Sie eine Nachricht mit demselben Präfix wie zuvor gesendet haben. Die IV muss nicht geheim sein, aber sie muss unvorhersehbar sein. Ein üblicher Weg besteht darin, dem Chiffretext einfach die IV voranzustellen und ihn vor der Entschlüsselung abzuschneiden.
Artjom B.
4
Downvote: Hardcoded IV, siehe Artjom B. Kommentar oben, warum es schlecht ist
Murmel
1
Der CTR-Modus sollte mit NoPadding gekoppelt werden. CTR - Modus ist sicherlich nicht statt CBC erforderlich (es sei denn , padding Orakeln gelten), aber wenn CTR wird verwendet, verwenden "/NoPadding". CTR ist ein Modus, der AES in einer Stream-Verschlüsselung aktiviert, und eine Stream-Verschlüsselung verarbeitet Bytes anstelle von Blöcken.
Maarten Bodewes
16

In dieser Antwort gehe ich auf das Hauptthema "Einfaches Java AES-Verschlüsselungs- / Entschlüsselungsbeispiel" und nicht auf die spezifische Debugging-Frage ein, da ich denke, dass dies den meisten Lesern zugute kommt.

Dies ist eine einfache Zusammenfassung meines Blogposts über AES-Verschlüsselung in Java. Ich empfehle daher, ihn vor der Implementierung durchzulesen. Ich werde jedoch weiterhin ein einfaches Beispiel zur Verfügung stellen und einige Hinweise geben, worauf zu achten ist.

In diesem Beispiel werde ich die authentifizierte Verschlüsselung im Galois / Counter-Modus oder im GCM- Modus verwenden. Der Grund dafür ist, dass Sie in den meisten Fällen Integrität und Authentizität in Kombination mit Vertraulichkeit wünschen (lesen Sie mehr im Blog ).

AES-GCM-Tutorial zur Verschlüsselung / Entschlüsselung

Hier sind die Schritte zum Ver- / Entschlüsseln mit AES-GCM mit der Java Cryptography Architecture (JCA) . Nicht mit anderen Beispielen mischen , da subtile Unterschiede Ihren Code völlig unsicher machen können.

1. Schlüssel erstellen

Da es von Ihrem Anwendungsfall abhängt, gehe ich vom einfachsten Fall aus: einem zufälligen geheimen Schlüssel.

SecureRandom secureRandom = new SecureRandom();
byte[] key = new byte[16];
secureRandom.nextBytes(key);
SecretKey secretKey = SecretKeySpec(key, "AES");

Wichtig:

2. Erstellen Sie den Initialisierungsvektor

Ein Initialisierungsvektor (IV) wird verwendet, so dass derselbe geheime Schlüssel unterschiedliche Chiffretexte erzeugt .

byte[] iv = new byte[12]; //NEVER REUSE THIS IV WITH SAME KEY
secureRandom.nextBytes(iv);

Wichtig:

3. Mit IV und Schlüssel verschlüsseln

final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv); //128 bit auth tag length
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
byte[] cipherText = cipher.doFinal(plainText);

Wichtig:

  • Verwenden Sie ein 16-Byte / 128-Bit- Authentifizierungs-Tag (zur Überprüfung der Integrität / Authentizität).
  • Das Authentifizierungs-Tag wird automatisch an den Chiffretext angehängt (in der JCA-Implementierung).
  • Da sich GCM wie eine Stream-Verschlüsselung verhält, ist kein Auffüllen erforderlich
  • Verwenden Sie diese CipherInputStreamOption, wenn Sie große Datenmengen verschlüsseln
  • Möchten Sie, dass zusätzliche (nicht geheime) Daten überprüft werden, wenn sie geändert wurden? Möglicherweise möchten Sie hier die zugehörigen Daten mit cipher.updateAAD(associatedData); More verwenden.

3. Serialisieren Sie zu einer einzelnen Nachricht

Fügen Sie einfach IV und Chiffretext hinzu. Wie oben erwähnt, muss die IV nicht geheim sein.

ByteBuffer byteBuffer = ByteBuffer.allocate(iv.length + cipherText.length);
byteBuffer.put(iv);
byteBuffer.put(cipherText);
byte[] cipherMessage = byteBuffer.array();

Optional mit Base64 codieren, wenn Sie eine Zeichenfolgendarstellung benötigen. Verwenden Sie entweder die integrierte Implementierung von Android oder Java 8 (verwenden Sie nicht den Apache Commons Codec - es ist eine schreckliche Implementierung). Die Codierung wird verwendet, um Byte-Arrays in eine Zeichenfolgendarstellung zu "konvertieren", um sie ASCII-sicher zu machen, z.

String base64CipherMessage = Base64.getEncoder().encodeToString(cipherMessage);

4. Entschlüsselung vorbereiten: Deserialisieren

Wenn Sie die Nachricht codiert haben, dekodieren Sie sie zuerst in ein Byte-Array:

byte[] cipherMessage = Base64.getDecoder().decode(base64CipherMessage)

Wichtig:

5. Entschlüsseln

Initialisieren Sie die Verschlüsselung und stellen Sie die gleichen Parameter wie bei der Verschlüsselung ein:

final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
//use first 12 bytes for iv
AlgorithmParameterSpec gcmIv = new GCMParameterSpec(128, cipherMessage, 0, 12);
cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmIv);
//use everything from 12 bytes on as ciphertext
byte[] plainText = cipher.doFinal(cipherMessage, 12, cipherMessage.length - 12);

Wichtig:

  • vergessen Sie nicht , hinzuzufügen zugehörige Daten mit , cipher.updateAAD(associatedData);wenn Sie es während der Verschlüsselung hinzugefügt.

Ein funktionierendes Code-Snippet finden Sie in dieser Übersicht.


Beachten Sie, dass die neuesten Android- (SDK 21+) und Java- (7+) Implementierungen AES-GCM haben sollten. Ältere Versionen können es fehlen. Ich wähle immer noch diesen Modus, da er einfacher zu implementieren ist und nicht nur effizienter als der ähnliche Modus von Encrypt-then-Mac (z. B. AES-CBC + HMAC ). In diesem Artikel erfahren Sie, wie Sie AES-CBC mit HMAC implementieren .

Patrick Favre
quelle
Das Problem ist, dass das Nachfragen nach Beispielen in SO explizit nicht zum Thema gehört. Und das größere Problem ist, dass dies nicht überprüfte Codeteile sind, die schwer zu validieren sind. Ich schätze die Mühe, aber ich denke nicht, dass SO der richtige Ort dafür sein sollte.
Maarten Bodewes
1
Ich bewundere die Bemühungen jedoch, daher möchte ich nur auf einen einzigen Fehler hinweisen: "Die iv muss in Kombination mit der Einzigartigkeit unvorhersehbar sein (dh zufällige iv verwenden)" - dies gilt für den CBC-Modus, nicht jedoch für GCM.
Maarten Bodewes
this is true for CBC mode but not for GCMmeinst du den ganzen teil oder nur muss er eigentlich nicht unvorhersehbar sein?
Patrick Favre
1
"Wenn Sie das Thema nicht verstehen, sollten Sie wahrscheinlich überhaupt keine Grundelemente auf niedriger Ebene verwenden." Sicher, dass dies der Fall sein sollte, tun es viele Entwickler immer noch. Ich bin mir nicht sicher, ob ich es unterlassen soll, qualitativ hochwertige Inhalte in Bezug auf Sicherheit / Kryptografie an Orten zu veröffentlichen, an denen oft nicht viel vorhanden ist. Dies ist die richtige Lösung dafür. - Danke, dass Sie auf meinen Fehler hingewiesen haben
Patrick Favre
1
OK, nur weil mir die Antwort auf den Inhalt gefällt (und nicht auf den Zweck): Die IV-Behandlung kann insbesondere während der Entschlüsselung vereinfacht werden: Java macht es schließlich einfach, eine IV direkt aus einem vorhandenen Byte-Array zu erstellen. Gleiches gilt für die Entschlüsselung, die nicht bei Offset 0 beginnen muss. Das Kopieren ist einfach nicht erforderlich. Auch wenn Sie eine Länge für die IV senden müssen (oder?), Warum dann nicht ein einzelnes (vorzeichenloses) Byte verwenden - Sie werden für die IV nicht über 255 Bytes hinausgehen, oder?
Maarten Bodewes
2

Online Editor Ausführbare Version: -

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
//import org.apache.commons.codec.binary.Base64;
import java.util.Base64;

public class Encryptor {
    public static String encrypt(String key, String initVector, String value) {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));

            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);

            byte[] encrypted = cipher.doFinal(value.getBytes());

            //System.out.println("encrypted string: "
              //      + Base64.encodeBase64String(encrypted));

            //return Base64.encodeBase64String(encrypted);
            String s = new String(Base64.getEncoder().encode(encrypted));
            return s;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    public static String decrypt(String key, String initVector, String encrypted) {
        try {
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);

            byte[] original = cipher.doFinal(Base64.getDecoder().decode(encrypted));

            return new String(original);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static void main(String[] args) {
        String key = "Bar12345Bar12345"; // 128 bit key
        String initVector = "RandomInitVector"; // 16 bytes IV

        System.out.println(encrypt(key, initVector, "Hello World"));
        System.out.println(decrypt(key, initVector, encrypt(key, initVector, "Hello World")));
    }
}
Bhupesh Pant
quelle
Cool, froh, dass es geholfen hat!
Bhupesh Pant
Ein Passwort ist kein Schlüssel, eine IV sollte nicht statisch sein. Immer noch streng getippter Code, der es unmöglich macht, den Schlüssel zu zerstören. Kein Hinweis darauf, was mit der IV zu tun ist, noch eine Vorstellung davon, dass sie unvorhersehbar sein sollte.
Maarten Bodewes
1

Es ist oft die gute Idee, sich auf eine von der Standardbibliothek bereitgestellte Lösung zu verlassen:

private static void stackOverflow15554296()
    throws
        NoSuchAlgorithmException, NoSuchPaddingException,        
        InvalidKeyException, IllegalBlockSizeException,
        BadPaddingException
{

    // prepare key
    KeyGenerator keygen = KeyGenerator.getInstance("AES");
    SecretKey aesKey = keygen.generateKey();
    String aesKeyForFutureUse = Base64.getEncoder().encodeToString(
            aesKey.getEncoded()
    );

    // cipher engine
    Cipher aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");

    // cipher input
    aesCipher.init(Cipher.ENCRYPT_MODE, aesKey);
    byte[] clearTextBuff = "Text to encode".getBytes();
    byte[] cipherTextBuff = aesCipher.doFinal(clearTextBuff);

    // recreate key
    byte[] aesKeyBuff = Base64.getDecoder().decode(aesKeyForFutureUse);
    SecretKey aesDecryptKey = new SecretKeySpec(aesKeyBuff, "AES");

    // decipher input
    aesCipher.init(Cipher.DECRYPT_MODE, aesDecryptKey);
    byte[] decipheredBuff = aesCipher.doFinal(cipherTextBuff);
    System.out.println(new String(decipheredBuff));
}

Dies druckt "Zu codierender Text".

Die Lösung basiert auf dem Java Cryptography Architecture Reference Guide und der Antwort https://stackoverflow.com/a/20591539/146745 .

andrej
quelle
5
Verwenden Sie niemals den EZB-Modus. Zeitraum.
Konstantino Sparakis
1
Die EZB sollte nicht verwendet werden, wenn mehr als ein Datenblock mit demselben Schlüssel verschlüsselt wird. Daher ist der "zu codierende Text" ausreichend. stackoverflow.com/a/1220869/146745
andrej
@AndroidDev Schlüssel wird im Vorbereitungsschlüsselabschnitt erzeugt: aesKey = keygen.generateKey ()
andrej
1

Dies ist eine Verbesserung gegenüber der akzeptierten Antwort.

Änderungen:

(1) Verwenden Sie zufällige IV und stellen Sie sie dem verschlüsselten Text voran

(2) Verwenden von SHA-256 zum Generieren eines Schlüssels aus einer Passphrase

(3) Keine Abhängigkeit von Apache Commons

public static void main(String[] args) throws GeneralSecurityException {
    String plaintext = "Hello world";
    String passphrase = "My passphrase";
    String encrypted = encrypt(passphrase, plaintext);
    String decrypted = decrypt(passphrase, encrypted);
    System.out.println(encrypted);
    System.out.println(decrypted);
}

private static SecretKeySpec getKeySpec(String passphrase) throws NoSuchAlgorithmException {
    MessageDigest digest = MessageDigest.getInstance("SHA-256");
    return new SecretKeySpec(digest.digest(passphrase.getBytes(UTF_8)), "AES");
}

private static Cipher getCipher() throws NoSuchPaddingException, NoSuchAlgorithmException {
    return Cipher.getInstance("AES/CBC/PKCS5PADDING");
}

public static String encrypt(String passphrase, String value) throws GeneralSecurityException {
    byte[] initVector = new byte[16];
    SecureRandom.getInstanceStrong().nextBytes(initVector);
    Cipher cipher = getCipher();
    cipher.init(Cipher.ENCRYPT_MODE, getKeySpec(passphrase), new IvParameterSpec(initVector));
    byte[] encrypted = cipher.doFinal(value.getBytes());
    return DatatypeConverter.printBase64Binary(initVector) +
            DatatypeConverter.printBase64Binary(encrypted);
}

public static String decrypt(String passphrase, String encrypted) throws GeneralSecurityException {
    byte[] initVector = DatatypeConverter.parseBase64Binary(encrypted.substring(0, 24));
    Cipher cipher = getCipher();
    cipher.init(Cipher.DECRYPT_MODE, getKeySpec(passphrase), new IvParameterSpec(initVector));
    byte[] original = cipher.doFinal(DatatypeConverter.parseBase64Binary(encrypted.substring(24)));
    return new String(original);
}
wvdz
quelle
Ein Hash ist immer noch keine kennwortbasierte Schlüsselgenerierungsfunktion / PBKDF. Entweder verwenden Sie einen zufälligen Schlüssel oder Sie verwenden eine PBKDF wie PBKDF2 / Password Based Encryption.
Maarten Bodewes
@ MaartenBodewes Kannst du eine Verbesserung vorschlagen?
wvdz
PBKDF2 ist in Java vorhanden, daher habe ich nur eine vorgeschlagen. OK, ich habe keinen codiert , aber das verlangt meiner Meinung nach etwas zu viel. Es gibt viele Beispiele für passwortbasierte Verschlüsselung.
Maarten Bodewes
@ MaartenBodewes Ich dachte, es könnte eine einfache Lösung sein. Was wären aus Neugier bestimmte Sicherheitslücken, wenn dieser Code unverändert verwendet wird?
wvdz
0

Eine andere Lösung mit java.util.Base64 mit Spring Boot

Verschlüsselungsklasse

package com.jmendoza.springboot.crypto.cipher;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

@Component
public class Encryptor {

    @Value("${security.encryptor.key}")
    private byte[] key;
    @Value("${security.encryptor.algorithm}")
    private String algorithm;

    public String encrypt(String plainText) throws Exception {
        SecretKeySpec secretKey = new SecretKeySpec(key, algorithm);
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);
        return new String(Base64.getEncoder().encode(cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8))));
    }

    public String decrypt(String cipherText) throws Exception {
        SecretKeySpec secretKey = new SecretKeySpec(key, algorithm);
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.DECRYPT_MODE, secretKey);
        return new String(cipher.doFinal(Base64.getDecoder().decode(cipherText)));
    }
}

EncryptorController-Klasse

package com.jmendoza.springboot.crypto.controller;

import com.jmendoza.springboot.crypto.cipher.Encryptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/cipher")
public class EncryptorController {

    @Autowired
    Encryptor encryptor;

    @GetMapping(value = "encrypt/{value}")
    public String encrypt(@PathVariable("value") final String value) throws Exception {
        return encryptor.encrypt(value);
    }

    @GetMapping(value = "decrypt/{value}")
    public String decrypt(@PathVariable("value") final String value) throws Exception {
        return encryptor.decrypt(value);
    }
}

application.properties

server.port=8082
security.encryptor.algorithm=AES
security.encryptor.key=M8jFt46dfJMaiJA0

Beispiel

http: // localhost: 8082 / cipher / encrypt / jmendoza

2h41HH8Shzc4BRU3hVDOXA ==

http: // localhost: 8082 / cipher / decrypt / 2h41HH8Shzc4BRU3hVDOXA ==

jmendoza

Jonathan Mendoza
quelle
-1

Optimierte Version der akzeptierten Antwort.

  • Keine Bibliotheken von Drittanbietern

  • schließt IV in die verschlüsselte Nachricht ein (kann öffentlich sein)

  • Das Passwort kann beliebig lang sein

Code:

import java.io.UnsupportedEncodingException;
import java.security.SecureRandom;
import java.util.Base64;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class Encryptor {
    public static byte[] getRandomInitialVector() {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            SecureRandom randomSecureRandom = SecureRandom.getInstance("SHA1PRNG");
            byte[] initVector = new byte[cipher.getBlockSize()];
            randomSecureRandom.nextBytes(initVector);
            return initVector;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    public static byte[] passwordTo16BitKey(String password) {
        try {
            byte[] srcBytes = password.getBytes("UTF-8");
            byte[] dstBytes = new byte[16];

            if (srcBytes.length == 16) {
                return srcBytes;
            }

            if (srcBytes.length < 16) {
                for (int i = 0; i < dstBytes.length; i++) {
                    dstBytes[i] = (byte) ((srcBytes[i % srcBytes.length]) * (srcBytes[(i + 1) % srcBytes.length]));
                }
            } else if (srcBytes.length > 16) {
                for (int i = 0; i < srcBytes.length; i++) {
                    dstBytes[i % dstBytes.length] += srcBytes[i];
                }
            }

            return dstBytes;
        } catch (UnsupportedEncodingException ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static String encrypt(String key, String value) {
        return encrypt(passwordTo16BitKey(key), value);
    }

    public static String encrypt(byte[] key, String value) {
        try {
            byte[] initVector = Encryptor.getRandomInitialVector();
            IvParameterSpec iv = new IvParameterSpec(initVector);
            SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);

            byte[] encrypted = cipher.doFinal(value.getBytes());
            return Base64.getEncoder().encodeToString(encrypted) + " " + Base64.getEncoder().encodeToString(initVector);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static String decrypt(String key, String encrypted) {
        return decrypt(passwordTo16BitKey(key), encrypted);
    }

    public static String decrypt(byte[] key, String encrypted) {
        try {
            String[] encryptedParts = encrypted.split(" ");
            byte[] initVector = Base64.getDecoder().decode(encryptedParts[1]);
            if (initVector.length != 16) {
                return null;
            }

            IvParameterSpec iv = new IvParameterSpec(initVector);
            SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);

            byte[] original = cipher.doFinal(Base64.getDecoder().decode(encryptedParts[0]));

            return new String(original);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }
}

Verwendung:

String key = "Password of any length.";
String encrypted = Encryptor.encrypt(key, "Hello World");
String decrypted = Encryptor.decrypt(key, encrypted);
System.out.println(encrypted);
System.out.println(decrypted);

Beispielausgabe:

QngBg+Qc5+F8HQsksgfyXg== yDfYiIHTqOOjc0HRNdr1Ng==
Hello World
ue7m
quelle
Ihre Passwortableitungsfunktion ist unsicher. Ich würde nicht e.printStackTrace()in so genannten optimierten Code erwarten .
Maarten Bodewes