web-dev-qa-db-fra.com

Chiffrement CryptoJS AES et déchiffrement Java AES

Je ne fais que poser cette question parce que je lis depuis 2 jours de nombreux articles sur le cryptage AES crypto, et juste au moment où je pensais l’obtenir, j’ai réalisé que je ne l’avais pas du tout. 

Cet article est le plus proche de mon numéro, j'ai exactement le même problème mais il reste sans réponse:

CryptoJS AES et incompatibilité des valeurs de déchiffrement Java AES

J'ai essayé de le faire de nombreuses façons mais je ne l'ai pas bien compris.

Tout d'abord

Je reçois la chaîne déjà chiffrée (j'ai seulement le code pour voir comment ils le faisaient), donc modifier la méthode de chiffrement n'est pas une option. C'est pourquoi toutes les questions similaires ne me sont pas utiles.

Seconde

J'ai accès à la clé secrète et je peux la modifier (le réglage de la longueur est donc une option si nécessaire).

Le cryptage est effectué sur CryptoJS et ils envoient la chaîne cryptée en tant que paramètre GET.

GetParamsForAppUrl.prototype.generateUrlParams = function() {
const self = this;
 return new Promise((resolve, reject) => {
   const currentDateInMilliseconds = new Date().getTime();
   const secret = tokenSecret.secret;
   var encrypted = CryptoJS.AES.encrypt(self.authorization, secret);
   encrypted = encrypted.toString();
   self.urlParams = {
     token: encrypted,
     time: currentDateInMilliseconds
   };
   resolve();
 });
};

Je peux facilement déchiffrer ceci sur javascript en utilisant CryptoJS avec:

var decrypted = CryptoJS.AES.decrypt(encrypted_string, secret);
    console.log(decrypted.toString(CryptoJS.enc.Utf8)); 

Mais je ne veux pas faire cela sur Javascript, pour des raisons de sécurité, alors j'essaye de déchiffrer ceci sur Java:

String secret = "secret";
byte[] cipherText = encrypted_string.getBytes("UTF8");
SecretKey secKey = new SecretKeySpec(secret.getBytes(), "AES");
Cipher aesCipher = Cipher.getInstance("AES");
aesCipher.init(Cipher.DECRYPT_MODE, secKey);
byte[] bytePlainText = aesCipher.doFinal(byteCipherText);
String myDecryptedText = = new String(bytePlainText);

Avant d’avoir une idée de ce que je faisais, j’ai essayé le décodage en base64, en ajoutant des IV et beaucoup de choses que j'ai lues, bien sûr que rien n’a fonctionné. 

Mais après avoir commencé à comprendre, un peu, ce que je faisais, j’ai écrit ce script simple ci-dessus, et j’ai eu la même erreur sur le message: Longueur de clé AES non valide

Je ne sais pas où aller d'ici. Après avoir lu beaucoup de choses à ce sujet, la solution semble être un hachage ou un bourrage, mais je n’ai aucun contrôle sur la méthode de cryptage, je ne peux donc pas vraiment hacher le secret ou le masquer.

Mais comme je l'ai dit, je peux changer la clé secrète pour qu'elle corresponde à une longueur spécifique. J'ai essayé de la changer, mais comme je tire dans le noir ici, je ne sais pas vraiment si c'est la solution.

Ma question est donc la suivante: si je reçois la chaîne chiffrée (en javascript comme le premier script) et la clé secrète, existe-t-il un moyen de le déchiffrer (en Java)? Dans l'affirmative, comment le faire?

8
Lauro182

CryptoJS implémente la même fonction de dérivation de clé que OpenSSL et le même format pour mettre le vecteur d'initialisation dans les données cryptées. Donc, tout le code Java qui traite des données encodées OpenSSL s’applique.

Étant donné le code Javascript suivant:

var text = "The quick brown fox jumps over the lazy dog. ???? ????";
var secret = "René Über";
var encrypted = CryptoJS.AES.encrypt(text, secret);
encrypted = encrypted.toString();
console.log("Cipher text: " + encrypted);

Nous obtenons le texte chiffré:

U2FsdGVkX1+tsmZvCEFa/iGeSA0K7gvgs9KXeZKwbCDNCs2zPo+BXjvKYLrJutMK+hxTwl/hyaQLOaD7LLIRo2I5fyeRMPnroo6k8N9uwKk=

Du côté de Java, nous avons

String secret = "René Über";
String cipherText = "U2FsdGVkX1+tsmZvCEFa/iGeSA0K7gvgs9KXeZKwbCDNCs2zPo+BXjvKYLrJutMK+hxTwl/hyaQLOaD7LLIRo2I5fyeRMPnroo6k8N9uwKk=";

byte[] cipherData = Base64.getDecoder().decode(cipherText);
byte[] saltData = Arrays.copyOfRange(cipherData, 8, 16);

MessageDigest md5 = MessageDigest.getInstance("MD5");
final byte[][] keyAndIV = GenerateKeyAndIV(32, 16, 1, saltData, secret.getBytes(StandardCharsets.UTF_8), md5);
SecretKeySpec key = new SecretKeySpec(keyAndIV[0], "AES");
IvParameterSpec iv = new IvParameterSpec(keyAndIV[1]);

byte[] encrypted = Arrays.copyOfRange(cipherData, 16, cipherData.length);
Cipher aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding");
aesCBC.init(Cipher.DECRYPT_MODE, key, iv);
byte[] decryptedData = aesCBC.doFinal(encrypted);
String decryptedText = new String(decryptedData, StandardCharsets.UTF_8);

System.out.println(decryptedText);

Le résultat est:

The quick brown fox jumps over the lazy dog. ???? ????

C'est le texte avec lequel nous avons commencé. Les émoticônes, les accents et les trémas fonctionnent également.

GenerateKeyAndIV est une fonction d'assistance qui réimplémente la fonction de dérivation de clé d'OpenSSL EVP_BytesToKey (voir https://github.com/openssl/openssl/blob/master/crypto/evp/evp_key.c ).

/**
 * Generates a key and an initialization vector (IV) with the given salt and password.
 * <p>
 * This method is equivalent to OpenSSL's EVP_BytesToKey function
 * (see https://github.com/openssl/openssl/blob/master/crypto/evp/evp_key.c).
 * By default, OpenSSL uses a single iteration, MD5 as the algorithm and UTF-8 encoded password data.
 * </p>
 * @param keyLength the length of the generated key (in bytes)
 * @param ivLength the length of the generated IV (in bytes)
 * @param iterations the number of digestion rounds 
 * @param salt the salt data (8 bytes of data or <code>null</code>)
 * @param password the password data (optional)
 * @param md the message digest algorithm to use
 * @return an two-element array with the generated key and IV
 */
public static byte[][] GenerateKeyAndIV(int keyLength, int ivLength, int iterations, byte[] salt, byte[] password, MessageDigest md) {

    int digestLength = md.getDigestLength();
    int requiredLength = (keyLength + ivLength + digestLength - 1) / digestLength * digestLength;
    byte[] generatedData = new byte[requiredLength];
    int generatedLength = 0;

    try {
        md.reset();

        // Repeat process until sufficient data has been generated
        while (generatedLength < keyLength + ivLength) {

            // Digest data (last digest if available, password data, salt if available)
            if (generatedLength > 0)
                md.update(generatedData, generatedLength - digestLength, digestLength);
            md.update(password);
            if (salt != null)
                md.update(salt, 0, 8);
            md.digest(generatedData, generatedLength, digestLength);

            // additional rounds
            for (int i = 1; i < iterations; i++) {
                md.update(generatedData, generatedLength, digestLength);
                md.digest(generatedData, generatedLength, digestLength);
            }

            generatedLength += digestLength;
        }

        // Copy key and IV into separate byte arrays
        byte[][] result = new byte[2][];
        result[0] = Arrays.copyOfRange(generatedData, 0, keyLength);
        if (ivLength > 0)
            result[1] = Arrays.copyOfRange(generatedData, keyLength, keyLength + ivLength);

        return result;

    } catch (DigestException e) {
        throw new RuntimeException(e);

    } finally {
        // Clean out temporary data
        Arrays.fill(generatedData, (byte)0);
    }
}

Notez que vous devez installer JCE (Java Cryptography Extension) Stratégie juridictionnelle à portée illimitée . Sinon, AES avec une taille de clé de 256 ne fonctionnera pas et lèvera une exception:

Java.security.InvalidKeyException: Illegal key size

Mettre à jour

J'ai remplacé le code Java de Ola Bini de EVP_BytesToKey, que j'ai utilisé dans la première version de ma réponse, par un code Java plus idiomatique et plus facile à comprendre (voir ci-dessus).

Voir aussi Comment décrypter un fichier en Java crypté avec la commande openssl en utilisant AES? .

19
Codo

Lorsque vous chiffrez sur un système et déchiffrez sur un autre, vous êtes à la merci des paramètres système par défaut. Si des valeurs système par défaut ne correspondent pas (et souvent non), le déchiffrement échouera.

Tout doit être octet pour octet le même des deux côtés. Effectivement, cela signifie tout spécifier des deux côtés plutôt que de se fier aux valeurs par défaut. Vous ne pouvez utiliser les valeurs par défaut que si vous utilisez le même système aux deux extrémités. Même dans ce cas, il est préférable de spécifier avec précision.

La clé, le mode IV, le mode de chiffrement, le remplissage et la conversion chaîne en octets doivent tous être identiques aux deux extrémités. Cela vaut surtout la peine de vérifier que les octets de clé sont les mêmes. Si vous utilisez une fonction de dérivation de clé (KDF) pour générer votre clé, tous les paramètres pour cela doivent être identiques, et donc spécifiés exactement.

Votre "longueur de clé AES non valide" peut indiquer un problème de génération de votre clé. Vous utilisez getBytes(). C'est probablement une erreur. Vous devez spécifier le type d'octets que vous obtenez: ANSI, UTF-8, EBCDIC, peu importe. L'hypothèse par défaut pour la conversion de chaîne en octet est la cause probable de ce problème. Spécifiez la conversion à utiliser explicitement aux deux extrémités. De cette façon, vous pouvez être sûr qu'ils correspondent.

Crypto est conçu pour échouer si les paramètres ne correspondent pas exactement pour le cryptage et le décryptage. Par exemple, même une différence d'un bit dans la clé entraînera son échec.

2
rossum