So, right to it.
I have setup a [simple] PHP REST API where I am receiving a hashed password via the X-API-KEY header key. This works great when interfacing with another PHP script and the phrase is hashed via PHP's password_hash() method. However, when i try to interface with the API via Python and the Requests library, the key is rejected. Here are some samples:
PHP:
<?php
$usrid = '123456';
$dt = new DateTime();
$secret = "secret{$usrid}{$dt->format('Ymd')}";
$hashed = password_hash($secret, PASSWORD_BCRYPT);
echo $secret."\n";
echo $hashed."\n";
echo(phpversion());
?>
Python:
#!/usr/bin/python
import bcrypt, datetime, sys
usrid = '123456' # user id
t = datetime.datetime.now().strftime('%Y%m%d')
secret = "secret{usrid}{t}".format(usrid=usrid,t=t)
hashed = bcrypt.hashpw(secret, bcrypt.gensalt())
print secret
print hashed
print '%d.%d.%d' % (sys.version_info[:3])
The output of each of these is as follows:
PHP:
secret12345620161116
$2y$10$/WUBS2RkTlfcgPxvmqYRI.EkBD/CPgnpE9rYvOqweERgSwFeENUDO
5.6.24
Python:
secret12345620161116
$2b$11$9v/l6KglHiNgOybw1Y8jWeCFHiAfv.cguO1Qmc7Noe4azSluoBeHO
2.7.11
Now, obviously they are different, that is the point, but when you pass the Python output to the PHP password_verify() function, it returns False. The PHP output verifies just fine.
There has to be something I'm missing here but, for the life of me, I cant find it. I have tried using different salt options with no success. What am I missing? Are the two just not compatible? That seems silly, if it's true.
Thank you in advanced, you intelligent internet peoples.
UPDATE
[I have updated the scripts with the following 2 lines for the tests]
PHP: $hashed = password_hash($secret, PASSWORD_BCRYPT, ['cost'=>11]);
Python: hashed = bcrypt.hashpw(secret, bcrypt.gensalt(11))
And I have used this [PHP] to verify the above:
<?php
$secret = 'secret12345620161116';
$php = '$2y$11$rMqK7PhWtYd3E6yqqor0K.p2XEOJqbxJSrknLLWfhqZKsbYRa1YRa'; // output from php script
$python = '$2b$11$yWzCNB4dfIIVH2FLWWEQ/efSmN/KlVmLq.MGJ54plgedE1OSQgvPu'; // putput from python script
$php_needs_rehash = password_needs_rehash($php, PASSWORD_BCRYPT);
$python_needs_rehash = password_needs_rehash($python, PASSWORD_BCRYPT);
echo 'php_needs_rehash: '.$php_needs_rehash."\n";
echo 'python_needs_rehash: '.$python_needs_rehash."\n";
echo "\n";
echo "php_info:\n";
print_r(password_get_info($php));
echo "\n";
echo "python_info:\n";
print_r(password_get_info($python));
echo "\n";
echo "php_verified: ".password_verify($secret, $php)."\n";
echo "python_verified: ".password_verify($secret, $python)."\n";
echo "\n";
?>
With the following output:
php_needs_rehash: 1
python_needs_rehash: 1
php_info:
Array
(
[algo] => 1
[algoName] => bcrypt
[options] => Array
(
[cost] => 11
)
)
python_info:
Array
(
[algo] => 0
[algoName] => unknown
[options] => Array
(
)
)
php_verified: 1
python_verified: 1
So, now I'm really confused as the server still doesn't recognize my python hashed key, if I don't replace the "$2b" with "$2y" as suggested by richardhsu in the comments, that is.
Technically they are both different versions of bcrypt or crypt-blowfish
in php the prefix is $2y$10$
in python the prefix is $2b$11$
This means the cost factors are slightly different 10 vs 11 respectively
in your update you have fixed the cost factors to both be 11
The other part of the prefix indicates php is using the CRYPT_BLOWFISH hashing where python is using bcrypt which is based on the Blowfish cipher.
Because of those differences the 2 passwords are not interchangeable.
I found passlib and works perfectly
you need install
pip install passlib
pip install bcrypt
a sample code
from passlib.hash import bcrypt
php_hashed='$2y$10$/WUBS2RkTlfcgPxvmqYRI.EkBD/CPgnpE9rYvOqweERgSwFeENUDO'
plain_secret='secret12345620161116'
bcrypt.verify(plain_secret,php_hashed)
#output True
Related
I have an app in vb.net and I want to store a password in my database that is understandable from PHP. The creation in PHP is like this:
$hash = password_hash("mypassword", PASSWORD_BCRYPT);
The result looks like this:
$hash = '$2y$07$BCryptRequires22Chrcte/VlQH0piJtjXl.0t1XkA8pw9dMXTpOq';
and the code in php to validate a password looks likethis
if (password_verify('mypassword', $hash)) {
echo 'Password is valid!';
} else {
echo 'Invalid password.';
}
How can I replicate this code
$hash = password_hash("mypassword", PASSWORD_BCRYPT);
iv VB.Net, so the results will match exactly?
In the php line, you provided, you use the bcrypt algorithm to hash the password.
This is a general hashing algorithm, not unique to php.
Im not very familar with VB.net, but this is what I found with a quick search:
' This example assumes the Chilkat API to have been previously unlocked.
' See Global Unlock Sample for sample code.
Dim crypt As New Chilkat.Crypt2
' The BCrypt cost factor (work factor) can be set to a value from 4 to 31.
' The default value is 10. We'll set it here explicitly to the default value
' to make this new property known. This line of code can be omitted
' if the default value of 10 is desired.
crypt.BCryptWorkFactor = 10
Dim bcryptHash As String = crypt.BCryptHash("mySecretPassword")
Debug.WriteLine("BCrypt hash = " & bcryptHash)
' Sample output:
' BCrypt hash = $2a$10$H5kIVktMGzAPKGKNAe9DVu0iwEqfhv/o4MMJ/Dzw/MPy1leOE9NOK
' Note: Your output will be different because the BCryptHash method
' automatically generates a random salt.
I'm using php 8.0.11, i have to generate a SHA256 encrypted messagesignature.When i test the API in postman with javascipt code in Pre-request script it give the right encrypted messagesignature, i converted the script to php when i test it in php it sends a different wrong encrypted messagesignature (key & msg are fake) :
javascript code (Pre-request script in postman):
let msg='mymessage'
const hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256,"myapipkey");
hmac.update(msg);
const messageSignature = hmac.finalize().toString();
pm.globals.set("messageSignature",messageSignature);
console.log('messageSi:',pm.globals.get('messageSignature'))
````
php code:
````php
$data_to_hash = "mymessage";
$data_hmac=hash('sha256', $data_to_hash);
$ctx = hash_init('sha256', HASH_HMAC, 'myapipkey');
hash_update($ctx, $data_hmac);
$result = hash_final($ctx);
echo $result;
````
A simple change to the PHP code should give the correct result.
It looks like you were hashing twice (or something like that!)
$data_to_hash = "mymessage";
$ctx = hash_init('sha256', HASH_HMAC, 'myapipkey');
hash_update($ctx, $data_to_hash);
$result = hash_final($ctx);
echo $result;
In any case, the output of the above code will be:
898786a1fa80da9b463c1c7c9045377451c40cf3684cbba73bdfee48cd3a5b8f
Which is the same as the JavaScript code, both match the output given here:
https://codebeautify.org/hmac-generator
With Algorithm = 'SHA256', Key = 'myapipkey' and Plaintext = 'mymessage'.
I have a need to verify password hashes generated using python passlib. My objective is to use passlib's pbkdf2_sha512 scheme for hashing all user passwords. However, due to the nature of our backend, I need to verify this password from php scripts, js and java. I haven't found libraries in either of them that can take a passlib hash and verify the password. I was wondering if there exist one before I set out to implement passlib's hashing algorithm in php, js and java.
I can offer this solution for php:
/*
* This function creates a passlib-compatible pbkdf2 hash result. Parameters are:
* $algo - one of the algorithms supported by the php `hash_pbkdf2()` function
* $password - the password to hash, `hash_pbkdf2()` format
* $salt - a random string in ascii format
* $iterations - the number of iterations to use
*/
function create_passlib_pbkdf2($algo, $password, $salt, $iterations)
{
$hash = hash_pbkdf2($algo, $password, base64_decode(str_replace(".", "+", $salt)), $iterations, 64, true);
return sprintf("\$pbkdf2-%s\$%d\$%s\$%s", $algo, $iterations, $salt, str_replace("+", ".", rtrim(base64_encode($hash), '=')));
}
I you copy the salt, iterations, and algorithm out of an existing passlib-generated hash string, and supply them with the plaintext password to this function, it will generated the same result as passlib.
Here's a php function to just verify a passlib pbkdf2 password, based on the above:
/*
* This function verifies a python passlib-format pbkdf2 hash against a password, returning true if they match
* only ascii format password are supported.
*/
function verify_passlib_pbkdf2($password, $passlib_hash)
{
if (empty($password) || empty($passlib_hash)) return false;
$parts = explode('$', $passlib_hash);
if (!array_key_exists(4, $parts)) return false;
/*
* Results in:
* Array
* (
* [0] =>
* [1] => pbkdf2-sha512
* [2] => 20000
* [3] => AGzdiek7yUzJ9iorZD6dBPdy
* [4] => 0298be2be9f2a84d2fcc56d8c88419f0819c3501e5434175cad3d8c44087866e7a42a3bd170a035108e18b1e296bb44f0a188f7862b3c005c5971b7b49df22ce
* )
*/
$t = explode('-', $parts[1]);
if (!array_key_exists(1, $t)) return false;
$algo = $t[1];
$iterations = (int) $parts[2];
$salt = $parts[3];
$orghash = $parts[4];
$hash = create_passlib_pbkdf2($algo, $password, $salt, $iterations);
return $passlib_hash === $hash;
}
Passlib's pbkdf2_sha256 hash format is custom to passlib, so there (probably?) won't be very many ports of it to other languages. But it's a very simple wrapper around the PBKDF2-HMAC-SHA256 algorithm + base64 encoding, both of which are standard and should have implementations for the other languages -- so it should be pretty easy to port.
However, if portability is a priority requirement, you might want to try using passlib's bcrypt or sha256_crypt hashes instead. Both of those are standard, and should have implementations across a number of languages.
Keep in mind, both bcrypt & sha256_crypt are pretty complex -- so if you can't find a port under one of the languages you need, porting pbkdf2_sha256 is going to be a LOT less effort than porting one of them.
Another option entirely is to invoke passlib under python via a subprocess.
Calling the following python oneliner...
python3 -c 'import sys; from passlib.hash import pbkdf2_sha256 as ph; print(ph.verify(input(), input()))'
... will let you write password\nhash\n to stdin, and have it write back True or False (or an error message if the hash is malformed).
Since the password is being written via stdin, this should be relatively secure (compared to passing it as an argument, or env var).
(The python2 equivalent is the same, just use raw_input() instead of input())
In java you can use jython, which allows to use python libraries and execute python code.
Here is sample java function to verify hash using passlib:
Boolean verify_pbkdf2_sha512(String pw, String hash) {
PythonInterpreter python = new PythonInterpreter();
python.exec("from passlib.hash import pbkdf2_sha512");
python.set("pw", new PyString(pw));
python.set("hash", new PyString(hash));
python.exec("valid = 1 if pbkdf2_sha512.identify(hash) and pbkdf2_sha512.verify(pw, hash) else 0");
Boolean valid = ((PyInteger)python.get("valid")).asInt()==1;
return (Boolean)valid;
}
You can find more information on my blog: http://codeinpython.blogspot.com/2015/11/using-python-passlib-in-java.html
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'm using the python hashlib to get a condensed md5 value using the following command, can someone help me out with a compatible function?
hashlib.md5('my string').hexdigest()
$string = 'my string'
$encoded_string = md5($string);
echo md5('apple');
// => 1f3870be274f6c49b3e31a0c6728957f
produces the same result as
require 'digest/md5';
print Digest::MD5.hexdigest('apple');
# => 1f3870be274f6c49b3e31a0c6728957f