How to always get the same results when encrypting a chain?

13

I have this function that serves to encrypt strings:

public function encriptar($string) {
    $iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC), 
                            MCRYPT_DEV_URANDOM);
    $encrypted = base64_encode($iv . mcrypt_encrypt(MCRYPT_RIJNDAEL_128,
                                                    hash('sha256', getKey(), true), 
                                                    $string, MCRYPT_MODE_CBC, $iv));    
    return $encrypted;
}

The problem is that I always return a different value even adding the same string. Example:

  

I introduce: StackOverflow

     

Result: InT3g0AUXXTrmCAxrlht5ZVe8GBmlgGDMotXuVu11hI =

If I run the script again:

  

I introduce: StackOverflow

     

Result: ImhWn5vPA / A2NY2wpUwg7VLWAiGBls80Z84fGU303Ws =

If I run the script again:

  

I introduce: StackOverflow

     

Result: FqvxSsblSwz5riaDnnq7h20PzZTPdk / K + dikLHbLHTY =

How can I make it always the same value?

    
asked by rnrneverdies 12.11.2016 в 10:39
source

8 answers

10

Short answer

If you always get the same result, the encryption mechanism is too weak.

Long answer

  

The problem is that I always return a different value even adding the same string.

This is the expected behavior. By specifying MCRYPT_RIJNDAEL_128 and MCRYPT_MODE_CBC you are using encryption AES compatible, in CBC . This mode includes / requires a random initialization vector , known as IV (which in your code you initialize in the first line). This is the reason why the value is always different, because of the random vector.

CBC mode (or cipher block chaining ) uses a IV when starting the encryption (and decryption) that makes the algorithm much more robust. The IV is not a random number without sense, you must share the initialization vector to the other side (the decryptor) so you can do the decryption. This only makes the key a bit more complex since it has two parts, the symmetric key (the key AES ) and the initialization vector (the IV ).

This is the reason why your method is "concatenating" the IV to the encrypted value, because then the other side is needed to start the decryption.

  

How can I make it always the same value?

My recommendation is that do not do this , it does not matter that the values are not equal. The most important thing is that the encryption is robust.

It would be best if you continue to use what you are using: CBC and send the IV and the other side will start the vector appropriately and decrypt the content.

But if you insist on receiving the same value, look for other answers that suggest changing the encryption method to ECB . It is not recommended because this mode is obsolete and although it is preserved for compatibility it is very vulnerable.

To complete the answer, the ECB mode encrypts each block (which are the same length as the key) independently. This means that if you encrypt an email, all those messages that come from the same source, once encrypted, will begin with the same encrypted sequence! (Well, they all start with the block: From: <[email protected]>\n , generating at least one pattern perceptible to anyone interested in its content.

The other modes (such as CBC ) that include a random% IV (or intialization vector), encrypt each block using part of the previous block, so they are much more robust. In this image you can see the difference between ECB mode and any other mode that includes randomness.

More Information:

link

link

    
answered by 26.12.2016 в 21:07
5

We go in parts:

  • Your code does not show the way in which the key getKey() is generated
  • You are using an encryption function that uses an initialization sector $iv
  • MCRYPT_DEV_URANDOM is generating random data every time you call the function encriptar()
  • Now, in order to get the same string you should always use the same set of $iv , key and text. You can generate the $iv in base64 and see it on the screen with print_r or echo , then you would use it in your function in something like:

    $iv = base64_decode('230487298384920weuiw'); //esta sería
    

    This way you will always get the same result when you encrypt your text.

    Depending on the application that you give this may or may not be good, if you are encrypting passwords I suggest you review the function password_hash () that implements bcrypt, then you can use password_verify to know if your password is ok or not.

    I hope it serves you ...

        
    answered by 25.12.2016 в 22:05
    5
      

    With the CBC mode, it is assumed that it is to get a different encrypted text each time the encryption function is called, even with the same plain text.

    This property provides protection against certain types of attacks, and is one of the reasons why CBC is more secure than BCE .

    How to always get the same results when encrypting a string?

    You are creating a different $iv use MCRYPT_DEV_URANDOM as the pseudo-random number-generator.

    If you still want to do it (and you should only do it if you really know what you are doing), you should use the same initialization vector $iv , and the result will be the same.

      

    Note: But as said, this can compromise the security of the system.

    An additional point: You can store it in your database, in the class instance, or store it as a prefix / suffix of the last hash.

    Although you should not reuse the $iv security purposes ...

    A second option: It is generate only once and save it in the database and reuse it.

    function getIv($database) {
        // fictive database abstraction layer
        $iv = $database->fetchIv();
        if (!$iv) {
            $iv = mcrypt_create_iv(
                mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC), 
                MCRYPT_DEV_URANDOM
            );
            $database->saveIv($iv);
        }
    
        return $iv;
    }
    
    // in your class
    
    public function encriptar($string) {
        $encrypted = base64_encode($iv . mcrypt_encrypt(MCRYPT_RIJNDAEL_128,
                                                        hash('sha256', getKey(), true), 
                                                        $string, MCRYPT_MODE_CBC, getIv()));    
        return $encrypted;
    }
    
        
    answered by 30.12.2016 в 15:17
    3

    I should be using openssl_encrypt() and openssl_decrypt()

    mcrypt is not a good idea since it has not been updated since 2007.

    There is even an RFC to remove mcrypt from PHP

    SO Source:

    Example: openssl_encrypt() and openssl_decrypt()

    <?php
     /**
     * https://stackoverflow.com/questions/9262109/php-simplest-two-way-encryption/30189841#30189841      
     */
        class UnsafeCrypto
        {
            const METHOD = 'aes-256-ctr';
    
            /**
             * Encrypts (pero no autentificar) un mensaje.
             * 
             * @param cadena $mensaje - mensaje texto plano
             * @param cadena $clave - clave encriptado (raw binary expected) (binario sin procesar)
             * @param booleano $codificar - establecido en TRUE para devolver un base64-encoded 
             * @return cadena (raw binary)
             */
            public static function encrypt($mensaje, $clave, $codificar = false)
            {
                $tamano = openssl_cipher_iv_length(self::METHOD);
                $mientras = openssl_random_pseudo_bytes($tamano);
    
                $texto_cifrado = openssl_encrypt(
                    $mensaje,
                    self::METHOD,
                    $clave,
                    OPENSSL_RAW_DATA,
                    $mientras
                );
    
                // Ahora empaquemos el IV y el texto cifrado
                // Nativamente, sólo podemos concatenar
                if ($codificar) {
                    return base64_encode($mientras.$texto_cifrado);
                }
                return $mientras.$texto_cifrado;
            }
    
            /**
             * Decrypts (pero no autentificar) un mensaje.
             * 
             * @param cadena $mensaje - mensaje texto plano
             * @param cadena $clave - clave encriptado (raw binary expected)
             * @param booleano $codificado - ¿Esperamos una cadena codificada?
             * @return cadena
             */
            public static function decrypt($mensaje, $clave, $codificado = false)
            {
                if ($codificado) {
                    $mensaje = base64_decode($mensaje, true);
                    if ($mensaje === false) {
                        throw new Exception('Encriptación fallido');
                    }
                }
    
                $tamano = openssl_cipher_iv_length(self::METHOD);
                $mientras = mb_substr($mensaje, 0, $tamano, '8bit');
                $texto_cifrado = mb_substr($mensaje, $tamano, null, '8bit');
    
                $texto_plano = openssl_decrypt(
                    $texto_cifrado,
                    self::METHOD,
                    $clave,
                    OPENSSL_RAW_DATA,
                    $mientras
                );
    
                return $texto_plano;
            }
        }
    
    $mensaje = 'Hola mundo.';
    $clave = hex2bin('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f');
    
    $encriptado = UnsafeCrypto::encrypt($mensaje, $clave);
    $descifrado  = UnsafeCrypto::decrypt($encriptado, $clave);
    
    echo 'Mensaje encriptada: ' . $encriptado . '<br />';
    echo 'Mensaje descifrada: ' . $descifrado  . '<br />';
    ?>
    

    Output of echo , it would be something like:

    Mensaje encriptada: I��Y6��� �q_��d���lO1�?�<br />
    Mensaje descifrada: Hola mundo.
    

    Source:

    SO Source:

      

    Note: If you want to save passwords using PHP and MySQL , I advise you to use password_hash() and password_verify()

    password_hash() creates a new password hash using a strong one-way hashing algorithm. password_hash() is compatible with crypt() . Therefore, password hashes created with crypt() can be used with password_hash() .


    password_verify - check that the provided hash matches the given password.

    Notice that password_hash() returns the algorithm, cost, and salt as part of the returned hash. Therefore, all the information that is necessary to verify the hash is included. This allows the verification function to check the hash without the need to store separately the salt or algorithm information.

    More information in SOes source:

    answered by 25.12.2016 в 20:34
    2

    If you want to save your users' passwords, it would be horrible if the same encrypted string always appears, but I have read in the comments that it is for emails, ok.

    So, if you figure a string you have to see another function that deciphers it and the function you use is not suitable for what I think you need.

    I leave you a feature extracted from here :

    These functions are not safe or passwords safe!

    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;
    }
    
    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;
    }
    
    
    define("LLAVE_SECRETA", "!@#$%^&*");
    
    $string = 'Hola Mari Carmen';
    
    $encrypt = encrypt($string, LLAVE_SECRETA);
    
    echo $encrypt; // ��0we��[�;���v
    
    $decrypt = decrypt($encrypt, LLAVE_SECRETA);
    
    echo $decrypt; // Hola Mari Carmen
    

    Watch Demo

        
    answered by 12.11.2016 в 11:37
    2

    Good morning.

    If you use Laravel, the way to authenticate is to use the functions of the framework, it is always less cumbersome.

    See an example from the official site :

    To create the Hash you can do

    // obtener la contraseña desde formulario
    $contrasena = Hash::make(Input::get('pass_usuario'));
    

    To verify that the password works:

    if (Hash::check('secret', $hashedPassword)){
        // The passwords match...
    }
    

    An example of an enlistment would be

    function login (Request $request){
        $idUsuario = $request['id_usuario'];
        $passUsuario = $request['pass_usuario'];
    
        $usuario = Usuarios::find ($idUsuario);
    
        // se usar la contraseña y el hash como parametros
        if (Hash::check($passUsuario, $usuario->CONTRASENA)) {
    
              // usar Session para mantener la sesion del usuario (esto es un ejemplo solamente :P)
              Session::put('id', $idUsuario);
    
              // redirigir al inicio
              return redirect('inicio/');
        } else {
              // devolver error al cliente
              return response()->json(array('error' => 'No se ha podido enrolar'));
        }
    }
    

    I have to emphasize that to add these functions you must create the seed in the configuration file .env in the following way:

     php artisan key:generate
    

    You must also add to the header:

    use Hash;
    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Redirect;
    use Session;
    

    The Session documentation is on the Official Site
    The Redirect documentation is in the Official Site
    The Response()->json documentation is in the Official Site

    Now if you ask me if it is correct to encrypt the emails, I will say no. Where do you plan to save the passwords with which they are encrypted? In the same database? How are you going to handle the permissions to access those passwords?
    I believe that encrypting emails only makes your application need more resources at the time of access, in addition to making your application less maintainable.

    See these answers in StackOverflow English: Is it worth encrypting email addresses in the database?

        
    answered by 25.12.2016 в 16:06
    2

    There is no problem if the generated string is not the same every time it is generated, as long as you have a key it is possible to decrypt it to the original string. Look at the following code:

    <?php
    function encriptar($key='', $cadena = ''){
        $iv = mcrypt_create_iv(
            mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC),
            MCRYPT_DEV_URANDOM
            );
    
        $encrypted = base64_encode(
            $iv .
            mcrypt_encrypt(
                MCRYPT_RIJNDAEL_128,
                hash('sha256', $key, true),
                $cadena,
                MCRYPT_MODE_CBC,
                $iv
            )
        );
        return $encrypted;
    }
    
    
    function desencriptar($key='', $cadena = ''){
        $data = base64_decode($cadena);
        $iv = substr($data, 0, mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC));
    
        $decrypted = rtrim(
            mcrypt_decrypt(
                MCRYPT_RIJNDAEL_128,
                hash('sha256', $key, true),
                substr($data, mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC)),
                MCRYPT_MODE_CBC,
                $iv
            ),
            "
    $string = 'stack overflow';
    // encriptar
    $hash = crypt($string, 'st');
    echo "<br>encriptado: ".$hash;
    // verificar
    if (crypt($string, $hash) == $hash) {
        echo "<br>si, correcto!!";
    }
    
    " ); return $decrypted; } $key = 'key para encriptar'; $string = 'stack overflow'; echo "Datos oginales:<br>key: ".$key."<br>cadena:".$string; $encryptedstring = encriptar($key, $string); echo "<br><br>cadena encriptada:".$encryptedstring; $decryptedstring = desencriptar($key, $encryptedstring); echo "<br><br>cadena desencriptada:".$decryptedstring; ?>

    However this is not a good practice to encrypt passwords, besides being an old method (although it is not deprecated ).

    If you want to encrypt a string and always get the same result when encrypting, try the following:

    // forma 1:
    $hash = password_hash('rasmuslerdorf', PASSWORD_DEFAULT);
    
    // forma 2:
    $options = [
       'cost' => 11
    ];
    $hash = password_hash('rasmuslerdorf', PASSWORD_BCRYPT, $options);
    

    the crypt(str,salt) function generates a constant key. see attributes here to generate salt correctly

    But, if you want to have high security despite not always generating the same password, try password_hash and password_verify . First, to encrypt:

    if (password_verify('rasmuslerdorf', $hash)) {
        echo 'Password is valid!';
    } else {
        echo 'Invalid password.';
    }
    

    To decrypt:

    <?php
    function encriptar($key='', $cadena = ''){
        $iv = mcrypt_create_iv(
            mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC),
            MCRYPT_DEV_URANDOM
            );
    
        $encrypted = base64_encode(
            $iv .
            mcrypt_encrypt(
                MCRYPT_RIJNDAEL_128,
                hash('sha256', $key, true),
                $cadena,
                MCRYPT_MODE_CBC,
                $iv
            )
        );
        return $encrypted;
    }
    
    
    function desencriptar($key='', $cadena = ''){
        $data = base64_decode($cadena);
        $iv = substr($data, 0, mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC));
    
        $decrypted = rtrim(
            mcrypt_decrypt(
                MCRYPT_RIJNDAEL_128,
                hash('sha256', $key, true),
                substr($data, mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC)),
                MCRYPT_MODE_CBC,
                $iv
            ),
            "
    $string = 'stack overflow';
    // encriptar
    $hash = crypt($string, 'st');
    echo "<br>encriptado: ".$hash;
    // verificar
    if (crypt($string, $hash) == $hash) {
        echo "<br>si, correcto!!";
    }
    
    " ); return $decrypted; } $key = 'key para encriptar'; $string = 'stack overflow'; echo "Datos oginales:<br>key: ".$key."<br>cadena:".$string; $encryptedstring = encriptar($key, $string); echo "<br><br>cadena encriptada:".$encryptedstring; $decryptedstring = desencriptar($key, $encryptedstring); echo "<br><br>cadena desencriptada:".$decryptedstring; ?>

    See Predefined Constants

        
    answered by 26.12.2016 в 19:25
    2

    What I see is that the function mcrypt_create_iv() as it makes the value of $iv is random, that fucks your function, because the parameter you send to the next function base64_encode() is always different, my suggestion is :

    Handle $iv as a global configuration variable in a file as config.php and change it manually for each installation of your system.

    You can also generate the file config.php during the installation and there you generate the value that the variable $iv would have.

        
    answered by 01.01.2017 в 00:40