¿Cómo se Encripta y Desencripta una cadena PHP?


Lo que quiero decir es:

Original String + Salt or Key --> Encrypted String
Encrypted String + Salt or Key --> Decrypted (Original String)

Tal vez algo como:

"hello world!" + "ABCD1234" --> Encrypt --> "2a2ffa8f13220befbe30819047e23b2c" (may be, for e.g)
"2a2ffa8f13220befbe30819047e23b2c" --> Decrypt with "ABCD1234" --> "hello world!"
  • En PHP, ¿cómo se puede hacer esto?

Intentado utilizar Crypt_Blowfish, pero no funcionó para mí.

Author: Scott Arciszewski, 2013-05-17

6 answers

Antes de hacer nada más, trate de entender la diferencia entre cifrado y autenticación, y por qué probablemente quieres cifrado autenticado en lugar de solo cifrado.

Para implementar el cifrado autenticado, desea cifrar luego MAC. El orden de cifrado y autenticación es muy importante! Una de las respuestas existentes a esta pregunta cometió este error; al igual que muchos criptografía bibliotecas escritas en PHP.

Debe evitar implementar su propia criptografía, y en su lugar usar una biblioteca segura escrita y revisada por expertos en criptografía.

Actualización: PHP 7.2 ahora proporciona libsodium! Actualizado a PHP 7.2 o superior y solo siga los consejos de libsodium en esta respuesta.

Use libsodium si tiene acceso PECL (o sodium_compat si quiere libsodium sin PECL); de lo contrario...
Use defuse / php-encryption ; ¡no tire su propia criptografía!

Ambas bibliotecas enlazadas anteriormente hacen que sea fácil e indoloro implementar el cifrado autenticado en sus propias bibliotecas.

Si todavía desea escribir e implementar su propia biblioteca de criptografía, en contra de la sabiduría convencional de todos los expertos en criptografía en Internet, estos son los pasos que tendría que seguir tomar.

Cifrado:

  1. Cifrar usando AES en modo CTR. También puede usar GCM (que elimina la necesidad de una MAC separada). Además, ChaCha20 y Salsa20 (proporcionados por libsodium ) son cifradores de flujo y no necesitan modos especiales.
  2. A menos que elija GCM arriba, debe autenticar el texto cifrado con HMAC-SHA-256 (o, para los cifrados de flujo, Poly1305 most la mayoría de las API de libsodium hacen esto por usted). El MAC debe cubrir el IV, así como el ciphertext!

Descifrado:

  1. A menos que se use Poly1305 o GCM, vuelva a calcular el MAC del texto cifrado y compárelo con el MAC que se envió usando hash_equals(). Si falla, aborta.
  2. Descifra el mensaje.

Otras consideraciones de Diseño:

  1. No comprima nada nunca. El texto cifrado no es compresible; comprimir texto plano antes del cifrado puede conducir a fugas de información (por ejemplo, DELITOS y VIOLACIONES de TLS).
  2. Asegúrese de usar mb_strlen() y mb_substr(), usando el modo de conjunto de caracteres '8bit' para evitar problemas mbstring.func_overload.
  3. IVs debería estar generando usando un CSPRNG ; Si está utilizando mcrypt_create_iv(), NO USAR MCRYPT_RAND!
  4. A menos que esté utilizando una construcción AEAD, SIEMPRE cifrar entonces MAC!
  5. bin2hex(), base64_encode(), etc. puede filtrar información sobre sus claves de cifrado a través de tiempo de caché. Evítalos si es posible.

Incluso si sigues el consejo dado aquí, muchas cosas pueden salir mal con la criptografía. Siempre pida a un experto en criptografía que revise su implementación. Si no tienes la suerte de ser amigo personal de un estudiante de criptografía en tu universidad local, siempre puedes probar el foro Cryptography Stack Exchange para obtener consejos.

Si necesita un análisis profesional de su implementación, siempre puede contratar un equipo de consultores de seguridad de buena reputación para revisar su código de criptografía PHP (divulgación: mi empleador).

Importante: Cuándo No usar Cifrado

No cifrar contraseñas. Usted quiere hash en su lugar, utilizando uno de estos algoritmos de hash de contraseña:

Nunca use una función hash de propósito general (MD5, SHA256) para el almacenamiento de contraseñas.

No cifrar Parámetros de URL . Es la herramienta equivocada para el trabajo.

Ejemplo de cifrado de cadenas PHP con Libsodium

Si está en PHP sodium_compat para lograr el mismo resultado (aunque más lento).

<?php
declare(strict_types=1);

/**
 * Encrypt a message
 * 
 * @param string $message - message to encrypt
 * @param string $key - encryption key
 * @return string
 * @throws RangeException
 */
function safeEncrypt(string $message, string $key): string
{
    if (mb_strlen($key, '8bit') !== SODIUM_CRYPTO_SECRETBOX_KEYBYTES) {
        throw new RangeException('Key is not the correct size (must be 32 bytes).');
    }
    $nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);

    $cipher = base64_encode(
        $nonce.
        sodium_crypto_secretbox(
            $message,
            $nonce,
            $key
        )
    );
    sodium_memzero($message);
    sodium_memzero($key);
    return $cipher;
}

/**
 * Decrypt a message
 * 
 * @param string $encrypted - message encrypted with safeEncrypt()
 * @param string $key - encryption key
 * @return string
 * @throws Exception
 */
function safeDecrypt(string $encrypted, string $key): string
{   
    $decoded = base64_decode($encrypted);
    $nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
    $ciphertext = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');

    $plain = sodium_crypto_secretbox_open(
        $ciphertext,
        $nonce,
        $key
    );
    if (!is_string($plain)) {
        throw new Exception('Invalid MAC');
    }
    sodium_memzero($ciphertext);
    sodium_memzero($key);
    return $plain;
}

Entonces para probarlo fuera:

<?php
// This refers to the previous code block.
require "safeCrypto.php"; 

// Do this once then store it somehow:
$key = random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);
$message = 'We are all living in a yellow submarine';

$ciphertext = safeEncrypt($message, $key);
$plaintext = safeDecrypt($ciphertext, $key);

var_dump($ciphertext);
var_dump($plaintext);

Halite - Libsodium Made Easier

Uno de los proyectos en los que he estado trabajando es una biblioteca de cifrado llamada Halite, que tiene como objetivo hacer que libsodium sea más fácil e intuitivo.

<?php
use \ParagonIE\Halite\KeyFactory;
use \ParagonIE\Halite\Symmetric\Crypto as SymmetricCrypto;

// Generate a new random symmetric-key encryption key. You're going to want to store this:
$key = new KeyFactory::generateEncryptionKey();
// To save your encryption key:
KeyFactory::save($key, '/path/to/secret.key');
// To load it again:
$loadedkey = KeyFactory::loadEncryptionKey('/path/to/secret.key');

$message = 'We are all living in a yellow submarine';
$ciphertext = SymmetricCrypto::encrypt($message, $key);
$plaintext = SymmetricCrypto::decrypt($ciphertext, $key);

var_dump($ciphertext);
var_dump($plaintext);

Toda la criptografía subyacente es manejada por libsodium.

Ejemplo con defuse / php-encryption

<?php
/**
 * This requires https://github.com/defuse/php-encryption
 * php composer.phar require defuse/php-encryption
 */

use Defuse\Crypto\Crypto;
use Defuse\Crypto\Key;

require "vendor/autoload.php";

// Do this once then store it somehow:
$key = Key::createNewRandomKey();

$message = 'We are all living in a yellow submarine';

$ciphertext = Crypto::encrypt($message, $key);
$plaintext = Crypto::decrypt($ciphertext, $key);

var_dump($ciphertext);
var_dump($plaintext);

Nota: Crypto::encrypt() devuelve salida codificada hexadecimal.

Gestión de Claves de Cifrado

Si estás tentado a usa una "contraseña", detente ahora mismo. Necesitas una clave de encriptación aleatoria de 128 bits, no una contraseña humana memorable.

Puede almacenar una clave de cifrado para uso a largo plazo como así:

$storeMe = bin2hex($key);

Y, bajo demanda, puede recuperarlo así:

$key = hex2bin($storeMe);

Yo fuertemente recomiendo simplemente almacenar una clave generada aleatoriamente para uso a largo plazo en lugar de cualquier tipo de contraseña como clave (o para derivar la clave).

Si estás usando Desactivación biblioteca:

"Pero yo realmente quiero usar una contraseña."

Esa es una mala idea, pero está bien, aquí está cómo hacerlo de forma segura.

Primero, genere una clave aleatoria y guárdela en una constante.

/**
 * Replace this with your own salt! 
 * Use bin2hex() then add \x before every 2 hex characters, like so:
 */
define('MY_PBKDF2_SALT', "\x2d\xb7\x68\x1a\x28\x15\xbe\x06\x33\xa0\x7e\x0e\x8f\x79\xd5\xdf");

Tenga en cuenta que está agregando trabajo adicional y podría usar esta constante como la clave y ahorrarse un montón de angustia!

Luego use PBKDF2 (como así) para derivar un adecuado clave de cifrado de su contraseña en lugar de cifrar con su contraseña directamente.

/**
 * Get an AES key from a static password and a secret salt
 * 
 * @param string $password Your weak password here
 * @param int $keysize Number of bytes in encryption key
 */
function getKeyFromPassword($password, $keysize = 16)
{
    return hash_pbkdf2(
        'sha256',
        $password,
        MY_PBKDF2_SALT,
        100000, // Number of iterations
        $keysize,
        true
    );
}

No use una contraseña de 16 caracteres. Su clave de cifrado se romperá cómicamente.

 334
Author: Scott Arciszewski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-24 12:11:36

Qué no hacer

ADVERTENCIA:
Esta respuesta utilizaECB . ECB no es un modo de encriptación, es solo un bloque de construcción. El uso de ECB como se muestra en esta respuesta no encripta realmente la cadena de forma segura. No utilice ECB en su código. Ver La respuesta de Scott para una buena solución.

Lo tengo yo mismo. En realidad he encontrado alguna respuesta en google y acaba de modificar algo. El resultado es completamente inseguro obstante.

<?php
define("ENCRYPTION_KEY", "!@#$%^&*");
$string = "This is the original data string!";

echo $encrypted = encrypt($string, ENCRYPTION_KEY);
echo "<br />";
echo $decrypted = decrypt($encrypted, ENCRYPTION_KEY);

/**
 * Returns an encrypted & utf8-encoded
 */
function encrypt($pure_string, $encryption_key) {
    $iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB);
    $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
    $encrypted_string = mcrypt_encrypt(MCRYPT_BLOWFISH, $encryption_key, utf8_encode($pure_string), MCRYPT_MODE_ECB, $iv);
    return $encrypted_string;
}

/**
 * Returns decrypted original string
 */
function decrypt($encrypted_string, $encryption_key) {
    $iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB);
    $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
    $decrypted_string = mcrypt_decrypt(MCRYPT_BLOWFISH, $encryption_key, $encrypted_string, MCRYPT_MODE_ECB, $iv);
    return $decrypted_string;
}
?>
 42
Author: 夏期劇場,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-11 22:12:32

Llego tarde a la fiesta, pero buscando la forma correcta de hacerlo me encontré con esta página que era una de las principales devoluciones de búsqueda de Google, así que me gustaría compartir mi opinión sobre el problema, que considero que está al día en el momento de escribir este post (principios de 2017). A partir de PHP 7.1.0, mcrypt_decrypt y mcrypt_encrypt van a ser obsoletos, por lo que la construcción de código a prueba de futuro debe usar openssl_encrypt y openssl_decrypt

, puedes hacer algo como:

$string_to_encrypt="Test";
$password="password";
$encrypted_string=openssl_encrypt($string_to_encrypt,"AES-128-ECB",$password);
$decrypted_string=openssl_decrypt($encrypted_string,"AES-128-ECB",$password);

Importante : Utiliza modo ECB, que no es seguro. Si desea una solución simple sin tomar un curso intensivo en ingeniería criptográfica, no la escriba usted mismo, solo use una biblioteca.

También puede utilizar cualquier otro método de astilladora, dependiendo de sus necesidades de seguridad. Para averiguar los métodos de astilladora disponibles, consulte la función openssl_get_cipher_methods.

 28
Author: Emil Borconi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-04-11 20:42:57

Para Laravel framework

Si está utilizando Laravel framework, entonces es más fácil cifrar y descifrar con funciones internas.

$string = 'Some text to be encrypted';
$encrypted = \Illuminate\Support\Facades\Crypt::encrypt($string);
$decrypted_string = \Illuminate\Support\Facades\Crypt::decrypt($encrypted);

var_dump($string);
var_dump($encrypted);
var_dump($decrypted_string);

Nota: Asegúrese de establecer una cadena aleatoria de 16, 24 o 32 caracteres en el opción clave de la config / app.archivo php. De lo contrario, los valores cifrados no será seguro.

 11
Author: Somnath Muluk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-14 12:58:54

Nota Histórica: Esto fue escrito en el momento de PHP4. Esto es lo que llamamos "código heredado" ahora.

He dejado esta respuesta para propósitos históricos - pero algunos de los métodos ahora están obsoletos, el método de cifrado DES no es una práctica recomendada, etc.

No he actualizado este código por dos razones: 1) Ya no trabajo con métodos de cifrado a mano en PHP, y 2) este código todavía sirve para el propósito para el que estaba destinado: demostrar el mínimo, concepto simplista de cómo el cifrado puede funcionar en PHP.

Si encuentras un tipo de fuente similar simplista, "cifrado PHP para dummies" que puede hacer que la gente comience en 10-20 líneas de código o menos, házmelo saber en los comentarios.

Más allá de eso, por favor disfrute de este Episodio Clásico de la primera era PHP4 minimalistic encryption answer.


Idealmente usted tiene - o puede obtener - acceso a la biblioteca PHP mcrypt, ya que es ciertamente popular y muy útil una variedad de tareas. Aquí hay una carrera abajo de los diferentes tipos de cifrado y algunos ejemplos de código: Técnicas de cifrado en PHP

//Listing 3: Encrypting Data Using the mcrypt_ecb Function 

<?php 
echo("<h3> Symmetric Encryption </h3>"); 
$key_value = "KEYVALUE"; 
$plain_text = "PLAINTEXT"; 
$encrypted_text = mcrypt_ecb(MCRYPT_DES, $key_value, $plain_text, MCRYPT_ENCRYPT); 
echo ("<p><b> Text after encryption : </b>"); 
echo ( $encrypted_text ); 
$decrypted_text = mcrypt_ecb(MCRYPT_DES, $key_value, $encrypted_text, MCRYPT_DECRYPT); 
echo ("<p><b> Text after decryption : </b>"); 
echo ( $decrypted_text ); 
?> 

Algunas advertencias:

1) Nunca use cifrado reversible o "simétrico" cuando un hash unidireccional lo hará.

2) Si los datos son verdaderamente sensibles, como los números de tarjeta de crédito o de seguro social, deténgase; necesita más de lo que cualquier simple fragmento de código proporcionará, sino que necesita una biblioteca criptográfica diseñada para este propósito y una cantidad significativa de tiempo para investigar la métodos necesarios. Además, el software crypto es probablemente

3) Cualquier tipo de cifrado fácilmente implementable, como se indica aquí, puede proteger razonablemente la información ligeramente importante que desea evitar que se entrometa ojos o limitar la exposición en caso de fuga accidental/intencional. Pero viendo cómo la clave se almacena en texto sin formato en el servidor web, si pueden obtener los datos, pueden obtener la clave de descifrado.

Sea como sea, diviértete:)

 6
Author: BrianH,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-11 22:12:01

Si no quieres usar la biblioteca (que deberías) entonces usa algo como esto (PHP 7):

function sign($message, $key) {
    return hash_hmac('sha256', $message, $key) . $message;
}

function verify($bundle, $key) {
    return hash_equals(
      hash_hmac('sha256', mb_substr($bundle, 64, null, '8bit'), $key),
      mb_substr($bundle, 0, 64, '8bit')
    );
}

function getKey($password, $keysize = 16) {
    return hash_pbkdf2('sha256',$password,'some_token',100000,$keysize,true);
}

function encrypt($message, $password) {
    $iv = random_bytes(16);
    $key = getKey($password);
    $result = sign(openssl_encrypt($message,'aes-256-ctr',$key,OPENSSL_RAW_DATA,$iv), $key);
    return bin2hex($iv).bin2hex($result);
}

function decrypt($hash, $password) {
    $iv = hex2bin(substr($hash, 0, 32));
    $data = hex2bin(substr($hash, 32));
    $key = getKey($password);
    if (!verify($data, $key)) {
      return null;
    }
    return openssl_decrypt(mb_substr($data, 64, null, '8bit'),'aes-256-ctr',$key,OPENSSL_RAW_DATA,$iv);
}

$string_to_encrypt='John Smith';
$password='password';
$encrypted_string=encrypt($string_to_encrypt, $password);
$decrypted_string=decrypt($encrypted_string, $password);
 1
Author: Ascon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-16 15:27:21