2017-03-29 1 views
1

J'ai crypté une chaîne avec AES 256-bit (mode CBC) en utilisant BouncyCastle (techniquement, SpongyCastle) sous Android et connecté les valeurs codées en base64 du texte chiffré, clé et iv. J'ai ensuite écrit un programme de test (en tant que test d'instrument Android) qui code les trois valeurs codées en base64. Il décrypte avec succès quand je cours le code ci-dessous sous Android.AES256-CBC Ciphertext de BouncyCastle décrypte en BC, mais PHP openssl_decrypt échoue w/mêmes entrées

Le même texte chiffré, la même clé et le même code iv encodés en base64 échouent sous PHP (5.6.30). J'ai du mal à comprendre pourquoi. Dans les deux cas, le texte chiffré, la clé et l'iv commencent tous par des chaînes encodées en base64 codées en dur avec exactement les mêmes valeurs.

Entre autres choses, il se plaint que la longueur de clé (32 octets) est invalide (la dernière fois que j'ai vérifié, 32 * 8 == 256).

applications (SpongyCastle) Code:

@Test 
    public void crossplatformTest() { 
     String ciphertext64 = "gfcC6t1BarndpzMuvYj2JFpWHqlWSJMhTtxPN7QjyEg="; 
     String key64 = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8="; 
     String iv64="AAECAwQFBgcICQoLDA0ODw=="; 

     byte[] ciphertext = Base64.decode(ciphertext64, Base64.DEFAULT); 
     byte[] key = Base64.decode(key64, Base64.DEFAULT); 
     byte[] iv = Base64.decode(iv64, Base64.DEFAULT); 

     byte[] decrypted = Crypto.decryptWithAesCBC(ciphertext, key, iv); 
     // the following assertion succeeds. 
     assertEquals("Ugh! Endless grief!", new String(decrypted, StandardCharsets.UTF_8)); 
    } 

PHP (OpenSSL) Code:

<?php 
    $ciphertext64 = "gfcC6t1BarndpzMuvYj2JFpWHqlWSJMhTtxPN7QjyEg="; 
    $key64 = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8="; 
    $iv64="AAECAwQFBgcICQoLDA0ODw=="; 

    // UPDATE: THE LINE BELOW IS THE PROBLEM. SEE ANSWER FOR SOLUTION. 
    $decrypted = openssl_decrypt($ciphertext64, 'aes-256-cbc', $key64, 0, $iv64); 
    if ($decrypted == false) { 
     while ($msg = openssl_error_string()) 
      echo "<p>$msg</p>\n"; 
     echo("key length=" . strlen(base64_decode($key64, true))."<BR>\n"); 
     echo("iv length=" . strlen(base64_decode($iv64, true))."<BR>\n"); 
    } 

la sortie de PHP:

error:0607A082:digital envelope routines:EVP_CIPHER_CTX_set_key_length:invalid key length 
error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt 

key length=32 
iv length=16 

Ac En raison de la petite documentation pour openssl_decrypt qui semble exister sur php.net (http://php.net/manual/en/function.openssl-decrypt.php), openssl_decrypt veut des chaînes codées en base64 pour le texte chiffré, la clé et iv.

Pour ce que ça vaut la peine, voici le code que j'utilisé pour chiffrer la chaîne d'origine, et le code que je utilise sur Android pour décrypter:

public static byte[] decryptWithAesCBC(byte[] ciphertext, byte[] key, byte[] iv) { 
     try { 
      PaddedBufferedBlockCipher aes = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine())); 
      CipherParameters ivAndKey = new ParametersWithIV(new KeyParameter(key), iv); 
      aes.init(false, ivAndKey); 
      return cipherData(aes, ciphertext); 
     } 
     catch (InvalidCipherTextException e) { 
      throw new RuntimeException(e); 
     } 
    } 

private static byte[] cipherData(PaddedBufferedBlockCipher cipher, byte[] data) throws InvalidCipherTextException { 
     int minSize = cipher.getOutputSize(data.length); 
     byte[] outBuf = new byte[minSize]; 
     int length1 = cipher.processBytes(data, 0, data.length, outBuf, 0); 
     int length2 = cipher.doFinal(outBuf, length1); 
     int actualLength = length1 + length2; 
     byte[] ciphertext = new byte[actualLength]; 
     for (int x=0; x < actualLength; x++) { 
      ciphertext[x] = outBuf[x]; 
     } 
     return ciphertext; 
    } 

public static byte[] encryptWithAesCBC(byte[] plaintext, byte[] key, byte[] iv) { 
     try { 
      Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", "SC"); 

      PaddedBufferedBlockCipher aes = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine())); 
      CipherParameters ivAndKey = new ParametersWithIV(new KeyParameter(key), iv); 
      aes.init(true, ivAndKey); 
      return cipherData(aes, plaintext); 
     } 
     catch (NoSuchAlgorithmException | NoSuchProviderException | NoSuchPaddingException | InvalidCipherTextException e) { 
      throw new RuntimeException(e); 
     } 
    } 
+1

commentaires Lire dans la documentation http://php.net/manual/es/function.openssl-decrypt.php, il ressemble à IV et le mot de passe devrait être binaire (pas base64). Je suppose que vous avez besoin de 'base64_decode' en premier – pedrofb

+0

Hmmm ... J'ai eu l'impression inverse du premier commentaire, qui dit" $ data peut être comme la description dit brut ou base64 .Si aucune option $ n'est définie (c'est, si valeur de 0 est passé dans ce paramètre), les données seront supposées être codées en base64. " – Bitbang3r

+0

Oui, c'est déroutant, mais dans le second commentaire, l'auteur dit explicitement: _Le paramètre string $ password doit être sous forme binaire_ – pedrofb

Répondre

0

OK, a trouvé la réponse définitive & a confirmé qu'il travaux.

La documentation officielle pour openssl_decrypt sur php.net est mauvaise, et les premiers commentaires pris isolément sont quelque part entre «incomplet» et «trompeur».

Selon la documentation la plus autorisée pour openssl_decrypt (ext/OpenSSL/openssl.c dans le code source de PHP 5.6.30 lui-même):

$ data est présumée être base64 à moins OPENSSL_RAW_DATA est explicitement spécifié Comme une option. Le code source lui-même fait clairement:

if (!(options & OPENSSL_RAW_DATA)) { 
    base64_str = (char*)php_base64_decode((unsigned char*)data, data_len, &base64_str_len); 
    if (!base64_str) { 
     php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to base64 decode the input"); 
     RETURN_FALSE; 
    } 
    data_len = base64_str_len; 
    data = base64_str; 
} 

CEPENDANT, les valeurs de mot de passe $ et iv $ sont exprimés DIRECTEMENT (* unsigned char).

Ainsi, l'utilisation correcte est:

$ciphertext64 = "gfcC6t1BarndpzMuvYj2JFpWHqlWSJMhTtxPN7QjyEg="; 
$key64 = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8="; 
$iv64="AAECAwQFBgcICQoLDA0ODw=="; 

$key = base64_decode($key64, true); 
$iv = base64_decode($iv64, true); 

$decrypted = openssl_decrypt($ciphertext64, 'aes-256-cbc', $key, 0, $iv); 

... qui sort la valeur de la chaîne I initialement chiffrée à l'aide BouncyCastle (SpongyCastle) sur Android - « Ugh chagrin sans fin!"

Quant à pourquoi la documentation officielle est si mauvais, les développeurs de PHP ont apparemment décidé de déprécier tranquillement les fonctions OpenSSL, avec l'espoir que PHP fois 7.2 devient mainstream, tout le monde utilisera libsodium aller de l'avant. L'ajout de libsodium par défaut les installations de PHP 7.2 sont impressionnantes, mais malheureusement, personne ne disposera d'un compte d'hébergement web partagé (qui manque d'admin, encore moins de root, d'accès shell au serveur et ne peut pas installer ses propres extensions) En d'autres termes, si vous avez trouvé cet article via Google quelque temps après la fin de 2017, alors que vous essayez de résoudre un problème que vous rencontrez avec openssl et PHP, vérifiez pour voir si er votre serveur web a PHP 7.2 disponible.

  • Si elle n'a pas, ni mise à niveau vers PHP 7.2+, ni obtenir libsodium installé sur le serveur est une option viable, procéder à ma solution.

  • Si elle le fait, oublier que le openssl_ * fonctions jamais existé, et aller directement à libsodium (qui, par la conception le rend beaucoup plus difficile à visser accidentellement)