mcrypt è deprecato, qual è l'alternativa?


103

L'estensione mcrypt è deprecata verrà rimossa in PHP 7.2 secondo il commento pubblicato qui . Quindi sto cercando un modo alternativo per crittografare le password.

In questo momento sto usando qualcosa di simile

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

Ho bisogno della tua opinione per il modo migliore / più sicuro per crittografare le password, la password crittografata dovrebbe ovviamente essere supportata da PHP 7.xx e dovrebbe anche essere decrittografabile perché i miei clienti vogliono avere un'opzione per `` recuperare '' le loro password senza generarne una nuova uno.


9
Perché hai bisogno di crittografare / decrittografare le password? Perché non solo hash password_hashe verificarli con password_verify?
Niente panico

3
"anche la password crittografata dovrebbe essere decifrabile" - perché? non sembra troppo sicuro. Qualche motivo speciale?
Funk Forty Niner

24
"perché i miei clienti vogliono avere la possibilità di" recuperare "le loro password senza generarne una nuova." - Non è sicuro e dovrebbe invece essere data loro la possibilità di reimpostare le password.
Funk Forty Niner

4
Non crittografare le password , quando l'aggressore ottiene il DB riceverà anche la chiave di crittografia. Itera su un HMAC con un sale casuale per circa 100 ms e salva il sale con l'hash. Usa funzioni come password_hash, PBKDF2, Bcrypt e funzioni simili. Il punto è che l'attaccante dedichi molto tempo alla ricerca delle password con la forza bruta.
Zaph

2
Dal manuale php -> Questa funzione è stata DEPRECATA da PHP 7.1.0. Affidarsi a questa funzione è altamente sconsigliato. Alternativa è sodio -> php.net/manual/en/book.sodium.php
MarcoZen

Risposte:


47

È consigliabile eseguire l'hash delle password in modo che non siano decrittografabili. Ciò rende le cose leggermente più difficili per gli aggressori che potrebbero aver ottenuto l'accesso al database o ai file.

Se devi crittografare i tuoi dati e renderli decrittografabili, una guida per la crittografia / decrittografia sicura è disponibile su https://paragonie.com/white-paper/2015-secure-php-data-encryption . Per riassumere quel collegamento:

  • Usa Libsodium : un'estensione PHP
  • Se non puoi usare Libsodium, usa defuse / php-encryption - Straight PHP code
  • Se non puoi usare Libsodium o disinnescare / php-encryption, usa OpenSSL - Molti server lo avranno già installato. In caso contrario, può essere compilato con --with-openssl [= DIR]

1
Dovrei prima provare openssl perché è molto comune, dove libsodium non lo è. Il php grezzo non dovrebbe essere usato a meno che tutte le estensioni native non siano fuori in caso di domanda
JSON

anche se openssl è molto comune, sembra che php 7 utilizzerà libsodium per la sua crittografia di base securityintelligence.com/news/…
shadi

1
Nota che c'è una libreria chiamata Sodium-compat( github.com/paragonie/sodium_compat ) che funziona in PHP> = 5.2.4
RaelB

30

Come suggerito da @rqLizard , puoi invece usare le funzioni openssl_encrypt/ openssl_decryptPHP che forniscono un'alternativa molto migliore per implementare AES (The Advanced Encryption Standard) noto anche come crittografia Rijndael.

Secondo il seguente commento di Scott su php.net :

Se stai scrivendo codice per crittografare / crittografare i dati nel 2015, dovresti usare openssl_encrypt()e openssl_decrypt(). La libreria sottostante ( libmcrypt) è stata abbandonata dal 2007 e ha prestazioni di gran lunga peggiori di OpenSSL (che utilizzaAES-NI processori moderni ed è sicura per il timing della cache).

Inoltre, MCRYPT_RIJNDAEL_256non lo è AES-256, è una variante diversa del cifrario a blocchi Rijndael. Se si desidera AES-256in mcrypt, è necessario utilizzare MCRYPT_RIJNDAEL_128con una chiave a 32 byte. OpenSSL rende più ovvio quale modalità stai utilizzando (cioè aes-128-cbcvs aes-256-ctr).

OpenSSL utilizza anche il riempimento PKCS7 con la modalità CBC piuttosto che il riempimento dei byte NULL di mcrypt. Pertanto, è più probabile che mcrypt renda il tuo codice vulnerabile agli attacchi di padding Oracle rispetto a OpenSSL.

Infine, se non stai autenticando i tuoi testi cifrati (Encrypt Then MAC), stai sbagliando.

Ulteriore lettura:

Esempi di codice

Esempio 1

Esempio di crittografia autenticata AES in modalità GCM per 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";
}
?>

Esempio n. 2

Esempio di crittografia autenticata AES per 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";
}
?>

Esempio n. 3

Sulla base degli esempi precedenti, ho modificato il seguente codice che mira a crittografare l'ID di sessione dell'utente:

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

}

in:

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

}

Per chiarire, la modifica di cui sopra non è una vera conversione poiché le due crittografie utilizzano una dimensione di blocco diversa e dati crittografati diversi. Inoltre, il riempimento predefinito è diverso, MCRYPT_RIJNDAELsupporta solo il riempimento nullo non standard. @zaph


Note aggiuntive (dai commenti di @zaph):

  • Rijndael 128 ( MCRYPT_RIJNDAEL_128) è equivalente ad AES , tuttavia Rijndael 256 ( MCRYPT_RIJNDAEL_256) non è AES-256 poiché 256 specifica una dimensione del blocco di 256 bit, mentre AES ha solo una dimensione del blocco: 128 bit. Quindi fondamentalmente Rijndael con una dimensione del blocco di 256 bit ( MCRYPT_RIJNDAEL_256) è stato erroneamente chiamato a causa delle scelte degli sviluppatori di mcrypt . @zaph
  • Rijndael con una dimensione del blocco di 256 potrebbe essere meno sicuro rispetto a una dimensione del blocco di 128 bit perché quest'ultimo ha avuto molte più revisioni e utilizzi. In secondo luogo, l'interoperabilità è ostacolata dal fatto che mentre AES è generalmente disponibile, mentre Rijndael con una dimensione del blocco di 256 bit non lo è.
  • La crittografia con blocchi di dimensioni diverse per Rijndael produce dati crittografati diversi.

    Ad esempio, MCRYPT_RIJNDAEL_256(non equivalente a AES-256) definisce una variante diversa del codice a blocchi Rijndael con dimensione di 256 bit e una dimensione della chiave basata sulla chiave passata, dove aes-256-cbcè Rijndael con una dimensione del blocco di 128 bit con una dimensione della chiave di 256 bit. Pertanto stanno utilizzando blocchi di dimensioni diverse che producono dati crittografati completamente diversi poiché mcrypt utilizza il numero per specificare la dimensione del blocco, dove OpenSSL ha utilizzato il numero per specificare la dimensione della chiave (AES ha solo una dimensione del blocco di 128 bit). Quindi fondamentalmente AES è Rijndael con una dimensione del blocco di 128 bit e dimensioni della chiave di 128, 192 e 256 bit. Quindi è meglio usare AES, che si chiama Rijndael 128 in OpenSSL.


1
In generale, l'utilizzo di Rijndael con una dimensione del blocco di 256 bit è un errore dovuto alle scelte degli sviluppatori di mcrypt. Inoltre Rijndael con una dimensione del blocco di 256 potrebbe essere meno sicuro che con una dimensione del blocco di 128 bit perché quest'ultimo ha avuto molte più revisioni e utilizzo. Inoltre l'interoperabilità è ostacolata in quanto, sebbene AES sia generalmente disponibile, Rijndael con una dimensione del blocco di 256 bit non lo è.
Zaf

Perché lo fai $session_id = rtrim($decryptedSessionId, "\0");? È possibile openssl_decryptrestituire alcuni personaggi indesiderati alla fine? Cosa succede se la variabile crittografata termina con 0 (cioè encrypt("abc0")?
hlscalon

@hiscalon "\0"non è "0"che il carattere NULL, il cui codice ASCII è 0x00 (0 esadecimale).
kiamlaluno,

11

L'implementazione Pure-PHP di Rijndael esiste con phpseclib disponibile come pacchetto compositore e funziona su PHP 7.3 (testato da me).

C'è una pagina nella documentazione di phpseclib, che genera codice di esempio dopo aver inserito le variabili di base (cifratura, modalità, dimensione della chiave, dimensione del bit). Emette quanto segue per Rijndael, ECB, 256, 256:

un codice con mycrypt

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

funziona in questo modo con la libreria

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

* $termerabase64_decoded


11

Come dettagliato da altre risposte qui, la migliore soluzione che ho trovato è usare OpenSSL. È integrato in PHP e non è necessaria alcuna libreria esterna. Ecco alcuni semplici esempi:

Per crittografare:

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

Per decrittografare:

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

Link di riferimento: https://www.shift8web.ca/2017/04/how-to-encrypt-and-execute-your-php-code-with-mcrypt/


Un sacco di buon karma a te amico! Solo una cosa: se la password, ad esempio, è stata crittografata con il vecchio codice, il nuovo codice decrittografato non sarà in grado di verificarla. Deve essere salvato nuovamente e crittografato con questo nuovo codice.
Lumis

Un semplice script di migrazione risolverebbe il problema. Usa il vecchio modo per decrittografare, quindi il nuovo modo per crittografare e archiviare. L'alternativa è l'aggiunta di un flag alla tabella dell'utente e l'inserimento di script in una reimpostazione forzata della password su tutti gli account utente che richiedono modifiche della password.
cecil merrel aka bringrainfire

8

Puoi usare il pacchetto phpseclib pollyfill. Non è possibile utilizzare open ssl o libsodium per crittografare / decrittografare con rijndael 256. Un altro problema, non è necessario sostituire alcun codice.


2
Grazie mille. Ho dovuto rimuovere l'estensione php-mcrypt e quindi funziona a meraviglia.
DannyB

Ho installato mcrypt_compateseguendo composer require phpseclib/mcrypt_compatma continuo a capire PHP Fatal error: Uncaught Error: Call to undefined function mcrypt_get_key_size() in /app/kohana/classes/Kohana/Encrypt.php:124che sto usando php 7.2.26e Kohana framwork. Ci sono altri passaggi da eseguire dopo averlo installato con Composer?
M-Dahab,

Fatto. Devi aggiungere require APPPATH . '/vendor/autoload.php';alla fine di bootstrap.php.
M-Dahab,

3

Dovresti usare OpenSSL mcryptperché è attivamente sviluppato e mantenuto. Fornisce maggiore sicurezza, manutenibilità e portabilità. In secondo luogo esegue la crittografia / decrittografia AES molto più velocemente. Utilizza il riempimento PKCS7 per impostazione predefinita, ma puoi specificare OPENSSL_ZERO_PADDINGse ne hai bisogno. Per utilizzare con una chiave binaria a 32 byte, è possibile specificare aes-256-cbcquale è molto ovvia diMCRYPT_RIJNDAEL_128 .

Ecco l'esempio di codice che utilizza Mcrypt:

Libreria di crittografia AES-256-CBC non autenticata scritta in Mcrypt con riempimento 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');
    }
}

Ed ecco la versione scritta usando 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
        );
    }
}

Fonte: se stai digitando la parola MCRYPT nel tuo codice PHP, stai sbagliando .


2

Lo sto usando su PHP 7.2.x, funziona bene per me:

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

e quindi autenticare l'hash con la seguente funzione:

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

Esempio:

  //create hash from user string

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

e per autenticare questo hash utilizzare il codice seguente:

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

È tutto.


1

Come sottolineato, non dovresti memorizzare le password dei tuoi utenti in un formato decifrabile. La crittografia reversibile fornisce agli hacker un percorso facile per scoprire le password dei tuoi utenti, il che si estende a mettere a rischio gli account dei tuoi utenti su altri siti se usano la stessa password lì.

PHP fornisce un paio di potenti funzioni per la crittografia hash unidirezionale con sale casuale password_hash()e password_verify(). Poiché l'hash viene automaticamente salato in modo casuale, non c'è modo per gli hacker di utilizzare tabelle precompilate di hash delle password per decodificare la password. Imposta l' PASSWORD_DEFAULTopzione e le versioni future di PHP useranno automaticamente algoritmi più potenti per generare hash delle password senza che tu debba aggiornare il tuo codice.



0

Sono stato in grado di tradurre il mio oggetto Crypto

  • Ottieni una copia di php con mcrypt per decrittografare i vecchi dati. Sono andato su http://php.net/get/php-7.1.12.tar.gz/from/a/mirror , l'ho compilato, quindi ho aggiunto l'estensione ext / mcrypt (configure; make; make install). Penso di aver dovuto aggiungere anche la riga extenstion = mcrypt.so al php.ini. Una serie di script per creare versioni intermedie dei dati con tutti i dati non crittografati.

  • Crea una chiave pubblica e una privata per openssl

    openssl genrsa -des3 -out pkey.pem 2048
    (set a password)
    openssl rsa -in pkey.pem -out pkey-pub.pem -outform PEM -pubout
  • Per crittografare (usando la chiave pubblica) usa openssl_seal. Da quello che ho letto, openssl_encrypt utilizzando una chiave RSA è limitato a 11 byte in meno rispetto alla lunghezza della chiave (vedere http://php.net/manual/en/function.openssl-public-encrypt.php commento di 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]);

Probabilmente potresti memorizzare il file binario grezzo.

  • Per decrittografare (utilizzando la chiave privata)

    $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 Non puoi crittografare la stringa vuota ("")

PPS Questo è per un database di password non per la convalida dell'utente.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.