118 votes

mcrypt est déprécié, quelle est l'alternative ?

L'extension mcrypt-extension est déprécié sera supprimé dans PHP 7.2 selon le commentaire posté ici . Je suis donc à la recherche d'un autre moyen de chiffrer les mots de passe.

Pour l'instant, j'utilise quelque chose comme

mcrypt_encrypt(MCRYPT_RIJNDAEL_128, md5($key, true), $string, MCRYPT_MODE_CBC, $iv)

J'ai besoin de votre avis sur le meilleur moyen de crypter les mots de passe. Le mot de passe crypté doit bien sûr être supporté par PHP 7.xx et doit également pouvoir être décrypté car mes clients veulent avoir la possibilité de "récupérer" leurs mots de passe sans en générer un nouveau.

49voto

Phil Points 988

La meilleure pratique consiste à hacher les mots de passe afin qu'ils ne soient pas décryptables. Cela rend les choses un peu plus difficiles pour les attaquants qui ont pu avoir accès à votre base de données ou à vos fichiers.

Si vous devez crypter vos données et faire en sorte qu'elles puissent être décryptées, un guide sur le cryptage/décryptage sécurisé est disponible à l'adresse suivante https://paragonie.com/white-paper/2015-secure-php-data-encryption . Pour résumer ce lien :

  • Utilisez Libsodium - Une extension PHP
  • Si vous ne pouvez pas utiliser Libsodium, utilisez defuse/php-encryption - Code PHP direct
  • Si vous ne pouvez pas utiliser Libsodium ou defuse/php-encryption, utilisez OpenSSL - De nombreux serveurs l'ont déjà installé. Si non, il peut être compilé avec --with-openssl[=DIR]

36voto

kenorb Points 2464

Comme le suggère @rqLizard vous pouvez utiliser openssl_encrypt / openssl_decrypt fonctions PHP à la place, ce qui constitue une meilleure alternative pour implémenter AES (The Advanced Encryption Standard) également connu sous le nom de cryptage Rijndael.

Conformément à ce qui suit Commentaire de Scott sur php.net :

Si vous écrivez du code pour crypter/chiffrer des données en 2015, vous devriez utiliser openssl_encrypt() et openssl_decrypt() . La bibliothèque sous-jacente ( libmcrypt ) a été abandonné depuis 2007, et est bien moins performant qu'OpenSSL (qui exploite AES-NI sur les processeurs modernes et est sans danger pour le cache).

Aussi, MCRYPT_RIJNDAEL_256 n'est pas AES-256 il s'agit d'une variante différente du chiffrement par blocs Rijndael. Si vous voulez AES-256 sur mcrypt vous devez utiliser MCRYPT_RIJNDAEL_128 avec une clé de 32 octets. OpenSSL rend plus évident le mode que vous utilisez (i.e. aes-128-cbc vs aes-256-ctr ).

OpenSSL utilise également le remplissage PKCS7 avec le mode CBC plutôt que le remplissage NULL byte de mcrypt. Ainsi, mcrypt est plus susceptible de rendre votre code vulnérable aux attaques de type padding oracle qu'OpenSSL.

Enfin, si vous n'authentifiez pas vos ciphertexts (Encrypt Then MAC), vous vous y prenez mal.

Pour en savoir plus :

Exemples de code

Exemple n° 1

Exemple de chiffrement authentifié AES en mode GCM pour PHP 7.1+.

<?php
//$key should have been previously generated in a cryptographically safe way, like openssl_random_pseudo_bytes
$plaintext = "message to be encrypted";
$cipher = "aes-128-gcm";
if (in_array($cipher, openssl_get_cipher_methods()))
{
    $ivlen = openssl_cipher_iv_length($cipher);
    $iv = openssl_random_pseudo_bytes($ivlen);
    $ciphertext = openssl_encrypt($plaintext, $cipher, $key, $options=0, $iv, $tag);
    //store $cipher, $iv, and $tag for decryption later
    $original_plaintext = openssl_decrypt($ciphertext, $cipher, $key, $options=0, $iv, $tag);
    echo $original_plaintext."\n";
}
?>

Exemple n° 2

Exemple de chiffrement authentifié AES en PHP 5.6+.

<?php
//$key previously generated safely, ie: openssl_random_pseudo_bytes
$plaintext = "message to be encrypted";
$ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC");
$iv = openssl_random_pseudo_bytes($ivlen);
$ciphertext_raw = openssl_encrypt($plaintext, $cipher, $key, $options=OPENSSL_RAW_DATA, $iv);
$hmac = hash_hmac('sha256', $ciphertext_raw, $key, $as_binary=true);
$ciphertext = base64_encode( $iv.$hmac.$ciphertext_raw );

//decrypt later....
$c = base64_decode($ciphertext);
$ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC");
$iv = substr($c, 0, $ivlen);
$hmac = substr($c, $ivlen, $sha2len=32);
$ciphertext_raw = substr($c, $ivlen+$sha2len);
$original_plaintext = openssl_decrypt($ciphertext_raw, $cipher, $key, $options=OPENSSL_RAW_DATA, $iv);
$calcmac = hash_hmac('sha256', $ciphertext_raw, $key, $as_binary=true);
if (hash_equals($hmac, $calcmac))//PHP 5.6+ timing attack safe comparison
{
    echo $original_plaintext."\n";
}
?>

Exemple n° 3

Sur la base des exemples ci-dessus, j'ai modifié le code suivant qui vise à crypter l'identifiant de session de l'utilisateur :

class Session {

  /**
   * Encrypts the session ID and returns it as a base 64 encoded string.
   *
   * @param $session_id
   * @return string
   */
  public function encrypt($session_id) {
    // Get the MD5 hash salt as a key.
    $key = $this->_getSalt();
    // For an easy iv, MD5 the salt again.
    $iv = $this->_getIv();
    // Encrypt the session ID.
    $encrypt = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $session_id, MCRYPT_MODE_CBC, $iv);
    // Base 64 encode the encrypted session ID.
    $encryptedSessionId = base64_encode($encrypt);
    // Return it.
    return $encryptedSessionId;
  }

  /**
   * Decrypts a base 64 encoded encrypted session ID back to its original form.
   *
   * @param $encryptedSessionId
   * @return string
   */
  public function decrypt($encryptedSessionId) {
    // Get the MD5 hash salt as a key.
    $key = $this->_getSalt();
    // For an easy iv, MD5 the salt again.
    $iv = $this->_getIv();
    // Decode the encrypted session ID from base 64.
    $decoded = base64_decode($encryptedSessionId);
    // Decrypt the string.
    $decryptedSessionId = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $decoded, MCRYPT_MODE_CBC, $iv);
    // Trim the whitespace from the end.
    $session_id = rtrim($decryptedSessionId, "\0");
    // Return it.
    return $session_id;
  }

  public function _getIv() {
    return md5($this->_getSalt());
  }

  public function _getSalt() {
    return md5($this->drupal->drupalGetHashSalt());
  }

}

dans :

class Session {

  const SESS_CIPHER = 'aes-128-cbc';

  /**
   * Encrypts the session ID and returns it as a base 64 encoded string.
   *
   * @param $session_id
   * @return string
   */
  public function encrypt($session_id) {
    // Get the MD5 hash salt as a key.
    $key = $this->_getSalt();
    // For an easy iv, MD5 the salt again.
    $iv = $this->_getIv();
    // Encrypt the session ID.
    $ciphertext = openssl_encrypt($session_id, self::SESS_CIPHER, $key, $options=OPENSSL_RAW_DATA, $iv);
    // Base 64 encode the encrypted session ID.
    $encryptedSessionId = base64_encode($ciphertext);
    // Return it.
    return $encryptedSessionId;
  }

  /**
   * Decrypts a base 64 encoded encrypted session ID back to its original form.
   *
   * @param $encryptedSessionId
   * @return string
   */
  public function decrypt($encryptedSessionId) {
    // Get the Drupal hash salt as a key.
    $key = $this->_getSalt();
    // Get the iv.
    $iv = $this->_getIv();
    // Decode the encrypted session ID from base 64.
    $decoded = base64_decode($encryptedSessionId, TRUE);
    // Decrypt the string.
    $decryptedSessionId = openssl_decrypt($decoded, self::SESS_CIPHER, $key, $options=OPENSSL_RAW_DATA, $iv);
    // Trim the whitespace from the end.
    $session_id = rtrim($decryptedSessionId, '\0');
    // Return it.
    return $session_id;
  }

  public function _getIv() {
    $ivlen = openssl_cipher_iv_length(self::SESS_CIPHER);
    return substr(md5($this->_getSalt()), 0, $ivlen);
  }

  public function _getSalt() {
    return $this->drupal->drupalGetHashSalt();
  }

}

Pour clarifier, le changement ci-dessus n'est pas une véritable conversion puisque les deux cryptages utilisent une taille de bloc différente et des données cryptées différentes. De plus, le remplissage par défaut est différent, MCRYPT_RIJNDAEL ne prend en charge que les remplissages nuls non standard. @zaph


Notes supplémentaires (tirées des commentaires de @zaph) :

  • Rijndael 128 ( MCRYPT_RIJNDAEL_128 ) est équivalent à AES Cependant Rijndael 256 ( MCRYPT_RIJNDAEL_256 ) n'est pas AES-256 car le 256 spécifie une taille de bloc de 256 bits, tandis que le AES n'a qu'une seule taille de bloc : 128-bits. Donc, fondamentalement, Rijndael avec une taille de bloc de 256 bits ( MCRYPT_RIJNDAEL_256 ) a été nommé par erreur en raison des choix de l mcrypt développeurs. @zaph
  • Rijndael avec une taille de bloc de 256 bits peut être moins sûr qu'avec une taille de bloc de 128 bits, car ce dernier a fait l'objet de beaucoup plus d'examens et d'utilisations. Deuxièmement, l'interopérabilité est entravée par le fait que si AES est généralement disponible, Rijndael avec une taille de bloc de 256 bits ne l'est pas.
  • Le chiffrement avec différentes tailles de blocs pour Rijndael produit différentes données chiffrées.

    Par exemple, MCRYPT_RIJNDAEL_256 (non équivalent à AES-256 ) définit une variante différente du chiffrement par blocs de Rijndael avec une taille de 256 bits et une taille de clé basée sur la clé transmise, où aes-256-cbc est Rijndael avec une taille de bloc de 128 bits et une taille de clé de 256 bits. Par conséquent, ils utilisent des tailles de bloc différentes, ce qui produit des données cryptées entièrement différentes, car mcrypt utilise le nombre pour spécifier la taille du bloc, alors qu'OpenSSL utilise le nombre pour spécifier la taille de la clé (AES n'a qu'une seule taille de bloc de 128 bits). Donc, AES est essentiellement Rijndael avec une taille de bloc de 128 bits et des tailles de clé de 128, 192 et 256 bits. Il est donc préférable d'utiliser AES, qui est appelé Rijndael 128 dans OpenSSL.

9voto

Vous pouvez utiliser phpseclib paquet pollyfill. Vous ne pouvez pas utiliser open ssl ou libsodium pour crypter/décrypter avec rijndael 256. Un autre problème, vous n'avez pas besoin de remplacer le code.

1voto

Comme nous l'avons souligné, vous ne devriez pas stocker les mots de passe de vos utilisateurs dans un format qui peut être décrypté. Le cryptage réversible offre aux pirates une voie facile pour découvrir les mots de passe de vos utilisateurs, ce qui va jusqu'à mettre en danger les comptes de vos utilisateurs sur d'autres sites s'ils y utilisent le même mot de passe.

PHP fournit une paire de fonctions puissantes pour le cryptage de hachage aléatoire à sens unique. password_hash() et password_verify() . Le hachage étant automatiquement aléatoire, les pirates n'ont aucun moyen d'utiliser des tables précompilées de hachage de mots de passe pour effectuer une rétro-ingénierie du mot de passe. Définissez le PASSWORD_DEFAULT et les futures versions de PHP utiliseront automatiquement des algorithmes plus puissants pour générer des hachages de mots de passe sans que vous ayez à mettre à jour votre code.

0voto

rqLizard Points 102

Vous devez utiliser openssl_encrypt() fonction.

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