Octets initiaux incorrects après le décryptage Java AES / CBC


116

Quel est le problème avec l'exemple suivant?

Le problème est que la première partie de la chaîne déchiffrée est absurde. Cependant, le reste va bien, je reçois ...

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();
  }
}

48
N'UTILISEZ AUCUNE RÉPONSE À CETTE QUESTION DANS UN PROJET SÉRIEUX! Tous les exemples fournis dans cette question sont vulnérables à l'oracle de remplissage et sont globalement très mauvais en cryptographie. Vous introduirez une grave vulnérabilité de cryptographie dans votre projet en utilisant l'un des extraits ci-dessous.
HoLyVieR

16
@HoLyVieR, Concernant les citations suivantes: "Vous ne devriez pas développer votre propre bibliothèque de cryptographie" et "utiliser une API de haut niveau fournie par votre framework". Personne ici ne développe sa propre bibliothèque de cryptographie. Nous utilisons simplement l'API de haut niveau déjà existante fournie par le framework java. Vous, monsieur, êtes extrêmement inexact.
k170 le

10
@MaartenBodewes, Ce n'est pas parce que vous êtes tous les deux d'accord que vous avez tous les deux raison. Les bons développeurs connaissent la différence entre l'encapsulation d'une API de haut niveau et la réécriture d'une API de bas niveau. Les bons lecteurs remarqueront que l'OP a demandé un "simple exemple de chiffrement / déchiffrement java AES" et c'est exactement ce qu'il a obtenu . Je ne suis pas non plus d'accord avec les autres réponses, c'est pourquoi j'ai publié ma propre réponse. Peut-être que vous devriez essayer la même chose et nous éclairer tous avec votre expertise.
k170 le

6
@HoLyVieR C'est vraiment la chose la plus absurde que j'aie jamais lue sur SO! Qui êtes-vous pour dire aux gens ce qu'ils peuvent et ne peuvent pas développer?
TedTrippin

14
Je ne vois toujours aucun exemple @HoLyVieR. Voyons quelques-uns, ou des pointeurs vers des bibliothèques? Pas du tout constructif.
danieljimenez

Réponses:


245

Beaucoup de gens, dont moi-même, rencontrent beaucoup de problèmes pour faire ce travail en raison du manque d'informations comme, oublier de convertir en Base64, les vecteurs d'initialisation, le jeu de caractères, etc. J'ai donc pensé à créer un code entièrement fonctionnel.

J'espère que cela vous sera utile à tous: Pour compiler, vous avez besoin d'un fichier jar Apache Commons Codec supplémentaire, disponible ici: 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")));
    }
}

47
Si vous ne voulez pas dépendre de la bibliothèque de codec Apache Commons tierce, vous pouvez utiliser javax.xml.bind.DatatypeConverter de JDK pour effectuer un encodage / décodage Base64: System.out.println("encrypted string:" + DatatypeConverter.printBase64Binary(encrypted)); byte[] original = cipher.doFinal(DatatypeConverter.parseBase64Binary(encrypted));
curd0

8
Utilisez-vous une IV constante?!
vianna77

36
Java 8 a déjà des outils Base64: java.util.Base64.getDecoder () et java.util.Base64.getEncoder ()
Hristo Stoyanov

11
Le IV n'a pas à être secret, mais il doit être imprévisible pour le mode CBC (et unique pour CTR). Il peut être envoyé avec le texte chiffré. Une façon courante de le faire est de préfixer le IV au texte chiffré et de le découper avant le déchiffrement. Il devrait être généré viaSecureRandom
Artjom B.

6
Un mot de passe n'est pas une clé. Une IV doit être aléatoire.
Maarten Bodewes

40

Voici une solution sans 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);
    }
}

Exemple d'utilisation:

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));

Impressions:

Hello world!
դ;��LA+�ߙb*
Hello world!

5
C'est un exemple parfaitement fonctionnel, tout comme celui de @ chandpriyankara. Mais pourquoi définir une signature de encrypt(String)et non encrypt(byte[] )?. Le cryptage (également le décryptage) est un processus basé sur l'octet (AES l'est de toute façon). Le chiffrement prend des octets en entrée et génère des octets, tout comme le déchiffrement (cas d'espèce: l' Cipherobjet fait). Maintenant, un cas d'utilisation particulier peut être d'avoir des octets chiffrés provenant d'une chaîne, ou d'être envoyés sous forme de chaîne (pièce jointe MIME base64 pour un courrier ...), mais c'est un problème de codage d'octets, pour lequel il existe des centaines de solutions, sans aucun rapport avec AES / cryptage.
GPI

3
@GPI: Oui, mais je trouve cela plus utile avec Stringspuisque c'est essentiellement ce avec quoi je travaille 95% du temps et que vous finissez par convertir de toute façon.
BullyWiiPlaza

9
Non, ce n'est pas équivalent au code de chandpriyankara! Votre code utilise ECB qui n'est généralement pas sécurisé et n'est pas souhaité. Devrait spécifier explicitement CBC. Lorsque CBC est spécifié, votre code est interrompu.
Dan le

Parfaitement fonctionnel, totalement non sécurisé et utilisant de très mauvaises pratiques de programmation. la classe est mal nommée. La taille de la clé n'est pas vérifiée à l'avance. Mais surtout, le code utilise le mode ECB non sécurisé, cachant le problème dans la question d'origine . Enfin, il ne spécifie pas de codage de caractères, ce qui signifie que le décodage en texte peut échouer sur d'autres plates-formes.
Maarten Bodewes

24

Il me semble que vous ne gérez pas correctement votre vecteur d’initialisation (IV). Cela fait longtemps que je n'ai pas lu pour la dernière fois sur AES, IVs et block chaining, mais votre ligne

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

ne semble pas être OK. Dans le cas d'AES, vous pouvez considérer le vecteur d'initialisation comme "l'état initial" d'une instance de chiffrement, et cet état est un peu d'informations que vous ne pouvez pas obtenir de votre clé mais du calcul réel du chiffrement de chiffrement. (On pourrait affirmer que si l'IV pouvait être extrait de la clé, alors il ne serait d'aucune utilité, car la clé est déjà donnée à l'instance de chiffrement pendant sa phase d'initialisation).

Par conséquent, vous devriez obtenir l'IV sous forme d'octet [] à partir de l'instance de chiffrement à la fin de votre chiffrement

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

et vous devez initialiser votre Cipherin DECRYPT_MODEavec cet octet []:

  IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);

Ensuite, votre décryptage devrait être OK. J'espère que cela t'aides.


Merci d'aider un débutant. J'ai bricolé cet exemple à partir d'autres messages. Je suppose que vous ne savez pas comment éviter d'avoir recours à une intraveineuse? J'ai vu, mais pas essayé, d'autres exemples AES qui ne l'utilisent pas.
TedTrippin

Ignorez cela, j'ai trouvé la réponse! J'ai besoin d'utiliser AES / ECB / PKCS5Padding.
TedTrippin

20
La plupart du temps, vous ne souhaitez pas utiliser ECB. Juste google pourquoi.
João Fernandes

2
@Mushy: a convenu que choisir et définir explicitement un IV, à partir d'une source aléatoire de confiance, est mieux que de laisser l'instance Cihper en choisir une. D'autre part, cette réponse résout le problème original de la confusion du vecteur d'initialisation de la clé. C'est pourquoi il a été voté au début. Maintenant, cet article est devenu plus un exemple de code de référence, et les gens ici ont fait un bon exemple - juste à côté de ce sur quoi portait la question originale.
GPI

3
@GPI a voté pour. Les autres «excellents exemples» ne sont pas très bons et ne répondent pas du tout à la question. Au lieu de cela, cela semble avoir été le lieu pour les débutants de copier aveuglément des échantillons cryptographiques sans comprendre qu'il peut y avoir des problèmes de sécurité possibles - et, comme toujours, il y en a.
Maarten Bodewes

17

Le IV que vous utilisez pour le décryptage est incorrect. Remplacez ce code

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

Avec ce code

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

Et cela devrait résoudre votre problème.


Vous trouverez ci-dessous un exemple de classe AES simple en Java. Je ne recommande pas d'utiliser cette classe dans les environnements de production, car elle peut ne pas prendre en compte tous les besoins spécifiques de votre application.

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)));
    }
}

Notez qu'AES n'a rien à voir avec l'encodage, c'est pourquoi j'ai choisi de le gérer séparément et sans avoir besoin de bibliothèques tierces.


Tout d'abord, vous n'avez pas répondu à la question initiale. Deuxièmement, pourquoi répondez-vous à une question déjà répondue et bien acceptée? Je pensais que la protection était censée arrêter ce spam.
TedTrippin

14
Comme la réponse acceptée, j'ai choisi de répondre à votre question par exemple. J'ai fourni un morceau de code entièrement fonctionnel, qui vous montre, entre autres, comment gérer correctement le vecteur d'initialisation. En ce qui concerne votre deuxième question, j'ai estimé qu'une réponse mise à jour était nécessaire car le codec Apache n'est plus nécessaire. Donc non ce n'est pas du spam. Arrêtez de trippin.
k170

7
Un IV a un but spécifique qui est de randomiser le texte chiffré et de fournir une sécurité sémantique. Si vous utilisez la même paire clé + IV, les attaquants peuvent déterminer si vous avez envoyé un message avec le même préfixe qu'auparavant. La IV n'a pas à être secrète, mais elle doit être imprévisible. Une manière courante consiste simplement à préfixer le IV au texte chiffré et à le découper avant le déchiffrement.
Artjom B.

4
downvote: hardcoded IV, voir Artjom B. commenter ci-dessus pourquoi c'est mauvais
Murmel

1
Le mode CTR doit être associé à NoPadding. Le mode CTR n'est certainement pas requis à la place de CBC (sauf si des oracles de remplissage s'appliquent), mais si CTR est utilisé, utilisez "/NoPadding". CTR est un mode qui transforme AES en un chiffrement de flux, et un chiffrement de flux fonctionne sur des octets au lieu de blocs.
Maarten Bodewes

16

Dans cette réponse, j'ai choisi d'aborder le thème principal «Exemple simple de chiffrement / déchiffrement de Java AES» et non la question de débogage spécifique car je pense que cela profitera à la plupart des lecteurs.

Ceci est un simple résumé de mon article de blog sur le cryptage AES en Java , je vous recommande donc de le lire avant de mettre en œuvre quoi que ce soit. Je vais cependant toujours fournir un exemple simple à utiliser et donner quelques conseils à surveiller.

Dans cet exemple, je choisirai d'utiliser le cryptage authentifié avec le mode Galois / compteur ou le mode GCM . La raison en est que dans la plupart des cas, vous voulez l' intégrité et l'authenticité en combinaison avec la confidentialité (en savoir plus dans le blog ).

Tutoriel de chiffrement / déchiffrement AES-GCM

Voici les étapes nécessaires pour crypter / décrypter avec AES-GCM avec Java Cryptography Architecture (JCA) . Ne mélangez pas avec d'autres exemples , car des différences subtiles peuvent rendre votre code totalement non sécurisé.

1. Créer une clé

Comme cela dépend de votre cas d'utilisation, je suppose le cas le plus simple: une clé secrète aléatoire.

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

Important:

2. Créez le vecteur d'initialisation

Un vecteur d'initialisation (IV) est utilisé pour que la même clé secrète crée différents textes de chiffrement .

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

Important:

3. Crypter avec IV et Key

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);

Important:

  • utiliser une balise d'authentification de 16 octets / 128 bits (utilisée pour vérifier l'intégrité / l'authenticité)
  • la balise d'authentification sera automatiquement ajoutée au texte chiffré (dans l'implémentation JCA)
  • puisque GCM se comporte comme un chiffrement de flux, aucun remplissage n'est requis
  • à utiliser CipherInputStreamlors du chiffrement de gros morceaux de données
  • voulez-vous que des données supplémentaires (non secrètes) soient vérifiées si elles ont été modifiées? Vous pouvez utiliser les données associées avec cipher.updateAAD(associatedData); Plus ici.

3. Sérialisation en message unique

Ajoutez simplement IV et le texte chiffré. Comme indiqué ci-dessus, l'IV n'a pas besoin d'être secrète.

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

Encodez éventuellement avec Base64 si vous avez besoin d'une représentation sous forme de chaîne. Utilisez l' implémentation intégrée d' Android ou de Java 8 (n'utilisez pas Apache Commons Codec - c'est une implémentation horrible). Le codage est utilisé pour "convertir" des tableaux d'octets en représentation sous forme de chaîne pour le rendre sûr ASCII, par exemple:

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

4. Préparez le décryptage: désérialiser

Si vous avez encodé le message, décodez-le d'abord en tableau d'octets:

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

Important:

5. Décrypter

Initialisez le chiffrement et définissez les mêmes paramètres qu'avec le chiffrement:

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);

Important:

  • n'oubliez pas d'ajouter les données associées à cipher.updateAAD(associatedData);si vous les avez ajoutées lors du chiffrement.

Un extrait de code fonctionnel peut être trouvé dans cet essentiel.


Notez que les implémentations Android (SDK 21+) et Java (7+) les plus récentes devraient avoir AES-GCM. Les anciennes versions peuvent en manquer. Je choisis toujours ce mode, car il est plus facile à mettre en œuvre en plus d'être plus efficace par rapport au mode similaire d' Encrypt-then-Mac (avec par exemple AES-CBC + HMAC ). Consultez cet article pour savoir comment implémenter AES-CBC avec HMAC .


Le problème est que demander des exemples est explicitement hors sujet sur SO. Et le plus gros problème est que ce sont des morceaux de code non révisés, difficiles à valider. J'apprécie l'effort, mais je ne pense pas que SO devrait être le lieu pour cela.
Maarten Bodewes

1
J'admire l'effort cependant, donc je vais juste signaler une seule erreur: "l'iv doit être imprévisible en combinaison avec être unique (c'est-à-dire utiliser iv aléatoire)" - c'est vrai pour le mode CBC mais pas pour GCM.
Maarten Bodewes

this is true for CBC mode but not for GCMvoulez-vous dire la partie entière, ou seulement elle n'a pas vraiment besoin d'être imprévisible?
Patrick Favre

1
"Si vous ne comprenez pas le sujet, vous ne devriez probablement pas utiliser de primitives de bas niveau en premier lieu" Bien sûr, cela DEVRAIT être le cas, de nombreux développeurs le font encore. Je ne suis pas sûr que s'abstenir de mettre en place un contenu de haute qualité concernant la sécurité / cryptographie dans des endroits où il n'y a souvent pas grand-chose est la bonne solution pour cela. - merci d'avoir pointé mon erreur btw
Patrick Favre

1
OK, juste parce que j'aime la réponse dans le contenu (plutôt que dans le but): la gestion des IV peut être simplifiée surtout lors du décryptage: Java facilite après tout la création d'un IV directement à partir d'un tableau d'octets existant. Il en va de même pour le déchiffrement, qui n'a pas besoin de commencer à l'offset 0. Toute cette copie n'est tout simplement pas nécessaire. Aussi, si vous devez envoyer une longueur pour l'IV (n'est-ce pas?), Alors pourquoi ne pas utiliser un seul octet (non signé) - vous n'allez pas dépasser 255 octets pour l'IV, n'est-ce pas?
Maarten Bodewes

2

Version exécutable de l'éditeur en ligne: -

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")));
    }
}

Cool, heureux que cela ait aidé!
Bhupesh Pant

Un mot de passe n'est pas une clé, un IV ne doit pas être statique. Code encore tapé de manière stringente, ce qui rend impossible la destruction de la clé. Aucune indication de ce qu'il faut faire avec l'IV, ni aucune idée qu'il devrait être imprévisible.
Maarten Bodewes

1

C'est souvent la bonne idée de s'appuyer sur la solution standard fournie par la bibliothèque:

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));
}

Cela imprime "Texte à encoder".

La solution est basée sur le Guide de référence de l'architecture de cryptographie Java et la réponse https://stackoverflow.com/a/20591539/146745 .


5
N'utilisez jamais le mode ECB. Période.
Konstantino Sparakis

1
ECB ne doit pas être utilisé si vous cryptez plus d'un bloc de données avec la même clé, donc pour le "Texte à encoder", c'est assez bon. stackoverflow.com/a/1220869/146745
andrej

La clé @AndroidDev est générée dans la section de préparation de la clé: aesKey = keygen.generateKey ()
andrej

1

C'est une amélioration par rapport à la réponse acceptée.

Changements:

(1) Utilisation de IV aléatoire et ajoutez-le au texte crypté

(2) Utilisation de SHA-256 pour générer une clé à partir d'une phrase de passe

(3) Aucune dépendance sur 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);
}

Un hachage n'est toujours pas une fonction de génération de clé basée sur un mot de passe / PBKDF. Soit vous utilisez une clé aléatoire, soit vous utilisez un PBKDF tel que PBKDF2 / Chiffrement basé sur le mot de passe.
Maarten Bodewes

@MaartenBodewes Pouvez-vous suggérer une amélioration?
wvdz

PBKDF2 est présent en Java, donc je pense que je viens d'en suggérer un. OK, je n'en ai pas codé un, mais c'est un peu trop à mon avis. Il existe de nombreux exemples de cryptage par mot de passe.
Maarten Bodewes

@MaartenBodewes J'ai pensé que ce pourrait être une solution simple. Par curiosité, quelles seraient les vulnérabilités spécifiques lors de l'utilisation de ce code tel quel?
wvdz

0

Une autre solution utilisant java.util.Base64 avec Spring Boot

Classe de chiffrement

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)));
    }
}

Classe EncryptorController

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

Exemple

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

2h41HH8Shzc4BRU3hVDOXA ==

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

jmendoza


-1

Version optimisée de la réponse acceptée.

  • pas de bibliothèques tierces

  • inclut IV dans le message crypté (peut être public)

  • le mot de passe peut être de n'importe quelle longueur

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;
    }
}

Usage:

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);

Exemple de sortie:

QngBg+Qc5+F8HQsksgfyXg== yDfYiIHTqOOjc0HRNdr1Ng==
Hello World

Votre fonction de dérivation de mot de passe n'est pas sécurisée. Je ne m'attendrais pas à ce que l' e.printStackTrace()on appelle un code optimisé.
Maarten Bodewes le
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.