it-swarm.com.de

So verschlüsseln Sie String in Java

Was ich brauche, ist eine Zeichenfolge zu verschlüsseln, die im 2D-Barcode (PDF-417) angezeigt wird. Wenn also jemand eine Idee zum Scannen bekommt, erhält er nichts Lesbares. 

Andere Vorraussetzungen: 

  • sollte nicht kompliziert sein
  • es sollte nicht aus RSA, PKI-Infrastruktur, Schlüsselpaaren usw. bestehen.

Es muss einfach genug sein, um die Leute, die herumschnüffeln, loszuwerden, und für andere Unternehmen, die an diesen Daten interessiert sind, leicht zu entschlüsseln. Sie rufen uns an, wir sagen ihnen den Standard oder geben ihnen einen einfachen Schlüssel, der dann zur Entschlüsselung verwendet werden kann. 

Wahrscheinlich könnten diese Unternehmen unterschiedliche Technologien einsetzen, daher wäre es gut, sich an einen Standard zu halten, der nicht an eine spezielle Plattform oder Technologie gebunden ist. 

Was schlagen Sie vor? Gibt es eine Java-Klasse, die encrypt () decrypt () ohne großen Aufwand beim Erreichen hoher Sicherheitsstandards erledigt? 

119
ante.sabo

Warnung

Verwenden Sie dies nicht als eine Art Sicherheitsmaßnahme.

Der Verschlüsselungsmechanismus in diesem Beitrag ist ein One-Time-Pad. Dies bedeutet, dass der geheime Schlüssel von einem Angreifer mithilfe von 2 verschlüsselten Nachrichten problemlos wiederhergestellt werden kann. XOR 2 verschlüsselte Nachrichten und Sie erhalten den Schlüssel. So einfach! 

Darauf weist Moussa hin


Ich verwende Suns Base64Encoder/Decoder, der in Suns JRE zu finden ist, um ein weiteres JAR in lib zu vermeiden. Das ist gefährlich, wenn Sie OpenJDK oder die JRE eines anderen verwenden. Gibt es außerdem einen anderen Grund, warum ich Apache commons lib mit Encoder/Decoder verwenden sollte? 

public class EncryptUtils {
    public static final String DEFAULT_ENCODING = "UTF-8"; 
    static BASE64Encoder enc = new BASE64Encoder();
    static BASE64Decoder dec = new BASE64Decoder();

    public static String base64encode(String text) {
        try {
            return enc.encode(text.getBytes(DEFAULT_ENCODING));
        } catch (UnsupportedEncodingException e) {
            return null;
        }
    }//base64encode

    public static String base64decode(String text) {
        try {
            return new String(dec.decodeBuffer(text), DEFAULT_ENCODING);
        } catch (IOException e) {
            return null;
        }
    }//base64decode

    public static void main(String[] args) {
        String txt = "some text to be encrypted";
        String key = "key phrase used for XOR-ing";
        System.out.println(txt + " XOR-ed to: " + (txt = xorMessage(txt, key)));

        String encoded = base64encode(txt);       
        System.out.println(" is encoded to: " + encoded + " and that is decoding to: " + (txt = base64decode(encoded)));
        System.out.print("XOR-ing back to original: " + xorMessage(txt, key));
    }

    public static String xorMessage(String message, String key) {
        try {
            if (message == null || key == null) return null;

            char[] keys = key.toCharArray();
            char[] mesg = message.toCharArray();

            int ml = mesg.length;
            int kl = keys.length;
            char[] newmsg = new char[ml];

            for (int i = 0; i < ml; i++) {
                newmsg[i] = (char)(mesg[i] ^ keys[i % kl]);
            }//for i

            return new String(newmsg);
        } catch (Exception e) {
            return null;
        }
    }//xorMessage
}//class
20
ante.sabo

Ich würde empfehlen, einige standardmäßige symmetrische Chiffren zu verwenden, die allgemein verfügbar sind, wie DES , 3DES oder ES . Dies ist zwar nicht der sicherste Algorithmus, aber es gibt unzählige Implementierungen, und Sie müssen nur den Schlüssel an jeden weitergeben, der die Informationen im Barcode entschlüsseln soll. javax.crypto.Cipher ist das, womit Sie hier arbeiten möchten.

Nehmen wir an, die zu verschlüsselnden Bytes sind in.

byte[] input;

Als nächstes benötigen Sie den Schlüssel und Initialisierungsvektor Bytes

byte[] keyBytes;
byte[] ivBytes;

Jetzt können Sie die Chiffre für den ausgewählten Algorithmus initialisieren:

// wrap key data in Key/IV specs to pass to cipher
SecretKeySpec key = new SecretKeySpec(keyBytes, "DES");
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
// create the cipher with the algorithm you choose
// see javadoc for Cipher class for more info, e.g.
Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");

Verschlüsselung würde so gehen:

cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
byte[] encrypted= new byte[cipher.getOutputSize(input.length)];
int enc_len = cipher.update(input, 0, input.length, encrypted, 0);
enc_len += cipher.doFinal(encrypted, enc_len);

Und Entschlüsselung wie folgt:

cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
byte[] decrypted = new byte[cipher.getOutputSize(enc_len)];
int dec_len = cipher.update(encrypted, 0, enc_len, decrypted, 0);
dec_len += cipher.doFinal(decrypted, dec_len);
110
VoidPointer

Dies ist die erste Seite, die über Google angezeigt wird, und die Sicherheitslücken in allen Implementierungen lassen mich zusammenzucken. Daher poste ich diese Seite, um Informationen zur Verschlüsselung für andere hinzuzufügen, wie sie seit 7 Jahren vorliegen. aus dem ursprünglichen Beitrag. Ich habe einen Master-Abschluss in Computertechnik und verbrachte viel Zeit damit, Kryptographie zu studieren und zu lernen, also werfe ich meine zwei Cent, um das Internet sicherer zu machen Ort.

Beachten Sie auch, dass viele Implementierungen für eine bestimmte Situation sicher sind, aber warum diese verwenden und möglicherweise versehentlich einen Fehler machen? Verwenden Sie die stärksten Werkzeuge, die Ihnen zur Verfügung stehen, es sei denn, Sie haben einen bestimmten Grund, dies nicht zu tun. Insgesamt rate ich dringend dazu, eine Bibliothek zu benutzen und mich von den wirklich wichtigen Details fernzuhalten, wenn Sie können.

UPDATE 4/5/18: Ich habe einige Teile umgeschrieben, um sie einfacher zu verstehen und änderte die empfohlene Bibliothek von Jasypt Für Googles neue Bibliothek Tink würde ich empfehlen, Jasypt vollständig aus einem vorhandenen Setup zu entfernen.

Vorwort

Ich werde im Folgenden die Grundlagen der sicheren symmetrischen Kryptografie skizzieren und auf häufige Fehler hinweisen, die ich online sehe, wenn Leute Krypto mit der Standardbibliothek Java selbst implementieren. Wenn Sie einfach alle Details überspringen möchten, gehen Sie zu Googles neuer Bibliothek Tink , importieren Sie diese in Ihr Projekt und verwenden Sie den AES-GCM-Modus für alle Ihre Verschlüsselungen, und Sie sind sicher.

Wenn Sie nun die Details zum Verschlüsseln in Java kennenlernen möchten, lesen Sie weiter :)

Block Chiffren

Als erstes müssen Sie einen symmetrischen Schlüssel Block Cipher auswählen. Eine Blockverschlüsselung ist eine Computerfunktion/ein Computerprogramm, mit der Pseudozufälligkeit erzeugt wird. Pseudozufälligkeit ist eine gefälschte Zufälligkeit, die von keinem anderen Computer als einem Quantencomputer als Unterschied zur tatsächlichen Zufälligkeit erkannt werden kann. Die Blockverschlüsselung ist wie ein Baustein für die Kryptografie, und wenn sie mit verschiedenen Modi oder Schemata verwendet wird, können wir Verschlüsselungen erstellen.

In Bezug auf die heute verfügbaren Blockchiffrieralgorithmen stelle ich sicher, dass [~ # ~] nie [~ # ~] gilt. Ich wiederhole [~ # ~] verwende niemals [~ # ~] [~ # ~] des [~ # ~] , ich würde sogar Sagen Sie NIEMALS 3DES . Die einzige Block-Chiffre, die selbst Snowdens NSA Veröffentlichung bestätigen konnte, dass sie wirklich so nahe wie möglich an Pseudo-Random ist, ist AES 256 . Es gibt auch AES 128, der Unterschied ist, dass AES 256 in 256-Bit-Blöcken arbeitet, während AES 128 in 128 Blöcken arbeitet. Alles in allem gilt AES 128 als sicher, obwohl einige Schwachstellen entdeckt wurden, aber 256 ist so solide wie es nur geht.

Fun fact [~ # ~] des [~ # ~] wurde von der NSA damals, als es gegründet wurde, gebrochen und tatsächlich ein paar Jahre lang geheim gehalten und Obwohl einige Leute immer noch behaupten , dass 3DES sicher ist, gibt es eine ganze Reihe von Forschungsarbeiten, die Schwachstellen in 3DES gefunden und analysiert haben.

Verschlüsselungsmodi

Die Verschlüsselung wird erstellt, wenn Sie eine Blockchiffre verwenden und ein bestimmtes Schema verwenden, sodass die Zufälligkeit mit einem Schlüssel kombiniert wird, um etwas zu erstellen, das reversibel ist, solange Sie den Schlüssel kennen. Dies wird als Verschlüsselungsmodus bezeichnet.

Hier ist ein Beispiel für einen Verschlüsselungsmodus und den einfachsten als EZB bekannten Modus, damit Sie visuell nachvollziehen können, was passiert:

ECB Mode

Folgende Verschlüsselungsmodi werden am häufigsten online angezeigt:

EZB CTR, CBC, GCM

Es gibt andere Modi als die aufgelisteten und die Forscher arbeiten immer an neuen Modi, um bestehende Probleme zu verbessern.

Kommen wir nun zu den Implementierungen und dem, was sicher ist. [~ # ~] niemals [~ # ~] EZB verwenden, dies ist schlecht, um sich wiederholende Daten zu verbergen, wie es die berühmte zeigt Linux-Pinguin . Linux Penguin Example

Beachten Sie bei der Implementierung in Java, dass der EZB-Modus standardmäßig eingestellt ist, wenn Sie den folgenden Code verwenden:

Cipher cipher = Cipher.getInstance("AES");

... GEFAHR DIESES IS IST EINE GEFÄHRLICHKEIT! Und leider ist dies in StackOverflow und online in Tutorials und Beispielen zu sehen.

Noncen und IVs

Als Antwort auf das Problem im EZB-Modus wurden Substantive, auch als IVs bezeichnet, erstellt. Die Idee ist, dass wir eine neue Zufallsvariable generieren und diese an jede Verschlüsselung anhängen, sodass beim Verschlüsseln von zwei identischen Nachrichten unterschiedliche Ergebnisse erzielt werden. Das Schöne daran ist, dass eine IV oder Nonce allgemein bekannt ist. Das bedeutet, dass ein Angreifer Zugriff darauf haben kann, aber solange er nicht über Ihren Schlüssel verfügt, kann er mit diesem Wissen nichts anfangen.

Häufige Probleme, die ich sehen werde, sind, dass Benutzer die IV als statischen Wert festlegen, der dem gleichen festen Wert in ihrem Code entspricht. Und hier ist die Gefahr für IVs, dass Sie, sobald Sie eine wiederholen, die gesamte Sicherheit Ihrer Verschlüsselung gefährden.

Erzeugen einer zufälligen IV

SecureRandom randomSecureRandom = SecureRandom.getInstance("SHA1PRNG");
byte[] iv = new byte[cipher.getBlockSize()];
randomSecureRandom.nextBytes(iv);
IvParameterSpec ivParams = new IvParameterSpec(iv);

Hinweis: SHA1 ist defekt, aber ich konnte SHA256 nicht richtig in diesen Anwendungsfall implementieren Update wäre super! Auch SHA1-Angriffe sind nach wie vor unkonventionell, da das Knacken eines riesigen Clusters einige Jahre dauern kann. Details finden Sie hier.

CTR-Implementierung

Für den CTR-Modus ist kein Auffüllen erforderlich.

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

CBC-Implementierung

Wenn Sie den CBC-Modus implementieren möchten, gehen Sie wie folgt mit PKCS7Padding vor:

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

Sicherheitsanfälligkeit in Bezug auf CBC und CTR und warum Sie GCM verwenden sollten

Obwohl einige andere Modi wie CBC und CTR sicher sind, kann ein Angreifer die verschlüsselten Daten umdrehen und ihren Wert beim Entschlüsseln ändern. Angenommen, Sie verschlüsseln eine imaginäre Banknachricht "Sell 100". Ihre verschlüsselte Nachricht sieht folgendermaßen aus: "eu23ng". Der Angreifer ändert ein Bit in "eu53ng". Wenn er Ihre Nachricht entschlüsselt, lautet sie plötzlich "Sell 900".

Um dies zu vermeiden, verwendet der Großteil des Internets GCM und jedes Mal, wenn Sie HTTPS sehen, wird wahrscheinlich GCM verwendet. GCM signiert die verschlüsselte Nachricht mit einem Hash und überprüft anhand dieser Signatur, ob die Nachricht geändert wurde.

Ich würde die Implementierung von GCM aufgrund seiner Komplexität vermeiden. Sie sind besser dran, wenn Sie Googles neue Bibliothek Tink verwenden, da Sie auch hier, wenn Sie versehentlich eine IV wiederholen, den Schlüssel im Fall von GCM gefährden, was die ultimative Sicherheitslücke darstellt. Neue Forscher arbeiten an IV-resistenten Verschlüsselungsmodi, bei denen der Schlüssel, selbst wenn Sie die IV wiederholen, nicht in Gefahr ist, dies jedoch noch nicht zum Mainstream gehört.

Wenn Sie nun GCM implementieren möchten, finden Sie hier einen Link zu einer Nice GCM-Implementierung . Allerdings kann ich die Sicherheit nicht gewährleisten oder ob sie ordnungsgemäß implementiert ist, aber die Basis wird heruntergefahren. Beachten Sie auch, dass bei GCM keine Polsterung vorhanden ist.

Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

Schlüssel vs Passwörter

Ein weiterer wichtiger Hinweis ist, dass ein Schlüssel und ein Passwort in Bezug auf die Kryptografie nicht dasselbe sind. Ein Schlüssel in der Kryptographie muss ein gewisses Maß an Entropie und Zufälligkeit aufweisen, um als sicher zu gelten. Aus diesem Grund müssen Sie sicherstellen, dass Sie die richtigen kryptografischen Bibliotheken verwenden, um den Schlüssel für Sie zu generieren.

Sie haben also wirklich zwei Implementierungen, die Sie hier ausführen können. Die erste besteht darin, den Code in diesem StackOverflow-Thread für die Zufallsschlüsselgenerierung zu verwenden. Diese Lösung verwendet einen sicheren Zufallszahlengenerator, um einen Schlüssel von Grund auf neu zu erstellen, den Sie verwenden können.

Die andere weniger sichere Option ist die Verwendung von Benutzereingaben wie einem Passwort. Das Problem, das wir besprochen haben, ist, dass das Passwort nicht genügend Entropie hat, daher müssten wir PBKDF2 verwenden, einen Algorithmus, der das Passwort nimmt und es stärkt. Hier ist eine StackOverflow-Implementierung, die mir gefallen hat . In der Google Tink-Bibliothek ist jedoch all dies integriert, und Sie sollten es nutzen.

Android-Entwickler

Ein wichtiger Punkt, auf den Sie hier hinweisen sollten, ist, dass Ihr Android Code Reverse Engineering-fähig ist und in den meisten Fällen auch der meiste Java Code. Das heißt, wenn Sie das Passwort im Klartext in Ihrem Code speichern. Ein Hacker kann es leicht abrufen. Normalerweise möchten Sie für diese Art der Verschlüsselung die asymmetrische Kryptografie usw. verwenden. Dies liegt außerhalb des Geltungsbereichs dieses Beitrags, sodass ich es vermeiden werde, darauf einzugehen.

Eine interessante Lektüre aus dem Jahr 2013 : Weist darauf hin, dass 88% der Crypto-Implementierungen in Android nicht ordnungsgemäß durchgeführt wurden.

Letzte Gedanken

Ich würde noch einmal vorschlagen, die Java - Bibliothek für Crypto nicht direkt zu implementieren und Google Tink zu verwenden, da sie wirklich gute Arbeit bei der Implementierung geleistet haben alle Algorithmen richtig. Und stellen Sie auch dann sicher, dass Sie nach Problemen suchen, die auf dem Tink-Github aufgetreten sind. Hier und da werden Schwachstellen angezeigt.

Wenn Sie Fragen oder Anregungen haben, können Sie diese gerne kommentieren! Die Sicherheit ändert sich ständig und Sie müssen Ihr Bestes geben, um Schritt zu halten :)

108

vielen Dank, dass ich diese Klasse mit Ihrem Code gemacht habe, vielleicht findet sie jemand nützlich

objekt-Crypter 

import Java.io.ByteArrayInputStream;
import Java.io.ByteArrayOutputStream;
import Java.io.IOException;
import Java.io.ObjectInputStream;
import Java.io.ObjectOutputStream;
import Java.security.InvalidAlgorithmParameterException;
import Java.security.InvalidKeyException;
import Java.security.NoSuchAlgorithmException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;


public class ObjectCrypter {

private Cipher deCipher;
private Cipher enCipher;
private SecretKeySpec key;
private IvParameterSpec ivSpec;


public ObjectCrypter(byte[] keyBytes,   byte[] ivBytes) {
    // wrap key data in Key/IV specs to pass to cipher


     ivSpec = new IvParameterSpec(ivBytes);
    // create the cipher with the algorithm you choose
    // see javadoc for Cipher class for more info, e.g.
    try {
         DESKeySpec dkey = new  DESKeySpec(keyBytes);
          key = new SecretKeySpec(dkey.getKey(), "DES");
         deCipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
         enCipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
    } catch (NoSuchAlgorithmException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (InvalidKeyException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}
public byte[] encrypt(Object obj) throws InvalidKeyException, InvalidAlgorithmParameterException, IOException, IllegalBlockSizeException, ShortBufferException, BadPaddingException {
    byte[] input = convertToByteArray(obj);
    enCipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);

    return enCipher.doFinal(input);




//  cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
//  byte[] encypted = new byte[cipher.getOutputSize(input.length)];
//  int enc_len = cipher.update(input, 0, input.length, encypted, 0);
//  enc_len += cipher.doFinal(encypted, enc_len);
//  return encypted;


}
public Object decrypt( byte[]  encrypted) throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, IOException, ClassNotFoundException {
    deCipher.init(Cipher.DECRYPT_MODE, key, ivSpec);

    return convertFromByteArray(deCipher.doFinal(encrypted));

}



private Object convertFromByteArray(byte[] byteObject) throws IOException,
        ClassNotFoundException {
    ByteArrayInputStream bais;

    ObjectInputStream in;
    bais = new ByteArrayInputStream(byteObject);
    in = new ObjectInputStream(bais);
    Object o = in.readObject();
    in.close();
    return o;

}



private byte[] convertToByteArray(Object complexObject) throws IOException {
    ByteArrayOutputStream baos;

    ObjectOutputStream out;

    baos = new ByteArrayOutputStream();

    out = new ObjectOutputStream(baos);

    out.writeObject(complexObject);

    out.close();

    return baos.toByteArray();

}


}
11
sherif

Wie wäre es damit:

private static byte[] xor(final byte[] input, final byte[] secret) {
    final byte[] output = new byte[input.length];
    if (secret.length == 0) {
        throw new IllegalArgumentException("empty security key");
    }
    int spos = 0;
    for (int pos = 0; pos < input.length; ++pos) {
        output[pos] = (byte) (input[pos] ^ secret[spos]);
        ++spos;
        if (spos >= secret.length) {
            spos = 0;
        }
    }
    return output;
}

Funktioniert gut für mich und ist ziemlich kompakt.

6
yegor256

Hier ist meine Implementierung von meta64.com als Spring Singleton. Wenn Sie für jeden Aufruf eine Ciper-Instanz erstellen möchten, funktioniert dies ebenfalls und Sie könnten die "synchronisierten" Aufrufe entfernen.

import Java.security.Key;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

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

@Component
@Scope("singleton")
public class Encryptor {

    @Value("${aeskey}")
    private String keyStr;

    private Key aesKey = null;
    private Cipher cipher = null;

    synchronized private void init() throws Exception {
        if (keyStr == null || keyStr.length() != 16) {
            throw new Exception("bad aes key configured");
        }
        if (aesKey == null) {
            aesKey = new SecretKeySpec(keyStr.getBytes(), "AES");
            cipher = Cipher.getInstance("AES");
        }
    }

    synchronized public String encrypt(String text) throws Exception {
        init();
        cipher.init(Cipher.ENCRYPT_MODE, aesKey);
        return toHexString(cipher.doFinal(text.getBytes()));
    }

    synchronized public String decrypt(String text) throws Exception {
        init();
        cipher.init(Cipher.DECRYPT_MODE, aesKey);
        return new String(cipher.doFinal(toByteArray(text)));
    }

    public static String toHexString(byte[] array) {
        return DatatypeConverter.printHexBinary(array);
    }

    public static byte[] toByteArray(String s) {
        return DatatypeConverter.parseHexBinary(s);
    }

    /*
     * DO NOT DELETE
     * 
     * Use this commented code if you don't like using DatatypeConverter dependency
     */
    // public static String toHexStringOld(byte[] bytes) {
    // StringBuilder sb = new StringBuilder();
    // for (byte b : bytes) {
    // sb.append(String.format("%02X", b));
    // }
    // return sb.toString();
    // }
    //
    // public static byte[] toByteArrayOld(String s) {
    // int len = s.length();
    // byte[] data = new byte[len / 2];
    // for (int i = 0; i < len; i += 2) {
    // data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i +
    // 1), 16));
    // }
    // return data;
    // }
}
4
user2080225

Sie können Jasypt verwenden

Mit Jasypt kann das Verschlüsseln und Überprüfen eines Passworts so einfach sein wie ...

StrongTextEncryptor textEncryptor = new StrongTextEncryptor();
textEncryptor.setPassword(myEncryptionPassword);

Verschlüsselung:

String myEncryptedText = textEncryptor.encrypt(myText);

Entschlüsselung:

String plainText = textEncryptor.decrypt(myEncryptedText);

Gradle:

compile group: 'org.jasypt', name: 'jasypt', version: '1.9.2'

Eigenschaften:

Jasypt bietet Ihnen einfache unidirektionale (bidirektionale) Verschlüsselungstechniken. 

Offene API zur Verwendung mit einem beliebigen JCE-Provider und nicht nur mit dem Standard-Java VM. Jasypt kann problemlos mit bekannten Anbietern wie Bouncy Castle verwendet werden. Mehr erfahren.

Höhere Sicherheit für die Passwörter Ihrer Benutzer. Mehr erfahren.

Unterstützung für binäre Verschlüsselung Jasypt ermöglicht das Digest und die Verschlüsselung von Binärdateien (Byte-Arrays). Verschlüsseln Sie Ihre Objekte oder Dateien bei Bedarf (z. B. zum Versenden über das Netz).

Unterstützung für Nummernverschlüsselung Neben Texten und Binärdateien können hier auch numerische Werte zusammengestellt und verschlüsselt werden (BigInteger und BigDecimal, andere numerische Typen werden bei der Verschlüsselung für Hibernate-Persistenz unterstützt). Mehr erfahren.

Komplett fadensicher.

Unterstützung für das Verschlüsseln von Verschlüsselung/Digester, um eine hohe Leistung in Multiprozessor/Multi-Core-Systemen zu erreichen.

Enthält eine leichtgewichtige ("Lite") Version der Bibliothek für eine bessere Verwaltbarkeit in größenbeschränkenden Umgebungen wie mobilen Plattformen. 

Bietet einfache Verschlüsselungs-Tools ohne Konfiguration für Benutzer, die sich mit der Verschlüsselung noch nicht auskennen, sowie hochgradig konfigurierbare Standard-Verschlüsselungs-Tools für Power-User.

Hibernate 3 und 4 optionale Integration für das Bestehen der Felder Ihrer zugeordneten Entitäten auf verschlüsselte Weise. Die Verschlüsselung von Feldern ist in den Hibernate-Zuordnungsdateien definiert und bleibt für den Rest der Anwendung transparent (nützlich für vertrauliche persönliche Daten, Datenbanken mit vielen lesefähigen Benutzern ...). Verschlüsseln Sie Texte, Binärdateien, Zahlen, Booleans, Datumsangaben ... Weitere Informationen.

Nahtlose Integration in eine Spring-Anwendung mit spezifischen Integrationsfunktionen für Spring 2, Spring 3.0 und Spring 3.1. Alle Fermenter und Verschlüsseler in Jasypt sind so konzipiert, dass sie von Spring einfach verwendet werden können (instanziiert, Abhängigkeit injiziert ...). Da sie Thread-sicher sind, können sie in einer auf Singleton ausgerichteten Umgebung wie Spring ohne Synchronisierungsprobleme verwendet werden. Erfahren Sie mehr: Frühling 2, Frühling 3.0, Frühling 3.1.

Spring Security (ehemals Acegi Security) optionale Integration zur Ausführung der Kennwortverschlüsselung und zum Abgleichen von Aufgaben für das Sicherheitsframework, zur Verbesserung der Sicherheit der Kennwörter Ihrer Benutzer durch die Verwendung sicherer Kennwortverschlüsselungsmechanismen sowie für ein höheres Maß an Konfiguration und Kontrolle. Mehr erfahren.

Bietet erweiterte Funktionen zum Verschlüsseln aller oder eines Teils der Konfigurationsdateien einer Anwendung, einschließlich vertraulicher Informationen wie Datenbankkennwörter. Nahtlose Integration einer verschlüsselten Konfiguration in einfache, Spring-basierte und/oder Hibernate-fähige Anwendungen. Mehr erfahren.

Bietet einfach zu verwendende CLI-Tools (Command Line Interface), mit denen Entwickler ihre verschlüsselten Daten initialisieren und Verschlüsselungs-/Entschlüsselungs-/Digest-Vorgänge in Wartungsaufgaben oder Skripts einschließen können. Mehr erfahren.

Integriert in Apache Wicket für eine zuverlässigere Verschlüsselung von URLs in Ihren sicheren Anwendungen. 

Umfassende Handbücher und Javadoc-Dokumentation, damit Entwickler besser verstehen können, was sie wirklich mit ihren Daten tun.

Robuste Zeichensatz-Unterstützung, die Texte entsprechend dem ursprünglichen Zeichensatz angemessen verschlüsseln und verdauen kann. Komplette Unterstützung für Sprachen wie Japanisch, Koreanisch, Arabisch ... ohne Codierungs- oder Plattformprobleme.

Sehr hohe Konfigurationsfähigkeiten: Der Entwickler kann Tricks implementieren, indem er beispielsweise einen "Verschlüsseler" anweist, beispielsweise einen entfernten HTTPS-Server nach dem Kennwort zu fragen, das für die Verschlüsselung verwendet werden soll. Damit können Sie Ihre Sicherheitsanforderungen erfüllen.

4
user3871754

Dies ist der Verschlüsselungs- und Entschlüsselungscode, den ich gerade in Java 8 geschrieben habe, unter Berücksichtigung der folgenden Punkte. Hoffe, jemand würde das nützlich finden:

  1. Encryption Algorithm: Blockchiffre-AES mit 256-Bit-Schlüssel gilt als sicher genug. Um eine vollständige Nachricht zu verschlüsseln, muss ein Modus ausgewählt werden. Eine authentifizierte Verschlüsselung (die sowohl Vertraulichkeit als auch Integrität bietet) wird empfohlen. GCM, CCM und EAX sind am häufigsten verwendete authentifizierte Verschlüsselungsmodi. GCM wird normalerweise bevorzugt und funktioniert gut in Intel-Architekturen, die dedizierte Anweisungen für GCM enthalten. Alle diese drei Modi sind CTR-basierte (Zähler-basierte) Modi und benötigen daher keine Auffüllung. Daher sind sie nicht anfällig für Angriffe im Zusammenhang mit dem Auffüllen

  2. Für GCM ist ein Initialisierungsvektor (IV) erforderlich. Die IV ist kein Geheimnis. Die einzige Voraussetzung ist, dass es zufällig oder unvorhersehbar sein muss. In Java soll die Klasse SecuredRandom kryptographisch starke Pseudozufallszahlen erzeugen. Der Pseudozufallszahlengenerierungsalgorithmus kann in der getInstance()-Methode angegeben werden. Seit Java 8 wird jedoch empfohlen, die getInstanceStrong()-Methode zu verwenden, die den stärksten Algorithmus verwendet, der von der Provider konfiguriert und bereitgestellt wird.

  3. NIST empfiehlt 96 Bit IV für GCM, um Interoperabilität, Effizienz und Einfachheit des Designs zu fördern

  4. Um zusätzliche Sicherheit zu gewährleisten, wird in der folgenden Implementierung SecureRandom neu erzeugt, nachdem alle 2 ^ 16 Bytes der Erzeugung von Pseudozufallsbytes erzeugt wurden

  5. Der Empfänger muss die IV kennen, um den Chiffretext zu entschlüsseln. Daher muss die IV zusammen mit dem Chiffretext übertragen werden. Bei einigen Implementierungen wird die IV als AD (Associated Data) gesendet. Dies bedeutet, dass das Authentifizierungs-Tag sowohl für den Chiffriertext als auch für die IV berechnet wird. Dies ist jedoch nicht erforderlich. Dem IV kann der Chiffretext einfach vorangestellt werden, da die Authentifizierungstagvalidierung trotzdem fehlschlägt, wenn der IV während der Übertragung aufgrund eines vorsätzlichen Angriffs oder eines Netzwerk-/Dateisystemfehlers geändert wird

  6. Strings sollten nicht zum Speichern der Klartextnachricht oder des Schlüssels verwendet werden, da Strings unveränderlich sind. Daher können wir sie nach der Verwendung nicht löschen. Diese nicht gelöschten Zeichenfolgen verbleiben dann im Speicher und werden möglicherweise in einem Heap-Dump angezeigt. Aus demselben Grund sollte der Client, der diese Ver- oder Entschlüsselungsmethoden aufruft, alle Variablen oder Arrays löschen, die die Nachricht oder den Schlüssel enthalten, nachdem sie nicht mehr benötigt werden. 

  7. Kein Anbieter ist im Code entsprechend den allgemeinen Empfehlungen fest codiert

  8. Zum Übertragen über ein Netzwerk oder einen Speicher sollte der Schlüssel oder der Chiffretext mit der Base64-Codierung codiert werden. Die Details zu Base64 finden Sie hier . Der Java 8-Ansatz sollte befolgt werden

Byte-Arrays können gelöscht werden mit:

Arrays.fill(clearTextMessageByteArray, Byte.MIN_VALUE);

Ab Java 8 gibt es jedoch keine einfache Möglichkeit, SecretKeyspec und SecretKey zu löschen, da die Implementierungen dieser beiden Schnittstellen die Methode destroy() der Schnittstelle Destroyable nicht implementiert zu haben scheinen. Im folgenden Code wird eine separate Methode geschrieben, um die SecretKeySpec und SecretKey mithilfe der Reflektion zu löschen.

Der Schlüssel sollte mit einer der beiden unten genannten Methoden generiert werden.

Beachten Sie, dass Schlüssel Geheimnisse wie Passwörter sind. Im Gegensatz zu Passwörtern, die für den Gebrauch durch Menschen bestimmt sind, sind Schlüssel dazu gedacht, von kryptographischen Algorithmen verwendet zu werden, und sollten daher nur auf die oben genannte Weise generiert werden.

package com.sapbasu.javastudy;

import Java.lang.reflect.Field;
import Java.security.NoSuchAlgorithmException;
import Java.security.SecureRandom;
import Java.util.Arrays;
import Java.util.List;
import Java.util.Objects;
import Java.util.Optional;

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

public class Crypto {

  private static final int AUTH_TAG_SIZE = 128; // bits

  // NIST recommendation: "For IVs, it is recommended that implementations
  // restrict support to the length of 96 bits, to
  // promote interoperability, efficiency, and simplicity of design."
  private static final int IV_LEN = 12; // bytes

  // number of random number bytes generated before re-seeding
  private static final double PRNG_RESEED_INTERVAL = Math.pow(2, 16);

  private static final String ENCRYPT_ALGO = "AES/GCM/NoPadding";

  private static final List<Integer> ALLOWED_KEY_SIZES = Arrays
      .asList(new Integer[] {128, 192, 256}); // bits

  private static SecureRandom prng;

  // Used to keep track of random number bytes generated by PRNG
  // (for the purpose of re-seeding)
  private static int bytesGenerated = 0;

  public byte[] encrypt(byte[] input, SecretKeySpec key) throws Exception {

    Objects.requireNonNull(input, "Input message cannot be null");
    Objects.requireNonNull(key, "key cannot be null");

    if (input.length == 0) {
      throw new IllegalArgumentException("Length of message cannot be 0");
    }

    if (!ALLOWED_KEY_SIZES.contains(key.getEncoded().length * 8)) {
      throw new IllegalArgumentException("Size of key must be 128, 192 or 256");
    }

    Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);

    byte[] iv = getIV(IV_LEN);

    GCMParameterSpec gcmParamSpec = new GCMParameterSpec(AUTH_TAG_SIZE, iv);

    cipher.init(Cipher.ENCRYPT_MODE, key, gcmParamSpec);
    byte[] messageCipher = cipher.doFinal(input);

    // Prepend the IV with the message cipher
    byte[] cipherText = new byte[messageCipher.length + IV_LEN];
    System.arraycopy(iv, 0, cipherText, 0, IV_LEN);
    System.arraycopy(messageCipher, 0, cipherText, IV_LEN,
        messageCipher.length);
    return cipherText;
  }

  public byte[] decrypt(byte[] input, SecretKeySpec key) throws Exception {
    Objects.requireNonNull(input, "Input message cannot be null");
    Objects.requireNonNull(key, "key cannot be null");

    if (input.length == 0) {
      throw new IllegalArgumentException("Input array cannot be empty");
    }

    byte[] iv = new byte[IV_LEN];
    System.arraycopy(input, 0, iv, 0, IV_LEN);

    byte[] messageCipher = new byte[input.length - IV_LEN];
    System.arraycopy(input, IV_LEN, messageCipher, 0, input.length - IV_LEN);

    GCMParameterSpec gcmParamSpec = new GCMParameterSpec(AUTH_TAG_SIZE, iv);

    Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
    cipher.init(Cipher.DECRYPT_MODE, key, gcmParamSpec);

    return cipher.doFinal(messageCipher);
  }

  public byte[] getIV(int bytesNum) {

    if (bytesNum < 1) throw new IllegalArgumentException(
        "Number of bytes must be greater than 0");

    byte[] iv = new byte[bytesNum];

    prng = Optional.ofNullable(prng).orElseGet(() -> {
      try {
        prng = SecureRandom.getInstanceStrong();
      } catch (NoSuchAlgorithmException e) {
        throw new RuntimeException("Wrong algorithm name", e);
      }
      return prng;
    });

    if (bytesGenerated > PRNG_RESEED_INTERVAL || bytesGenerated == 0) {
      prng.setSeed(prng.generateSeed(bytesNum));
      bytesGenerated = 0;
    }

    prng.nextBytes(iv);
    bytesGenerated = bytesGenerated + bytesNum;

    return iv;
  }

  private static void clearSecret(Destroyable key)
      throws IllegalArgumentException, IllegalAccessException,
      NoSuchFieldException, SecurityException {
    Field keyField = key.getClass().getDeclaredField("key");
    keyField.setAccessible(true);
    byte[] encodedKey = (byte[]) keyField.get(key);
    Arrays.fill(encodedKey, Byte.MIN_VALUE);
  }
}

Der Verschlüsselungsschlüssel kann auf zwei Arten generiert werden:

  • Ohne Passwort

    KeyGenerator keyGen = KeyGenerator.getInstance("AES");
    keyGen.init(KEY_LEN, SecureRandom.getInstanceStrong());
    SecretKey secretKey = keyGen.generateKey();
    SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(),
        "AES");
    Crypto.clearSecret(secretKey);
    // After encryption or decryption with key
    Crypto.clearSecret(secretKeySpec);
    
  • Mit Passwort

    SecureRandom random = SecureRandom.getInstanceStrong();
    byte[] salt = new byte[32];
    random.nextBytes(salt);
    PBEKeySpec keySpec = new PBEKeySpec(password, salt, iterations, 
       keyLength);
    SecretKeyFactory keyFactory = 
        SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
    SecretKey secretKey = keyFactory.generateSecret(keySpec);
    SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(),
        "AES");
    Crypto.clearSecret(secretKey);
    // After encryption or decryption with key
    Crypto.clearSecret(secretKeySpec);
    

Update Basierend auf Kommentaren

Wie von @MaartenBodewes ausgeführt, behandelte meine Antwort keine String, wie von der Frage verlangt. Deshalb werde ich versuchen, diese Lücke zu füllen, nur für den Fall, dass jemand auf diese Antwort stößt und sich über den Umgang mit String wundert.

Wie bereits in der Antwort erwähnt, ist der Umgang mit sensiblen Informationen in einer String generell keine gute Idee, da String unveränderlich ist und wir sie daher nach der Verwendung nicht löschen können. Und wie wir wissen, auch wenn eine String keinen starken Bezug hat, rauscht der Garbage Collector nicht sofort davon, um sie vom Heap zu entfernen. Daher befindet sich die Variable String noch für ein unbekanntes Zeitfenster im Speicher, auch wenn sie für das Programm nicht zugänglich ist. Das Problem dabei ist, dass ein Heap-Dump während dieses Zeitraums die sensiblen Informationen enthüllen würde. Daher ist es immer besser, alle vertraulichen Informationen in einem Bytearray oder Char-Array zu behandeln und das Array dann mit 0 zu füllen, wenn der Zweck erfüllt ist.

Wenn wir jedoch trotzdem in einer Situation landen, in der sich die zu verschlüsselnden sensiblen Informationen in einer String befinden, müssen wir sie in ein Byte-Array konvertieren und die oben eingeführten encrypt- und decrypt-Funktionen aufrufen. (Der andere Eingabeschlüssel kann mit dem oben angegebenen Code-Snippet generiert werden.)Eine String kann auf folgende Weise in Bytes umgewandelt werden:.

byte[] inputBytes = inputString.getBytes(StandardCharsets.UTF_8);

Ebenso kann das verschlüsselte Byte-Array wie folgt in einen String umgewandelt werden:.

UTF-16

String encryptedString = new String(encryptedBytes, StandardCharsets.UTF_8);
3
Saptarshi Basu

Ich würde in Betracht ziehen, so etwas wie https://www.bouncycastle.org/ zu verwenden. Es ist eine vorgefertigte Bibliothek, die es Ihnen ermöglicht, beliebige Verschlüsselungen mit einer Reihe verschiedener Verschlüsselungen zu verschlüsseln Wenn Sie die Informationen wirklich schützen möchten, werden Sie durch die Verwendung von Base64 nicht wirklich geschützt.

3
hdost

Hier sind einige Links, die Sie lesen können, was Java unterstützt

Verschlüsseln/Entschlüsseln eines Datenstroms.

Dieses Beispiel zeigt, wie verschlüsseln Sie (mit einem symmetrischen Verschlüsselungsalgorithmus, z. B. AES, Blowfish, RC2, 3DES usw.) eine große Datenmenge. Das Daten werden in Stücken an einen der .__ übergeben. Verschlüsselungsmethoden: EncryptBytes, EncryptString, EncryptBytesENC oder EncryptStringENC. (Der Methodenname Gibt den Typ der Eingabe (String oder Byte-Array) und den Rückgabetyp (Kodierter String oder Byte-Array) an. Die FirstChunk- und LastChunk-Eigenschaften Werden verwendet Geben Sie an, ob ein Block der erste, mittlere oder letzte in einem -Stream ist, der verschlüsselt werden soll. Standardmäßig sind FirstChunk und LastChunk gleich true. Dies bedeutet, dass die Daten ist der gesamte Betrag.

JCERefGuide

Java-Verschlüsselungsbeispiele

2
Markus Lausberg

Hier ist eine Lösung zum Kopieren/Einfügen. Ich empfehle auch, die Antwort von @ Konstantino zu lesen und abzustimmen obwohl sie keinen Code liefert. Der Initialisierungsvektor (IV) ist wie ein Salz - er muss nicht geheim gehalten werden. Ich bin neu bei GCM und anscheinend ist AAD optional und wird nur unter bestimmten Umständen verwendet. Setzen Sie den Schlüssel in die Umgebungsvariable SECRET_KEY_BASE. Verwenden Sie etwas wie KeePass , um ein Kennwort mit 32 Zeichen zu generieren. Diese Lösung ist meiner Ruby-Lösung nachempfunden.

    public static String encrypt(String s) {
        try {
            byte[] input = s.getBytes("UTF-8");
            String keyString = System.getProperty("SECRET_KEY_BASE", System.getenv("SECRET_KEY_BASE"));
            if (keyString == null || keyString.length() == 0) {
                Logger.error(Utils.class, "encrypt()", "$SECRET_KEY_BASE is not set.");
                return null;
            }
            byte[] keyBytes = keyString.getBytes("UTF-8");
            SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
            // generate IV
            SecureRandom secureRandom = SecureRandom.getInstanceStrong();
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            byte[] ivBytes = new byte[cipher.getBlockSize()];
            secureRandom.nextBytes(ivBytes);
            GCMParameterSpec gcmSpec = new GCMParameterSpec(96, ivBytes); // 96 bit tag length
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmSpec);
            // generate AAD
//          byte[] aadBytes = new byte[cipher.getBlockSize()];
//          secureRandom.nextBytes(aadBytes);
//          cipher.updateAAD(aadBytes);
            // encrypt
            byte[] encrypted = cipher.doFinal(input);
            byte[] returnBytes = new byte[ivBytes.length + encrypted.length];
//          byte[] returnBytes = new byte[ivBytes.length + aadBytes.length + encrypted.length];
            System.arraycopy(ivBytes, 0, returnBytes, 0, ivBytes.length);
//          System.arraycopy(aadBytes, 0, returnBytes, ivBytes.length, aadBytes.length);
            System.arraycopy(encrypted, 0, returnBytes, ivBytes.length, encrypted.length);
//          System.arraycopy(encrypted, 0, returnBytes, ivBytes.length+aadBytes.length, encrypted.length);
            String encryptedString = Base64.getEncoder().encodeToString(returnBytes);
            return encryptedString;
        } catch (UnsupportedEncodingException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | 
                InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
            Logger.error(Utils.class, "encrypt()", "Could not encrypt string: " + e.getMessage());
            return null;
        }
    }

    public static String decrypt(String s) {
        if (s == null || s.length() == 0) return "";
        try {
            byte[] encrypted = Base64.getDecoder().decode(s);
            String keyString = System.getProperty("SECRET_KEY_BASE", System.getenv("SECRET_KEY_BASE"));
            if (keyString == null || keyString.length() == 0) {
                Logger.error(Utils.class, "encrypt()", "$SECRET_KEY_BASE is not set.");
                return null;
            }
            byte[] keyBytes = keyString.getBytes("UTF-8");
            SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            byte[] ivBytes = new byte[cipher.getBlockSize()];
            System.arraycopy(encrypted, 0, ivBytes, 0, ivBytes.length);
            GCMParameterSpec gcmSpec = new GCMParameterSpec(96, ivBytes);
            cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmSpec);
//          cipher.updateAAD(encrypted, ivBytes.length, cipher.getBlockSize());
            byte[] decrypted = cipher.doFinal(encrypted, cipher.getBlockSize(), encrypted.length - cipher.getBlockSize());
//          byte[] decrypted = cipher.doFinal(encrypted, cipher.getBlockSize()*2, encrypted.length - cipher.getBlockSize()*2);
            String decryptedString = new String(decrypted, "UTF-8");
            return decryptedString;
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | UnsupportedEncodingException | InvalidKeyException | 
                InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
            Logger.error(Utils.class, "decrypt()", "Could not decrypt string: " + e.getMessage());
            return null;
        }
    }

Hier ist ein Beispiel:

    String s = "This is a test.";
    String enc = Utils.encrypt(s);
    System.out.println(enc);
    // fQHfYjbD+xAuN5XzH2ojk/EWNeKXUrKRSfx8LU+5dpuKkM/pueCMBjKCZw==
    String dec = Utils.decrypt(enc);
    System.out.println(dec);
    // This is a test.
0
Chloe

Hier eine einfache Lösung mit nur Java.* und javax.crypto.* Abhängigkeiten für die Verschlüsselung von Bytes, die vertraulichkeit und Integrität liefern. Es soll bei einem gewählten Klartextangriff für kurze Nachrichten in der Größenordnung von Kilobytes nicht unterscheidbar sein.

Es verwendet AES im Modus GCM ohne Auffüllung. Ein 128-Bit-Schlüssel wird von PBKDF2 mit vielen Iterationen und einem statischen Salt aus dem bereitgestellten Kennwort abgeleitet. Dies stellt sicher, dass das Brute-Forcing-Passwort schwer ist, und verteilt die Entropie über den gesamten Schlüssel.

Ein zufälliger Initialisierungsvektor (IV) wird erzeugt und dem Chiffretext vorangestellt. Außerdem wird das statische Byte 0x01 als erstes Byte als 'Version' vorangestellt.

Die gesamte Nachricht wird in den von AES/GCM generierten Message Authentication Code (MAC) übertragen.

Hier geht es um null Verschlüsselungsklasse für externe Abhängigkeiten, die vertraulichkeit und Integrität bereitstellt:

package ch.n1b.tcrypt.utils;

import Java.nio.charset.StandardCharsets;
import Java.security.InvalidAlgorithmParameterException;
import Java.security.InvalidKeyException;
import Java.security.NoSuchAlgorithmException;
import Java.security.NoSuchProviderException;
import Java.security.SecureRandom;
import Java.security.spec.InvalidKeySpecException;
import Java.security.spec.KeySpec;

import javax.crypto.*;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

/**
 * This class implements AES-GCM symmetric key encryption with a PBKDF2 derived password.
 * It provides confidentiality and integrity of the plaintext.
 *
 * @author Thomas Richner
 * @created 2018-12-07
 */
public class AesGcmCryptor {

    // https://crypto.stackexchange.com/questions/26783/ciphertext-and-tag-size-and-iv-transmission-with-aes-in-gcm-mode
    private static final byte VERSION_BYTE = 0x01;
    private static final int VERSION_BYTE_LENGTH = 1;
    private static final int AES_KEY_BITS_LENGTH = 128;


    // fixed AES-GCM constants
    private static final String GCM_CRYPTO_NAME = "AES/GCM/NoPadding";
    private static final int GCM_IV_BYTES_LENGTH = 12;
    private static final int GCM_TAG_BYTES_LENGTH = 16;

    // can be tweaked, more iterations = more compute intensive to brute-force password
    private static final int PBKDF2_ITERATIONS = 1024;

    // protects against Rainbow tables
    private static final byte[] PBKDF2_SALT = hexStringToByteArray("4d3fe0d71d2abd2828e7a3196ea450d4");

    public String encryptString(char[] password, String plaintext) throws CryptoException {

        byte[] encrypted = null;
        try {
            encrypted = encrypt(password, plaintext.getBytes(StandardCharsets.UTF_8));
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException //
                | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException //
                | InvalidKeySpecException e) {
            throw new CryptoException(e);
        }
        return byteArrayToHexString(encrypted);
    }

    public String decryptString(char[] password, String ciphertext)
            throws CryptoException {

        byte[] ct = hexStringToByteArray(ciphertext);
        byte[] plaintext = null;
        try {
            plaintext = decrypt(password, ct);
        } catch (AEADBadTagException e) {
            throw new CryptoException(e);
        } catch ( //
                NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeySpecException //
                        | InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException //
                        | BadPaddingException e) {
            throw new CryptoException(e);
        }
        return new String(plaintext, StandardCharsets.UTF_8);
    }

    /**
     * Decrypts an AES-GCM encrypted ciphertext and is
     * the reverse operation of {@link AesGcmCryptor#encrypt(char[], byte[])}
     *
     * @param password   passphrase for decryption
     * @param ciphertext encrypted bytes
     * @return plaintext bytes
     * @throws NoSuchPaddingException
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws InvalidKeySpecException
     * @throws InvalidAlgorithmParameterException
     * @throws InvalidKeyException
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     * @throws IllegalArgumentException           if the length or format of the ciphertext is bad
     * @throws CryptoException
     */
    public byte[] decrypt(char[] password, byte[] ciphertext)
            throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException,
            InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {

        // input validation
        if (ciphertext == null) {
            throw new IllegalArgumentException("ciphertext cannot be null");
        }

        if (ciphertext.length <= VERSION_BYTE_LENGTH + GCM_IV_BYTES_LENGTH + GCM_TAG_BYTES_LENGTH) {
            throw new IllegalArgumentException("ciphertext too short");
        }

        // the version must match, we don't decrypt other versions
        if (ciphertext[0] != VERSION_BYTE) {
            throw new IllegalArgumentException("wrong version: " + ciphertext[0]);
        }

        // input seems legit, lets decrypt and check integrity

        // derive key from password
        SecretKey key = deriveAesKey(password, PBKDF2_SALT, AES_KEY_BITS_LENGTH);

        // init cipher
        Cipher cipher = Cipher.getInstance(GCM_CRYPTO_NAME);
        GCMParameterSpec params = new GCMParameterSpec(GCM_TAG_BYTES_LENGTH * 8,
                ciphertext,
                VERSION_BYTE_LENGTH,
                GCM_IV_BYTES_LENGTH
        );
        cipher.init(Cipher.DECRYPT_MODE, key, params);

        final int ciphertextOffset = VERSION_BYTE_LENGTH + GCM_IV_BYTES_LENGTH;

        // add version and IV to MAC
        cipher.updateAAD(ciphertext, 0, ciphertextOffset);

        // decipher and check MAC
        return cipher.doFinal(ciphertext, ciphertextOffset, ciphertext.length - ciphertextOffset);
    }

    /**
     * Encrypts a plaintext with a password.
     * <p>
     * The encryption provides the following security properties:
     * Confidentiality + Integrity
     * <p>
     * This is achieved my using the AES-GCM AEAD blockmode with a randomized IV.
     * <p>
     * The tag is calculated over the version byte, the IV as well as the ciphertext.
     * <p>
     * Finally the encrypted bytes have the following structure:
     * <pre>
     *          +-------------------------------------------------------------------+
     *          |         |               |                             |           |
     *          | version | IV bytes      | ciphertext bytes            |    tag    |
     *          |         |               |                             |           |
     *          +-------------------------------------------------------------------+
     * Length:     1B        12B            len(plaintext) bytes            16B
     * </pre>
     * Note: There is no padding required for AES-GCM, but this also implies that
     * the exact plaintext length is revealed.
     *
     * @param password  password to use for encryption
     * @param plaintext plaintext to encrypt
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws NoSuchPaddingException
     * @throws InvalidAlgorithmParameterException
     * @throws InvalidKeyException
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     * @throws InvalidKeySpecException
     */
    public byte[] encrypt(char[] password, byte[] plaintext)
            throws NoSuchAlgorithmException, NoSuchPaddingException,
            InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException,
            InvalidKeySpecException {

        // initialise random and generate IV (initialisation vector)
        SecretKey key = deriveAesKey(password, PBKDF2_SALT, AES_KEY_BITS_LENGTH);
        final byte[] iv = new byte[GCM_IV_BYTES_LENGTH];
        SecureRandom random = SecureRandom.getInstanceStrong();
        random.nextBytes(iv);

        // encrypt
        Cipher cipher = Cipher.getInstance(GCM_CRYPTO_NAME);
        GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_BYTES_LENGTH * 8, iv);
        cipher.init(Cipher.ENCRYPT_MODE, key, spec);

        // add IV to MAC
        final byte[] versionBytes = new byte[]{VERSION_BYTE};
        cipher.updateAAD(versionBytes);
        cipher.updateAAD(iv);

        // encrypt and MAC plaintext
        byte[] ciphertext = cipher.doFinal(plaintext);

        // prepend VERSION and IV to ciphertext
        byte[] encrypted = new byte[1 + GCM_IV_BYTES_LENGTH + ciphertext.length];
        int pos = 0;
        System.arraycopy(versionBytes, 0, encrypted, 0, VERSION_BYTE_LENGTH);
        pos += VERSION_BYTE_LENGTH;
        System.arraycopy(iv, 0, encrypted, pos, iv.length);
        pos += iv.length;
        System.arraycopy(ciphertext, 0, encrypted, pos, ciphertext.length);

        return encrypted;
    }

    /**
     * We derive a fixed length AES key with uniform entropy from a provided
     * passphrase. This is done with PBKDF2/HMAC256 with a fixed count
     * of iterations and a provided salt.
     *
     * @param password passphrase to derive key from
     * @param salt     salt for PBKDF2 if possible use a per-key salt, alternatively
     *                 a random constant salt is better than no salt.
     * @param keyLen   number of key bits to output
     * @return a SecretKey for AES derived from a passphrase
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    private SecretKey deriveAesKey(char[] password, byte[] salt, int keyLen)
            throws NoSuchAlgorithmException, InvalidKeySpecException {

        if (password == null || salt == null || keyLen <= 0) {
            throw new IllegalArgumentException();
        }
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        KeySpec spec = new PBEKeySpec(password, salt, PBKDF2_ITERATIONS, keyLen);
        SecretKey pbeKey = factory.generateSecret(spec);

        return new SecretKeySpec(pbeKey.getEncoded(), "AES");
    }

    /**
     * Helper to convert hex strings to bytes.
     * <p>
     * May be used to read bytes from constants.
     */
    private static byte[] hexStringToByteArray(String s) {

        if (s == null) {
            throw new IllegalArgumentException("Provided `null` string.");
        }

        int len = s.length();
        if (len % 2 != 0) {
            throw new IllegalArgumentException("Invalid length: " + len);
        }

        byte[] data = new byte[len / 2];
        for (int i = 0; i < len - 1; i += 2) {
            byte b = (byte) toHexDigit(s, i);
            b <<= 4;
            b |= toHexDigit(s, i + 1);
            data[i / 2] = b;
        }
        return data;
    }

    private static int toHexDigit(String s, int pos) {
        int d = Character.digit(s.charAt(pos), 16);
        if (d < 0) {
            throw new IllegalArgumentException("Cannot parse hex digit: " + s + " at " + pos);
        }
        return d;
    }

    private static String byteArrayToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02X", b));
        }
        return sb.toString();
    }

    public class CryptoException extends Exception {

        public CryptoException(Throwable cause) {
            super(cause);
        }
    }
}

Hier das gesamte Projekt mit einer Nice CLI: https://github.com/trichner/tcrypt

Edit: jetzt mit entsprechender encryptString und decryptString

0
trichner