I need to provide users with a unique link that contains their USER ID (please do not suggest usage of UUID etc in this case)
It's not that important, but I still rather make sure it's very difficult to extract the user id or guess the next one etc! (even if it's achieved with security by obscurity...)
I came up with this solution:
// #var $id int|string
function obfuscate_number($id, bool $reverse=FALSE)
{
$changing = (int)substr($id, -1);
$multiplier = '45' . $changing;
$base = 25;
// Obfuscate Number
if($reverse === FALSE)
{
$new = bcmul("$id", "$multiplier", 0);
$convert = bcadd("$new", "$changing", 0);
$obf = base_convert($convert, 10, $base) . $changing;
return $obf;
}
// Reverse to Number
else
{
$deobf = base_convert(substr($id, 0, -1), $base, 10);
$convert = bcsub("$deobf", "$changing", 0);
// Simple Validation
if($convert % $multiplier !== 0) return FALSE;
$number = (int)bcdiv("$convert", "$multiplier", 0);
return $number;
}
}
// For example number 123456 => 5dnpfi6
// After reversing 5dnpfi6 => 123456
// For example number 563 => g81h3
// After reversing g81h3 => 563
If it is possible please help me improve it.
Also I think the chance of collision is 0 here, am I correct?
Possible Solutions
Slow
Hashing, hashing will allow you to send the hash out and without an extremely moderate amount of computations will not be put reversed into the user id. The server would hash, send string to client, client visits webpage, you lookup in a database of some sort matching hash with user id.
Fast (Recommended)
Using AES encryption will allow you to encrypt data that is generally guaranteed to be unbreakable if you follow AES guidelines. So an approach would be to encrypt data with AES and convert to base 64. Send the base64 to the user and when the user clicks the link, you simply just need to convert base 64 to binary, and decrypt. I would say this is considerably faster than the hashing approach.
OK... to the point...
I have a e-mailshot program that sends out thousands of emails - each with str_replace() merge fields (fields that get replaced by a row value in a recordset).
One of the important parts is my ability to track those mails on open so I include a single server-side generated pixel...
<img src="http://...trace.php?email=<<<EMAIL>>>" alt="" height="1" width="1">
The str_replace() replaces <<<EMAIL>>> with a unique real email address.
The trace.php file reads the $_GET['email'] and either logs it or sends mail confirmation.
My issue is security :)
I want to use two-way encryption so that the $_GET variable sent in the URL is an encrypted email. The trace.php file then needs to decrypt it.
As it's being sent in a URL, It has to be in ASCII format otherwise it will corrupt before decrypting.
I can't use openssl_encrypt() & openssl_decrypt() and I'm having to work with php 5.2.0 (don't hurl abuse at me!).
Any help would be greatly appreciated!!
While many of the comments you have received offer other valid ways of solving the problem e.g. a table of email addresses with primary keys, I am of the position that the best way to solve the problem is the way you originally intended: including the email address encrypted in the query URL.
I feel that this way is better because:
Computing the email address does not require database access. Database bottle-necking is generally the biggest offender for high-latency requests.
Encryption means that the same email address will produce a different IV/ciphertext pair each time you encrypt it. Thus, if you send multiple emails at different times (say, for two different marketing campaigns), the URL will be different each time. This may not have an effect, but it does provide a security advantage in that an attacker can't "pretend" that an email has been opened simply by visiting a URL.
The issue is that for this way to be better, you have to do it well. I've included an excerpt in PHP from this repository below. If you can't use openssl_* then upgrade your PHP version. Do not, ever, use the mcrypt_ functions. They are deprecated for a reason. You may need to hex encode instead of base64 encode the email addresses as is done in the example below.
<?php
define("ALGORITHM_NAME", "aes-128-gcm");
define("ALGORITHM_NONCE_SIZE", 12);
define("ALGORITHM_TAG_SIZE", 16);
define("ALGORITHM_KEY_SIZE", 16);
define("PBKDF2_NAME", "sha256");
define("PBKDF2_SALT_SIZE", 16);
define("PBKDF2_ITERATIONS", 32767);
function encryptString($plaintext, $password) {
// Generate a 128-bit salt using a CSPRNG.
$salt = random_bytes(PBKDF2_SALT_SIZE);
// Derive a key.
$key = hash_pbkdf2(PBKDF2_NAME, $password, $salt, PBKDF2_ITERATIONS, ALGORITHM_KEY_SIZE, true);
// Encrypt and prepend salt and return as base64 string.
return base64_encode($salt . encrypt($plaintext, $key));
}
function decryptString($base64CiphertextAndNonceAndSalt, $password) {
// Decode the base64.
$ciphertextAndNonceAndSalt = base64_decode($base64CiphertextAndNonceAndSalt);
// Retrieve the salt and ciphertextAndNonce.
$salt = substr($ciphertextAndNonceAndSalt, 0, PBKDF2_SALT_SIZE);
$ciphertextAndNonce = substr($ciphertextAndNonceAndSalt, PBKDF2_SALT_SIZE);
// Derive the key.
$key = hash_pbkdf2(PBKDF2_NAME, $password, $salt, PBKDF2_ITERATIONS, ALGORITHM_KEY_SIZE, true);
// Decrypt and return result.
return decrypt($ciphertextAndNonce, $key);
}
function encrypt($plaintext, $key) {
// Generate a 96-bit nonce using a CSPRNG.
$nonce = random_bytes(ALGORITHM_NONCE_SIZE);
// Encrypt and prepend nonce.
$ciphertext = openssl_encrypt($plaintext, ALGORITHM_NAME, $key, OPENSSL_RAW_DATA, $nonce, $tag);
return $nonce . $ciphertext . $tag;
}
function decrypt($ciphertextAndNonce, $key) {
// Retrieve the nonce and ciphertext.
$nonce = substr($ciphertextAndNonce, 0, ALGORITHM_NONCE_SIZE);
$ciphertext = substr($ciphertextAndNonce, ALGORITHM_NONCE_SIZE, strlen($ciphertextAndNonce) - ALGORITHM_NONCE_SIZE - ALGORITHM_TAG_SIZE);
$tag = substr($ciphertextAndNonce, strlen($ciphertextAndNonce) - ALGORITHM_TAG_SIZE);
// Decrypt and return result.
return openssl_decrypt($ciphertext, ALGORITHM_NAME, $key, OPENSSL_RAW_DATA, $nonce, $tag);
}
?>
I am creating an application that will store passwords, which the user can retrieve and see. The passwords are for a hardware device, so checking against hashes are out of the question.
What I need to know is:
How do I encrypt and decrypt a password in PHP?
What is the safest algorithm to encrypt the passwords with?
Where do I store the private key?
Instead of storing the private key, is it a good idea to require users to enter the private key any time they need a password decrypted? (Users of this application can be trusted)
In what ways can the password be stolen and decrypted? What do I need to be aware of?
Personally, I would use mcrypt like others posted. But there is much more to note...
How do I encrypt and decrypt a password in PHP?
See below for a strong class that takes care of everything for you:
What is the safest algorithm to encrypt the passwords with?
safest? any of them. The safest method if you're going to encrypt is to protect against information disclosure vulnerabilities (XSS, remote inclusion, etc.). If it gets out, the attacker can eventually crack the encryption (no encryption is 100% un-reversible without the key - As #NullUserException points out this is not entirely true. There are some encryption schemes that are impossible to crack such as one-time pad).
Where do I store the private key?
I would use three keys. One is user supplied, one is application specific and the other is user specific (like a salt). The application specific key can be stored anywhere (in a configuration file outside of the web-root, in an environmental variable, etc.). The user specific one would be stored in a column in the db next to the encrypted password. The user supplied one would not be stored. Then, you'd do something like this:
$key = $userKey . $serverKey . $userSuppliedKey;
The benefit there, is that any two of the keys can be compromised without the data being compromised. If there's a SQL injection attack, they can get the $userKey, but not the other two. If there's a local server exploit, they can get $userKey and $serverKey, but not the third $userSuppliedKey. If they go beat the user with a wrench, they can get the $userSuppliedKey, but not the other two (but then again, if the user is beaten with a wrench, you're too late anyway).
Instead of storing the private key, is it a good idea to require users to enter the private key any time they need a password decrypted? (Users of this application can be trusted)
Absolutely. In fact, that's the only way I would do it. Otherwise you'd need to store an unencrypted version in a durable storage format (shared memory, such as APC or Memcached, or in a session file). That's exposing yourself to additional compromises. Never store the unencrypted version of the password in anything except a local variable.
In what ways can the password be stolen and decrypted? What do I need to be aware of?
Any form of compromise of your systems will let them view encrypted data. If they can inject code or get to your filesystem, they can view decrypted data (since they can edit the files that decrypt the data). Any form of replay or MITM attack will also give them full access to the keys involved. Sniffing the raw HTTP traffic will also give them the keys.
Use SSL for all traffic. And make sure nothing on the server has any kind of vulnerabilities (CSRF, XSS, SQL injection, privilege escalation, remote code execution, etc.).
Here's a PHP class implementation of a strong encryption method:
/**
* A class to handle secure encryption and decryption of arbitrary data
*
* Note that this is not just straight encryption. It also has a few other
* features in it to make the encrypted data far more secure. Note that any
* other implementations used to decrypt data will have to do the same exact
* operations.
*
* Security Benefits:
*
* - Uses Key stretching
* - Hides the Initialization Vector
* - Does HMAC verification of source data
*
*/
class Encryption {
/**
* #var string $cipher The mcrypt cipher to use for this instance
*/
protected $cipher = '';
/**
* #var int $mode The mcrypt cipher mode to use
*/
protected $mode = '';
/**
* #var int $rounds The number of rounds to feed into PBKDF2 for key generation
*/
protected $rounds = 100;
/**
* Constructor!
*
* #param string $cipher The MCRYPT_* cypher to use for this instance
* #param int $mode The MCRYPT_MODE_* mode to use for this instance
* #param int $rounds The number of PBKDF2 rounds to do on the key
*/
public function __construct($cipher, $mode, $rounds = 100) {
$this->cipher = $cipher;
$this->mode = $mode;
$this->rounds = (int) $rounds;
}
/**
* Decrypt the data with the provided key
*
* #param string $data The encrypted datat to decrypt
* #param string $key The key to use for decryption
*
* #returns string|false The returned string if decryption is successful
* false if it is not
*/
public function decrypt($data, $key) {
$salt = substr($data, 0, 128);
$enc = substr($data, 128, -64);
$mac = substr($data, -64);
list ($cipherKey, $macKey, $iv) = $this->getKeys($salt, $key);
if (!hash_equals(hash_hmac('sha512', $enc, $macKey, true), $mac)) {
return false;
}
$dec = mcrypt_decrypt($this->cipher, $cipherKey, $enc, $this->mode, $iv);
$data = $this->unpad($dec);
return $data;
}
/**
* Encrypt the supplied data using the supplied key
*
* #param string $data The data to encrypt
* #param string $key The key to encrypt with
*
* #returns string The encrypted data
*/
public function encrypt($data, $key) {
$salt = mcrypt_create_iv(128, MCRYPT_DEV_URANDOM);
list ($cipherKey, $macKey, $iv) = $this->getKeys($salt, $key);
$data = $this->pad($data);
$enc = mcrypt_encrypt($this->cipher, $cipherKey, $data, $this->mode, $iv);
$mac = hash_hmac('sha512', $enc, $macKey, true);
return $salt . $enc . $mac;
}
/**
* Generates a set of keys given a random salt and a master key
*
* #param string $salt A random string to change the keys each encryption
* #param string $key The supplied key to encrypt with
*
* #returns array An array of keys (a cipher key, a mac key, and a IV)
*/
protected function getKeys($salt, $key) {
$ivSize = mcrypt_get_iv_size($this->cipher, $this->mode);
$keySize = mcrypt_get_key_size($this->cipher, $this->mode);
$length = 2 * $keySize + $ivSize;
$key = $this->pbkdf2('sha512', $key, $salt, $this->rounds, $length);
$cipherKey = substr($key, 0, $keySize);
$macKey = substr($key, $keySize, $keySize);
$iv = substr($key, 2 * $keySize);
return array($cipherKey, $macKey, $iv);
}
/**
* Stretch the key using the PBKDF2 algorithm
*
* #see http://en.wikipedia.org/wiki/PBKDF2
*
* #param string $algo The algorithm to use
* #param string $key The key to stretch
* #param string $salt A random salt
* #param int $rounds The number of rounds to derive
* #param int $length The length of the output key
*
* #returns string The derived key.
*/
protected function pbkdf2($algo, $key, $salt, $rounds, $length) {
$size = strlen(hash($algo, '', true));
$len = ceil($length / $size);
$result = '';
for ($i = 1; $i <= $len; $i++) {
$tmp = hash_hmac($algo, $salt . pack('N', $i), $key, true);
$res = $tmp;
for ($j = 1; $j < $rounds; $j++) {
$tmp = hash_hmac($algo, $tmp, $key, true);
$res ^= $tmp;
}
$result .= $res;
}
return substr($result, 0, $length);
}
protected function pad($data) {
$length = mcrypt_get_block_size($this->cipher, $this->mode);
$padAmount = $length - strlen($data) % $length;
if ($padAmount == 0) {
$padAmount = $length;
}
return $data . str_repeat(chr($padAmount), $padAmount);
}
protected function unpad($data) {
$length = mcrypt_get_block_size($this->cipher, $this->mode);
$last = ord($data[strlen($data) - 1]);
if ($last > $length) return false;
if (substr($data, -1 * $last) !== str_repeat(chr($last), $last)) {
return false;
}
return substr($data, 0, -1 * $last);
}
}
Note that I'm using a function added in PHP 5.6: hash_equals. If you're on lower than 5.6, you can use this substitute function which implements a timing-safe comparison function using double HMAC verification:
function hash_equals($a, $b) {
$key = mcrypt_create_iv(128, MCRYPT_DEV_URANDOM);
return hash_hmac('sha512', $a, $key) === hash_hmac('sha512', $b, $key);
}
Usage:
$e = new Encryption(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
$encryptedData = $e->encrypt($data, $key);
Then, to decrypt:
$e2 = new Encryption(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
$data = $e2->decrypt($encryptedData, $key);
Note that I used $e2 the second time to show you different instances will still properly decrypt the data.
Now, how does it work/why use it over another solution:
Keys
The keys are not directly used. Instead, the key is stretched by a standard PBKDF2 derivation.
The key used for encryption is unique for every encrypted block of text. The supplied key therefore becomes a "master key". This class therefore provides key rotation for cipher and auth keys.
Important note, the $rounds parameter is configured for true random keys of sufficient strength (128 bits of cryptographically secure random at a minimum). If you are going to use a password, or non-random key (or less random then 128 bits of CS random), you must increase this parameter. I would suggest a minimum of 10000 for passwords (the more you can afford, the better, but it will add to the runtime)...
Data Integrity
The updated version uses ENCRYPT-THEN-MAC, which is a far better method for ensuring the authenticity of the encrypted data.
Encryption:
It uses mcrypt to actually perform the encryption. I would suggest using either MCRYPT_BLOWFISH or MCRYPT_RIJNDAEL_128 cyphers and MCRYPT_MODE_CBC for the mode. It's strong enough, and still fairly fast (an encryption and decryption cycle takes about 1/2 second on my machine).
Now, as to point 3 from the first list, what that would give you is a function like this:
function makeKey($userKey, $serverKey, $userSuppliedKey) {
$key = hash_hmac('sha512', $userKey, $serverKey);
$key = hash_hmac('sha512', $key, $userSuppliedKey);
return $key;
}
You could stretch it in the makeKey() function, but since it's going to be stretched later, there's not really a huge point to doing so.
As far as the storage size, it depends on the plain text. Blowfish uses a 8 byte block size, so you'll have:
16 bytes for the salt
64 bytes for the hmac
data length
Padding so that data length % 8 == 0
So for a 16 character data source, there will be 16 characters of data to be encrypted. So that means the actual encrypted data size is 16 bytes due to padding. Then add the 16 bytes for the salt and 64 bytes for the hmac and the total stored size is 96 bytes. So there's at best a 80 character overhead, and at worst a 87 character overhead...
How do I encrypt and decrypt a password in PHP?
By implementing one of many encryption algorithms (or using one of many libraries)
What is the safest algorithm to encrypt the passwords with?
There are tons of different algorithms, none of which are 100% secure. But many of them are secure enough for commerce and even military purposes
Where do I store the private key?
If you have decided to implement a public key - cryptography algorithm (e.g., RSA), you don't store the private key. The user has a private key. Your system has a public key which could be stored anywhere you wish.
Instead of storing the private key, is it a good idea to require users to enter the private key any time they need a password decrypted? (Users of this application can be trusted)
Well, if your user can remember ridiculously long prime numbers then - yes, why not. But generally you would need to come up with the system which will allow user to store their key somewhere.
In what ways can the password be stolen and decrypted? What do I need to be aware of?
This depends on the algorithm used. However, always make sure that you don't send password unencrypted to or from the user. Either encrypt/decrypt it on the client side, or use HTTPS (or user other cryptographic means to secure connection between server and client).
However if all you need is to store passwords in encrypted way, I would suggest you to use a simple XOR cipher. The main problem with this algorithm is that it could be easily broken by frequency analysis. However, as generally passwords are not made from long paragraphs of English text I don't think you should worry about it. The second problem with an XOR cipher is that if you have a message in both encrypted and decrypted form you could easily find out password with which it was encrypted. Again, not a big problem in your case as it only affects the user who already was compromised by other means.
The PHP module you are after is Mcrypt.
The example from the manual is slightly edited for this example):
<?php
$iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$key = "This is a very secret key";
$pass = "PasswordHere";
echo strlen($pass) . "\n";
$crypttext = mcrypt_encrypt(MCRYPT_BLOWFISH, $key, $pass, MCRYPT_MODE_ECB, $iv);
echo strlen($crypttext) . "\n";
?>
You would use mcrypt_decrypt to decrypt your password.
The best algorithm is rather subjective - ask five people, and get five answers. Personally, if the default (Blowfish) isn't good enough for you, you probably have bigger problems!
Given that it is needed by PHP to encrypt, I am not sure you can hide it anywhere. Standard PHP best coding practices apply of course!
Given that the encryption key will be in your code anyway, I am not sure what you will gain, providing the rest of your application is secure.
Obviously, if the encrypted password and the encryption key are stolen, then game over.
I'd put a rider on my answer. I'm not a PHP cryptography expert, but, I think what I have answered is standard practice.
A lot of users have suggested using mcrypt... which is correct, but I like to go a step further to make it easily stored and transferred (as sometimes encrypted values can make them hard to send using other technologies like cURL, or JSON).
After you have successfully encrypted using mcrypt, run it through base64_encode and then convert it to hexadecimal code. Once in hexadecimal code it's easy to transfer in a variety of ways.
$td = mcrypt_module_open('tripledes', '', 'ecb', '');
$iv = mcrypt_create_iv (mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
$key = substr("SUPERSECRETKEY", 0, mcrypt_enc_get_key_size($td));
mcrypt_generic_init($td, $key, $iv);
$encrypted = mcrypt_generic($td, $unencrypted);
$encrypted = $ua . "||||" . $iv;
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
$encrypted = base64_encode($encrypted);
$encrypted = array_shift(unpack('H*', $encrypted));
And on the other side:
$encrypted = pack('H*', $encrypted);
$encrypted = base64_decode($encrypted);
list($encrypted, $iv) = explode("||||", $encrypted, 2);
$td = mcrypt_module_open('tripledes', '', 'ecb', '');
$key = substr("SUPERSECRETKEY", 0, mcrypt_enc_get_key_size($td));
mcrypt_generic_init($td, $key, $iv);
$unencrypted = mdecrypt_generic($td, $encrypted);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
I'd only suggest public key encryption if you want the ability to set a user's password without their interaction (this can be handy for resets and shared passwords).
Public key
The OpenSSL extension, specifically openssl_public_encrypt and openssl_private_decrypt
This would be straight RSA assuming your passwords will fit in key size - padding, otherwise you need a symmetric layer
Store both keys for each user, the private key's passphrase is their application password
Symmetric
The Mcrypt extension
AES-256 is probably a safe bet, but this could be a SO question in itself
You don't - this would be their application password
Both
4. Yes - users would have to enter their application password every time, but storing it in the session would raise other issues
5.
If someone steals the application data, it's as secure as the symmetric cipher (for the public key scheme, it's used to protect the private key with the passphrase.)
Your application should definitely be only accessible over SSL, preferably using client certificates.
Consider adding a second factor for authentication which would only be used once per session, like a token sent via SMS.
The passwords are for a hardware device, so checking against hashes are out of the question
Eh? I don't understand. Do you just mean that passwords must be recoverable?
As others have said, the mcrypt extension provides access to lots of cryptographic functions - however you are inviting your users to put all their eggs in one basket - one which will be potentially be a target for attackers - and if you don't even know how to start solving the problem then you are doing your users a disservice. You are not in a position to understand how to protect the data.
Most security vulnerabilities come about not because the underlying algorithm is flawed or insecure - but because of problems with the way the algorithm is used within the application code.
Having said that, it is possible to build a reasonably secure system.
You should only consider asymmetric encryption if you have a requirement for a user to create a secure message which is readable by another (specific) user. The reason being that it’s computationally expensive. If you just want to provide a repository for users to enter and retrieve their own data, symmetric encryption is adequate.
If, however, you store the key for decrypting the message in the same place as the encrypted message (or where the encrypted message is stored) then the system is not secure. Use the same token for authenticating the user as for the decryption key (or in the case of asymmetric encryption, use the token as the private key pass phrase). Since you will need to store the token on the server where the decryption takes place at least temporarily, you might want to consider using a non-searchable session storage substrate, or passing the token directly to a daemon associated with the session which would store the token in memory and perform the decryption of messages on demand.
I tried something like this, but please note that I am not cryptographer nor do I hold in-depth knowledge about PHP or any programming language. It's just an idea.
My idea is to store a key in some file or database (or enter manually) whose (location) cannot be easily predicted (and of course anything will be decrypted some day. The concept is to lengthen the decryption time) and encrypt sensitive information.
Code
$iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$key = "evenifyouaccessmydatabaseyouwillneverfindmyemail";
$text = "myemail#domain.com";
echo "Key: " . $key . "<br/>";
echo "Text: " . $text . "<br/>";
echo "MD5: " . md5($text) . "<br/>";
echo "SHA-1: " . sha1($text) . "<br/>";
$crypttext = mcrypt_encrypt(MCRYPT_BLOWFISH, $key, $text, MCRYPT_MODE_ECB, $iv);
echo "Encrypted Data: " . $crypttext . "<br>";
$base64 = base64_encode($crypttext);
echo "Encoded Data: " . $base64 . "<br/>";
$decode = base64_decode($base64);
$decryptdata = mcrypt_decrypt(MCRYPT_BLOWFISH, $key, $crypttext, MCRYPT_MODE_ECB, $iv);
echo "Decoded Data: " . ereg_replace("?", null, $decryptdata);
// Even if I add '?' to the sting to the text it works. I don't know why.
Please note that it is just a concept. Any improvement to this code would be highly appreciated.
Use password_hash and password_verify
<?php
/**
* In this case, we want to increase the default cost for BCRYPT to 12.
* Note that we also switched to BCRYPT, which will always be 60 characters.
*/
$options = [
'cost' => 12,
];
echo password_hash("rasmuslerdorf", PASSWORD_BCRYPT, $options)."\n";
?>
And to decrypt:
<?php
// See the password_hash() example to see where this came from.
$hash = '$2y$07$BCryptRequires22Chrcte/VlQH0piJtjXl.0t1XkA8pw9dMXTpOq';
if (password_verify('rasmuslerdorf', $hash)) {
echo 'Password is valid!';
} else {
echo 'Invalid password.';
}
?>
For an app I'm working on, nodejs needs to verify hashes created by PHP and vice-versa.
The problem is, the hashes generated in PHP (via Laravel's Hash class, which just uses PHP's password_hash function) return false when tested in node.js.
The following node.js script:
var bcrypt = require('bcrypt');
var password = 'password';
var phpGeneratedHash = '$2y$10$jOTwkwLVn6OeA/843CyIHu67ib4RixMa/N/pTJVhOjTddvrG8ge5.';
var nodeGeneratedHash = '$2a$10$ZiBH5JtTDtXqDajO6f4EbeBIXGwtcGg2MGwr90xTH9ki34SV6rZhO';
console.log(
bcrypt.compareSync(password, phpGeneratedHash) ? 'PHP passed' : 'PHP failed',
bcrypt.compareSync(password, nodeGeneratedHash) ? 'nodejs passed' : 'nodejs failed'
);
outputs: 'PHP failed nodejs passed', whereas the following PHP script:
<?php
$password = 'password';
$phpGeneratedHash = '$2y$10$jOTwkwLVn6OeA/843CyIHu67ib4RixMa/N/pTJVhOjTddvrG8ge5.';
$nodeGeneratedHash = '$2a$10$ZiBH5JtTDtXqDajO6f4EbeBIXGwtcGg2MGwr90xTH9ki34SV6rZhO';
print password_verify($password, $phpGeneratedHash) ? 'PHP passed' : 'PHP failed';
print password_verify($password, $nodeGeneratedHash) ? 'nodejs passed' : 'nodejs failed';
outputs 'PHP passed nodejs passed'.
I've run the tests in Ubuntu 14.04.1 using PHP 5.5.18, node.js v0.10.32 and the npm bcrypt module.
This fails because the types of bcrypt hashes being generated from php and node are different. Laravel generates the $2y$ while node generates the $2a$. But the good news is the only difference between 2a and 2y are their prefixes.
So what you can do is make one of the prefix similar to the other. Like:
$phpGeneratedHash = '$2y$10$jOTwkwLVn6OeA/843CyIHu67ib4RixMa/N/pTJVhOjTddvrG8ge5.';
$nodeGeneratedHash = '$2a$10$ZiBH5JtTDtXqDajO6f4EbeBIXGwtcGg2MGwr90xTH9ki34SV6rZhO';
To something like:
$phpGeneratedHash = '$2y$10$jOTwkwLVn6OeA/843CyIHu67ib4RixMa/N/pTJVhOjTddvrG8ge5.';
$nodeGeneratedHash = '$2y$10$ZiBH5JtTDtXqDajO6f4EbeBIXGwtcGg2MGwr90xTH9ki34SV6rZhO';
Notice that I replaced the $2a$ of the node hash to $2y$. You can simply do this with:
PHP
$finalNodeGeneratedHash = str_replace("$2a$", "$2y$", $nodeGeneratedHash);
Node
finalNodeGeneratedHash = nodeGeneratedHash.replace('$2a$', '$2y$');
Then compare phpGeneratedHash to finalNodeGeneratedHash.
Note: It is recommended that if you're comparing in PHP, change the prefix of the NodeJS generated hash to $2y$ and if you're comparing in NodeJS; change the prefix of the PHP generated hash to $2a$.
I have tried to compute what was said before to get codes that work. As you can see I don't need to replace anything.
On the PHP 7.2.4 side:
<?php
$password = "test123";
$hash = password_hash($password, PASSWORD_BCRYPT);
echo $hash; // I get $2y$10$5EaF4lMSCFWe7YqqxyBnR.QmDu1XhoiaQxrOFw.AJZkGCYmpsWDU6
On the nodeJS side:
Install bcryptjs package: npm i bcryptjs
var bcrypt = require('bcryptjs');
let hash1="$2y$10$5EaF4lMSCFWe7YqqxyBnR.QmDu1XhoiaQxrOFw.AJZkGCYmpsWDU6";
console.log(bcrypt.compareSync("test123", hash1)); // display true
The implementation of bcrypt in different language might be differ.
For example, in Node.js version bcrypt.js, the salt length applied are 29 characters
bcrypt.getSalt = function(hash) {
if (typeof hash !== 'string')
throw Error("Illegal arguments: "+(typeof hash));
if (hash.length !== 60)
throw Error("Illegal hash length: "+hash.length+" != 60");
return hash.substring(0, 29);
};
But, in Go version golang.org/x/crypto/bcrypt, the salt size are 22 of bytes:
const (
majorVersion = '2'
minorVersion = 'a'
maxSaltSize = 16
maxCryptedHashSize = 23
encodedSaltSize = 22
encodedHashSize = 31
minHashSize = 59
)
So, it might happen that hashed string in Node.js gets error when compared in Go, other languages likewise.
I am currently working on a project which requires closed loop email verification. As part of the process I need to generate a random hash string which can be appended to a link sent to the user. When they click the link they will be directed to my site at which time the app will confirm the hash and complete the registration process. For all my hashing I have been using:
hash('sha256', $string);
But for this process, I need to seed $string with a random value. I have Zend Framework available and was looking to do something like this:
$crypt = new Zend_Filter_Encrypt_Mcrypt(array());
$hash = hash('sha256', $crypt->getVector());
My question is, is this a viable algorithm for generating random hash codes?
Here is the Zend_Filter_Encrypt_Mcrypt::setVector() method (generates the value returned via getVector():
public function setVector($vector = null)
{
$cipher = $this->_openCipher();
$size = mcrypt_enc_get_iv_size($cipher);
if (empty($vector)) {
$this->_srand();
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<')) {
$method = MCRYPT_RAND;
} else {
if (file_exists('/dev/urandom') || (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN')) {
$method = MCRYPT_DEV_URANDOM;
} elseif (file_exists('/dev/random')) {
$method = MCRYPT_DEV_RANDOM;
} else {
$method = MCRYPT_RAND;
}
}
$vector = mcrypt_create_iv($size, $method);
} else if (strlen($vector) != $size) {
require_once 'Zend/Filter/Exception.php';
throw new Zend_Filter_Exception('The given vector has a wrong size for the set algorithm');
}
$this->_encryption['vector'] = $vector;
$this->_closeCipher($cipher);
return $this;
}
I'm not very familiar with ZF, but something that has the word Encrypt in it just sounds like the wrong approach.
The ->getVector() sounds similar to what the Initialization Vector does in symmetric encryption; the problem is that such a vector doesn't need to be cryptographically safe, just random. For instance, it may well be just implemented as uniqid(mt_rand()) or something.
->getVector() uses mcrypt to first initialize the encryption cipher to know how big the IV should be; this is typically 8 bytes, but it largely depends on the block size of the used cipher. The thing is, you're not encrypting anything; you just want a random sequence.
The better way to get a random sequence is by using openssl_random_pseudo_bytes() with a size of 8 bytes.
In its absence, you could also read from an entropy file such as /dev/random or /dev/urandom. Afterwards you can run it through binh2hex() to generate a hexadecimal string.
Something like this is pretty rudimentary but should work on Linux'y systems:
$rnd = bin2hex(file_get_contents('/dev/urandom', false, null, 0, 8));
As a fallback for Windows, you can still use something like:
$rnd = hash('sha256', uniqid(mt_rand(), true));
You may want to look into ircmaxell's CryptLib which has a fairly comprehensive suite of random generation features. If you use the medium strength random string generator, like so:
$generator = ( new CryptLib\Random\Factory() )->getMediumStrengthGenerator();
$string = $generator->generateString(LENGTH);
The library will use multiple cryptographically secure sources and run them through a mixer to generate a string. It's worth checking into if you just want a simple solution and don't want to recompile PHP with openssl.
See the readme on secure string generation.