This question has been asked before, but a lot of the threads are very old.
I need to hash email addresses to store in my database. I later need to match a hash, so the hash needs to be the same each time for each unique email. is there a risk of same hash? collision with say md5() or hash() ? what is the recommended way these days (2021)
thanks in advance
As of PHP 7.2 you would use Argon2 like this:
password_hash('your_value', PASSWORD_ARGON2I);
PHP 7.3 comes with an improved algorithm:
password_hash('your_value', PASSWORD_ARGON2ID);
To compare a value to the hash you can just use password_verify like this:
if (password_verify($user_value, $stored_hash)) {
// valid
}
This is normally meant to check hashed passwords, but e-mail addresses can be handled the same way (e.g. for data protection reasons).
The Argon2 algorithm is explained in Wikipedia, the Installation is described on php.net.
It's very simple. The password_hash() is a replacement for md5().
But what you're talking about here is hashing an e-mail. E-mail is not the same as password.
You as administrator don't need to know user passwords in order to give the user access. However you will need to know the user e-mail if you want to send out newsletters, registration confirmation and even provide an option via e-mail to retrieve a lost access to user account. Neither of the above hashing options will help you with that. Because the above options destroy the original data and leave you with crumbs of it.
In any case the password_hash() is 60 characters give or take vs. the md5() of 32 characters. So the password_hash() is the safest option and md5() is long outdated and shouldn't be used. Collisions is not something you should be worried about for passwords. Because users can have same passwords and even same hash data. But since the data belonging to different accounts, without the correct username, the hash is useless for user login.
So it now all boils down to e-mail. That you can achieve using this simple encryption and decryption. In this case since the data is not destroyed and is preserved. That means there is no chance of a collision especially considering that each user should have a different e-mail. But even if the data is exactly the same, the hashing results will still be different each time. So you can't go wrong with this option.
function enc($data, $key, $mode=0){
$cipher = "aes-256-gcm";
if(in_array($cipher, openssl_get_cipher_methods())){
if(!$mode){ // encrypt
$ivlen = openssl_cipher_iv_length($cipher);
$iv = openssl_random_pseudo_bytes($ivlen);
$data = openssl_encrypt($data, $cipher, $key, $options=0, $iv, $tag);
$data = base64_encode($data.'::'.$iv.'::'.$tag);
}else{ // decrypt
list($data, $iv, $tag) = explode('::', base64_decode($data), 3);
$data = openssl_decrypt($data, $cipher, $key, $options=0, $iv, $tag);
}
}
return $data;
}
ENCRYPTING DATA: enc('DATA','KEY'); (where KEY could be a user password) => RETURNS: ENC_KEY
DECRYPTING DATA: enc('ENC_KEY','KEY',1); (where KEY could be a user password) => RETURNS: DATA
I'm building a login system and I want it to be secure so that I can re-use it on several projects. I'm using the function below to encrypt the given password with my secret key (32 chars).
function Encrypt($key, $payload)
{
$iv = mcrypt_create_iv(IV_SIZE, MCRYPT_DEV_URANDOM);
$crypt = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $payload, MCRYPT_MODE_CBC, $iv);
$combo = $iv . $crypt;
$garble = base64_encode($iv . $crypt);
return $garble;
}
My IV_SIZE is the following:
mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC))
Before you ask I don't allow users to enter a password. I generate a very complicated and long password for them with everything inside (uppercase, lowercase, digits and symbols). I've also implemented a ban system that allows you to insert a wrong password for a verly limited amount of times and, as if it wasn't enough, I also lock the entire account so that you can't try again even when the ban has expired.
This is the decrypt function still pretty standard:
function Decrypt($key, $garble)
{
$combo = base64_decode($garble);
$iv = substr($combo, 0, IV_SIZE);
$crypt = substr($combo, IV_SIZE, strlen($combo));
$payload = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $crypt, MCRYPT_MODE_CBC, $iv);
return $payload;
}
Said that it's 2 hours that I'm trying to figure out what's the best approach to issue a login session/cookie. I want to adopt a token-based approach like follows.
As soon as login is successful, I want to store in a cookie the first part of an hash. The entire hash is stored in my DB and it is unique. The second part of it would be something like a salt + sha/md5/base64 (...) of userid, email, timestamp etc. Is it a good approach?
No.
Unless you need to have access to the plain-text password (e.g. if it's a password for a configured SMTP server that you'd use), you MUST NOT encrypt passwords, but hash them.
And use password_hash() (bcrypt) to hash them; it is specifically designed for this purpose.
Also, it is not made clear in your question what these hashes are for, but it sounds like a flawed choice at best - MD5 and SHA1 shouldn't be used for any kind of security mechanisms today, and if you want to verify the authenticity of something, you should be using a keyed hash function like hash_hmac() instead anyway.
Use a generic PHP session to maintain the login state.
It seems to me that you're over-thinking the problem at hand and in turn over-engineering the solution. Keep it simple.
How to decrypt password in plain text which are in ms-SQL database?
$encrypted_password="k??aU?????y-??N???tDRz????{?4R???G?aS4t?T";
$salt = "611233880";
So I need to decrypt password so that I insert into other database with md5 encryption.
I used this code, but not get success
$iv2 = '';
for($i=0;$i<16;$i++){
$iv2 .= "\0";
}
$plain_text_CBC = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $salt, $encrypted_password, MCRYPT_MODE_CBC, $iv2);
var_dump($plain_text_CBC);
$plaintext = openssl_decrypt($encrypted_password, 'AES-256-CBC', $salt, 0, $iv2);
var_dump($plaintext);
Need Help
The idea behind encrypted (or hashed) passwords is that it is a one way operation. Not quite like shredding, but that's the idea. If you take exactly the same input and shred it you should get exactly the same output. You may not be able to reconstruct the input from it, but you can confirm someone gave you the right input by looking at the output.
Some weak algorithms have been know to be hacked buy in principle what you are asking for is impossible.
The ought to be no reason reason to decrypt. You can always do the hashing operation twice - first with the old algorithm, then with the new one - and then compare with the entry in the database.
NEVER EVER store plaintext (or weakly encrypted) passwords. Just ask LinkedIn...
You don't simply decrypt a password. It should be hashed which means it is a one way encryption.
If you want to change your password hashing implementation, here is a way to do it.
You have the clear text password available when a user is in the process of logging in. So that's where you will have to place code to rehash the password with the new algorithm.
If you are using the new native password hashing functions (PHP Version >= 5.5) then you can use password_needs_rehash. If you are on a lower PHP Version but still >= 5.3.7 then you can use the userland implementation to get the same API to the password hashing functions.
So when a user is attempting to log in and the password needs rehashing, check if the hashes match with the old hashing function and then create and save the new one to the database. Over time you will be able to migrate most users and then you can think about a solution to migrate the rest of your userbase with a forced password reset if they never logged in during your migration timeframe.
Firstly, you encrypting your data by 2 different algorithms. Why? One algorithm is enough.
Answer: You can't decrypt old password.
Solution: You should encrypt data you wrote into password field and compare result with data in database. If they are equal, you will pass password check.
For example:
$login = mysqli_real_escape_string($_POST['login']);
$password = mysqli_real_escape_string($_POST['password']);
$password_hash = md5($input); // you can use there any other algorithm, just example
// make next query and control result
$sql = 'select count(id) from users where login = \'$login\' and password = \'$password_hash\'';
// now if there are 1 row with this login and same password hash let user log in to your site
If you write your code in the MVC structure, you can use the function n_decrypt() to decrypt passwords.
so I have this website that allows users to write every day. It then get stocked in a database in plain text. It's not a blog so everything is private, and the biggest complain I regularly get is that "I" could still read what they wrote. It was still not "perfectly" private. Also I don't want to be the one who leaked thousand of private diaries.
So here is my train of thought on how to rend it private only to them.
When they log in : key = sha1(salt + password) and store this key in a SESSION (how secure is that ?)
When they save their text : encrypt it with their $_SESSION['key'] before saving it to the database
When they read something they've saved, decrypt it with their $_SESSION['key'] before displaying it.
Is that secure ? Also what is the best way to encrypt/decrypt UTF-8 ?
Also if someone changes its password it has to decrypt/re-crypt everything.
You should instead store the hash of the password in the SESSION.
Never store plain passwords anywhere - anywhere!!
Also, consider reading this stackoverflow thread: Secure hash and salt for PHP passwords
To hash the password, you can use this approach:
Generate a salt for a particular user (a salt is a random string of characters), and store it somewhere, or generate a global salt (in your use case)
Use the following function to generate a hash for the password, and store that hash in the SESSION
function generate_hash($password) {
$salt = "<some random string of characters>"; // do not change it later.
return md5($salt . $password);
}
For the encryption, you can use the mCrypt library. A typical algorithm can be:
$key = 'password to (en/de)crypt';
$string = 'string to be encrypted';
$encrypted = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, md5($key), $string, MCRYPT_MODE_CBC, md5(md5($key))));
$decrypted = rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, md5($key), base64_decode($encrypted), MCRYPT_MODE_CBC, md5(md5($key))), "\0");
var_dump($encrypted);
var_dump($decrypted);
You should be using some form of encryption. PHP provides mCrypt for this purpose. Point by Point:
Saving a password in the clear in a $_SESSION is inherently insecure. At the very least, hash it in both the session and the database. Then you can compare the hashes to one another. Sensitive data should never be stored in the clear anywhere.
You can simplify this by using mCrypt. However, I think the focus here is incorrect. Rather than hashing all of this "diary" text, I think you should be more focused on abstracting the user information from the text itself.
No need to use their password. Just use a common key and use mcrypt for this.
I hope this helps!
Do not use password to encrypt the key, password should never be used anywhere in the logic, and should only be read on login as a hash not plain text. You can user other things like user email to generate a key.
I did a series of research on this topic, but unfortunately I couldn't find a perfect way to encrypt and decrypt files in PHP. Which mean what I'm trying to do is find some way to encrypt & decrypt my items without worry of cracker knew my algorithm. If some algorithm that need to secrete & hide, it can't solve my problems while once the logic shared through anywhere, or they broke into my server and get the source file, then it should be some way to decrypt it using the same decryption algorithm. Previously I found several great posts on StackOverFlow website, but it still couldn't answer my question.
The best way to encrypt password of the world, from what I conclude through reading. Blowfish encryption. It's one way hashing algorithm with 1000's times iteration which make cracker need 7 years to decrypt by using the same specification GPU.
Obviously, this makes it impossible to decrypt while it's one-way hashing.
How do you use bcrypt for hashing passwords in PHP?
Why do salts make dictionary attacks 'impossible'?
The best way to encrypt and decrypt password in PHP, as this question quote as it is. Refer to what I found through the web, sha1 and md5 both are cracked & broken algorithm, even we change the algorithm from
$encrypted = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, md5($key), $string, MCRYPT_MODE_CBC, md5(md5($key))));
To
$encrypted = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, sha1(md5($key)), $string, MCRYPT_MODE_CBC, sha1(md5(md5($key)))));
Are not it's just increasing the toughness to decrypt it but still crack-able while just time issue ?
Best way to use PHP to encrypt and decrypt passwords?
I'm thinking of using our server processor / harddisc GUID to generate the salt and encrypt the password.
It's still some stupid way to do while cracker got the access to the server and they can just use PHP to echo the GUID and do the decryption. Or if it works, a few years later my website will be in trouble. The reason is harddisc, processor never last forever. When the time my processor or harddisc down, it's a time when my website down and lost all the credential.
Update
Found this question which doing with blowfish for decryption in PHP. Is it solving the question of finding secured way to encrypt and hard to decrypt by others ?
How to decrypt using Blowfish algorithm in php?
Can anyone please suggest on how should I overcome this issue ? Thanks.
Checkout this well documented article A reversible password encryption routine for PHP, intended for those PHP developers who want a password encryption routine that is reversible.
Even though this class is intended for password encryption, you can use it for encryption/decryption of any text.
function encryption_class() {
$this->errors = array();
// Each of these two strings must contain the same characters, but in a different order.
// Use only printable characters from the ASCII table.
// Do not use single quote, double quote or backslash as these have special meanings in PHP.
// Each character can only appear once in each string.
$this->scramble1 = '! #$%&()*+,-./0123456789:;<=>?#ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~';
$this->scramble2 = 'f^jAE]okIOzU[2&q1{3`h5w_794p#6s8?BgP>dFV=m D<TcS%Ze|r:lGK/uCy.Jx)HiQ!#$~(;Lt-R}Ma,NvW+Ynb*0X';
if (strlen($this->scramble1) <> strlen($this->scramble2)) {
trigger_error('** SCRAMBLE1 is not same length as SCRAMBLE2 **', E_USER_ERROR);
} // if
$this->adj = 1.75; // this value is added to the rolling fudgefactors
$this->mod = 3; // if divisible by this the adjustment is made negative
}
Caution:
If you are using PHP version >= 5.3.3, then you have to change the class name from encryption_class to __construct
Reason:
As of PHP 5.3.3, methods with the same name as the last element of a namespaced class name will no longer be treated as constructor.
Usage:
$crypt = new encryption_class();
$crypt->setAdjustment(1.75); // 1st adjustment value (optional)
$crypt->setModulus(3); // 2nd adjustment value (optional)
/**
*
* #param string $key - Your encryption key
* #param string $sourceText - The source text to be encrypted
* #param integer $encLen - positive integer indicating the minimum length of encrypted text
* #return string - encrypted text
*/
$encrypt_result = $crypt->encrypt($key, $sourceText, $encLen);
/**
*
* #param string $key - Your encryption key (same used for encryption)
* #param string $encrypt_result - The text to be decrypted
* #return string - decrypted text
*/
$decrypt_result = $crypt->decrypt($key, $encrypt_result);
Update:
Above class is not intended for encrypting files, but you can!!!
base64_encode your source text (file contents)
for actual encryption, apply above enc/dec class over base64-encoded text
for decryption, apply above enc/dec class over actually encrypted text
base64_decode will give you the actual file contents (you can save a copy of file with this content)
I've encrypted an image, decrypted back and saved to a new file!!! checkout the code.
//class for encrypt/decrypt routines
require 'class.encryption.php';
//configuring your security levels
$key = 'This is my secret key; with symbols (#$^*&<?>/!#_+), cool eh?!!! :)';
$adjustment = 1.75;
$modulus = 2;
//customizing
$sourceFileName = 'source-image.png';
$destFileName = 'dest-image.png';
$minSpecifiedLength = 512;
//base64 encoding file contents, to get all characters in our range
//binary too!!!
$sourceText = base64_encode(file_get_contents($sourceFileName));
$crypt = new encryption_class();
$crypt->setAdjustment($adjustment); //optional
$crypt->setModulus($modulus); //optional
//encrypted text
$encrypt_result = $crypt->encrypt($key, $sourceText, $minSpecifiedLength);
//receive initial file contents after decryption
$decrypt_result = base64_decode($crypt->decrypt($key, $encrypt_result));
//save as new file!!!
file_put_contents($destFileName, $decrypt_result);
Bear in mind that, in order to crack passwords, a hacker would have to have access to the encrypted passwords in the first place. In order to do that they would have to compromise the server's security, which should be impossible if the site is coded correctly (proper escaping or prepared statements).
One of the strongest yet simplest forms of encryption is XOR, however it is entirely dependent on the key. If the key is the same length as the encoded text, then it is completely unbreakable without that key. Even having the key half the length of the text is extremely unlikely to be broken.
In the end, though, whatever method you choose is secured by your FTP/SSH/whatever password that allows you to access the server's files. If your own password is compromised, a hacker can see everything.
Your question leads to two different answers. It's an important difference, whether you need to decrypt the data later (like files), or if you can use a one way hash (for passwords).
One-Way-Hash
If you do not need to decrypt your data (passwords), you should use a hash function. This is safer, because even if an attacker has control over your server and your database, he should not be able to retrieve the original password. Since users often use their password for several websites, at least he doesn't gain access to other sites as well.
As you already stated, one of the most recommended hash functions today, is bcrypt. Despite it's origin in the blowfish algorithm, it is in fact a hash function (not encryption). Bcrypt was designed especially to hash passwords, and is therefore slow (needs computing time). It's recommended to use a well established library like phpass, and if you want to understand how to implement it, you can read this article, where i tried to explain the most important points.
Encryption
If you need to decrypt your data later (files), you cannot prevent, that an attacker with control over your server, can decrypt the files as well (after all the server has to be able to decrypt it). All adds up to the question of where to store the secret key. The only thing you can do, is to make it harder to get the key.
That means, if you store the key in a file, it should be outside the http root directory, so it can on no account be accessed from the internet. You could store it on a different server, so the attacker would need control over both servers, though then you face the problem of the secure communication between the servers. In every case, you can make theft harder, but you cannot prevent it completely.
Depending on your scenario, you could encrypt the files locally on your computer, and only store the encrypted files on the server. The server would not be able to decrypt the files on it's own then, so they are safe.
So you already know about salting and hashing, but you can also "stretch" your passwords, where instead of just hashing each password once, you hash it several thousand times. This will slow down brute force attacks and increase the lifespan of your hashing algorithm. Interestingly it works by intentionally slowing down your server...
What I would recommend is writing your own custom hash function. First, you add salt to the password, then you pick a hash algorithm (say sha512, or perhaps a newer algorithm that is designed to be inefficient for this very purpose) and hash it, say, 10,000 times, then store it in the database. And as you already know, when a user logs in, instead of reversing the hash, you simply run their input through the same algorithm and see if it matches.
The beauty of writing your own hash function is that when it comes time to update your hash algorithm because the old one has become vulnerable to brute force attacks, all you have to do is add to your hash function, taking the result of the old hash algorithm, re-salting it, and hashing it again using your new algorithm. You can use whatever hash algorithm is considered secure at the time. Then, you can simply re-hash every password already stored in your database with the new part of your hash function, thus ensuring backwards compatibility. Depending on how many users you have and how fast your server is, it might only take a couple of seconds to perform this update.
There is still a vulnerability, however. If a hacker has an old copy of your database and cracks it, he still knows the passwords of any users who haven't changed their passwords yet. The only way around this is to require your users to occasionally change their passwords, which may or may not be suitable for your site depending on the nature of the information it contains. Some security professionals suggest that users only change their passwords if they are compromised because if the system makes it too difficult to manage passwords, they will begin doing insecure things like keeping their passwords under their keyboards, which for some organizations is a bigger threat than having users that never change their passwords. If your website is a forum or review site or something of that nature, you should consider how much users have to lose by having their account hacked, how easy it is to restore their data to the way it was before it was hacked, and whether they will consider your site worth updating their password for if your password policy is too annoying.
One possible hash function:
function the_awesomest_hash($password)
{
$salt1 = "awesomesalt!";
$password = $salt1 . $password;
for($i = 0; $i < 10000; $i++)
{
$password = hash('sha512', $password);
}
// Some time has passed, and you have added to your hash function
$salt2 = "niftysalt!";
$password = $salt2 . $password;
for($i = 0; $i < 10000; $i++)
{
$password = hash('futuresuperhash1024', $password);
}
return $password;
}
Now, in order to update all the passwords already in your database, you would run them through this function:
function update_hash($password)
{
// This is the last part of your the_awesomest_hash() function
$salt2 = "niftysalt!";
$password = $salt2 . $password;
for($i = 0; $i < 10000; $i++)
{
$password = hash('futuresuperhash1024', $password);
}
return $password;
}
I like to write my own hash functions because it's easier to keep track of what exactly is happening for when it comes time to update them.
After some study of PHP, particularly the random number generation, the only way to securely encrypt with PHP is by using an OpenSSL wrapper. Especially the creators of mcrypt are a bunch of morons, just look at the example of not how to perform cryptography in their sample:
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$key = "This is a very secret key";
$text = "Meet me at 11 o'clock behind the monument.";
echo strlen($text) . "\n";
$crypttext = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $text, MCRYPT_MODE_ECB, $iv);
echo strlen($crypttext) . "\n";
Note that by default MCRYPT_RAND is not seeded well. Furthermore, there is at least about 5 mistakes in above code alone, and they won't fix it.
[EDIT] See below for an ammended sample. Note that this sample is not very safe either (as explained above). Furthermore normally you should not encrypt passwords...
# the key should be random binary, use scrypt, bcrypt or PBKDF2 to convert a string into a key
# key is specified using hexadecimals
$key = pack('H*', "bcb04b7e103a0cd8b54763051cef08bc55abe029fdebae5e1d417e2ffb2a00a3");
echo "Key size (in bits): " . $key_size * 8 . "\n";
$plaintext = "This string was AES-256 / CBC / ZeroBytePadding encrypted.";
echo "Plain text: " . $plain_text . "\n";
$ciphertext_base64 = encryptText($key, $plaintext);
echo $ciphertext_base64 . "\n";
function encryptText(string $key_hex, string $plaintext) {
# --- ENCRYPTION ---
# show key size use either 16, 24 or 32 byte keys for AES-128, 192 and 256 respectively
$key_size = strlen($key);
# create a random IV to use with CBC encoding
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
# use an explicit encoding for the plain text
$plaintext_utf8 = utf8_encode($plaintext);
# creates a cipher text compatible with AES (Rijndael block size = 128) to keep the text confidential
# only suitable for encoded input that never ends with value 00h (because of default zero padding)
$ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $plaintext_utf8, MCRYPT_MODE_CBC, $iv);
# prepend the IV for it to be available for decryption
$ciphertext = $iv . $ciphertext;
# encode the resulting cipher text so it can be represented by a string
$ciphertext_base64 = base64_encode($ciphertext);
return $ciphertext_base64;
}
# === WARNING ===
# Resulting cipher text has no integrity or authenticity added
# and is not protected against padding oracle attacks.
# --- DECRYPTION ---
$ciphertext_dec = base64_decode($ciphertext_base64);
# retrieves the IV, iv_size should be created using mcrypt_get_iv_size()
$iv_dec = substr($ciphertext_dec, 0, $iv_size);
# retrieves the cipher text (everything except the $iv_size in the front)
$ciphertext_dec = substr($ciphertext_dec, $iv_size);
# may remove 00h valued characters from end of plain text
$plaintext_utf8_dec = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $ciphertext_dec, MCRYPT_MODE_CBC, $iv_dec);
echo $plaintext_utf8_dec . "\n";
So far i know the best way to save password is with salted hash like used in joomla. You can also add extra keys to md5 hash along with traditional base64.I wrote a script like that sometime ago, tried to find it but can't.
Joomla uses salted md5 passwords. Take the hashed password you gave: 30590cccd0c7fd813ffc724591aea603:WDmIt53GwY2X7TvMqDXaMWJ1mrdZ1sKb
If your password was say 'password', then:
md5('passwordWDmIt53GwY2X7TvMqDXaMWJ1mrdZ1sKb') = 30590cccd0c7fd813ffc724591aea603
So, take your password. Generate a random 32 character string. Compute the md5 of the password concatenated with the random string. Store the md5 result plus a : plus the random 32 character string in the database.