I'm using the code below to create salt in PHP.
$length=100;
$bool=true;
$salt=openssl_random_pseudo_bytes ( $length, $bool );
when I echo the value it display this value (one instance)
×YOEú ßPŽžJZýr³€žYM½N±~ÄŽ¼D‚ÝÆFÕ`O$I$îÇF üKøäƒþ¥_5ûù„Ð… Ïq®ùä. ³æ¤ljî¬}Të‚´ùB#3U96
but on storing in the database it gets changed to
Ñx€gþ‚΀³€Œ¯´N§Å·.:gºÈá•ïjÇÖ…áf6uIùYbx}û€·iÀ0èFšDö¼6¥qzMéÁi‡±
The field is using latin1_swedish_ci encoding.
signup script
script containing hashing function, salt generator
table structure
openssl_random_pseudo_bytes returns a raw byte stream, as the name suggests. It doesn't return "characters" which are intended to be readable. How those bytes are being displayed as characters depends entirely on the interpreting party; here quite apparently MySQL is interpreting the bytes in a different encoding than the browser/console where you're displaying it from PHP.
If you're storing those bytes in MySQL, you should be storing them in a BLOB type column, because they're a meaningless blob.
If you want to treat them as characters, you need to encode them. The best is probably a simple bin2hex($salt), which encodes the binary data to hex. Alternatively use base64_encode.
Secondarily, the second parameter to openssl_random_pseudo_bytes is not supposed to be passed as a boolean. It's a pass-by-reference variable which allows you to check after the fact whether the bytes were generated with strong security:
$salt = openssl_random_pseudo_bytes($length, $strong);
if ($strong) {
// $salt is strong
} else {
// $salt is weak
}
Related
I am currently working on translating an encryption algorithm from PHP to Typescript, to use in a very specific API that requires the posted data to be encrypted with the API key and Secret. Here is the provided example of how to correctly encrypt data in PHP for use with the API (the way of implementing the key and IV can't be changed):
$iv = substr(hash("SHA256", $this->ApiKey, true), 0, 16);
$key = md5($this->ApiSecret);
$output = openssl_encrypt($Data, "AES-256-CBC", $key, OPENSSL_RAW_DATA, $iv);
$completedEncryption = $this->base64Url_Encode($output);
return $completedEncryption;
In the above code, the only thing the base64Url_Encode function does is convert the binary data to a valid Base64URL string.
And now the code as I have implemented it inside Typescript:
import { createHash, createCipheriv } from 'node:crypto'
const secretIV = createHash('sha256').update(this.ApiKey).digest().subarray(0, 16)
// Generate key
/*
Because the OpenSSL function in PHP automatically pads the string with /null chars,
do the same inside NodeJS, so that CreateCipherIV can accept it as a 32-byte key,
instead of a 16-byte one.
*/
const md5 = createHash('md5').update(this.ApiSecret).digest()
const key = Buffer.alloc(32)
key.set(md5, 0)
// Create Cipher
const cipher = createCipheriv('aes-256-cbc', key, secretIV)
let encrypted = cipher.update(data, 'utf8', 'binary');
encrypted += cipher.final('binary');
// Return base64URL string
return Buffer.from(encrypted).toString('base64url');
The above Typescript code only does NOT give the same output as the PHP code given earlier. I have looked into the original OpenSSL code, made sure that the padding algorithms are matching (pcks5 and pcks7) and checked if every input Buffer had the same byte length as the input inside PHP. I am currently thinking if it is some kind of binary malform that is causing the data to change inside Javascript?
I hope some expert can help me out with this question. Maybe I have overlooked something. Thanks in advance.
The stupidity is in the md5 function in PHP, which defaults to hexadecimal output instead of binary output:
md5(string $string, bool $binary = false): string
This is also why the code doesn't complain about the key (constructed from the MD5 hash) is being too small, it is fed 32 bytes after ASCII or UTF8 encoding, instead of the 16 bytes you'd use for AES-128.
Apparently it is using lowercase encoding, although not even that has been specified. You can indicate the encoding for NodeJS as well, see the documentation of the digest method. It also seems to be using lowercase, although I cannot directly find the exact specification of the encoding either.
Once you have completed your assignment, please try and remove the code ASAP, as you should never calculate the IV from the key; they key and IV combination should be unique, so the above code is not IND-CPA secure if the key is reused.
In case you are wondering why it is so stupid: the output of MD5 has been specified in standards, and is binary. Furthermore, it is impossible from the function to see what it is doing, you have to lookup the code. It will also work very badly if you're doing a compare; even if you are comparing strings then it is easy to use upper instead of lowercase (and both are equally valid, uppercase hex is actually easier to read for humans as we focus on the top part of letters more for some reason or other).
Basically it takes the principle of least surprise and tosses it out of the window. The encoding of the output could be made optimal instead, the NodeJS implementation does this correctly.
I have a big problem, I have to write a php login page using an db where password are stored as PBKDF2 (with another perl script). When I get the password with a query I read this:
sha256:1000:2SeBDP88w4bqKbJaCJNpNuRHQhUM96X1:jgh/SZtmRWH5iDIwtXyFLtuuDf7YE+7HQEJZ4KFFNAg= (I know this password but I cannot regenerate it in php).
I tried with this script (get from php.net):
$password = "qqqqq";
$iterations = 1000;
$salt = "2SeBDP88w4bqKbJaCJNpNuRHQhUM96X1";
$hash = hash_pbkdf2("sha256", $password, $salt, $iterations, 20);
echo $hash // result a2ba3349194c38f828af
but the pass generate is a2ba3349194c38f828af and not jgh/SZtmRWH5iDIwtXyFLtuuDf7YE+7HQEJZ4KFFNAg=
who wrote the perl script that store these password told me "The passwords are getting encoded though one-way hashing scheme named 'PBKDF2'"
Some ideas? Someone know where I'm wrong?
jgh/SZtmRWH5iDIwtXyFLtuuDf7YE+7HQEJZ4KFFNAg= is Base64 (the = on the end is a dead giveaway, though Base64 exists without a trailing =).
Converted to hex, the value is 8E087F499B664561F9883230B57C852EDBAE0DFED813EEC7404259E0A1453408
This is still not your answer, but now we can easily see it is 64 hex characters => 32 bytes.
You asked for 20 bytes.
It also looks like your salt input is base64, but the function you're passing it to expects ... whatever the output of base64_decode is.
So, you need to consistently process the base64-encoded data. And then you'll need to make sure that your hash algorithm, iteration count, and output byte count all match what the perl script says.
try this
$hash = strtoupper(bin2hex($hash));
I am encrypting my string using the following php code.
$encryption_key = "mickey";
$value = "ddd";
function encrypt($value)
{
global $encryption_key;
if(function_exists("mcrypt_ecb"))
{
return mcrypt_ecb(MCRYPT_DES, $encryption_key, $value, MCRYPT_ENCRYPT);
}
else return $value;
}
I am storing the encrypted value in the database. This stores "?P??" in the database in column "Encrypt"
However when I run this query
select DES_DECRYPT(Encrypt,"mickey") from test_encrypt
It gives me
3f503f1b3f1b20
How can I retrieve my original $value from the sql query?
Thanks
Sounds like a character set issue to me.
Likely you're storing the binary value into a VARCHAR (or other non-binary type), and a character set conversion is being applied, and some of the bytes aren't valid "characters" in the characterset for the column, either that, or, the stored values are being translated on retrieval, and "unknown" encodings are being replaced with question marks.
As a test, you could try using the MySQL HEX() and UNHEX() functions on the binary values, though that will effectively double the size of the strings. There shouldn't be any characterset problem with character strings representing hexadecimal digits. (I don't think there are native base-64 encoding/decoding functions in MySQL.)
Alternatively, you might try storing the encrypted values in a column with a datatype that supports binary data, with no characterset translation, e.g. VARBINARY rather than VARCHAR.
Trying to understand last parameter raw_data of php's hash method.
Documentation states raw_data:
When set to TRUE, outputs raw binary data. FALSE outputs lowercase
hexits.
What scenarios would this be used?
When would we want binary data vs hexits?
You can use binary mode to change the encoding from hexits to something else, e.g.
echo base64_encode(hash('md5', 'hello', true));
// XUFAKrxLKna5cZ2REBfFkg==
Additionally, storing the raw data in your database takes up less space; you do compromise on readability though, it's not practical to copy/paste from your database anymore.
If you set this variable true you will get binary result. Such result is smaller but there are appliances where you cannot use it (for instance, you cannot pass it in url) - in such cases you can set raw_data=false to get just a string where every byte of hash is represented by two hexadecimal numbers (so byte 10111001 will be represented as B5). This representation can be easily converted back to binary sequence and can be used everywhere you can use regular string but will be much larger.
I'm wanting to store hashed passwords in MySQL, I'm using PHP:
<?php
$salt = '!£$%^&*()#';
$username = 'abc';
$password = '123';
$hash = hash('sha1', $username . $salt . $password, true);
?>
The true parameter in hash() will return the value as raw binary data. But I don't understand what this means exactly. How should it be correctly stored in MySQL?
I found the solution.
Normal (hexed) hashes of sha1() are always CHAR(40) in length. When you return a hash as raw binary data in php, it will return a string as CHAR(20), saving 50% database space yet representing the exact same value. This is because 2 characters of hex can be compressed into 1 character, thus halving it the space needed.
So store the password as CHAR(20) and use the *_bin collation.
The last parameter of the hash() function indicates how the hash value is returned:
Either as raw binary data. This way
you get the direct output of the
specific hash function you're using, in this case sha1.
Or as a string containing a hexadecimal representation of the same raw binary data.
They are both the same and differ only in their representation. Unless you have a good reason, I would suggest that you use the hexadecimal representation and store it as a string in your database. This way it is much easier to debug problems, since you could easily print out the hexadecimal hash value.
If you do want to store a raw binary string in MySQL, declare the column BINARY(16) if it's of a known fixed length of 16 bytes, VARBINARY(32) if it's of variable length up to 32 bytes, or one of the BLOB types for binary fields that potentially get very long (e.g., BLOB up to 64K, LONGBLOB up to 4G).
The "normal" way of doing this, AFAIK, is to use the addslashes() function.
e.g.:
$hash = hash('sha1', $username . $salt . $password, true);
$query_safe_hash = addslashes($hash);
$query_safe_username = addslashes($username);
$query = "INSERT INTO DBTable(username, password) VALUES ('$query_safe_username', '$query_safe_hash')";
mysql_query($query) or die("Failed to store credentials!");
Side note: from a crypto best practices standpoint, the salt should be a known length, should be generated randomly, and prepended to your hash before being stored to the database. Something like
$salt = generate_random_salt();
$query_safe_hash = addslashes($salt) . addslashes(hash('sha1', $salt . $username . $password, true);
Then to verify the user's credentials, you retrieve the stored hash, remove the slashes, and strip the known salt length from the beginning of the stored hash and use the same salt to generate a hash of the provided credentials, then compare. This helps harden your hash algo against various cryptanalysis attacks (in particular, differential cryptanalysis).