mcrypt está en desuso, ¿cuál es la alternativa?

Resuelto Piet asked hace 54 años • 10 respuestas

La extensión mcrypt está en desuso y se eliminará en PHP 7.2 según el comentario publicado aquí . Por eso estoy buscando una forma alternativa de cifrar contraseñas.

Ahora mismo estoy usando algo como

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

Necesito su opinión sobre cuál es la mejor y más segura forma de cifrar contraseñas. La contraseña cifrada, por supuesto, debería ser compatible con PHP 7.xx y también debería ser descifrable porque mis clientes quieren tener la opción de "recuperar" sus contraseñas sin generar una nueva. uno.

Piet avatar Jan 01 '70 08:01 Piet
Aceptado

Es una buena práctica codificar las contraseñas para que no se puedan descifrar. Esto dificulta un poco las cosas para los atacantes que pueden haber obtenido acceso a su base de datos o archivos.

Si debe cifrar sus datos y hacerlos descifrables, hay una guía para cifrar/descifrar de forma segura disponible en https://paragonie.com/white-paper/2015-secure-php-data-encryption . Para resumir ese enlace:

  • Utilice Libsodium : una extensión PHP
  • Si no puedes usar Libsodium, usa defuse/php-encryption - Código PHP directo
  • Si no puede usar Libsodium o desactivar/php-encryption, use OpenSSL : muchos servidores ya lo tendrán instalado. Si no, se puede compilar con --with-openssl[=DIR]
Phil avatar Dec 21 '2016 22:12 Phil

Como lo sugiere @rqLizard , puede usar funciones openssl_encrypt/ openssl_decryptPHP en su lugar, lo que proporciona una alternativa mucho mejor para implementar AES (Estándar de cifrado avanzado), también conocido como cifrado Rijndael.

Según el siguiente comentario de Scott en php.net :

Si está escribiendo código para cifrar/cifrar datos en 2015, debe usar openssl_encrypt()y openssl_decrypt(). La biblioteca subyacente ( libmcrypt) ha estado abandonada desde 2007 y tiene un rendimiento mucho peor que OpenSSL (que aprovecha los AES-NIprocesadores modernos y es seguro para la sincronización de caché).

Además, MCRYPT_RIJNDAEL_256no lo es AES-256, es una variante diferente del cifrado de bloques de Rijndael. Si lo desea AES-256, mcryptdebe utilizar MCRYPT_RIJNDAEL_128una clave de 32 bytes. OpenSSL hace que sea más obvio qué modo está utilizando (es decir, aes-128-cbcvs aes-256-ctr).

OpenSSL también utiliza el relleno PKCS7 con el modo CBC en lugar del relleno de bytes NULL de mcrypt. Por lo tanto, es más probable que mcrypt haga que su código sea vulnerable a ataques de relleno de Oracle que OpenSSL.

Finalmente, si no está autenticando sus textos cifrados (Cifrar y luego MAC), lo está haciendo mal.

Otras lecturas:

  • Uso correcto del cifrado y la autenticación (para desarrolladores de PHP) .
  • Si está escribiendo la palabra MCRYPT en su código PHP, lo está haciendo mal .

Ejemplos de código

Ejemplo 1

Ejemplo de cifrado autenticado AES en modo GCM para 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";
}
?>

Ejemplo #2

Ejemplo de cifrado autenticado AES para 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";
}
?>

Ejemplo #3

Según los ejemplos anteriores, cambié el siguiente código que tiene como objetivo cifrar la identificación de la sesión del usuario:

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

}

en:

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

}

Para aclarar, el cambio anterior no es una conversión verdadera ya que los dos cifrados utilizan un tamaño de bloque diferente y datos cifrados diferentes. Además, el relleno predeterminado es diferente, MCRYPT_RIJNDAELsolo admite relleno nulo no estándar. @zaph


Notas adicionales (de los comentarios de @zaph):

  • Rijndael 128 ( MCRYPT_RIJNDAEL_128) es equivalente a AES , sin embargo, Rijndael 256 ( MCRYPT_RIJNDAEL_256) no es AES-256 ya que 256 especifica un tamaño de bloque de 256 bits, mientras que AES solo tiene un tamaño de bloque: 128 bits. Básicamente, Rijndael con un tamaño de bloque de 256 bits ( MCRYPT_RIJNDAEL_256) ha sido nombrado erróneamente debido a las elecciones de los desarrolladores de mcrypt . @zaph
  • Rijndael con un tamaño de bloque de 256 puede ser menos seguro que con un tamaño de bloque de 128 bits porque este último ha tenido muchas más revisiones y usos. En segundo lugar, la interoperabilidad se ve obstaculizada porque, mientras que AES está generalmente disponible, Rijndael con un tamaño de bloque de 256 bits no lo está.
  • El cifrado con diferentes tamaños de bloque para Rijndael produce diferentes datos cifrados.

    Por ejemplo, MCRYPT_RIJNDAEL_256(no es equivalente a AES-256) define una variante diferente del cifrado de bloque Rijndael con un tamaño de 256 bits y un tamaño de clave basado en la clave pasada, donde aes-256-cbcestá Rijndael con un tamaño de bloque de 128 bits con un tamaño de clave de 256 bits. Por lo tanto, están usando diferentes tamaños de bloque, lo que produce datos cifrados completamente diferentes, ya que mcrypt usa el número para especificar el tamaño del bloque, mientras que OpenSSL usó el número para especificar el tamaño de la clave (AES solo tiene un tamaño de bloque de 128 bits). Básicamente, AES es Rijndael con un tamaño de bloque de 128 bits y tamaños de clave de 128, 192 y 256 bits. Por lo tanto, es mejor utilizar AES, que se llama Rijndael 128 en OpenSSL.

kenorb avatar Jan 05 '2018 19:01 kenorb

Como se detalla en otras respuestas aquí, la mejor solución que encontré es usar OpenSSL. Está integrado en PHP y no necesita ninguna biblioteca externa. A continuación se muestran ejemplos sencillos:

Para cifrar:

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

Para descifrar:

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

Enlace de referencia: https://www.shift8web.ca/2017/04/how-to-encrypt-and-execute-your-php-code-with-mcrypt/

Ariston Cordeiro avatar Apr 04 '2019 12:04 Ariston Cordeiro

La implementación de Rijndael en PHP puro existe con phpseclib disponible como paquete de compositor y funciona en PHP 7.3 (probado por mí).

Hay una página en los documentos de phpseclib, que genera código de muestra después de ingresar las variables básicas (cifrado, modo, tamaño de clave, tamaño de bits). Genera lo siguiente para Rijndael, BCE, 256, 256:

un código con mycrypt

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

funciona así con la biblioteca

$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

Pentium10 avatar Dec 26 '2018 21:12 Pentium10

Puede utilizar el paquete phpseclib pollyfill. No puede usar open ssl o libsodium para cifrar/descifrar con rijndael 256. Otro problema, no necesita reemplazar ningún código.

Ahmet Erkan ÇELİK avatar Apr 06 '2017 13:04 Ahmet Erkan ÇELİK