Hashing or encrypting variables to be sent in a url - php

I have a link which needs to be generated so that it can be placed in an email. When the user clicks on this link, the system is meant to match the code sent in the email to a user, so it can pull up the records for that user.
However, I am not quite sure which encryption/hashing method to use. For the login for the admin system on this site, I use PBKDF2 in the database for the password (with salting) and AES encryption when it is sent to the session variables, but I don't know if the characters used by PBKDF2 & AES are url compatible.
Basically, I need the best method of hashing/generating a random code for storage in the database and an encryption method so that I can place a year and the code (which I previously mentioned) in a url. I am using PHP and MySQL if that helps.
What do you guys think?

The output of most encryption (or hashing, etc.) routines is arbitrary binary data, which cannot be safely included in a URL without encoding it.
As eggyal notes, simply using urlencode() on the data should be enough. However, standard URL-encoding may not be the most compact way to encode random binary data. Base64 encoding would be more efficient, but unfortunately is not completely URL-safe due to its use of the + character.
Fortunately, there exists a standardized URL-safe variant of the base64 encoding, specified in RFC 4648 as "base64url". Here's a pair of functions for encoding and decoding data using this encoding in PHP, based on this answer by "gutzmer at usa dot net":
function base64url_encode($data) {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
function base64url_decode($data) {
return base64_decode(strtr($data, '-_', '+/'));
}
(I've simplified the decoding function a bit, since at least current versions of PHP apparently don't require = padding characters in the input to base64_decode().)
Ps. For securely generating the random tokens in the first place, see e.g. this question.

Perform the hash however you wish, then urlencode() the result prior to inserting it into the URL.

Related

Trouble creating auth string using PHP HMAC SHA1 and Base64

So I am working with this API and using Laravel, and I am trying to build an auth string. This is the documentation I was given, but I am having a little trouble as this is something relatively new to me.
Here are the auth instructions:
The authentication parameter is a string and it can calculated by the
caller or the caller can choose to save this value as a parameter
together with connection ID and API key.
The authentication is a base64 string of a HMAC SHA1 hash. This is
computed by using the binary of API Key in in
########################## format in all lower case and UTF8 encoding as the key and computer HMAC SHA1 hash on the binary of
Connection ID in ################################ format in all lower
case and UTF8 encoding.
The result binary hash is then base64 encoded and the text result is
what should be passed as the authentication parameter. In C# the code
to calculate the authentication may look like:
HMACSHA1 hmac = new HMACSHA1(
UTF8Encoding.UTF8.GetBytes(apiKey.ToString("N").ToLower())
);
string authentication = Convert.ToBase64String(
hmac.ComputeHash(
UTF8Encoding.UTF8.GetBytes(connectionId.ToString("N").ToLower())
)
);
As an example the following credentials:
Connection ID: 5fecbc200f0e4a7cbf41040e11047e56
API Key: 2de51c4fd0f04b9fabeb95225e87da70
Should result in a computed authentication value of
m5/Vc1RzhUETQvEtx/JdIglQpTg=
So what i have been trying is:
$a = strtolower('5fecbc200f0e4a7cbf41040e11047e56');
$b = strtolower('2de51c4fd0f04b9fabeb95225e87da70');
$z = hash_hmac("sha1", utf8_encode(decbin($b)), utf8_encode(decbin($a)), true);
dd(base64_encode($z));
Which outputs QjG3kzUs7U1UukNd++3t24pBWNk=
I have tried a few more variations, but I am just lost on this one. First time really decoding or encoding anything. Would greatly appreciate any tips, ideas, or snippets that can help me figure this out. Already spent a few hours on this and it's bugging me..
First: Get rid of utf8_encode() and just generally don't use it. It assumes that the input string is ISO-88591-1 and if it is anything else it will silently corrupt the data. This function has an incredibly misleading name, and I would go as far as to suggest that no one should ever use it or the corresponding utf8_decode() which will break your data in the same manner, but reversed.
If you need to convert string encodings in PHP use something that explicitly defines the input and output encodings, eg: mb_convert_encoding(). [you still don't need it for this]
Second: Whatever you think decbin() does, you're incorrect. It converts an integer into a literal, capital-S String composed of 0 and 1 characters.
Third: PHP strings have no inherent encoding and are roughly equivalent to byte arrays if you twisted my arm for a description. The bytes you put into them are the bytes you get out of them.
Fourth: I'm not exactly a C# expert [or intermediate, or even beginner] but that example code is horrendous. What even is the significance of the N in connectionId.ToString("N")? I can't find any documentation about this.
Start simple, use meaningful variable names, build up, and read the docs.
$connectionID = strtolower('5fecbc200f0e4a7cbf41040e11047e56');
$apiKey = strtolower('2de51c4fd0f04b9fabeb95225e87da70');
$hash = hash_hmac("sha1", $connectionID, $apiKey, true);
var_dump(base64_encode($hash));
Output:
string(28) "m5/Vc1RzhUETQvEtx/JdIglQpTg="

esp32 and php XXTEA strings encryption

I'm using esp32 (Arduino platform not esp-idf) with the "HTTPClient.h" library to send get requests with parameters to my PHP server.
I want to encrypt the parameter values and decrypt them in my PHP code And vice versa (my server sends back JSON data to my esp32).
I tried using the XXTEA protocol with these libraries for PHP, and for esp32.
But the encrypted string won't decrypt properly on PHP.
Example:
When I encrypt "HELLO WORLD" on my esp32 with the key "ENCRYPTION KEY" I get this:
35bd3126715874f741518f4d
And when I decrypt it on PHP it returns blank.
Moreover, when I encrypt it on my PHP server I get this:
T1YNYC4P4R2Y5eCxUqtjuw==
My esp32 sketch looks like this:
#include <xxtea-iot-crypt.h>
void setup() {
Serial.begin(115200);
}
void loop() {
String plaintext = "HELLO WORLD";
// Set the Password
xxtea.setKey("ENCRYPTION KEY");
// Perform Encryption on the Data
Serial.print(F(" Encrypted Data: "));
String result = xxtea.encrypt(plaintext);
Serial.println(result);
// Perform Decryption
Serial.print(F(" Decrypted Data: "));
Serial.println(xxtea.decrypt(result));
delay(2000);
}
My PHP code looks like this:
require_once('xxtea.php');
$str = "HELLO WORLD"
$key = "ENCRYPTION KEY";
$encrypt_data = xxtea_encrypt($str, $key);
error_log($encrypt_data);
Is there a way to have an encrypted strings communication between PHP and esp32?
Thanks in advance.
This problem may result from inputs being of different data type, since no current XXTEA implementation seems to do any type or range checking.
Or it could be due to different endian behavior of the two computers involved, since binary is typically stored as an array of words constructed from bytes.
Or it could be due to lack of official or standard reference examples for correct encryption of a specific string and key. In the absence of reference examples (using either hexadecimal or base64 conversion of the binary encryption result) there is no way to tell whether an implementation of encryption is correct, even if its results decrypt correctly using a corresponding decryption implementation.
ADDED:
I think I've found one compatibility problem in the published code for XXTEA. It may be worth taking some space here to discuss it.
Specifically, the problem is that different implementations create different results for encrypting the same plaintext and key.
Discussion:
This problem results from the addition of the length of the plaintext as the last element of the array of longs. While this solves the problem of plaintext that has a length that is not a multiple of 4, it generates a different encrypted value than is generated by the JavaScript implementation.
If you insert "$w=false;" at the start of the long2str and str2long functions, the encrypted value for the PHP implementation becomes the same as the JavaScript implementation, but the decrypted value has garbage at the end.
Here are some test case results with this change:
PHP:
text: >This is an example. !##$%^&*(){}[]:;<
Base64: PlRoaXMgaXMgYW4gZXhhbXBsZS4gIUAjJCVeJiooKXt9W106Ozw=
key: 8GmZWww5T97jb39W
encrypt: sIubYrII6jVXvMikX1oQivyOXC07bV1CoC81ZswcCV4tkg5CnrTtqQ==
decrypt: >This is an example. !##$%^&*(){}[]:;<��
Note: there are two UTF-8 question-mark characters at the end of the "decrypt" line.
JavaScript:
text: >This is an example. !##$%^&*(){}[]:;<
Base64: PlRoaXMgaXMgYW4gZXhhbXBsZS4gIUAjJCVeJiooKXt9W106Ozw=
key: 8GmZWww5T97jb39W
encrypt: sIubYrII6jVXvMikX1oQivyOXC07bV1CoC81ZswcCV4tkg5CnrTtqQ==
decrypt: >This is an example. !##$%^&*(){}[]:;<
The reason there is no garbage in the JavaScript implementation even though it does not save the length of the plaintext is given in a comment there: "note running off the end of the string generates nulls since bitwise operators treat NaN as 0". In other words, the generated string is padded with NULs that are never seen, even though JavaScript, like PHP, can include NULs in strings because it stores the length separately.
I don't have an opinion about which approach is best, but one should be chosen for all implementations.
The reason that there should be a standard for the result of encryption (regardless of whether the binary is converted to hex or to base64 for safe transit) is that one might want to use, say, PHP for encoding but JavaScript for decoding, depending on which languages are natural to use at two locations. After all, encryption is most often used to communicate between two locations, and the language used at the target location might not even be known.
Why not using the wificlientsecure library? Works great on the esp32.

Hash parameters to validate request

I have a socket Server, written C++, and an API Client in PHP to register new users for special locations.
To validate the request from the PHP Client, i will use an hash with HAMC with an unique key that are only know by the API Client and Socket Server.
The PHP Client send the content as JSON over HTTP POST, after that the Socket Server read and processe this.
That works fine.
Now, my questions.
I have three fields that i will hash:
message(String)
objectIDs(integer Array)
type(String)
How i can create one hash for this three fields?
When i use JSON should it possible that the String of the JSON hasn’t the same sorting in PHP and C++(https://github.com/nlohmann/json) or more tabs, whitespaces or anything else?
And still as a little information the message can containes any characters.
Currently i use this
$str = "type=".$type. ";message=".$message;
foreach ($objectIDs as $index => $objectID) {
$str .= ";objectID[" . $index . "]=" . $objectID;
}
//generate from a String a SHA256 hash with HMAC
$hash = getSignature($str);
Someone tells me that this sigining not unique and it should possible withe a special message that the system create a wrong hash. (But currently he isn‘t reachable)
In my tests i dind‘t can reproduce it.
Now i hope someone other can help me.
Hashes are equal if strings are equal.
That is, the aspect of that JSON is a bad choice for hashing is true: Semantically identical JSON objects can have different string representations.
On the Internet, you should, however, expect that also string representations can be complicated depending on the locale of both involved computers. If the locale is equal, everything is usually fine (choose UTF-8 everywhere is valid). However, I expect PHP to convert strings to the locale of the server. This can change the bit-representation of the string (similar to the JSON issue). Therefore, make sure that both work in the same locale and that such magic can never interefere with your system.

Hash an array in Javascript and PHP

I'm trying to pass a message from a Javascript Client to a PHP webserver. To add a layer of security I would like to sign the data object with an hash.
/* Generate signature of the data with the password */
that.signEnvelope = function(data,password)
{
return CryptoJS.SHA256(JSON.stringify(data) + password).toString();
};
This quickly falls apart on the server. The JSON.stringify function does not generate a 1:1 matching string to json_encode on the server making it impossible to verify the hash.
protected function verifySignature($remoteSignature,$data,$privateKey)
{
/* Combine json & key samen , then sha256 */
$localSignature = hash('sha256',json_encode($data) . $privateKey);
return ($localSignature === $remoteSignature);
}
Is there another algorithm that I can implement in both PHP and Javascript that will generate a hashable string ?
Conclusion
Allowing json_encode accross platforms was not a smart thing todo. There is no standard implementation.
Instead I now only allow arrays with string key/value pairs which are much easier to concat and verify.
What you experiencing there is not limited to certain differing whitepace/linebreak-characters. It is also worth mentioning, that different charsets can lead to different output. A ISO8859-15 Euro-Sign is 1 byte long, a UTF8 Euro-Sign is 3 bytes long and there is always the chance to encode a Char with the \u####-declaration. JSON-libs is not intended to produce comparable strings over different plattforms.
If you still want to utilize JSON, you have to either use libs, that behave identical on all input, or build your own. JSON is easy to generate by hand.
You could use the JS version of json_encode to get a 1:1 match:
http://phpjs.org/functions/json_encode

md5 encrypt cookiedata with serialized array

I was attempting to
encrypt de cookie data with md5, but I can not validate the hash back.
It has got to do, with the fact that cookie_data is a serialized array, because normal stringvalues work ok.
It's actually from a codeigniter class, but it does not work??
Does anyone know what the problem might be?
$hash = substr($session, strlen($session)-32);
$session= substr($session, 0, strlen($session)-32);
if ($hash !== md5($session.$this->encrypt_key))
{........
and the cookie value is encrypted like this
$cookie_data = $cookie_data.md5($cookie_data.$this->encrypt_key);
EDIT
I found that the answer is to use urlencode en urldecode in the proces of creating and validate
md5 hashes, because setcookie does urlencode automaticly, and thereby possibly changing the hash.
thanks, Richard
You have a typo:
md5($sessie.$this->encrypt_key))
should be
md5($session.$this->encrypt_key))
If you develop with notices turned on you'll catch this kind of thing much more easily.
You're not encrypting your data, you're signing it.
md5 is a oneway function. It is not a reversible one, so you can't decrypt the data.
The only thing you can do is encrypt the original data (if you saved it elsewhere) and check the result of this second computation.
If the value retrieved and the new value calculated are the same, the hash you received is valid (As you are doing in your code).
EDIT
You know, with just three lines of code I will guess some possible causes:
$session doesn't contains at the beginning of your code the same value of cookie_data.
you are using multibyte strings and strlen is not mb aware (use the idioms substr($session,0,-32) to get the payload part of the string.
maybe substr doesn't cope with multibyte strings too, use explicitally mb_substr (or whatever it is called).
To me the first case is the more probable. For what I can see.
I was attempting to encrypt de cookie
data with md5, but I can not decrypt
it back for validation.
md5 isnt an encryption method. it creates a one-way hash that cant be turned back into the original data.
If you want to encrypt data try mcrypt

Categories