How to verify password hash ( hash_hmac, sha512 ) in php? - php

I need to convert VerifyPasswordHash method in C# to php. I have salt and stored_hash in DB for this user.
stored_hash = 0x0C3F6C5921CCD0305B2EDEDE1553B1DF55B87A9D55FEE3384A3833611BC40D106BBB48CCE1093AE35B9D0E3A1FE62E86186A6EC143BA00E53945E99C259B4913
salt=0xC62C5A645280DBCC615ED4A3E861D800B00A929856A9664B3AED50A06481ED19AFB09F74D3D7A9EA25327D93F23FDFBD2DE8CF3A75D65A3EA97290E0486F1F4322D2B5853AE6FE848E50355C35B62A993CF6689D9F9ABC861C5E7D88B099617E6A6C7792E285EFBB809FD69CD926C9BD9129AD1BE7DDB5DD459C2B9A2B945B31
Here is example in C#:
`
private bool VerifyPasswordHash(string password, byte[] storedHash, byte[] storedSalt)
{
using (var hmac = new System.Security.Cryptography.HMACSHA512(storedSalt))
{
var computedHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
for (int i = 0; i < computedHash.Length; i++)
{
if (computedHash[i] != storedHash[i])
{
return false;
}
}
}
return true;
}
Here is what I tried in PHP:
`
<?php
$password = 'Password202!';
$pass_hash = '0x0C3F6C5921CCD0305B2EDEDE1553B1DF55B87A9D55FEE3384A3833611BC40D106BBB48CCE1093AE35B9D0E3A1FE62E86186A6EC143BA00E53945E99C259B4913';
$pass_salt = '0xC62C5A645280DBCC615ED4A3E861D800B00A929856A9664B3AED50A06481ED19AFB09F74D3D7A9EA25327D93F23FDFBD2DE8CF3A75D65A3EA97290E0486F1F4322D2B5853AE6FE848E50355C35B62A993CF6689D9F9ABC861C5E7D88B099617E6A6C7792E285EFBB809FD69CD926C9BD9129AD1BE7DDB5DD459C2B9A2B945B31';
function VerifyPasswordHash($password,$pass_hash,$pass_salt){
$password_utf8 = utf8_encode($password);
$sig = hash_hmac('sha512', $pass_salt, $password_utf8, true);
echo base64_encode($sig);
}
VerifyPasswordHash($password,$pass_hash,$pass_salt);
I know that i need to compare $sig and stored_hash but they don`t even match.. Any help would be appreciated!
Expected result is successful comparison of $pass_hash and computed_hash. I simply need to verify password

There are a few things wrong with your code, but you're almost there.
Firstly, drop the utf8_encode(). This rarely does what you expect and, as #Chris mentioned in a comment, the function has been deprecated and will be removed in the future.
According to the manual, the data to hash comes before the key (salt), so you have your parameters in the wrong order:
$sig = hash_hmac('sha512', $password, $pass_salt, true);
// $password first, then $pass_salt
Next, you're using base64_encode. That is not what you want, you're looking for a hexadecimal representation of the created signature. You're looking for bin2hex():
echo bin2hex($sig);
However, this has the same effect as just passing false as the final argument to hash_hmac -- the true argument indicates you want a binary result, then you're turning it into hexadecimal representation. Passing false will return the hexadecimal notation instead of a binary result so you can skip the bin2hex step:
$sig = hash_hmac('sha512', $password, $pass_salt, false);
echo $sig;
The final problem is the notation of the salt. PHP does not use the 0x prefix for hexadecimal strings. After a bit of trial-and-error, I also found that the correct way to calculate the hash is to actually use the binary representation of the hexadecimal salt-string (using hex2bin, the reverse of bin2hex):
function VerifyPasswordHash($password,$pass_hash,$pass_salt){
// The substr() removes the leading "0x" from the salt string.
$sig = hash_hmac('sha512', $password, hex2bin(substr($pass_salt, 2)), false);
echo $sig;
}
For the values you provided, this outputs:
0c3f6c5921ccd0305b2edede1553b1df55b87a9d55fee3384a3833611bc40d106bbb48cce1093ae35b9d0e3a1fe62e86186a6ec143ba00e53945e99c259b4913
This matches your sample hash, if you'd uppercase it and put the redundant "0x" in front of it:
function VerifyPasswordHash($password,$pass_hash,$pass_salt){
$sig = hash_hmac('sha512', $password, hex2bin(substr($pass_salt, 2)), false);
$hash = '0x' . strtoupper($sig);
return $hash === $pass_hash;
}
This returns true for the given sample hashes.

Related

Decrypt crypt-js encrypted string in PHP

I have encrypted mutiple strings one-by-one in using crypto-js in react.'
For encryption I used -
encryptAES = (text, key) => {
return CryptoJS.AES.encrypt(text, key).toString();
};
For decryption, I used function like following -
decryptAES = (encryptedBase64, key) => {
const decrypted = CryptoJS.AES.decrypt(encryptedBase64, key);
if (decrypted) {
try {
console.log(decrypted);
const str = decrypted.toString(CryptoJS.enc.Utf8);
if (str.length > 0) {
return str;
} else {
return 'error 1';
}
} catch (e) {
return 'error 2';
}
}
return 'error 3';
};
I have uploaded a working sample project of this encryption - decryption here.
For e.g., if I encrypt "I live in India" using key - "earth", it would output as - "U2FsdGVkX1+cBvU9yH5fIGVmliJYPXsv4AIosUGH4tA=", and similary it would decrypt successfully with the correct key.
Now I have multiple encrypted strings stored in my database, but now require them to store un-encrypted, so I wanted to decrypt them in PHP. I can decrypt them in js using the function mentioned above but I am unable to figure out how to do so in PHP. I have tried this github repository but I couldn't customize it for my use case.
For decryption, salt and ciphertext must first be determined. To do this, the encrypted data of the CryptoJS code must be Base64 decoded. The salt are the second 8 bytes of the Base64 decoded data, followed by the actual ciphertext (the first 8 bytes are the ASCII encoding of Salted__ and can be ignored).
After determining the salt, key and IV are to be derived with EVP_BytesToKey(). You can find various PHP implementations on the web, e.g. here. Note that CryptoJS uses MD5 as digest, so the digest in the linked code must be modified accordingly.
Once key and IV have been determined, the actual ciphertext can be decrypted.
All together:
<?php
// Separate salt and actual ciphertext
$ctOpenSSL = base64_decode("U2FsdGVkX1+cBvU9yH5fIGVmliJYPXsv4AIosUGH4tA=");
$salt = substr($ctOpenSSL, 8, 8);
$ciphertext = substr($ctOpenSSL, 16);
// Derive key and IV
$keyIv = EVP_BytesToKey($salt, "earth");
$key = substr($keyIv, 0, 32);
$iv = substr($keyIv, 32, 16);
// Decrypt
$decrypted = openssl_decrypt($ciphertext, "aes-256-cbc", $key, OPENSSL_RAW_DATA, $iv);
print($decrypted . PHP_EOL); // I live in India
function EVP_BytesToKey($salt, $password) {
$bytes = '';
$last = '';
while(strlen($bytes) < 48) {
$last = hash('md5', $last . $password . $salt, true);
$bytes.= $last;
}
return $bytes;
}
?>

How to decrypt a string encrypted by SQL Server's EncryptByPassPhrase() in PHP?

I have an encrypted string and its key, which is created with SQL Server using "EncryptByPassPhrase", how can i decrypt it in PHP?
I have read the documentation of "EncryptByPassPhrase" which states that this is Triple DES encryption of 128 Length. I tried 3DES decryption of PHP but it is not returning the expected output.
Encryption in MS SQL is done with
declare #encrypt varbinary(200)
select #encrypt = EncryptByPassPhrase('key', 'taskseq=10000&amt=200.5' )
select #encrypt
I am decrypting it in PHP as following:
function decryptECB($encrypted, $key) {
$iv_size = mcrypt_get_iv_size(MCRYPT_3DES, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
// decrypting
$stringText = mcrypt_decrypt(MCRYPT_3DES, $key, $encrypted,
MCRYPT_MODE_ECB, $iv);
return $stringText;
}
I took the liberty of translating this Stack Overflow answer into PHP.
This is the result:
<?php
// SQL Server's DecryptByPassphrase translated into PHP.
function decrypt(string $data, string $password): ?string {
// SQL Server <2017 uses SHA1 for the key and the DES-EDE-CBC crypto algorithm
// whereas SQL Server >= 2017 uses SHA256 and AES-256-CBC.
// Version 1 is the SHA1 + DES-EDE-CBC version, Version 2 is the AES-256-CBC version.
// Version is stored in the first four bytes as a little endian int32.
$version_bytes = substr($data, 0, 4);
$version = unpack('V', $version_bytes)[1];
// Password must be converted to the UTF-16LE encoding.
$passwordUtf16 = mb_convert_encoding($password, 'UTF-16LE');
if ($version === 1) {
// Key is hashed using SHA1, The first 16 bytes of the hash are used.
$key = substr(hash('sha1', $passwordUtf16, true), 0, 16);
$method = 'des-ede-cbc';
$options = OPENSSL_RAW_DATA;
$iv = substr($data, 4, 8); // initialization vector of 8 bytes
$encrypted_data = substr($data, 12); // actual encrypted data
} else if ($version === 2) {
// Key is hashed using sha256. Key length is always 32 bytes.
$key = hash('sha256', $passwordUtf16, true);
$method = 'aes-256-cbc';
$options = OPENSSL_RAW_DATA;
$iv = substr($data, 4, 16); // iv of 16 bytes
$encrypted_data = substr($data, 20);
} else {
throw new \InvalidArgumentException('Invalid version');
}
$decrypted = openssl_decrypt($encrypted_data, $method, $key, $options, $iv);
if ($decrypted === false) {
return null;
}
// First 8 bytes contain the magic number 0xbaadf00d and the length
// of the decrypted data
$decrypted = substr($decrypted, 8);
// UTF-16 encoding should be converted to UTF-8. Note that
// there might be a better way to accomplish this.
$isUtf16 = strpos($decrypted, 0) !== false;
if ($isUtf16) {
return mb_convert_encoding($decrypted, 'UTF-8', 'UTF-16LE');
}
return $decrypted;
}
// A version 1 encrypted string. Taken directly from the Stack Overflow answer linked above
$s = '010000007854E155CEE338D5E34808BA95367D506B97C63FB5114DD4CE687FE457C1B5D5';
$password = 'banana';
$bin = hex2bin($s);
$d = decrypt($bin, $password);
var_dump($d); // string(6) "turkey"
// A version 2 encrypted string. Taken directly from the Stack Overflow answer linked above
$s = '02000000266AD4F387FA9474E825B013B0232E73A398A5F72B79BC90D63BD1E45AE3AA5518828D187125BECC285D55FA7CAFED61';
$password = 'Radames';
$bin = hex2bin($s);
$d = decrypt($bin, $password);
var_dump($d); // string(16) "LetTheSunShining"
Sidenote: mcrypt is deprecated as it has been abandoned for over a decade.
EncryptByPassPhrase() uses a proprietary format that don't seem to have readily available documentation. Best bet for decrypt is DecryptByPassPhrase().
The purpose of this proprietary format is to be used in the database layer of your application - not cross application / network / languages.
If you are dead set of using this format (which i would recommend not to), you would need to obtain the specification of this format, including what kind of key deviation functions are used to turn passwords into actual encryption keys etc.
When you have this specification, you would then have to implement this on your own.
Use the below in query
DECRYPTBYPASSPHRASE('key', [field] )
Reference

HMAC-SHA-256 in PHP

I have to build an authorization hash from this string:
kki98hkl-u5d0-w96i-62dp-xpmr6xlvfnjz:20151110171858:b2c13532-3416-47d9-8592-a541c208f755:hKSeRD98BHngrNa51Q2IgAXtoZ8oYebgY4vQHEYjlmzN9KSbAVTRvQkUPsjOGu4F
This secret is used for a HMAC hash function:
LRH9CAkNs-zoU3hxHbrtY0CUUcmqzibPeN7x6-vwNWQ=
The authorization hash I have to generate is this:
P-WgZ8CqV51aI-3TncZj5CpSZh98PjZTYxrvxkmQYmI=
There are some things to take care of:
The signature have to be built with HMAC-SHA-256 as specified in RFC 2104.
The signature have to be encoded with Base64 URL-compatible as specified in RFC 4648 Section 5 (Safe alphabet).
There is also some pseudo-code given for the generation:
Signatur(Request) = new String(encodeBase64URLCompatible(HMAC-SHA-256(getBytes(Z, "UTF-8"), decodeBase64URLCompatible(getBytes(S, "UTF-8")))), "UTF-8")
I tried various things in PHP but have not found the correct algorithm yet. This is the code I have now:
if(!function_exists('base64url_encode')){
function base64url_encode($data) {
$data = str_replace(array('+', '/'), array('-', '_'), base64_encode($data));
return $data;
}
}
$str = "kki98hkl-u5d0-w96i-62dp-xpmr6xlvfnjz:20151110171858:b2c13532-3416-47d9-8592-a541c208f755:hKSeRD98BHngrNa51Q2IgAXtoZ8oYebgY4vQHEYjlmzN9KSbAVTRvQkUPsjOGu4F";
$sec = "LRH9CAkNs-zoU3hxHbrtY0CUUcmqzibPeN7x6-vwNWQ=";
$signature = mhash(MHASH_SHA256, $str, $sec);
$signature = base64url_encode($signature);
if($signature != "P-WgZ8CqV51aI-3TncZj5CpSZh98PjZTYxrvxkmQYmI=")
echo "wrong: $signature";
else
echo "correct";
It gives this signature:
K9lw3V-k5gOedmVwmO5vC7cOn82JSEXsNguozCAOU2c=
As you can see, the length of 44 characters is correct. Please help me with finding the mistake, this simple problem takes me hours yet and there is no solution.
There's a couple of things to notice:
Your key is base64-encoded. You have to decode it before you could use it with php functions. That's the most important thing you have missed.
Mhash is obsoleted by Hash extension.
You want output to be encoded in a custom fashion, so it follows that you need raw output from hmac function (php, by default, will hex-encode it).
So, using hash extension this becomes:
$key = "LRH9CAkNs-zoU3hxHbrtY0CUUcmqzibPeN7x6-vwNWQ=";
$str = "kki98hkl-u5d0-w96i-62dp-xpmr6xlvfnjz:20151110171858:b2c13532-3416-47d9-8592-a541c208f755:hKSeRD98BHngrNa51Q2IgAXtoZ8oYebgY4vQHEYjlmzN9KSbAVTRvQkUPsjOGu4F";
function encode($data) {
return str_replace(['+', '/'], ['-', '_'], base64_encode($data));
}
function decode($data) {
return base64_decode(str_replace(['-', '_'], ['+', '/'], $data));
}
$binaryKey = decode($key);
var_dump(encode(hash_hmac("sha256", $str, $binaryKey, true)));
Outputs:
string(44) "P-WgZ8CqV51aI-3TncZj5CpSZh98PjZTYxrvxkmQYmI="
Simply use hash_hmac() function available in PHP.
Example :
hash_hmac('sha256', $string, $secret);
Doc here : http://php.net/manual/fr/function.hash-hmac.php

SCRAM-SHA-1 auth by PHP

help me please with implementing semantic code from manual about SCRAM-SHA-1 authorization in XMPP server. So, we got:
clientFinalMessageBare = "c=biws,r=" .. serverNonce
saltedPassword = PBKDF2-SHA-1(normalizedPassword, salt, i)
clientKey = HMAC-SHA-1(saltedPassword, "Client Key")
storedKey = SHA-1(clientKey)
authMessage = initialMessage .. "," .. serverFirstMessage .. "," .. clientFinalMessageBare
clientSignature = HMAC-SHA-1(storedKey, authMessage)
clientProof = clientKey XOR clientSignature
clientFinalMessage = clientFinalMessageBare .. ",p=" .. base64(clientProof)
My PHP code:
$cfmb = 'c=biws,r='.$salt;
$saltpass = hash_pbkdf2('sha1', 'IDoMdGuFE9S0', $ps, $iter);
//hash_pbkdf2('sha1', 'IDoMdGuFE9S0', $salt, $iter, 0, true); maybe like that???
$ckey = hash_hmac('sha1', $saltpass, 'Client Key');
$sckey = sha1($ckey);
$authmsg = $im.','.$chal.','.$cfmb;
$csign = hash_hmac('sha1', $sckey, $authmsg);
$cproof = bin2hex(pack('H*',$ckey) ^ pack('H*',$csign));
$cfm = $cfmb.',p='.base64_encode($cproof);
Somewhere error (maybe ALL big error ;)) and I very need your help for correcting my code, maybe I am use wrong functions, or arguments in wrong positions? Because result - fail, server sends me that:
"The response provided by the client doesn't match the one we calculated."
PS: Sorry for my bad English ;)
First of all, it's very confusing to use $salt for the serverNonce and $ps for the salt.
But more importantly, you should take some care to keep track of whether the functions you use return binary data or hexadecimal encoded strings. hash_pbkdf2, sha1 and hash_hmac by default return hexadecimal encoded strings. You call pack('H*', ...) to decode them for the $cproof, but not when you calculate $ckey and $csign.
A much easier way is to compute binary data directly, by always passing $raw_data = TRUE:
$cfmb = 'c=biws,r='.$salt;
$saltpass = hash_pbkdf2('sha1', 'IDoMdGuFE9S0', $ps, $iter, 0, TRUE);
$ckey = hash_hmac('sha1', 'Client Key', $saltpass, TRUE);
$sckey = sha1($ckey, TRUE);
$authmsg = $im.','.$chal.','.$cfmb;
$csign = hash_hmac('sha1', $authmsg, $sckey, TRUE);
$cproof = $ckey ^ $csign;
$cfm = $cfmb.',p='.base64_encode($cproof);
Also, your hash_hmac calls were the wrong way around: first the data, then the key.

C# to PHP AES Decryption

Hi i have c# sample of code but i can't turn it to php.
İ tried to rewrite code but i can't do it.
In my project other server encrypts data with c# and i have to decrypt it using PHP.
I have password and salt value.
Here is C# code includes encrypt and decrypt function.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.IO;
namespace EncryptionSample
{
public static class CipherUtility
{
public static string Encrypt(string plainText, string password, string salt)
{
if (plainText == null || plainText.Length <= 0)
{
throw new ArgumentNullException("plainText");
}
if (String.IsNullOrEmpty(password))
{
throw new ArgumentNullException("password");
}
if (String.IsNullOrEmpty(salt))
{
throw new ArgumentNullException("salt");
}
byte[] encrypted;
byte[] saltBytes = Encoding.UTF8.GetBytes(salt);
using (Rfc2898DeriveBytes derivedBytes = new Rfc2898DeriveBytes(password, saltBytes))
{
using (AesCryptoServiceProvider aesAlg = new AesCryptoServiceProvider())
{
aesAlg.Key = derivedBytes.GetBytes(32);
aesAlg.IV = derivedBytes.GetBytes(16);
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
swEncrypt.Write(plainText);
}
encrypted = msEncrypt.ToArray();
}
}
}
}
return Convert.ToBase64String(encrypted);
}
public static string Decrypt(string cipherValue, string password, string salt)
{
byte[] cipherText = Convert.FromBase64String(cipherValue);
if (cipherText == null
|| cipherText.Length <= 0)
{
throw new ArgumentNullException("cipherValue");
}
if (String.IsNullOrWhiteSpace(password))
{
throw new ArgumentNullException("password");
}
if (String.IsNullOrWhiteSpace(password))
{
throw new ArgumentNullException("salt");
}
string plaintext = null;
byte[] saltBytes = Encoding.UTF8.GetBytes(salt);
using (Rfc2898DeriveBytes deriveBytes = new Rfc2898DeriveBytes(password, saltBytes))
{
using (AesCryptoServiceProvider aesAlg = new AesCryptoServiceProvider())
{
aesAlg.Key = deriveBytes.GetBytes(32);
aesAlg.IV = deriveBytes.GetBytes(16);
ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
using (MemoryStream msDecrypt = new MemoryStream(cipherText))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
plaintext = srDecrypt.ReadToEnd();
}
}
}
}
}
return plaintext;
}
}
}
My php code is here but i think i am totally wrong.
function decrypt($encrypted, $password, $salt) {
// Build a 256-bit $key which is a SHA256 hash of $salt and $password.
$key = hash('SHA256', $salt . $password, true);
// Retrieve $iv which is the first 22 characters plus ==, base64_decoded.
$iv = base64_decode(substr($encrypted, 0, 22) . '==');
// print_r($iv);die();
// Remove $iv from $encrypted.
$encrypted = substr($encrypted, 22);
//print_r($encrypted);die();
// Decrypt the data. rtrim won't corrupt the data because the last 32 characters are the md5 hash; thus any \0 character has to be padding.
$decrypted = rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, base64_decode($encrypted), MCRYPT_MODE_CBC, $iv), "\0\4");
// Retrieve $hash which is the last 32 characters of $decrypted.
$hash = substr($decrypted, -32);
// Remove the last 32 characters from $decrypted.
$decrypted = substr($decrypted, 0, -32);
// Integrity check. If this fails, either the data is corrupted, or the password/salt was incorrect.
if (md5($decrypted) != $hash) return false;
return $decrypted;
}
On first glance, I can see that your keys are going to be different. Your C# code generates your key using Rfc2898DeriveBytes, which is a key generator based on PBKDF2. Your php code, on the other hand, is using SHA256 to generate the key. These are going to return different values. With different keys, you are done before you even start.
Also, I don't know that CryptoStream is going to append the IV on the beginning of the ciphertext, nor a MAC value at the end of the ciphertext. Stripping out that text will make your plaintext garbled if it will decrypt at all. Note in the C# decryption method you derive the IV based on the key derivation object (which is not smart, since the same key will generate the same IV for every message, which reduces the security of the first block of your ciphertext, but that's an entirely separate issue).
Do you know for a fact that the C# server is generating the ciphertext exactly the same as your code sample? You need to know the exact parameters of the cryptography being used on the server side
I would suggest that you actually try to research and understand the format of the ciphertext that C# is going to emit, then figure out how to consume that in PHP. Cryptography can be very tricky to work with, especially when trying to integrate heterogenous systems.
I'm no crypto expert, but I think you might find phpseclib useful.

Categories