Encryption issue with php - php

Doing AES256 encryption on server and decryption on ios. That didn't work for a long time, but I decided to base 64 encode text to encrypt before encrypting.
Accidentally I put braces around base64_encode($source) and that started work. So now this wrong code works(decrypts well on ios):
$ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, "base64_encode($source)", MCRYPT_MODE_CBC, $iv);
and right thing doesn't work at all:
$ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, base64_encode($source), MCRYPT_MODE_CBC, $iv);
Using cake php if that makes the difference. What is wrong and what is the difference? Why braces make thing to work and without braces it just fails on decryption? Sorry, if it is very stupid question, but I am new to cryptography.

You should character-encode the plain text (e.g. use UTF-8, there is a utf8-encode function in PHP), and then base64 encode the outcome if you want to transfer the result as a string.
Note that the output of mcrypt_encrypt() will be indistinguishable from random bytes. That mean that any bytes can be output, including those that are not valid character encodings (such as a 00h valued byte, or EOF in C). As the IV is random (hopefully) sometimes everything will seem peachy, and sometimes your decryption will fail because of an invalid character.
With encryption, if you are not in full control over the (charachter) encoding/decoding principles (not so much encryption/decryption), then you may get unexpected outcomes once in a while - just the thing you need to avoid when programming something.

Related

How to encrypt XOR hex string 32 in PHP

I'm trying to encrypt some of my passwords using the XOR encryption algorithm. I tested it on CyberChef but I don't know how to convert it from PHP. I look forward to helping. Thanks a lot.
XOR HEX string 32
It helps to start with the basic data structures involved.
Your objective is to use a secret key to transform your plain text -- the message you wish to keep secret -- into encrypted text. By definition your plain text is easy for an adversary to understand, and your encrypted text is not.
Then, the rest of your objective is to use the key to transform the encrypted text back into plain text. XOR is a symmetric cipher: it uses exactly the same key to encrypt and to decrypt.
Basic data structures
You have text strings like ATTACK for example.
You have arrays like
array( 65, 84, 84, 65, 67, 75)
And you have base 64 encoded strings like
base64_encode( "ATTACK" ); //QVRUQUNL
Your data structures
Let's say your message is ATTACK. (This kind of crypto started with military applications, of course.) That's an array of numbers. Your php example converts your message into an array of ASCII character values. This little bit of code does that. It uses the ord() function to convert a letter into a number, called a codepoint. Run it.
$plaintext = 'ATTACK';
$plaintextASCII = [];
foreach( str_split( $plaintext ) as $letter) {
$plaintextAscii[] = ord( $letter );
}
print_r( $plaintextAscii );
It does the same thing as this line of code from your example, but using an explicit loop rather than the array_map() shortcut.
return array_map('ord', str_split($text));
Then you can mangle that array of character values. The XOR cipher is a way to do that.
Finally you convert it back to a string using the chr() function.
$encryptedString = '';
foreach ( $plaintextAscii as $codepoint ) {
$encryptedString .= chr( $codepoint );
}
But, because you mangled your codepoints to encrypt them, this $encryptedString contains non-printable characters. So you cannot just paste the string into an email or something like that. Instead, you must encode the string using only printable characters.
base64_encode( $encryptedString );
That's the encrypted message. To decrypt it you reverse the process.
That should get you started understanding this example code.
Pro tip when trying to understand an algorithm, don't try to use open-source packages. Instead, look at their code and copy the interesting lines into your own sample code.
Pro tip get a decent debugger program so you can step through your code.
Online security tip do not, repeat not, use this kind of encryption to store peoples' passwords online. It's not secure enough to slow down cybercreeps. php has a really good set of password-hashing functions. Read about them here.

PHP: Warning mcrypt_generic_init(): Iv size is incorrect; supplied length: 12, needed: 8

Basic Facts:
$algorithm = MCRYPT_BLOWFISH;
$mode = MCRYPT_MODE_CBC;
$randSource = MCRYPT_DEV_URANDOM;
Note
This is not a strict coding question.
Context:
CentOS 7, Apache 2.4.12, & PHP 5.6.20.
I am making an HTML email with a "verify your email address" link that allows a registration process to complete. Everything on my virtual private server is UTF-8, and all form and query string input is processed with multi-byte (mb) funcions.
Background
As an experiment (I know about the age and state of the mcrypt library), I am attempting to decrypt Blowfish encrypted query string parameters. Assume that on the way up, the encryption sequence works perfectly and I am receiving email with the link.
On the way down, the hmac_hash() signing (SHA-512, just for this experiment) is working and I am able to separate each independent message (32 characters) from its hash checksum (128 characters). Base64 decoding of the separated message portion is working. For each parameter, I am left with the composite cipher text, where composite cipher text equals the IV + base cipher text. Assume I use a version of substr() to obtain the IV and the base cipher text independently (which is par for the course).
Problem
PHP: Warning mcrypt_generic_init(): Iv size is incorrect; supplied length: 12, needed: 8
Assume I have combed the PHP manual and Stackoverflow. Assume I have looked at other questions similar, but not exactly like this one. Assume I have searched the Internet to no avail. Assume I have enough experience to setup mb_string properly. Assume that I will take care of mcrypt padding when I get past this current problem.
Could multi-byte issues be interfering with decryption?
Could base64 encoding the IV + base cipher text corrupt the IV?
Could base64 padding be an issue?
Should I be specifying a more specific MCRYPT_BLOWFISH_*?
Why does the blowfish IV size report 8 bytes, but rarely produces an 8 byte IV?
Which substr() should I use, substr() or mb_substr(), for a setup that leans towards making everything UTF-8 and processes all other input as multi-byte UTF-8. I know that is an odd question, but all of the PHP Manual mycrypt decryption sequence examples use substr(), and none use mb_substr(). Everything on my site works with mb_functions when possible, and I would not mind using substr() if it solved my problem, but it does not solve it. When I use mb_substr(), I get the following warning.
PHP: Warning mcrypt_generic_init(): Iv size is incorrect; supplied length: 11, needed: 8
Does anyone have any experience with this exact issue? Constructive answers will be rewarded!
Latest
Above is an example Blowfish hash that I am trying to reconstruct from an array, received via a SHA512 HMACed, symmetricly Blowfish encrypted (CBC), url safe Base64 encoded, urlencoded, query string (phew!).
Below, is what the strings for the query string (having chopped up the blowfish hash above) look like after encrypting, signing, and base64 encoding, but before being urlencoded. Each one is 128 characters long (each string gets longer as you do more stuff).
Above is the Base64 decoded and Blowfish decrypted array derived from the query string (Obviously, there are security steps in between this result, but I am just trying to show the latest state of things.) Something is not right. Encryption appears to work without any errors. Decryption does not produce any errors either. The plain text is just wrong. If I join/implode these elements, they will not be like the Blowfish hash above.
I would guess that the issue will hide somewhere with the UTF-8 encoding, as you use it in incorrect contexts. It could also be that your framework does some magic for all use-cases. This could be too much and generally end up in security hole or just bugs like that, as you don't do what really needs to be done when it really needs to be done.
Strings in PHP are just collections of bytes. You can store text there, in encoding of your choosing, or you could just store binary data there, like images. PHP knows neither what kind of data is in what string nor what encoding is used there. This is up to developer to track this information.
When working with encryption, you get binary data when generating random strings or encrypting some payloads. It's saved in strings, but it does not have UTF-8 encoding, as it's just bytes. I wouldn't even say that it's encoding is ISO-8859-1, as this would mean that byte 77 (0x4D) stands for letter "M". But for real, it's just numbers - 77 does not stand for any letter at all.
One more thing to add - for ASCII symbols (Latin letters, digits etc. - 0-127 byte values) it takes one byte to represent that symbol in UTF-8 encoding (same as in ISO-8859). So as far as you pass base64_encoded data, you shouldn't worry too much about it. mb_substr will also work in the same way as substr. But! for the binary data, you cannot use mb_* functions, as it works with characters. For example, if encrypted data is two bytes 0xC5 0xA1, it's only single symbol in UTF-8. Encryption works with bytes (up until the final result, which could be anything - even binary files), not characters.
As you've not provided any code, I've put some for you - I hope it will help with your issue (if it's still relevant at all).
To show passing parameters in URL, there are two files: encrypt.php and decrypt.php. Save to a directory, run php -S localhost:8000 in it and go to http://localhost:8000/encrypt.php
encrypt.php:
<?php
// mcrypt_enc_get_key_size($td) gives 56, so it's longest that this key can be
$key = 'LedsoilgarvEwAbDavVenpirabUfjaiktavKekjeajUmshamEsyenvoa';
$data = 'This is very important data, with some š UTF-8 ĘĖ symbols';
$td = mcrypt_module_open(MCRYPT_BLOWFISH, '', MCRYPT_MODE_CBC, '');
// create random IV - it's just random 8 bytes. You should use random_bytes() instead if available
$ivSize = mcrypt_enc_get_iv_size($td);
$iv = mcrypt_create_iv($ivSize, MCRYPT_DEV_URANDOM);
mcrypt_generic_init($td, $key, $iv);
$encrypted = mcrypt_generic($td, $data);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
// payload that you want to send - binary. It's neither UTF-8 nor ISO-8859-1 - it's just bytes
$payload = $iv . $encrypted;
// base64 to pass safely
$base64EncodedPayload = base64_encode($payload);
// URL encode for URL. No need to do both URL-safe base64 *and* base64 + urlencode
$link = 'http://localhost:8000/decrypt.php?encryptedBase64=' . urlencode($base64EncodedPayload);
// in fact, just for the reference, you don't even need base64_encode - urlencode also works at byte level
// base64_encode takes about 1.33 more space, but urlencode takes 3 times more than original for non-safe symbols, so base_64 will probably be shorter
$link2 = 'http://localhost:8000/decrypt.php?encrypted=' . urlencode($payload);
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<pre><?php
var_dump('Data:', $data);
var_dump('Data size in bytes:', strlen($data));
var_dump('Data size in characters - smaller, as 3 of the characters take 2 bytes:', mb_strlen($data, 'UTF-8'));
var_dump('Encrypted data size in bytes - same as original:', strlen($encrypted));
var_dump('Encrypted data size in characters - will be pseudo-random each time:', mb_strlen($encrypted, 'UTF-8'));
var_dump('IV base64 encoded:', base64_encode($iv));
var_dump('Encrypted string base64 encoded:', base64_encode($encrypted));
?></pre>
<!-- Link will not contain any special characters, so htmlentities should not make any difference -->
<!-- In any case, I would still recommend to use right encoding at the right context to avoid any issues if something changes -->
Link to decrypt<br/>
Link to decrypt2
</body>
</html>
decrypt.php:
<?php
$key = 'LedsoilgarvEwAbDavVenpirabUfjaiktavKekjeajUmshamEsyenvoa';
if (isset($_GET['encryptedBase64'])) {
// just get base64_encoded symbols (will be ASCII - same in UTF-8 or other encodings)
$base64EncodedPayload = $_GET['encryptedBase64'];
$payload = base64_decode($base64EncodedPayload);
} else {
// just get binary string from URL
$payload = $_GET['encrypted'];
}
$td = mcrypt_module_open(MCRYPT_BLOWFISH, '', MCRYPT_MODE_CBC, '');
$ivSize = mcrypt_enc_get_iv_size($td);
$iv = substr($payload, 0, $ivSize);
$encrypted = substr($payload, $ivSize);
mcrypt_generic_init($td, $key, $iv);
/* Decrypt encrypted string */
$decrypted = mdecrypt_generic($td, $encrypted);
/* Terminate decryption handle and close module */
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<pre><?php
var_dump('IV base64 encoded:', base64_encode($iv));
var_dump('Encrypted string base64 encoded:', base64_encode($encrypted));
var_dump('Result:', $decrypted);
?></pre>
</body>
</html>

Common PHP mcrypt_encrypt() code sometimes fails decryption

This code is from several websites, the source of which has mentioned SO as a source for help in its creation.
This is the uncompressed version, of which I changed the base64 encode and decode to hex2bin and bin2hex as I thought this was giving me problems when passing between webpages, but was because I was on occations getting trash data as the decryption result.
With either code, using the same key and different data, on occasions gives me trash data as the decryption result. It seems to be the data itself, as opposed to repetition, the data size may be between 30 - 80 characters.
The data is just a series of numbers and letters, all within the usual ASCII standard, no nulls, nothing beyond standard email address characters.
I'm using Ubuntu 14.04 LTS, with PHP 5.5.9-1ubuntu4.5 (Zend: 2.5.0)
Has anyone experienced this kind of problem?
EDIT: From reading more SO, apparently $iv should be the same for encrypt and at the time of decrypt, but if this is true why would it work most of the time anyway?
function mc_encrypt($encrypt, $mc_key) {
$iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND);
$passcrypt = trim(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $mc_key, trim($encrypt), MCRYPT_MODE_ECB, $iv));
$encode = bin2hex($passcrypt);
return $encode;
}
function mc_decrypt($decrypt, $mc_key) {
$decoded = hex2bin($decrypt);
$iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND);
$decrypted = trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $mc_key, trim($decoded), MCRYPT_MODE_ECB, $iv));
return $decrypted;
}
Rijndael is a symmetric block cipher. It operates on blocks and in this case, the blocks are 256-bit wide. The ciphertext is supposed to look random, but it isn't. Otherwise, it wouldn't be possible to reverse the encryption. Since this is ECB mode, there is no IV which randomizes the whole thing. And because of this every plaintext block combined with the key will result in the same ciphertext block. So the issue is reproducible only based on the same data.
Since the encryption is supposed to look random, it will happen that the first byte of the ciphertext will contain a whitespace (when shown as characters). trim removes 6 types of whitespace and will remove at least the first byte in those cases. So, the problem will happen in 6/256 of cases (~2%).
If for example the first byte is a space, then it will be removed. Now the first block doesn't have all the bytes and the block boundary for decryption is moved. So, the first byte from the next block is used as the last byte of the first block to decrypt the block. Since Rijndael is a block cipher, only garbage will come out. And this happens for all blocks.
Usually rtrim is used instead of trim to unpad the plaintext: rtrim(mcrypt_decrypt(...), '\0');. The trim of the ciphertext should be removed completely.
This isn't whole story, because the end of the encryption/decryption is trimmed which can break the result. It's best to actually use PKCS#7 padding by implementing it yourself and completely lose the *trim. Sadly php_mcrypt doesn't provide the PKCS#7/PKCS#5 padding.

Extra Characters Output From mcrypt_decrypt

I am in need of symmetric encryption and decryption in a pair of PHP scripts. I am using mcrypt_encrypt and crypt_decrypt. To test this, I have the following code:
$encrypted_token = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $ENCRYPTION_SECRET, $refresh_token, MCRYPT_MODE_ECB);
$encrypted_encoded_token=base64_encode($encrypted_token);
echo "\nEncrypted Token: " . $encrypted_encoded_token . "\n";
To test this, in the same PHP script I do the following:
$decoded_refresh_token = base64_decode($encrypted_encoded_token);
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $ENCRYPTION_SECRET, $decoded_refresh_token, MCRYPT_MODE_ECB);
echo "\nDecrypted Token After decrypt: " . $decrypted . "\n";
My input $refresh_token is a long string of characters that looks something like this (only longer):
AQCNKTZhaWTmUl3WvHOtlkv2Vc-Ybkl24D5Zp1lZPLBraTxrr-YQhErXrI2IWWEIWk5lnBc1k
The Decrypted Token After decrypt looks something like this:
AQCNKTZhaWTmUl3WvHOtlkv2Vc-Ybkl24D5Zp1lZPLBraTxrr-YQhErXrI2IWWEIWk5lnBc1k�������������������
My $ENCRYPTION_SECRET is 32 characters long. The base64_encode and decode are because I am json_encoding and decoding the token in a Post.
What am I doing wrong?
Those extra characters are indeed bytes valued zero. PHP's mcrypt uses 0..n - 1 bytes of zero padding, where n is the blocksize. In other words, if the plaintext is already a multiple of the blocksize then it doesn't pad. Otherwise it pads up to the block size.
Now you are using MCRYPT_RIJNDAEL_256 which is not AES, but Rijndael with a block size of 256 bits. So the number of bytes added are 0..31 bytes. If you view those as a string they get converted to question marks or removed, depending on what you view the string with. In the C-library of mcrypt that probably made more sense as zero terminates a null-terminated string.
Nowadays the ad-hoc standard is PKCS#7 padding, which adds 1..blocksize of padding bytes. If x is the number of padding bytes then x is also the value of the bytes added. PKCS#7 padding is deterministic, i.e. you can always unpad, no matter the value of the plaintext. Zero padding behaves largely the same way, unless the plaintext contains zero characters at the end. This is however never the case for printable strings (in ASCII, latin or UTF-8).
Finally, to remove the padding, simply perform rtrim(plaintext, "\0") and you'll get the original string.

How to avoid that "%2500" gets added to string after encryption? [duplicate]

Working with OAuth and encrypting the keys with the following function with a string which we'll call 'foo' (actually an OAuth token)
public function encrypt( $text )
{
// add end of text delimiter
$data = mcrypt_encrypt( MCRYPT_RIJNDAEL_128, $this->key, $text, MCRYPT_MODE_ECB, $this->iv );
return base64_encode( $data );
}
When I decrypt it using the inverse function, I end up with:
Function:
public function decrypt( $text )
{
$text = base64_decode( $text );
return mcrypt_decrypt( MCRYPT_RIJNDAEL_128, $this->key, $text, MCRYPT_MODE_ECB, $this->iv );
}
Result:
foo%00%00%00%00%00%00%00%00%00%00%00%00%00%00
Edit:
Looking at it a little more, I realized that it is actually URL encoding to %00, which means that my strings are somehow being padded by null characters? So I am currently using trim() to get rid of them, but I would like to understand why this is happening.
Rijndael is a block cypher, which means that it operates on chunks of data of a particular length (128 bits in this case). This means that if the length of the input text is not a multiple of the block size, it must be padded out to fit. In this case, the padding is zeros; there are a number of other possible padding schemes that could be used, but if you want them with PHP's mcrypt you'll have to apply them manually.
You can Fix it with this method to get rid of padding characters: In our case we are using Zend.
$filter = new Zend_Filter_Decrypt(array('adapter' => 'mcrypt'));
$filter->setVector($lpt->_seed);
str_replace("\x0", '', trim($filter->filter(base64_decode($textToDecrypt))));
MCRYPT_MODE_ECB means that you are using ECB, a block cipher mode of operation. Block ciphers can be handled either for block cipher modes of operation or for stream cipher modes of operation. Common block cipher modes are ECB and CBC, a common stream cipher mode is CTR, better known as counter mode operation.
MCRYPT_RIJNDAEL_128 is an implementation of AES. AES is the Rijndael cipher with a block size of 128 bits, and three possible key sizes, 128, 192 and 256 bits. So if you use a block cipher mode of encryption then you need to divide up the plain text is sizes of 128 bits - 16 bytes - each. Of course this leaves you with the question what to do when the last block is not 16 bytes.
PHP's mcrypt_encrypt more or less leaves this up to the user. It pads with 00 valued characters if the block is not full up to the block size. This is fine if the input is a string; you can simply trim off the 00 characters from the returned string. If the input is however binary data that ends with a 00 character than that character is lost (+ any other character that is taken from the start and end of the string of course). You can also send the length of the string encrypted together with the plaintext of course.
For a better scheme you only have to look at PKCS#7 padding. Multiple code snippets for implementing the padding can be found in the comments section of mcrypt_encrypt.
mcrypt_encrypt currently does not seem to support stream modes for AES, so that option is out if you want to keep with the PHP mcrypt library.

Categories