I have a function that I use for encoding which is like this:
$skey = "whateveryoulike"; // you can change it
if(!$value){return false;}
$text = $value;
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_DEV_URANDOM);
$crypttext = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $skey, $text, MCRYPT_MODE_ECB, $iv);
but thing is when I run PHP RIPS scanner for the sake of PCI I get an error on the following line that says: Weak Cryptography (broken algorithm)
--> $crypttext = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $skey, $text, MCRYPT_MODE_ECB, $iv);
Is there anyone who can point me which Cryptography should I use here. Thanks in advance.
MCRYPT_RIJNDAEL_256 is not AES, it is Rijndael with a block size of 256-bits, not a key size of 256-bits. It is best to use AES which has a block size of 128-bits which is RIJNDAEL_128.
Insure th ekey is exactly the correct size, 16, 24 or 32 bytes in length, other wist the function will reject the key or pad it with something, there is no standard for key padding.
Do not use ECB mode, it is insecure, see ECB mode, scroll down to the Penguin. Instead use CBC mode with a random IV, just prefix the encrypted data with the IV for use in decryption.
mcrypt does not support PKCS#7 padding, only non-standard null padding that can't even be used with binary data.
This finally resolved the issue:
Increasing key length from 15 to 16 because it has to be 16, 24 or 32
$skey = "whateveryoulike";
to
$skey = "whateveryoulikek";
and then:
from:
$crypttext = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $skey, $text, MCRYPT_MODE_ECB, $iv);
to:
$crypttext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $skey, $text, MCRYPT_MODE_CFB, $iv);
Related
I'm upgrading a legacy application from PHP 7.0 to 7.2 and my decrypt function isn't working when I switch out mcrypt for openssl.
I tried the existing answers like mcrypt is deprecated, what is the alternative?, and Gists like https://gist.github.com/odan/c1dc2798ef9cedb9fedd09cdfe6e8e76, but I'm still not able to make the code work.
Can anyone shed some light what I'm doing wrong?
Old code
function decrypt($value, $key) {
$ivLength = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = substr($value, 0, $ivLength);
return rtrim(
mcrypt_decrypt(
MCRYPT_RIJNDAEL_128,
hash('sha256', $key, true),
substr($value, $ivLength),
MCRYPT_MODE_CBC,
$iv
),
"\0"
);
}
New code (not working with existing inputs)
function decrypt($value, $key) {
$ivLength = openssl_cipher_iv_length('AES-128-CBC');
$iv = substr($value, 0, $ivLength);
// Note: $key is hashed because it was hashed in the old encrypt function below
return openssl_decrypt(substr($value, $ivLength), 'AES-128-CBC', hash('sha256', $key, true), OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
}
For context, here's how the old code encrypted the values:
function encrypt($value, $key) {
$iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC), MCRYPT_DEV_URANDOM);
return $iv . mcrypt_encrypt(
MCRYPT_RIJNDAEL_128,
hash('sha256', $key, true),
$value,
MCRYPT_MODE_CBC,
$iv
);
}
Also, the raw value in $value is twice as long when output to the error log in the new code than the old code and looks similar but with more characters.
The main issue here is the key size. You're creating the key with SHA256 which returns a 256 bit hash, therefore you're using AES / Rijndael with a 256 bit key.
The number in Rijndael-128 defines the block size, the key size is determined by the length of the key we use. The number in AES-128 defines the key size (the block size is constant, 128 bits), and if the actual key length is different than this number, then the key is shortened or expanded (with zero bytes) to fit the selected key size.
This means that your mcrypt code uses Rijndael-128 (AES) with a 256 bit key, in CBC mode. The openssl equivalent is AES-256-CBC, and if we use this algorithm then mcrypt and openssl should produce compatible results.
function decrypt_mcrypt_with_openssl($value, $key) {
$iv = substr($value, 0, 16);
$ciphertext = substr($value, 16);
$key = hash('sha256', $key, true);
$plaintext = openssl_decrypt(
$ciphertext, 'AES-256-CBC', $key,
OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING,
$iv
);
return rtrim($plaintext, "\0");
}
SHA256 is not suitable as a key derivation function. If you're deriving your key from a password you can use hash_pbkdf2 with a random salt and a high enough number of iterations. Or you could create a cryptographically secure pseudo-random key with openssl_random_pseudo_bytes.
I think it's best to stop using mcrypt completely and use only openssl. Mcrypt doesn't support PKCS7 padding, it doesn't provide any authenticated encryption algorithms, and it is not being maintained anymore.
So as time moves on mcrypt will go in PHP 7.2.
Of course there is an alternative: openssl.
I find it difficult to switch from mcrypt to openssl, using AES 256 CBC and preserving IVs. I am sort of new to cryptography, so I don't really know everything, but I understand the basics.
Let's say I have the following code
function encrypt($masterPassword, $data)
{
$keySize = mcrypt_get_key_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC);
$ivSize = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($ivSize, MCRYPT_DEV_URANDOM);
$key = mb_substr(hash('SHA256', $masterPassword), 0, $keySize);
$encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $data, MCRYPT_MODE_CBC, $iv);
return base64_encode($iv . $encrypted);
}
function decrypt($masterPassword, $base64)
{
$keySize = mcrypt_get_key_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC);
$ivSize = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC);
$key = mb_substr(hash('SHA256', $masterPassword), 0, $keySize);
$data = base64_decode($base64);
$iv = substr($data, 0, $ivSize);
$encrypted = substr($data, $ivSize, strlen($data));
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $encrypted, MCRYPT_MODE_CBC, $iv);
return trim($decrypted);
}
How can I "convert" this code to use openssl insted of mcrypt?
You can't convert it, because Rijndael-256 is not AES-256, and the OpenSSL extension doesn't ship with Rijndael-256 support.
AES-256 is Rijndael-128 with a 256-bit (32-byte) key.
Unfortunately, you'll have to re-encrypt all of your data.
Edit: Also, the scheme you're currently using has some problems:
It lacks authentication (HMACs are the easiest way to do it in PHP)
It lacks proper padding (mcrypt pads with zero bytes; you need something like PKCS#5 padding instead), which is required for block mode encryption to be safe.
It's not byte-safe (you're using mb_substr())
The good news is that OpenSSL will do PKCS#5 padding for you automatically, but you should go even further and use a solid encryption library like defuse/php-encryption.
$secretKey = "MYSECRETKEY";
$plaintext = 'Plain Text Will Be here';
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$ivDecode = base64_encode(mcrypt_create_iv($iv_size, MCRYPT_RAND));
$encrypted = trim(mcrypt_encrypt(MCRYPT_RIJNDAEL_128,
substr(sha1($secretKey), 0, 32),
$plaintext,
MCRYPT_MODE_CBC,
$iv), "\0..\32");
$encrypted = $iv . $encrypted;
$ciphertext_base64 = base64_encode($encrypted);
#echo $ciphertext_base64 . "\n";
$decrypted = trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_128,
substr(sha1($secretKey), 0, 32),
base64_decode($ciphertext_base64),
MCRYPT_MODE_CBC,
base64_decode($ivDecode)), "\0..\32");
echo $decrypted;
when I run above code I got this output.
»_w>ø9â„6ÅkžPlain Text Will Be here
I can't edit $decrypted string because I can't access it. I just can edit $encrypted only. So how can remove extra special characters(»_w>ø9â„6Åkž) from out put by editing $encrypted string. I want to send encrypted text using JSON to the different server to decrypt it.
It is not possible to split the iv and encrypted data prior to Base64 decoding, first Base64 decode and then split them.
MCRYPT_RIJNDAEL_128 which is also AES has a block size of 128-bits or 16-bytes. The iv must be that size. Instead of including base64_decode($iv) as a parameter actually create a 16-byte iv. Base64 decoding the iv will not work if it is is not Base64 encoded, it isn't in this case.
The key should be 128, 192 or 256 bits (16, 24 or 32 bytes), exactly the correct size for interoperability, do not rely on padding by the encryption algorithms.
Similarly, for the input to be encrypted and the key prepare it in a separate statement so that debugging is easier.
Do not trim the output, the mcrypt_decrypt is correct. Padding may add an additional block, that is required.
Do not Base64 decode the result of the decryption, the plaintext was not Base64 encoded. – zaph just now edit
"text like this ïÕ[pI¤;Køv" probably occurs when attempting to print data as a string, not all binary bytes have a print representation and many have special characters as their print representation in the 0x80-0xff range.
Here is the concept, not tested, I have not used php in 20 years so fix any errors:
$secretKey = "1234567890123456"; # Note the length is 16-bytes, a full key
$plaintext = 'XXXXXXXXX';
echo $plaintext . "\n";
# --- ENCRYPTION ---
$key = substr(sha1($secretKey), 0, 32)
$iv = mcrypt_create_iv(16, MCRYPT_RAND);
$ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128,
$key,
$plaintext,
MCRYPT_MODE_CBC,
$iv);
# prepend the IV for it to be available for decryption
$ciphertext = $iv . $ciphertext;
$ciphertext_base64 = base64_encode($ciphertext);
echo $ciphertext_base64 . "\n";
# --- DECRYPTION ---
$key = substr(sha1($secretKey), 0, 32)
$cipher_text_iv = base64_decode($ciphertext_base64)
# split the iv and encrypted text
$iv = substr($cipher_text_iv, 0, 16)
$ciphertext = substr($cipher_text_iv, 16)
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128,
$key,
$ciphertext,
MCRYPT_MODE_CBC,
$iv);
echo $decrypted;
I got the code below from this site. When I run it, I get
Warning: mcrypt_encrypt(): Attempt to use an empty IV, which is NOT recommend in C:\web\apache\htdocs\dev\encrypt.php on line 7
What wrong am I doing here and how do I get this right?
//Key
$key = 'SuperSecretKey';
//To Encrypt:
$encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, 'I want to encrypt this', MCRYPT_MODE_CFB);
//To Decrypt:
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $encrypted, MCRYPT_MODE_CFB);
echo $encrypted;
echo "<br>";
echo $decrypted;
It is not error its warning, it clearly explained in Documentation
IV - Used for the initialization in CBC, CFB, OFB modes, and in some algorithms in STREAM mode. If you do not supply an IV, while it is needed for an algorithm, the function issues a warning and uses an IV with all its bytes set to "\0".
just take a look at the documentation.
IV is the fifth parameter of mcrypt_encrypt.
Used for the initialization in CBC, CFB, OFB modes, and in some algorithms in STREAM mode. If you do not supply an IV, while it is needed for an algorithm, the function issues a warning and uses an IV with all its bytes set to "\0".
Same code with a IV (Initialization Vector) with all bytes to 0
$encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, 'I want to encrypt this', MCRYPT_MODE_CFB, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0");
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $encrypted, MCRYPT_MODE_CFB, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0");
Tonight I've pretty much gone through a crash course on basic cryptography, and I figured I don't want to send an IV through clearly, so I'm encrypting it with the string.
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$encrypt = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key1, $string, MCRYPT_MODE_CBC, $iv);
$string = $iv.$encrypt;
$encrypt = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key2, $string, MCRYPT_MODE_ECB);
$return = urlencode(base64_encode($encrypt));
and to decrypt I've got this, but it's not working
$encrypted = base64_decode(urldecode($_GET['password']));
$encrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key2, $encrypted, MCRYPT_MODE_ECB);
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = mb_strcut($encrypted, 0, $iv_size);
$password = mb_strcut($encrypted, $iv_size);
$password = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key1, $password, MCRYPT_MODE_CBC, $iv);
Can anyone see the problem with my code and explain to me what I'm doing wrong?
You don't need to encrypt your IV, it is safe to send it in clear. Usually it is prepended to the cyphertext. If you analyse the structure of CBC mode, you can see that the IV only affects the first cypher block. The "IV" for the second cypher block is the first cypher block, the "IV" for the third cypher block is the second cypher block. Any attacker will already know every cypher block, so they will already know most of the "IV"s.
The security of CBC mode lies in the key, and only in the key. An attacker with the cyphertext and the IV cannot recover the key, so there is no point n doing the extra work to encrypt the IV.
I believe you need to write:
$encrypted = base64_decode(urldecode($_GET['password']));
instead of what is there in wrong order for decryption.