Saltar al contenido

El cifrado bidireccional más simple con PHP

Nuestro team de trabajo ha estado mucho tiempo investigando para darle espuestas a tu duda, te regalamos la resolución de modo que deseamos resultarte de mucha apoyo.

Solución:

Importante: A menos que tenga un muy caso de uso particular, no cifre las contraseñas, utilice un algoritmo de hash de contraseñas en su lugar. Cuando alguien dice que cifrar sus contraseñas en una aplicación del lado del servidor, no están informadas o están describiendo un diseño de sistema peligroso. El almacenamiento seguro de contraseñas es un problema totalmente independiente del cifrado.

Ser informado. Diseñar sistemas seguros.

Cifrado de datos portátil en PHP

Si está usando PHP 5.4 o más reciente y no desea escribir un módulo de criptografía usted mismo, le recomiendo usar una biblioteca existente que proporcione cifrado autenticado. La biblioteca que vinculé se basa solo en lo que proporciona PHP y está bajo revisión periódica por un puñado de investigadores de seguridad. (Yo incluido.)

Si sus objetivos de portabilidad no le impiden requerir extensiones PECL, libsodium es muy recomendado sobre cualquier cosa que usted o yo podamos escribir en PHP.

Actualización (2016-06-12): Ahora puede usar sodium_compat y usar las mismas ofertas de cripto libsodium sin instalar extensiones PECL.

Si quieres probar suerte con la ingeniería criptográfica, sigue leyendo.


Primero, debe tomarse el tiempo para aprender los peligros del cifrado no autenticado y el Principio Criptográfico de Perdición.

  • Los datos cifrados aún pueden ser manipulados por un usuario malintencionado.
  • La autenticación de los datos cifrados evita la manipulación.
  • La autenticación de los datos no cifrados no evita la manipulación.

Cifrado y descifrado

El cifrado en PHP es realmente simple (vamos a usar openssl_encrypt() y openssl_decrypt() una vez que haya tomado algunas decisiones sobre cómo cifrar su información. Consultar openssl_get_cipher_methods() para obtener una lista de los métodos admitidos en su sistema. La mejor opción es AES en modo CTR:

  • aes-128-ctr
  • aes-192-ctr
  • aes-256-ctr

Actualmente no hay ninguna razón para creer que la AES key El tamaño es un tema importante del que preocuparse (más grande probablemente sea no mejor, por mal key-programación en el modo de 256 bits).

Nota: No estamos usando mcrypt porque es abandonware y tiene errores sin parchear que pueden afectar la seguridad. Por estas razones, animo a otros desarrolladores de PHP a que también lo eviten.

Envoltorio de cifrado / descifrado simple con OpenSSL

class UnsafeCrypto

    const METHOD = 'aes-256-ctr';

    /**
     * Encrypts (but does not authenticate) a message
     * 
     * @param string $message - plaintext message
     * @param string $key - encryption key (raw binary expected)
     * @param boolean $encode - set to TRUE to return a base64-encoded 
     * @return string (raw binary)
     */
    public static function encrypt($message, $key, $encode = false)
    
        $nonceSize = openssl_cipher_iv_length(self::METHOD);
        $nonce = openssl_random_pseudo_bytes($nonceSize);

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

        // Now let's pack the IV and the ciphertext together
        // Naively, we can just concatenate
        if ($encode) 
            return base64_encode($nonce.$ciphertext);
        
        return $nonce.$ciphertext;
    

    /**
     * Decrypts (but does not verify) a message
     * 
     * @param string $message - ciphertext message
     * @param string $key - encryption key (raw binary expected)
     * @param boolean $encoded - are we expecting an encoded string?
     * @return string
     */
    public static function decrypt($message, $key, $encoded = false)
    
        if ($encoded) 
            $message = base64_decode($message, true);
            if ($message === false) 
                throw new Exception('Encryption failure');
            
        

        $nonceSize = openssl_cipher_iv_length(self::METHOD);
        $nonce = mb_substr($message, 0, $nonceSize, '8bit');
        $ciphertext = mb_substr($message, $nonceSize, null, '8bit');

        $plaintext = openssl_decrypt(
            $ciphertext,
            self::METHOD,
            $key,
            OPENSSL_RAW_DATA,
            $nonce
        );

        return $plaintext;
    

Ejemplo de uso

$message = 'Ready your ammunition; we attack at dawn.';
$key = hex2bin('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f');

$encrypted = UnsafeCrypto::encrypt($message, $key);
$decrypted = UnsafeCrypto::decrypt($encrypted, $key);

var_dump($encrypted, $decrypted);

Manifestación: https://3v4l.org/jl7qR


La biblioteca de cifrado simple anterior todavía no es segura de usar. Necesitamos autenticar textos cifrados y verificarlos antes de descifrarlos.

Nota: Por defecto, UnsafeCrypto::encrypt() devolverá un binario en bruto string. Llámelo así si necesita almacenarlo en un formato binario seguro (codificado en base64):

$message = 'Ready your ammunition; we attack at dawn.';
$key = hex2bin('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f');

$encrypted = UnsafeCrypto::encrypt($message, $key, true);
$decrypted = UnsafeCrypto::decrypt($encrypted, $key, true);

var_dump($encrypted, $decrypted);

Manifestación: http://3v4l.org/f5K93

Envoltorio de autenticación simple

class SaferCrypto extends UnsafeCrypto

    const HASH_ALGO = 'sha256';

    /**
     * Encrypts then MACs a message
     * 
     * @param string $message - plaintext message
     * @param string $key - encryption key (raw binary expected)
     * @param boolean $encode - set to TRUE to return a base64-encoded string
     * @return string (raw binary)
     */
    public static function encrypt($message, $key, $encode = false)
    
        list($encKey, $authKey) = self::splitKeys($key);

        // Pass to UnsafeCrypto::encrypt
        $ciphertext = parent::encrypt($message, $encKey);

        // Calculate a MAC of the IV and ciphertext
        $mac = hash_hmac(self::HASH_ALGO, $ciphertext, $authKey, true);

        if ($encode) 
            return base64_encode($mac.$ciphertext);
        
        // Prepend MAC to the ciphertext and return to caller
        return $mac.$ciphertext;
    

    /**
     * Decrypts a message (after verifying integrity)
     * 
     * @param string $message - ciphertext message
     * @param string $key - encryption key (raw binary expected)
     * @param boolean $encoded - are we expecting an encoded string?
     * @return string (raw binary)
     */
    public static function decrypt($message, $key, $encoded = false)
    
        list($encKey, $authKey) = self::splitKeys($key);
        if ($encoded) 
            $message = base64_decode($message, true);
            if ($message === false) 
                throw new Exception('Encryption failure');
            
        

        // Hash Size -- in case HASH_ALGO is changed
        $hs = mb_strlen(hash(self::HASH_ALGO, '', true), '8bit');
        $mac = mb_substr($message, 0, $hs, '8bit');

        $ciphertext = mb_substr($message, $hs, null, '8bit');

        $calculated = hash_hmac(
            self::HASH_ALGO,
            $ciphertext,
            $authKey,
            true
        );

        if (!self::hashEquals($mac, $calculated)) 
            throw new Exception('Encryption failure');
        

        // Pass to UnsafeCrypto::decrypt
        $plaintext = parent::decrypt($ciphertext, $encKey);

        return $plaintext;
    

    /**
     * Splits a key into two separate keys; one for encryption
     * and the other for authenticaiton
     * 
     * @param string $masterKey (raw binary)
     * @return array (two raw binary strings)
     */
    protected static function splitKeys($masterKey)
    
        // You really want to implement HKDF here instead!
        return [
            hash_hmac(self::HASH_ALGO, 'ENCRYPTION', $masterKey, true),
            hash_hmac(self::HASH_ALGO, 'AUTHENTICATION', $masterKey, true)
        ];
    

    /**
     * Compare two strings without leaking timing information
     * 
     * @param string $a
     * @param string $b
     * @ref https://paragonie.com/b/WS1DLx6BnpsdaVQW
     * @return boolean
     */
    protected static function hashEquals($a, $b)
    
        if (function_exists('hash_equals')) 
            return hash_equals($a, $b);
        
        $nonce = openssl_random_pseudo_bytes(32);
        return hash_hmac(self::HASH_ALGO, $a, $nonce) === hash_hmac(self::HASH_ALGO, $b, $nonce);
    

Ejemplo de uso

$message = 'Ready your ammunition; we attack at dawn.';
$key = hex2bin('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f');

$encrypted = SaferCrypto::encrypt($message, $key);
$decrypted = SaferCrypto::decrypt($encrypted, $key);

var_dump($encrypted, $decrypted);

Población: binario sin formato, codificado en base64


Si alguien desea usar esto SaferCrypto biblioteca en un entorno de producción, o su propia implementación de los mismos conceptos, le recomiendo que se comunique con sus criptógrafos residentes para obtener una segunda opinión antes de hacerlo. Podrán informarle sobre errores de los que quizás ni siquiera sea consciente.

Estará mucho mejor si utiliza una biblioteca de criptografía de buena reputación.

Editado:

Realmente deberías estar usando openssl_encrypt () & openssl_decrypt ()

Como dice Scott, Mcrypt no es una buena idea ya que no se ha actualizado desde 2007.

Incluso hay un RFC para eliminar Mcrypt de PHP: https://wiki.php.net/rfc/mcrypt-viking-funeral

Usar mcrypt_encrypt() y mcrypt_decrypt() con los parámetros correspondientes. Realmente fácil y directo, y utiliza un paquete de cifrado probado en batalla.

EDITAR

5 años y 4 meses después de esta respuesta, el mcrypt La extensión está ahora en proceso de desaprobación y eventual eliminación de PHP.

Si piensas que ha resultado provechoso nuestro artículo, te agradeceríamos que lo compartas con el resto seniors así nos ayudas a dar difusión a nuestra información.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *