Question

Trying to achieve encrypting and decryption using following strategy, but ending up with random characters mostly.

class Crypt {

public static function encrypt($string, $account) {
    // create a random initialization vector to use with CBC encoding
    $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
    $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);

    $key = pack('H*', $account . $account);

    $output = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $string, MCRYPT_MODE_CBC, $iv);
    $output = $iv . $output;
    $output = base64_encode($output);
    $output = urlencode($output);

    return $output;
}

public static function decrypt($token, $account) {
    $ciphertext_dec = base64_decode($token);

    // retrieves the IV, iv_size should be created using mcrypt_get_iv_size()
    $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
    $iv_dec = substr($ciphertext_dec, 0, $iv_size);

    // retrieves the cipher text (everything except the $iv_size in the front)
    $ciphertext_dec = substr($ciphertext_dec, $iv_size);

    $key = pack('H*', $account . $account);

    $token = urldecode($token);

    $output = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $ciphertext_dec, MCRYPT_MODE_CBC, $iv_dec);
    $output = rtrim($output, "");
    return $output;
}

   }

Can't get exact values back, sometimes it decrypts but I see some garbage values, but mostly just random characters.

$a = \Crypt::encrypt("MyPassword", "1974246e");
echo \Crypt::decrypt($a, "1974246e");

Edits after the discussion

class Crypt {

public static function encrypt($data, $passphrase) {
    $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC); //create a random initialization vector to use with CBC encoding
    $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
    $key = pack('H*', $passphrase . $passphrase);
    return base64_encode($iv . mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $data, MCRYPT_MODE_CBC, $iv));
}

public static function decrypt($data, $passphrase) {
    $data = base64_decode($data);
    $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC); //retrieves the IV, iv_size should be created using mcrypt_get_iv_size()
    $iv = substr($data, 0, $iv_size);
    $data = substr($data, $iv_size); //retrieves the cipher text (everything except the $iv_size in the front)
    $key = pack('H*', $passphrase . $passphrase);
    return rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $data, MCRYPT_MODE_CBC, $iv), chr(0));
}
}

Usage:

$pass = "MyPassword*&^*&^(*&^(";
$token = \Crypt::encrypt($pass, "1974246e8e8a479bb0233495e8a3ed12");
$answer = \Crypt::decrypt($token, "1974246e8e8a479bb0233495e8a3ed12");
echo $answer == $pass ? "yes" : "no";
Was it helpful?

Solution

  1. Don't urlencode. Unnecessary.
  2. trim for NULL bytes, not empty strings: rtrim($str, chr(0)); (Instead, you might want to save the source string length in the encrypted result too, so you won't rtrim() too much.)

Why pack('H*', $account) for $key? Also unnecessary.

Rijndael 128 uses 16 byte keys (128 bits), so make sure your key is at least that long:

$key = $account . $account

will do, but it obviously imperfect. (mcrypt will do something like that if it's too short.) If every account had its own passphrase, that would be good. (Even more so in combination with an app secret, but details.)

rtrim() with chr(0) is fine, very probably, because your source string won't have trailing NUL bytes.

I usually use these en/decrypt functions, or alike, but these have a static secret/key, so yours is better.

To send an encrypted token to the client:

$enc_token = Crypt::encrypt($token, $key);
// $enc_token might contain `/` and `+` and `=`
$url = 'page.php?token=' . urlencode($enc_token);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top