Md5 unique id good enough for hidden form tokens? - php

I'm using md5(uniqid()) to generate a unique hash for my token hidden input on my forms (like Log in, Sign up, Settings, etc) for my File Sharing and Hosting service and for the user session, so I can compare those two after the form is submitted.
But I'm wondering if md5(uniqid()) is good enough after I've read that md5 has lots of security flaws.
Are there better or more secure ways of generating tokens for my forms?
Output example
<input type="hidden" name="token" value="4c1dd84d3458964ee6d59c728dc70160">

This token should just be an unpredictable code. The best you can do to get such an unpredictable code with a deterministic computer, is to generate a really random number.
When you use the MD5 function with your uniqid, it does not add any randomness/unpredictability to your token, you (mis)use it as an encoder. The same goal you get with using the bin2hex() function, that's what MD5 does by default after calculating the binary hash. That said, the MD5 function is not unsafe here but has no advantage neither.
The more important point is, that the function uniqid() is not unpredictable, it is based on the current timestamp. This is the unsafe part in your code. To get an unpredictable number you can use the function mcrypt_create_iv() which reads from the random source of the operating system.
I would recommend to let PHP create the session token for you, with the session_start() function. If you really have reasons not to use a normal PHP session, then use mcrypt_create_iv() together with an encoding function like bin2hex() or base64_encode().
EDIT:
From your comments i see that this token is not used to maintain the session, instead to mitigate csrf. In this case of course the session_start function won't help (the session id should not be used as csrf token), but creating an unpredictable token is still important. This is an example of how this can be done:
/**
* Generates a random string of a given length, using the random source of
* the operating system. The string contains only characters of this
* alphabet: +/0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
* #param int $length Number of characters the string should have.
* #return string A random base64 encoded string.
*/
protected static function generateRandomBase64String($length)
{
if (!defined('MCRYPT_DEV_URANDOM')) throw new Exception('The MCRYPT_DEV_URANDOM source is required (PHP 5.3).');
// Generate random bytes, using the operating system's random source.
// Since PHP 5.3 this also uses the random source on a Windows server.
// Unlike /dev/random, the /dev/urandom does not block the server, if
// there is not enough entropy available.
$binaryLength = (int)($length * 3 / 4 + 1);
$randomBinaryString = mcrypt_create_iv($binaryLength, MCRYPT_DEV_URANDOM);
$randomBase64String = base64_encode($randomBinaryString);
return substr($randomBase64String, 0, $length);
}

There was a great answer to the first half of this on
https://security.stackexchange.com/a/19710 , the second half god a good answer from the manpage, as posted by rmcfrazier: http://www.php.net/manual/en/function.uniqid.php
quote combinatorics:
"There are devastating collision attacks on MD5. (...) In contrast, SHA1 appears to be much more secure. While there are some known attacks on SHA1, they are much less serious than the attacks on MD5. For this reason, SHA1 is a much better choice than MD5 in many settings." <- MD5
"Warning: This function does not create random nor unpredictable strings. This function must not be used for security purposes. Use a cryptographically secure random function/generator and cryptographically secure hash functions to create unpredictable secure IDs." <- uniqid

Per the man page, this should not be used for cryptographically secure tokens
http://www.php.net/manual/en/function.uniqid.php
You should use openssl_random_pseudo_bytes for your tokens
http://www.php.net/manual/en/function.openssl-random-pseudo-bytes.php

It really depends on how secure you need the token to be. Using the PHP session ID should suffice unless you need to verify the integrity of the key after submission.
Eg. PHP session id:
php > session_start();
php > echo session_id();
ritig5ecgp6ebmnq8p5imbdhl3
However, you can always generate more secure IDs using the hash() function eg:
http://us2.php.net/manual/en/function.hash.php
Don't use RipeMD
echo hash('ripemd160', 'The quick brown fox jumped over the lazy dog.');
ec457d0a974c48d5685a7efa03d137dc8bbde7e3
Unless the token needs to be cryptographically secure, the session key should suffice.

Related

where hash() function is used and why

The function in php string hash ( string $algo , string $data [, bool $raw_output = false ] ) where algo=Name of selected hashing algorithm (i.e. "md5", "sha256", "haval160,4", etc..), data=Message to be hashed., raw_output=When set to TRUE, outputs raw binary data. FALSE outputs lowercase hexits. so if I have this example
<?php
echo hash('ripemd128', 'The quick brown fox jumped over the lazy dog.');
?>
The above example output (which looks completely random): 51d43720fd516108ef5ed20e9031bb865ede861e
So I'm wondering where such functions is used and why? also Is there a way or a function to revert the output to the original string back again?
So I'm wondering where such functions is used and why?
They're used in digital signature algorithms, digital fingerprinting algorithms, content-addressable storage, constructing tamper-resistant data structures that can be traversed rapidly and securely, and for fast lookups that also resist complexity attacks.
also Is there a way or a function to revert the output to the original string back again?
No. In many cases, having this ability would defeat the point of the hash. Also, it is trivial to prove that this is, as stated, impossible, using a counting argument.
It is use for digital signatures like hashing the concatenated string with the secret key against the other to check if both hash string is correct, its like a key in order for you to gain access to do something.
There's no way to decrypt it because that is how it's made, it is a 1-way hash method.
if you want a method that encrypts and decrypts the string use mcrypt_encrypt and mcrypt_decrypt
this functions are used to compute a kind of "fingerprint" for some data. in your example this will be your string. same algorithm will produce the same hash for the same input data. if you change input data the computed hash will be different.
a popular usage is storing passwords. so you don't store passwords in clear text but hashed values.
for the second part of your question: hash algorithms are "one-way" only (should be ;)). so you can not restore the input data from hashed value.

Hashing or encrypting variables to be sent in a url

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.

Encryption using crypt()

I'm currently doing a very safe login system, but I'm new to the crypt() function and need some quick assistance.
I used crypt() to encrypt the password string during signup and saved it to the database. However, how will I be able to decrypt the key during login? Or how am I supposed to do otherwise? Or would it be possibly to do some magic with the submitted password string to compare it to the encrypted key in the database?
crypt() doesn't encrypt passwords, it hashes them. The fundamental difference is, you can't get hashed passwords back (think of hash browns - if you have hash browns, you can't get the potatoes back).
So you apply the same function to the input and compare its result to the value stored in the database:
$stored_pw = get_hashed_password_from_db($_POST['username']);
crypt($_POST['password'], $stored_pw) == $stored_pw
Read the documentation on crypt() to understand the "magic" behind the code above works.
Do not encrypt the password. Instead, store it with a hash.
Popular SO thread: How should I ethically approach user password storage for later plaintext retrieval?
crypt() the provided password at login. Compare the output to the previous crypt()'s output. If they match, the passwords match.
This is the basic theory of operation of a one-way hash function.
Also this is how you should be doing it. Please note that this code is how I'd do it, and you may want to change a few of the things. And you must define your own unique salt, whether in the config file or elsehwere. It must either a) be in the global scope as I've posted, or you can change it to make it defined in the function. Also you're not encrypting, you're actually hashing. Encryption is both ways, hashing is encryption in one way. Meaning you cannot decrypt a hash. You can only bruteforce guess the original plain text.
/*
* Copyright (c) 2012, Macarthur Inbody
* The following code was posted on http://stackoverflow.com/questions/8195689/encryption-using-crypt
* The license is simply CC-by https://creativecommons.org/licenses/by/3.0/
*
*
*
*/
/*
*
* This is used to hash their password.
*
* #param $password string the users supplied password
* #param $username string the users supplied username
* #param $rand_salt int the secondary salt -2^31-1 to 2^31-1 Must be defined previously.
* #return string the hashed password
*/
function hash_pass($username,$password,$rand_salt){
global $unique_salt;
$main_salt=base64_encode(hash('sha512',$username.$password.$config_salt);
$main_salt=str_replace('+', '.', $salt);
$main_salt=str_replace('=','/',$salt);
$main_salt='$2$06'.$main_salt; //change this here to the cost factor that you want
$hashed=crypt($unique_salt.$username.$password.$rand_salt,$main_salt);
return $hashed;
}
function gen_rand_salt(){
return rand();
}
function rand_str($length,$additional_entropy){
$max_length=ceil($length/28);
if(!is_defined($additional_entropy)){
$additional_entropy='';
}
$str='';
for($i=0;$i<=$max_length;++$i){
$str.=base64_encode(sha1($i.''.microtime().$additional_entropy,true));
}
$str=substr($str,0,$length);
return $str;
}
/*
*
* Generate A temp password/token
*
* This function generates a temporary password and also gives you
* the hashed password too. It is an array, arr[0]=password, arr[1]=
* hashed password. If it fails it'll return -1;
*
* #param $username the username
* #param $rand_salt the random salt value, must be given.
*
* #return array if it is successful array, if it fails it's a number of -1
*/
function generate_temp_password($username,$rand_salt){
global $unique_salt;
if(!is_defined($rand_salt)){
return -1;
}
$pass_len=12; // change this to what you want for password recovery
$pass_arr=Array();
$password=rand_str($pass_len,$unique_salt.rand().$rand_salt);
$password=substr(base64_encode(sha1($rand_str.$rand_salt,true)),0,$pass_len);
$hashed_password=hash_pass($username,$password,$rand_salt);
$pass_arr[0]=$password;
$pass_arr[1]=$hashed_password;
return $pass_arr;
}
As stated in teh code, the license is CC-By as I figure it's good enough for most things. Also please keep the block the same as far as the link to this page as that's what I do with all of my own source code. Also I realize the "random" string isn't really that random, but it's random enough to be useable by you for the purpose that it is going to be.
Edit 2:Also be sure to escape the user's username. I'm not escaping the password, since I'm hashing it, thus escaping it isn't necessary since it's already mitigated and would just waste cycles. But only if you're doing something like this. Make sure to escape the username with mysql_real_escape_string. If you're using php5+ you should look into mysqli(if you're using mysql). If you're using another system, then you'll have to look it up yourself as I only know mysql. I'm going to be away for a couple of days, so I really hope this works for you. I will check it from time to time, but I may forget... so yeah. I hope this helps you, as it's safe, secure, and should work fine for you.
Edit 3: Changed the random string function to make it a bit stronger since I forgot that this is going to be use to generate temporary passwords. That should make it random enough to be used for this purpose since otherwise the generated password might be able to be known by someone knowing the exact time(with the current microtime) though highly unlikely, this still makes it a bit stronger and should make it safe from those kinds of attacks. It should not be completely production ready, and should be secure for your system. Just make sure to set the $unique_salt variable somwhere in the global scope, or set it in every time that it's used in each of those functions.
See
http://php.net/manual/en/function.password-hash.php
and
http://php.net/manual/en/function.password-verify.php
Also never use rand() if you need safe random values. It's the worst source for random values in PHP.
In PHP 7 you should use
http://php.net/manual/en/function.random-bytes.php
instead. For earlier versions see
http://php.net/manual/en/function.openssl-random-pseudo-bytes.php

Using a time-based, rotating hash or string for security

In a CMS app I occasionally need to open an iframe of another domain. At the moment I am setting the URL for that iframe to something very obscure. Like http://domain.com/iframe/jhghjg34787386/. This works but theoretically that iframe source url will get saved in the user's history and could be accessed from the outside world.
So, I am wondering about using a time-based approach to an ever-changing hash or string that is processed on the request side and is checked on the iframe source side. However I would like it to be time based.
I could do this to get my hash:
<?php
$seed = '123456789'; // a password that both the parent and source have
$string = md5(time().$seed);
?>
But then the two servers have to be exactly synced. Any way to make the time constraint more fuzzy?
I am also open to other approaches. Is there any way to validate that the parent window for an iframe is of a certain domain?
You could add a key to your hash and send the timestamp with the query, e.g.:
$key = "YOUR_SECRET_KEY";
$time = time();
$hash = hash_hmac('sha256', $time, $key);
$url = "https://example.com/iframe?hash=$hash&time=$time";
On the other side you should first check if the timestamp is in the limits (e.g. not older than five minutes) and than rehash with the key and the submitted timestamp. If you get the same hash the request is valid.
Notes:
don't use MD5: the algorithm is completely broken and doesn't provide any security anymore (although it's supposed to still be ok when used with an HMAC…)
you should use hash_equals for comparing hashes to prevent timing attacks
we use an HMAC to guarantee data integrity and authentication. See https://crypto.stackexchange.com/questions/1070/why-is-hkx-not-a-secure-mac-construction for why we mustn't just concatenate time & key
You shouldn't use plain MD5 that; MD5 is not designed for ensuring message authenticity. Instead you can just give the timestamp publicly, alongside with other information (message), base64 encoded, so that it does not contain the ':' character. Then you can calculate the HMAC code of the message for example with
$hmac = hash_hmac("md5", $message, $secret)
$signed_message = $message . ":" . $hmac
On the other end you can then check this signature, by first splitting with ":", getting $message and $hmac, then you can check authenticity with
$hmac == hash_hmac("md5", $message, $secret)
If codes match, then check if the timestamp in the $message is still within the limits.
Be careful of using MD5 for hashing - it is cryptographically broken. There are any number of online sites to help create collisions. Rather use something like SHA256 and always include a long salting string.
If the user does not have to interact with the site in the iframe you could consider scraping the site code and inserting it directly into your code. There are a number of libraries available for this.
What about using something like
$hash = hash ( "sha256" , date("h") . 'myverylongsaltstring' );
So long as the servers have their timezones correct and are synchronized to within an hour this approach will work like your time() hash.
Additionally you could use something like TinyUrl to obfuscate the link a little further. Something along the lines of http://www.technabled.com/2008/12/create-your-own-tinyurl-with-php-and.html
If it is time based, then the amount of possible keys that a person would have to guess would be tiny.
Since I would know approximately when a URl might be generated, and I know how you are hashing it, then I can just create hundreds of thousands of links and test them out.
You should use UUID or something equivalent. The probability of a collission would be essentially impossible.

Session hash does size matter?

Does size matter when choosing the right algorithm to use for a session hash.
I recently read this article and it suggested using whirlpool to create a hash for session id. Whirlpool generates a 128 character hash string, is this too large?
The plan is to store the session hash in a db. Is there much of a difference between maybe using 64 character field (sha256), 96 character field (sha384) or 128 character field (whirlpool)? One of the initial arguments made for whirlpool was the speed vs other algorithms but looking at the speed results sha384 doesn't fair too badly.
There is the option truncate the hash to make it smaller than 128 characters.
I did modify the original code snippet, to allow changing of the algorithm based of the needs.
Update: There was some discussion about string being hashed, so I've included the code.
function generateUniqueId($maxLength = null) {
$entropy = '';
// try ssl first
if (function_exists('openssl_random_pseudo_bytes')) {
$entropy = openssl_random_pseudo_bytes(64, $strong);
// skip ssl since it wasn't using the strong algo
if($strong !== true) {
$entropy = '';
}
}
// add some basic mt_rand/uniqid combo
$entropy .= uniqid(mt_rand(), true);
// try to read from the windows RNG
if (class_exists('COM')) {
try {
$com = new COM('CAPICOM.Utilities.1');
$entropy .= base64_decode($com->GetRandom(64, 0));
} catch (Exception $ex) {
}
}
// try to read from the unix RNG
if (is_readable('/dev/urandom')) {
$h = fopen('/dev/urandom', 'rb');
$entropy .= fread($h, 64);
fclose($h);
}
// create hash
$hash = hash('whirlpool', $entropy);
// truncate hash if max length imposed
if ($maxLength) {
return substr($hash, 0, $maxLength);
}
return $hash;
}
The time taken to create the hash is not important, and as long as your database is properly indexed, the storage method should not be a major factor either.
However, the hash has to be transmitted with the client's request every time, frequently as a cookie. Large cookies can add a small amount of additional time to each request. See Yahoo!'s page performance best practices for more information. Smaller cookies, thus a smaller hash, have benefits.
Overall, large hash functions are probably not justified. For their limited scope, good old md5 and sha1 are probably just fine as the source behind a session token.
Yes, size matters.
If it's too short, you run the risk of collisions. You also make it practical for an attacker to find someone else's session by brute-force attack.
Being too long matters less, but every byte of the session ID has to be transferred from the browser to the server with every request, so if you're really optimising things, you may not want an ID that's too long.
You don't have to use all the bits of a hash algorithm, though - there's nothing stopping you from using something like Whirlpool, then only taking the first 128 bits (32 characters in hex). Practically speaking, 128 bits is a good lower bound on length, too.
As erickson points out, though, using a hash is a bit odd. Unless you have at least as much entropy as input as the length of the ID you're using, you're vulnerable to attacks that guess the input to your hash.
The article times out when I try to read it, but I can't think of a good reason to use a hash as a session identifier. Session identifiers should be unpredictable; given the title of the article, it sounds like the authors acknowledge that principle. Then, why not use a cryptographic random number generator to produce session identifiers?
A hash takes input, and if that input is predictable, so is the hash, and that's bad.
SHA1 or MD5 is probably enough for your needs. In practice, the probability of a collision is so small that it will likely never happen.
Ultimately, though, it all depends upon your required level of security. Do also keep in mind that longer hashes are both more expensive to compute and require more storage space.

Categories