Questions about Laravel encryption - php

I have some questions about the Encrypt class from Laravel. I am hoping somebody could answer them.
It's mostly about the encrypt method on line 70 here:
https://github.com/illuminate/encryption/blob/master/Encrypter.php#L70
public function encrypt($value)
{
$iv = random_bytes(16);
$value = \openssl_encrypt(
serialize($value), $this->cipher, $this->key, 0, $iv
);
if ($value === false) {
throw new EncryptException('Could not encrypt the data.');
}
$mac = $this->hash($iv = base64_encode($iv), $value);
$json = json_encode(compact('iv', 'value', 'mac'));
if (! is_string($json)) {
throw new EncryptException('Could not encrypt the data.');
}
return base64_encode($json);
}
I have learned about openssl_encrypt and it seems like a good fit for a personal use case. I have made encrypt and decrypt methods using it.
The Laravel does a whole lot more than simply encrypting though.
Why does laravel serialize the value on encryption? If it's always a string that this method takes what is the advantages of serializing the data?
Why is base64_encode being used here?
Why are the values json_encoded? Is this used to keep a clean array or for other intents as well? Currently in my class's encrypt() method I simply concat the encrypted $value and $iv(Initialization vector). On decrypt I break them apart again.
Won't the openssl_encrypt throw already throw an exception when it can not encrypt data? Why is the return value checked if this is the case?
If somebody could take a minute to take a look at the github repo on the specified line I would be really happy.
Cheers!

Why does laravel serialize the value on encryption? If it's always a string that this method takes what is the advantages of serializing the data?
The function doesn't take just a string. It takes any (serializable) data type. It serializes the input value to convert it to a string; one that can be returned back to its original type upon decryption. I know the PhpDoc above the function states that the parameter is a string, but that seems like it is wrong and needs to be changed to mixed.
Why is base64_encode being used here?
base64 encoding is used on the $iv to convert the random bytes to a usable string by the hash method.
As far as base64 encoding the return value, I do not know. It seems as if they could have left it as the json encoded string, but maybe they wanted to make sure that the returned string did not have any special characters, or maybe they wanted one more piece of obfuscation.
Why are the values json_encoded? Is this used to keep a clean array or for other intents as well? Currently in my class's encrypt() method I simply concat the encrypted $value and $iv(Initialization vector). On decrypt I break them apart again.
Yes, the json_encoding is just to help facilitate a clean and easily parsible payload. They have three values they need to keep track of, and the easiest and cleanest way to be able to keep the values separated is with an array.
Won't the openssl_encrypt throw already throw an exception when it can not encrypt data? Why is the return value checked if this is the case?
I don't see any indications in the PHP documentation that openssl_encrypt will throw an exception. It does state, however, that it returns false on failure, which is the reason for the strict check on false.
Now, there are two reasons why it may emit a PHP Warning, which Laravel converts to an exception, but those aren't the only two ways that encryption may fail.

Related

Nodejs how to implement OpenSSL AES-CBC encryption (from PHP)?

I am currently working on translating an encryption algorithm from PHP to Typescript, to use in a very specific API that requires the posted data to be encrypted with the API key and Secret. Here is the provided example of how to correctly encrypt data in PHP for use with the API (the way of implementing the key and IV can't be changed):
$iv = substr(hash("SHA256", $this->ApiKey, true), 0, 16);
$key = md5($this->ApiSecret);
$output = openssl_encrypt($Data, "AES-256-CBC", $key, OPENSSL_RAW_DATA, $iv);
$completedEncryption = $this->base64Url_Encode($output);
return $completedEncryption;
In the above code, the only thing the base64Url_Encode function does is convert the binary data to a valid Base64URL string.
And now the code as I have implemented it inside Typescript:
import { createHash, createCipheriv } from 'node:crypto'
const secretIV = createHash('sha256').update(this.ApiKey).digest().subarray(0, 16)
// Generate key
/*
Because the OpenSSL function in PHP automatically pads the string with /null chars,
do the same inside NodeJS, so that CreateCipherIV can accept it as a 32-byte key,
instead of a 16-byte one.
*/
const md5 = createHash('md5').update(this.ApiSecret).digest()
const key = Buffer.alloc(32)
key.set(md5, 0)
// Create Cipher
const cipher = createCipheriv('aes-256-cbc', key, secretIV)
let encrypted = cipher.update(data, 'utf8', 'binary');
encrypted += cipher.final('binary');
// Return base64URL string
return Buffer.from(encrypted).toString('base64url');
The above Typescript code only does NOT give the same output as the PHP code given earlier. I have looked into the original OpenSSL code, made sure that the padding algorithms are matching (pcks5 and pcks7) and checked if every input Buffer had the same byte length as the input inside PHP. I am currently thinking if it is some kind of binary malform that is causing the data to change inside Javascript?
I hope some expert can help me out with this question. Maybe I have overlooked something. Thanks in advance.
The stupidity is in the md5 function in PHP, which defaults to hexadecimal output instead of binary output:
md5(string $string, bool $binary = false): string
This is also why the code doesn't complain about the key (constructed from the MD5 hash) is being too small, it is fed 32 bytes after ASCII or UTF8 encoding, instead of the 16 bytes you'd use for AES-128.
Apparently it is using lowercase encoding, although not even that has been specified. You can indicate the encoding for NodeJS as well, see the documentation of the digest method. It also seems to be using lowercase, although I cannot directly find the exact specification of the encoding either.
Once you have completed your assignment, please try and remove the code ASAP, as you should never calculate the IV from the key; they key and IV combination should be unique, so the above code is not IND-CPA secure if the key is reused.
In case you are wondering why it is so stupid: the output of MD5 has been specified in standards, and is binary. Furthermore, it is impossible from the function to see what it is doing, you have to lookup the code. It will also work very badly if you're doing a compare; even if you are comparing strings then it is easy to use upper instead of lowercase (and both are equally valid, uppercase hex is actually easier to read for humans as we focus on the top part of letters more for some reason or other).
Basically it takes the principle of least surprise and tosses it out of the window. The encoding of the output could be made optimal instead, the NodeJS implementation does this correctly.

mcrypt_decrypt not returning correct data only when used in Laravel seeding

We are using the built in Database Seeding in Laravel 5.3 under PHP 7.0 on Windows. Problem is that whenever we use mcrypt_encrypt to encrypt some data, the data we get back from mcrypt_decrypt is not the same as what we passed in.
$data = #mcrypt_encrypt(MCRYPT_RIJNDAEL_128, self::$key, $data, MCRYPT_MODE_CFB, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0");
$data = #mcrypt_decrypt(MCRYPT_RIJNDAEL_128, self::$key, $data, MCRYPT_MODE_CFB, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0");
In this case, $data is some binary string and not the original string.
This only happens during seeding. mcrypt_encrypt/mcrypt_decrypt will work through a web request or tinker.
We know it's deprecated. We have to use mcrypt_encrypt as opposed to the more recent encrypt (https://laravel.com/docs/5.3/encryption), because we have legacy data already encrypted using mcrypt_encrypt.
$key is a static variable that loads from an encryption key
This is no error or warning to indicate a problem
Anybody have an idea of what could be going on here?
Based on the feedback from zaph and some soul searching, we are going to migrate our encrypted data and use something more modern. We've been avoiding it, because it still works in most cases. But we're just postponing the inevitable. Thanks for the feedback.

Encrypt binary files with openssl_private_encrypt in php [duplicate]

I am trying to use the PHP function openssl_private_encrypt() to encrypt an uploaded file prior to saving it (see code snippet below), however it's bool is returning false and the encrypted content return is returning nothing. No errors are being displayed or reported.
$data = file_get_contents($_FILES['files']['tmp_name'][0]);
openssl_private_encrypt($data,$encrypted,$key);
$hash = sha1($encrypted);
file_put_contents('/path/to/folder/'.$hash,$encrypted);
Does anyone have any ideas as to why this isn't working?
Thanks
I'm not sure about PHP but in C/C++(OpenSSL) asymmetric encryption(RSA mainly) works on data with length less than the key size. And normally it is used to encrypt hash values. If you want to encrypt large(more the ~256 bytes)amount of data you'd better use some symmetric(block) cipher like AES or TriDES. Symmetric ciphers are much faster by the way.
PS Sorry I don't have enough reputation to put this post into comments.
You should proper initialize private key (http://pl1.php.net/manual/en/function.openssl-pkey-get-private.php)
$key = openssl_pkey_get_private ('file://path/to/file.pem');
$data = file_get_contents($_FILES['files']['tmp_name'][0]);
openssl_private_encrypt($data,$encrypted,$key);
$hash = sha1($encrypted);
file_put_contents('/path/to/folder/'.$hash,$encrypted);

openssl_public_encrypt and JSON

Let's say we need to store in a crypted way some confidential data into a db. And say that we need them into json format as will be more suitable for data reconstruction.
There's something that I miss that is driving me crazy.
Take that json for instance
$json = {"customer":{"customer_address":"Fake address 123","customer_city":"Fake City","customer_company":"","customer_countrycode":"it","customer_email":"","customer_telephone":"+39.347.xxxxxxx","customer_zip":"yyyyy"},"currency_code":"EUR","commision_amount":"84"}
now I want to crypt this json and I do the following
$pubKey = openssl_pkey_get_public($puk);
openssl_public_encrypt($json, $json_crypted, $pubKey);
if I echo $json_crypted it doesn't show anything, but if I remove some field (like customer_company, that is empty) all seems to work. I've tried to find something into documentation about this strange behaviour but I can't find anything.
Is someone aware of the reason behind that result?
Edit
Even if I remove other field (not an empty one) all seems to work. I'm speechless because it has to be a silly thing that I can't understand
From the comments in documentation:
http://www.php.net/manual/en/function.openssl-public-encrypt.php#95307
openssl_private_encrypt() has a low limit for the length of the data
it can encrypt due to the nature of the algorithm.
To encrypt the larger data you can use openssl_encrypt() with a random
password (like sha1(microtime(true))), and encrypt the password with
openssl_public_encrypt(). This way the data can be encrypted with a
public key and decrypted with the private one.
Your json must exceed the length limit...

Validate a string using pack('H*')

I'm working on an encrypted database... I have been using m_crypt functions.. I have sucessfully got my method of encryption/decryption.. But a problem lies with creating my OO class to serve this function.. I have the following:
class Encryption {
public function __construct($Hex = null){
if (isset($Hex)){
if (ctype_xdigit($Hex)){
echo "Is Hex";
}
if (preg_match('~^[01]+$~', $Hex)) {
echo "Is Binary";
}
}
}
}
$key = pack('H*', "bcb04b7e103a0cd8b54763051cef08bc55abe029fdebae5e1d417e2ffb2a00a3");
$Class_OO = new Encryption($key);
The echos are for testing purposes.. But I want to validate this as a valid hexidecimal/binary or the datatype this string is.
performing:
print_r($key);
Returns the following:
¼°K~:صGcï¼U«à)ýë®^A~/û*£
But what datatype is this? On the documentation: http://www.php.net/manual/en/function.mcrypt-encrypt.php The line is presented:
convert a string into a key
key is specified using hexadecimal
So my question is what datatype is this? I understand this is in the ASCII range, but that is as far as my knowledge goes.. Furthermore, a successful answer for this will also assist me in creating another key which is not the one specified by the actual documentation
Your $key is the return value from pack, which in this case is a binary string (essentially raw binary values). See the first line in the documentation for the pack() function return value: http://php.net/manual/en/function.pack.php
Pack given arguments into binary string [emphasis added] according to format.
You would normally base64 encode a binary string before attempting any kind of output, because by definition, a binary string may (and often does) include non-printable characters, or worse - terminal control/escape sequences which can hose up your screen.
Think of it like printing a raw Word or Excel file: you'll probably see recognizable values (although in this case occasional alpha-numerics), but lots of garbage too.
Base64 encoding is a technique to inspect these strings in a safe way.
But what your question implies is that you are very much entering this territory new. You should probably take a look at the Matasano crypto tutorial here: http://www.matasano.com/articles/crypto-challenges/. It is an excellent starting point, and completing exercise #1 in it (maybe 20 minutes of work) will shed complete light on your question above.
In response to your question.. The only viable viable datatype this is submitted in is a string. As you said in your comment:
I have figured using the IV functions of mcrypt then using bin2hex,
using this in the second param of the pack function seems to work
without a fail.. BUT, my overall question is how to validate:
¼°K~:صGcï¼U«à)ýë®^A~/û*£ down to a specific datatype
You have answered how to create an acceptable format for the pack('H*') but as far as validation goes:
if (is_string($Var)){
}
Is the way to go, as this is how it's submitted. It's not a bool, hex, binary, int.. So the only valid method of validating is to validate it as a string.

Categories