119 votes

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

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

Le problème est que la première partie de la chaîne décryptée n'a aucun sens. Cependant, le reste va bien, j'obtiens...

Result: `£eB6OgeSi 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();
  }
}

228voto

chandpriyankara Points 909

Beaucoup de personnes, y compris moi-même, rencontrent de nombreux problèmes pour faire fonctionner ce système en raison de l'absence de certaines informations, comme l'oubli de la conversion 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 jar Apache Commons Codec supplémentaire, qui est 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")));
    }
}

85voto

for3st Points 2727

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

Il s'agit d'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 fournir un exemple simple à utiliser et donner quelques indications sur ce à quoi il faut faire attention.

Dans cet exemple, je vais choisir d'utiliser cryptage authentifié con Mode Galois/Compteur ou GCM mode. La raison en est que, dans la plupart des cas, vous voulez intégrité et authenticité en combinaison avec la confidentialité (lire la suite dans le blog ).

Tutoriel de chiffrement/déchiffrement AES-GCM

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

1. Créer une clé

Comme cela dépend de votre cas d'utilisation, je vais supposer 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éer le vecteur d'initialisation

Un site vecteur d'initialisation (IV) est utilisé pour qu'une même clé secrète crée des clés différentes. textes chiffrés .

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

Important :

3. Cryptage avec IV et clé

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 16 octets / 128 bits étiquette d'authentification (utilisé pour vérifier l'intégrité/authenticité)
  • l'étiquette d'authentification sera automatiquement ajoutée au texte chiffré (dans l'implémentation JCA).
  • puisque le GCM se comporte comme un chiffrement de flux, aucun remplissage n'est nécessaire.
  • utiliser CipherInputStream lors du chiffrement de gros morceaux de données.
  • vous souhaitez que des données supplémentaires (non secrètes) soient vérifiées si elles ont été modifiées ? Vous pouvez utiliser données associées con cipher.updateAAD(associatedData); Plus d'informations ici.

3. Sérialisation en un seul message

Il suffit d'ajouter l'IV et le texte chiffré. Comme indiqué ci-dessus, l'IV n'a pas besoin d'être secret.

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

Encodage facultatif avec Base64 si vous avez besoin d'une représentation sous forme de chaîne. Soit vous utilisez Android o L'intégration de Java 8 (n'utilisez pas Apache Commons Codec - c'est une implémentation horrible). L'encodage est utilisé pour "convertir" les tableaux d'octets en représentation de chaîne de caractères afin de les rendre ASCII sûrs, par exemple :

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

4. Préparer le décryptage : Désérialiser

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

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

Important :

5. Décrypter

Initialiser le chiffrement et définir les mêmes paramètres que pour 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 données associées con cipher.updateAAD(associatedData); si vous l'avez ajouté pendant le cryptage.

Un extrait de code fonctionnel peut être trouvé dans ce gist.


Notez que les implémentations Android (SDK 21+) et Java (7+) les plus récentes devraient avoir AES-GCM. Les versions plus anciennes peuvent en être dépourvues. Je choisis toujours ce mode, car il est plus facile à implémenter en plus d'être plus efficace comparé à un mode similaire de Cryptage puis Mac (avec par exemple AES-CBC + HMAC ). Voir cet article sur la façon d'implémenter AES-CBC avec HMAC. .

38voto

BullyWiiPlaza Points 5382

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

Imprimés :

Hello world!
;LA+b*
Hello world!

24voto

GPI Points 5552

Il me semble que vous ne traitez pas correctement votre vecteur d'initialisation (IV). Cela fait longtemps que je n'ai pas lu sur AES, les IVs et le chaînage de blocs, 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 une information que vous ne pouvez pas obtenir à partir de votre clé mais à partir du calcul réel du chiffrement. (On pourrait dire que si l'IV pouvait être extrait de la clé, alors il ne serait d'aucune utilité, puisque la clé est déjà donnée à l'instance de chiffrement pendant sa phase d'initialisation).

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

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

et vous devez initialiser votre Cipher en DECRYPT_MODE avec cet octet[] :

  IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);

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

19voto

k170 Points 383

L'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 d'une classe AES simple en Java. Je ne recommande pas d'utiliser cette classe dans des environnements de production, car elle peut ne pas tenir compte de 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 que 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.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X