web-dev-qa-db-fra.com

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

L'extension mcrypt est obsolète sera supprimée dans PHP 7.2 en fonction du commentaire posté ici . Je cherche donc un autre moyen de chiffrer 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 moyen de chiffrer les mots de passe. Le mot de passe chiffré devrait bien entendu être pris en charge par PHP 7.xx et devrait également être déchiffrable car mes clients souhaitent pouvoir récupérer le contenu de l'option. leurs mots de passe sans en générer un nouveau.

84
Piet

Il est recommandé de hacher les mots de passe afin qu'ils ne soient pas déchiffrables. Cela complique un peu les choses pour les attaquants ayant pu accéder à votre base de données ou à vos fichiers.

Si vous devez chiffrer vos données et les faire déchiffrer, un guide sur le chiffrement/déchiffrement sécurisé est disponible à l'adresse https://paragonie.com/white-paper/2015-secure-php-data- cryptage . Pour résumer ce lien:

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

Comme suggéré par @ rqLizard , vous pouvez utiliser les fonctions openssl_encrypt / openssl_decrypt PHP qui offre une alternative bien meilleure à implémenter AES (The Advanced Encryption Standard), également appelé cryptage Rijndael.

Selon ce qui suit le commentaire de Scott sur php.net :

Si vous écrivez du code pour chiffrer/chiffrer des données en 2015, vous devez utiliser openssl_encrypt() et openssl_decrypt(). La bibliothèque sous-jacente (libmcrypt) est abandonnée depuis 2007 et son fonctionnement est bien pire que celui d’OpenSSL (qui tire parti de _AES-NI_ sur les processeurs modernes et qui est sûr pour la synchronisation du cache).

De plus, _MCRYPT_RIJNDAEL_256_ n'est pas _AES-256_, c'est une variante différente du chiffrement par blocs de Rijndael. Si vous voulez _AES-256_ dans mcrypt, vous devez utiliser _MCRYPT_RIJNDAEL_128_ avec une clé de 32 octets. OpenSSL indique plus clairement le mode que vous utilisez (c'est-à-dire _aes-128-cbc_ vs _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 par Oracle que de le faire par OpenSSL.

Enfin, si vous n’authentifiez pas vos textes chiffrés (Encrypt Then MAC), vous le faites mal.

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 n ° 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 n ° 3

Sur la base des exemples ci-dessus, j'ai modifié le code suivant afin de chiffrer 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, la modification ci-dessus n'est pas une conversion vraie, car le cryptage deux utilise 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 le remplissage nul non standard. @ zaph


Notes complé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 que AES n'a qu'un seul bloc: 128 -morceaux. Donc, fondamentalement, Rijndael avec une taille de bloc de 256 bits (_MCRYPT_RIJNDAEL_256_) a été nommé par erreur en raison des choix des développeurs mcrypt@zaph
  • Rijndael avec une taille de bloc de 256 peut être moins sécurisé que 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 par le fait qu’AES est généralement disponible, contrairement à Rijndael avec une taille de bloc de 256 bits.
  • Le cryptage avec différentes tailles de bloc pour Rijndael produit différentes données cryptées.

    Par exemple, _MCRYPT_RIJNDAEL_256_ (non é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é passée, où _aes-256-cbc_ est 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 blocs différentes qui produisent des données cryptées totalement différentes, mcrypt utilisant le nombre pour spécifier la taille du bloc, OpenSSL l’utilisant pour indiquer 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 une taille de clé de 128, 192 et 256 bits. Par conséquent, il est préférable d'utiliser AES, appelé Rijndael 128 dans OpenSSL.

23
kenorb

Une implémentation pure PHP de Rijndael existe avec le paquet 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 (chiffre, mode, taille de la clé, taille en bits). 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 était base64_decoded

7
Pentium10

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

5

Vous devez utiliser OpenSSL sur mcrypt car 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 PKCS7 padding par défaut, mais vous pouvez spécifier OPENSSL_ZERO_PADDING si vous en avez besoin. Pour utiliser une clé binaire de 32 octets, vous pouvez spécifier aes-256-cbc, ce qui est beaucoup plus évident que MCRYPT_RIJNDAEL_128.

Voici l'exemple de code utilisant Mcrypt:

Bibliothèque de chiffrement non authentifié AES-256-CBC écrite en Mcrypt avec un 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 avec 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 .

3
kenorb

Comme indiqué, vous ne devez pas stocker les mots de passe de vos utilisateurs dans un format déchiffrable. Le chiffrement réversible permet aux pirates de rechercher facilement les mots de passe de vos utilisateurs. Cela implique également de mettre en péril 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 chiffrement de hachage unidirectionnel salé de manière aléatoire - password_hash() et password_verify(). Étant donné que le hachage est automatiquement salé de manière aléatoire, les pirates informatiques n'ont aucun moyen d'utiliser des tables précompilées de hachages de mots de passe pour procéder à un reverse engineering du mot de passe. Définissez l'option PASSWORD_DEFAULT et les versions futures 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.

1

Vous devriez utiliser la fonction openssl_encrypt() .

1
rqLizard

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

Pour chiffrer:

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

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/

1
Ariston Cordeiro

J'ai pu traduire mon objet Crypto

  • Obtenez une copie de php avec mcrypt pour déchiffrer les anciennes données. Je suis allé à 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 faire installer). Je pense que je devais aussi ajouter la ligne extenstion = mcrypt.so au fichier php.ini. Une série de scripts pour créer des versions intermédiaires des données avec toutes les données non chiffrées.

  • Construire 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 chiffrer (à l'aide d'une clé publique), utilisez openssl_seal. D'après ce que j'ai lu, openssl_encrypt à l'aide d'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.

  • Décrypter (en utilisant une 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>";
    

P.S. Vous ne pouvez pas chiffrer la chaîne vide ("")

P.P.S. Ceci est pour une base de données de mot de passe pas pour la validation de l'utilisateur.

0
Joshua Goldstein