mcrypt est obsolète, quelle est l'alternative?


103

L'extension mcrypt est obsolète sera supprimée dans PHP 7.2 selon le commentaire publié ici . Je recherche donc une autre façon de crypter les mots de passe.

En ce moment, j'utilise quelque chose comme

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

J'ai besoin de votre avis pour le meilleur / le plus fort moyen de crypter les mots de passe, le mot de passe crypté doit bien sûr être pris en charge par PHP 7.xx et doit également être décryptable car mes clients veulent avoir une option pour `` récupérer '' leurs mots de passe sans générer de nouveau une.


9
Pourquoi avez-vous besoin de crypter / décrypter les mots de passe? Pourquoi ne pas simplement les hacher password_hashet les vérifier avec password_verify?
Don't Panic

3
"le mot de passe chiffré devrait également être déchiffrable" - pourquoi? ne semble pas trop sûr. Une raison particulière?
Funk Forty Niner

24
"parce que mes clients veulent avoir la possibilité de 'récupérer' leurs mots de passe sans en générer un nouveau." - Ce n'est pas sûr et ils devraient avoir la possibilité de réinitialiser leurs mots de passe à la place.
Funk Forty Niner

4
Ne cryptez pas les mots de passe , lorsque l'attaquant obtient la base de données, il obtient également la clé de cryptage. Itérez sur un HMAC avec un sel aléatoire pendant environ 100 ms et enregistrez le sel avec le hachage. Utilisez des fonctions telles que password_hash, PBKDF2, Bcrypt et des fonctions similaires. Le but est de faire en sorte que l'attaquant passe beaucoup de temps à trouver des mots de passe par force brute.
zaph

2
Depuis le manuel php -> Cette fonction est obsolète depuis PHP 7.1.0. Il est fortement déconseillé de se fier à cette fonction. L'alternative est le sodium -> php.net/manual/en/book.sodium.php
MarcoZen

Réponses:


47

Il est recommandé de hacher les mots de passe afin qu'ils ne soient pas déchiffrables. Cela rend les choses légèrement plus difficiles pour les attaquants qui peuvent avoir eu accès à votre base de données ou à vos fichiers.

Si vous devez crypter vos données et les faire déchiffrer, un guide de cryptage / décryptage sécurisé est disponible sur 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 simple
  • Si vous ne pouvez pas utiliser Libsodium ou defuse / php-encryption, utilisez OpenSSL - De nombreux serveurs l'ont déjà installé. Sinon, il peut être compilé avec --with-openssl [= DIR]

1
Devrait d'abord essayer openssl car c'est très courant, là où libsodium ne l'est pas. Le php brut ne doit pas être utilisé à moins que toutes les extensions natives ne soient pas disponibles en cas de question
JSON

même si openssl est très courant, il semble que php 7 utilisera libsodium pour sa cryptographie de base securityintelligence.com/news/…
shadi

1
Notez qu'il existe une bibliothèque appelée Sodium-compat( github.com/paragonie/sodium_compat ) qui fonctionne en PHP> = 5.2.4
RaelB

30

Comme suggéré par @rqLizard , vous pouvez utiliser les fonctions openssl_encrypt/ openssl_decryptPHP à la place, ce qui offre une bien meilleure alternative pour implémenter AES (The Advanced Encryption Standard) également connu sous le nom de cryptage Rijndael.

Selon le commentaire suivant de Scott sur php.net :

Si vous écrivez du code pour crypter / crypter des données en 2015, vous devez utiliser openssl_encrypt()et openssl_decrypt(). La bibliothèque sous-jacente ( libmcrypt) a été abandonnée depuis 2007, et fonctionne bien moins bien qu'OpenSSL (qui s'appuie AES-NIsur les processeurs modernes et est sûr pour la synchronisation du cache).

Ce MCRYPT_RIJNDAEL_256n'est pas non AES-256plus une variante différente du chiffrement par blocs de Rijndael. Si vous voulez AES-256en mcrypt, vous devez utiliser MCRYPT_RIJNDAEL_128avec une clé de 32 octets. OpenSSL rend plus évident le mode que vous utilisez (c.-à-d. aes-128-cbcVs aes-256-ctr).

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

Enfin, si vous n'authentifiez pas vos textes chiffrés (Encrypt Then MAC), vous vous trompez.

Lectures complémentaires:

Exemples de code

Exemple 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 # 2

Exemple de chiffrement authentifié AES pour 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 # 3

Sur la base des exemples ci-dessus, j'ai changé le code suivant qui vise à chiffrer l'ID 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 vraie conversion car les deux cryptages utilisent une taille de bloc différente et des données cryptées différentes. En outre, le remplissage par défaut est différent, MCRYPT_RIJNDAELprend uniquement en charge le remplissage nul non standard. @zaph


Notes supplémentaires (à partir 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, alors qu'AES n'a qu'une taille de bloc: 128 bits. Donc, fondamentalement, Rijndael avec une taille de bloc de 256 bits ( MCRYPT_RIJNDAEL_256) a été nommé à tort en raison des choix des développeurs mcrypt . @zaph
  • Rijndael avec une taille de bloc de 256 peut être moins sûr qu'avec une taille de bloc de 128 bits car ce dernier a eu beaucoup plus de critiques et d'utilisations. Deuxièmement, l'interopérabilité est entravée dans la mesure où AES est généralement disponible, alors que Rijndael avec une taille de bloc de 256 bits ne l'est pas.
  • Le chiffrement avec différentes tailles de bloc pour Rijndael produit différentes données chiffrées.

    Par exemple, MCRYPT_RIJNDAEL_256(pas équivalent à AES-256) définit une variante différente du chiffrement par blocs Rijndael avec une taille de 256 bits et une taille de clé basée sur la clé transmise, où aes-256-cbcest Rijndael avec une taille de bloc de 128 bits avec une taille de clé de 256 bits. Par conséquent, ils utilisent des tailles de bloc différentes qui produisent des données chiffrées entièrement différentes car mcrypt utilise le nombre pour spécifier la taille de bloc, où OpenSSL a utilisé le nombre pour spécifier la taille de la clé (AES n'a qu'une taille de bloc de 128 bits). Donc, fondamentalement, AES est Rijndael avec une taille de bloc de 128 bits et des tailles de clé de 128, 192 et 256 bits. Par conséquent, il est préférable d'utiliser AES, qui s'appelle Rijndael 128 dans OpenSSL.


1
En général, l'utilisation de Rijndael avec une taille de bloc de 256 bits est une erreur due aux choix des développeurs mcrypt. De plus, Rijndael avec une taille de bloc de 256 peut être moins sûr qu'avec une taille de bloc de 128 bits car ce dernier a été beaucoup plus examiné et utilisé. De plus, l'interopérabilité est entravée en ce que, bien qu'AES soit généralement disponible, Rijndael avec une taille de bloc de 256 bits ne l'est pas.
zaph

Pourquoi fais-tu $session_id = rtrim($decryptedSessionId, "\0");? Est-il possible openssl_decryptde renvoyer des caractères indésirables à la fin? Que faire si la variable chiffrée se termine par 0 (c'est encrypt("abc0")-à- dire ?
hlscalon

@hiscalon "\0"n'est pas "0"mais le caractère NULL, dont le code ASCII est 0x00 (hexadécimal 0).
kiamlaluno le

11

L'implémentation Pure-PHP de Rijndael existe avec phpseclib disponible en tant que package composer et fonctionne sur PHP 7.3 (testé par moi).

Il y a une page sur la documentation phpseclib, qui génère un exemple de code après avoir entré les variables de base (chiffrement, mode, taille de clé, taille de bit). Il génère les éléments suivants pour Rijndael, ECB, 256, 256:

un code avec mycrypt

$decoded = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, ENCRYPT_KEY, $term, MCRYPT_MODE_ECB);

fonctionne comme ça avec la bibliothèque

$rijndael = new \phpseclib\Crypt\Rijndael(\phpseclib\Crypt\Rijndael::MODE_ECB);
$rijndael->setKey(ENCRYPT_KEY);
$rijndael->setKeyLength(256);
$rijndael->disablePadding();
$rijndael->setBlockLength(256);

$decoded = $rijndael->decrypt($term);

* $termétaitbase64_decoded


11

Comme détaillé par d'autres réponses ici, la meilleure solution que j'ai trouvée est d'utiliser OpenSSL. Il est intégré à PHP et vous n'avez besoin d'aucune bibliothèque externe. Voici des exemples simples:

Pour crypter:

function encrypt($key, $payload) {
  $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
  $encrypted = openssl_encrypt($payload, 'aes-256-cbc', $key, 0, $iv);
  return base64_encode($encrypted . '::' . $iv);
}

Pour décrypter:

function decrypt($key, $garble) {
    list($encrypted_data, $iv) = explode('::', base64_decode($garble), 2);
    return openssl_decrypt($encrypted_data, 'aes-256-cbc', $key, 0, $iv);
}

Lien de référence: https://www.shift8web.ca/2017/04/how-to-encrypt-and-execute-your-php-code-with-mcrypt/


Beaucoup de bon karma à ton ami! Juste une chose: si le mot de passe, par exemple, a été chiffré avec l'ancien code, le nouveau code de déchiffrement ne pourra pas le vérifier. Il doit être réenregistré et chiffré avec ce nouveau code.
Lumis

Un simple script de migration résoudrait ce problème. Utilisez l'ancienne méthode pour décrypter puis la nouvelle façon de crypter et de stocker. L'alternative consiste à ajouter un indicateur à la table de l'utilisateur et à créer des scripts dans une réinitialisation forcée du mot de passe sur tous les comptes d'utilisateurs nécessitant des changements de mot de passe.
cecil merrel aka bringrainfire

8

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


2
Cela a été très utile merci. J'ai dû supprimer l'extension php-mcrypt, puis cela fonctionne comme un charme.
DannyB

J'ai installé mcrypt_compaten exécutant composer require phpseclib/mcrypt_compatmais j'obtiens toujours que PHP Fatal error: Uncaught Error: Call to undefined function mcrypt_get_key_size() in /app/kohana/classes/Kohana/Encrypt.php:124j'utilise le 7.2.26framwork php et Kohana. Y a-t-il d'autres étapes à effectuer après son installation avec composer?
M-Dahab

Je l'ai. Vous devez ajouter require APPPATH . '/vendor/autoload.php';au bas de bootstrap.php.
M-Dahab

3

Vous devriez utiliser OpenSSL plus mcryptcar il est activement développé et maintenu. Il offre une meilleure sécurité, maintenabilité et portabilité. Deuxièmement, il effectue le cryptage / décryptage AES beaucoup plus rapidement. Il utilise le remplissage PKCS7 par défaut, mais vous pouvez spécifier OPENSSL_ZERO_PADDINGsi vous en avez besoin. Pour utiliser avec une clé binaire de 32 octets, vous pouvez spécifier aes-256-cbcce qui est beaucoup plus évident que MCRYPT_RIJNDAEL_128.

Voici l'exemple de code utilisant Mcrypt:

Bibliothèque de chiffrement AES-256-CBC non authentifiée écrite en Mcrypt avec remplissage PKCS7.

/**
 * This library is unsafe because it does not MAC after encrypting
 */
class UnsafeMcryptAES
{
    const CIPHER = MCRYPT_RIJNDAEL_128;

    public static function encrypt($message, $key)
    {
        if (mb_strlen($key, '8bit') !== 32) {
            throw new Exception("Needs a 256-bit key!");
        }
        $ivsize = mcrypt_get_iv_size(self::CIPHER);
        $iv = mcrypt_create_iv($ivsize, MCRYPT_DEV_URANDOM);

        // Add PKCS7 Padding
        $block = mcrypt_get_block_size(self::CIPHER);
        $pad = $block - (mb_strlen($message, '8bit') % $block, '8bit');
        $message .= str_repeat(chr($pad), $pad);

        $ciphertext = mcrypt_encrypt(
            MCRYPT_RIJNDAEL_128,
            $key,
            $message,
            MCRYPT_MODE_CBC,
            $iv
        );

        return $iv . $ciphertext;
    }

    public static function decrypt($message, $key)
    {
        if (mb_strlen($key, '8bit') !== 32) {
            throw new Exception("Needs a 256-bit key!");
        }
        $ivsize = mcrypt_get_iv_size(self::CIPHER);
        $iv = mb_substr($message, 0, $ivsize, '8bit');
        $ciphertext = mb_substr($message, $ivsize, null, '8bit');

        $plaintext = mcrypt_decrypt(
            MCRYPT_RIJNDAEL_128,
            $key,
            $ciphertext,
            MCRYPT_MODE_CBC,
            $iv
        );

        $len = mb_strlen($plaintext, '8bit');
        $pad = ord($plaintext[$len - 1]);
        if ($pad <= 0 || $pad > $block) {
            // Padding error!
            return false;
        }
        return mb_substr($plaintext, 0, $len - $pad, '8bit');
    }
}

Et voici la version écrite en utilisant OpenSSL:

/**
 * This library is unsafe because it does not MAC after encrypting
 */
class UnsafeOpensslAES
{
    const METHOD = 'aes-256-cbc';

    public static function encrypt($message, $key)
    {
        if (mb_strlen($key, '8bit') !== 32) {
            throw new Exception("Needs a 256-bit key!");
        }
        $ivsize = openssl_cipher_iv_length(self::METHOD);
        $iv = openssl_random_pseudo_bytes($ivsize);

        $ciphertext = openssl_encrypt(
            $message,
            self::METHOD,
            $key,
            OPENSSL_RAW_DATA,
            $iv
        );

        return $iv . $ciphertext;
    }

    public static function decrypt($message, $key)
    {
        if (mb_strlen($key, '8bit') !== 32) {
            throw new Exception("Needs a 256-bit key!");
        }
        $ivsize = openssl_cipher_iv_length(self::METHOD);
        $iv = mb_substr($message, 0, $ivsize, '8bit');
        $ciphertext = mb_substr($message, $ivsize, null, '8bit');

        return openssl_decrypt(
            $ciphertext,
            self::METHOD,
            $key,
            OPENSSL_RAW_DATA,
            $iv
        );
    }
}

Source: Si vous tapez le mot MCRYPT dans votre code PHP, vous le faites mal .


2

J'utilise ceci sur PHP 7.2.x, cela fonctionne bien pour moi:

public function make_hash($userStr){
        try{
            /** 
             * Used and tested on PHP 7.2x, Salt has been removed manually, it is now added by PHP 
             */
             return password_hash($userStr, PASSWORD_BCRYPT);
            }catch(Exception $exc){
                $this->tempVar = $exc->getMessage();
                return false;
            }
        }

puis authentifiez le hachage avec la fonction suivante:

public function varify_user($userStr,$hash){
        try{
            if (password_verify($userStr, $hash)) {
                 return true;
                }
            else {
                return false;
                }
            }catch(Exception $exc){
                $this->tempVar = $exc->getMessage();
                return false;
            }
        }

Exemple:

  //create hash from user string

 $user_password = $obj->make_hash2($user_key);

et pour authentifier ce hachage, utilisez le code suivant:

if($obj->varify_user($key, $user_key)){
      //this is correct, you can proceed with  
    }

C'est tout.


1

Comme indiqué, vous ne devez pas stocker les mots de passe de vos utilisateurs dans un format décryptable. Le cryptage réversible permet aux pirates de trouver facilement 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 utilisent le même mot de passe.

PHP fournit une paire de fonctions puissantes pour le chiffrement de hachage unidirectionnel à sel aléatoire - password_hash()et password_verify(). Étant donné que le hachage est automatiquement salé de manière aléatoire, il n'y a aucun moyen pour les pirates d'utiliser des tables précompilées de hachages de mots de passe pour rétroconcevoir le mot de passe. Définissez l' PASSWORD_DEFAULToption 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.



0

J'ai pu traduire mon objet Crypto

  • Obtenez une copie de php avec mcrypt pour décrypter les anciennes données. Je suis allé sur http://php.net/get/php-7.1.12.tar.gz/from/a/mirror , je l'ai compilé, puis j'ai ajouté l'extension ext / mcrypt (configure; make; make install). Je pense que j'ai dû ajouter la ligne extenstion = mcrypt.so au php.ini également. Une série de scripts pour créer des versions intermédiaires des données avec toutes les données non chiffrées.

  • Créer une clé publique et privée pour openssl

    openssl genrsa -des3 -out pkey.pem 2048
    (set a password)
    openssl rsa -in pkey.pem -out pkey-pub.pem -outform PEM -pubout
  • Pour crypter (à l'aide de la clé publique), utilisez openssl_seal. D'après ce que j'ai lu, openssl_encrypt utilisant une clé RSA est limité à 11 octets de moins que la longueur de la clé (voir http://php.net/manual/en/function.openssl-public-encrypt.php commentaire de Thomas Horsten)

    $pubKey = openssl_get_publickey(file_get_contents('./pkey-pub.pem'));
    openssl_seal($pwd, $sealed, $ekeys, [ $pubKey ]);
    $encryptedPassword = base64_encode($sealed);
    $key = base64_encode($ekeys[0]);

Vous pourriez probablement stocker le binaire brut.

  • Pour déchiffrer (à l'aide de la clé privée)

    $passphrase="passphrase here";
    $privKey = openssl_get_privatekey(file_get_contents('./pkey.pem'), $passphrase);
    // I base64_decode() from my db columns
    openssl_open($encryptedPassword, $plain, $key, $privKey);
    echo "<h3>Password=$plain</h3>";

PS Vous ne pouvez pas crypter la chaîne vide ("")

PPS Ceci est pour une base de données de mots de passe et non pour la validation de l'utilisateur.

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.