Questions about password security in PHP/MySQL - php

function password_encrypt($password) {
$hash_format = "$2y$10$"; // Tells PHP to use Blowfish with a "cost" of 10
$salt_length = 22; // Blowfish salts should be 22-characters or more
$salt = generate_salt($salt_length);
$format_and_salt = $hash_format . $salt;
$hash = crypt($password, $format_and_salt);
return $hash;
}
function generate_salt($length) {
// Not 100% unique, not 100% random, but good enough for a salt
// MD5 returns 32 characters
$unique_random_string = md5(uniqid(mt_rand(), true));
// Valid characters for a salt are [a-zA-Z0-9./]
$base64_string = base64_encode($unique_random_string);
// But not '+' which is valid in base64 encoding
$modified_base64_string = str_replace('+', '.', $base64_string);
// Truncate string to the correct length
$salt = substr($modified_base64_string, 0, $length);
return $salt;
}
Do you guys thnk this is secure? What could have been done differently? What's maybe easier to use to secure a password and hash it?

Blowfish itself is already really secure. One thing: don't do too much hashing etc. to generate a salt. Also, why not make it easier and use password_hash?
http://php.net/manual/en/function.password-hash.php
Example:
echo password_hash("rasmuslerdorf", PASSWORD_BCRYPT)."\n";
And to check a password:
if (password_verify($password_nonhashed, $password_hashed)) {
You don't need to hash a salt too much with blowfish. Just use sha1 hash for a salt if you really don't want to use password_hash.
Good luck!

Related

PHP mcrypt to Python

I'm trying to convert a PHP encryption function to Python. For the sake of the example and making things easier, the iv and relevant data is preset.
$salt = sha1('12345'.'654321');
encrypt('12345678', 'cutekittens12345', $salt);
function encrypt($decrypted, $password, $salt)
{
// Build a 256-bit $key which is a SHA256 hash of $salt and $password.
$key = hash('SHA256', $salt . $password, true);
$hexkey = hash('SHA256', $salt . $password, false);
// Build $iv and $iv_base64. We use a block size of 128 bits (AES compliant) and CBC mode. (Note: ECB mode is inadequate as IV is not used.)
#$iv = mcrypt_create_iv(16, MCRYPT_RAND);
$iv = "0000000000000000";
if (strlen($iv_base64 = rtrim(base64_encode($iv), '=')) != 22)
return false;
// Encrypt $decrypted and an MD5 of $decrypted using $key. MD5 is fine to use here because it's just to verify successful decryption.
$concatdecrypted = ($decrypted . md5($decrypted));
$encrypted = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $concatdecrypted, MCRYPT_MODE_CBC, $iv));
// We're done!
return $iv_base64 . $encrypted;
}
Which results in
$salt: 6ed52d21d5cc15e76e9879675f4bd0dd51593652
$hexkey: 879522bb98c1f5fb16acd6bf3454b0d4e313e8b71e0aa3cdf5cbf91158dfde71
$iv_base64: MDAwMDAwMDAwMDAwMDAwMA
$concatdecrypted: 1234567825d55ad283aa400af464c76d713c07ad
$encrypted: C3CWJPt2gtmg+id1ySmSazMvvWC7cgDpovJ/tDN0GeuVv9Pf/9+9ZSG+wjl6qD5h
My cleanest attempt at recreating the function is here and:
from base64 import b64encode, b64decode
from Crypto.Cipher import AES
import hashlib
charid = "654321"
apikey = "12345"
vcode = "12345678"
password = "cutekittens12345"
salt = hashlib.sha1((apikey + charid).encode('utf-8')).hexdigest()
key = hashlib.sha256(salt + password).digest()
iv = "0000000000000000"
concatkey = vcode + hashlib.md5(vcode).hexdigest()
AES.key_size=128
encryptor=AES.new(key=key,mode=AES.MODE_CBC,IV=iv)
encoded = encryptor.encrypt(concatkey_pad)
encoded = b64encode(encoded)
print 'Encrypted string:', encoded
I can recreate everything except the final result. I've tried a combination of unhex to see if it's something to do with passing raw inputs, but none of the $encrypted results have been a match.
Any help would be appreciated thank you.
I tried M2Crypto, Crypto.Cipher and pyDes. After reading 'Lessons learned implementing AES in PHP using Mcrypt' I had success with Crypto.Cipher after padding the string with ascii 0's to bypass the multiples of 16 issue and the first half of the encoded string was a match to my php function.
concatkey_pad = concatkey.ljust(48, '\0')
AES.key_size=128
encryptor=AES.new(key=key,mode=AES.MODE_CBC,IV=iv)
encoded = encryptor.encrypt(concatkey_pad)
encoded = b64encode(encoded)
print 'Encrypted string:', encoded
Padding concatkey with binary 0's using ljust generated the correct encoded value.

Security of generating keys for AES Encryption

I found this following code while researching about AES encryption on the internet. In this code I found that the key and the iv are generated using hash function and uses sha256. I would like to know whether this method is safe for encryption of text using PHP.
The code which I found online is given below,
<?php
function encrypt_decrypt($action, $string) {
$output = false;
$encrypt_method = "AES-256-CBC";
$secret_key = 'This is my secret key';
$secret_iv = 'This is my secret iv';
// hash
$key = hash('sha256', $secret_key);
// iv - encrypt method AES-256-CBC expects 16 bytes - else you will get a warning
$iv = substr(hash('sha256', $secret_iv), 0, 16);
if ( $action == 'encrypt' ) {
$output = openssl_encrypt($string, $encrypt_method, $key, 0, $iv);
$output = base64_encode($output);
} else if( $action == 'decrypt' ) {
$output = openssl_decrypt(base64_decode($string), $encrypt_method, $key, 0, $iv);
}
return $output;
}
$plain_txt = "This is my plain text";
echo "Plain Text =" .$plain_txt. "\n";
$encrypted_txt = encrypt_decrypt('encrypt', $plain_txt);
echo "Encrypted Text = " .$encrypted_txt. "\n";
$decrypted_txt = encrypt_decrypt('decrypt', $encrypted_txt);
echo "Decrypted Text =" .$decrypted_txt. "\n";
if ( $plain_txt === $decrypted_txt ) echo "SUCCESS";
else echo "FAILED";
echo "\n";
?>
No it is not a secure method to derive an encryption key from a password.
Just using a hash function is not sufficient and just adding a salt does little to improve the security. Instead iterate over an HMAC with a random salt for about a 100ms duration and save the salt with the hash. Use a function such as PBKDF2, Rfc2898DeriveBytes, Bcrypt or similar functions. The point is to make the attacker spend a lot of time finding passwords by brute force.
The generally accepted method is PBKDF2 (Password Key Derivation Function 2), sometimes referred to as Rfc2898DeriveBytes in some implementations.
Note: One generally accepted way to handle the IV is to prefix the encrypted message with the IV for use in decryption. The IV does not need to be secret, it does need to be different for each encryption with the same key, this is generally achieved by using a random byte array from a CSPRNG (Cryptographically Secure PseudoRandom Number Generator).
The IV isn't a secret, but should be unique to make two strings encrypted with the same password have different encrypted values. So this is a bad way to generate it. Use http://php.net/manual/kr/function.openssl-random-pseudo-bytes.php or a similar function to generate an unique IV for each time you encrypt some data and store the IV with the data.
#zaph have already commented on the issues with how the key is derived from the password

How to encrypt with openssl_encrypt using AES and salt in place of Base64

Currently I am using openssl_encrypt to encrypt the data and it return base64 value. I have to use AES encryption with salt.
Can any one tell how to implement AES encryption with salt?
Here is the code I use:
function encrypt_decrypt($action, $string)
{
$output = false;
$encrypt_method = "AES-256-CBC";
$secret_key = 'This is my secret key';
$secret_iv = 'This is my secret iv';
// hash
$key = hash('sha256', $secret_key);
// iv - encrypt method AES-256-CBC expects 16 bytes
$iv = substr(hash('sha256', $secret_iv), 0, 16);
if ($action == 'encrypt') {
$output = openssl_encrypt($string, $encrypt_method, $key, 0, $iv);
$output = base64_encode($output);
} else if ($action == 'decrypt') {
$output = openssl_decrypt(base64_decode($string), $encrypt_method, $key, 0, $iv);
}
return $output;
}
Always use tested libraries for such purposes. Your encryption is vulnerable and completely insecure because you're not using IV correctly.
Consider using defuse/php-encryption library and get rid of what you've done.
Why is what you've done wrong:
The same IV (initialization vector) is used.
There is no salt in encryption, it's called Initialization Vector and it must be different every time you encrypt - your IV is always the same
When encryption is done, you must deliver the encrypted data and IV - you are not returning IV with encryption result, only the result.
Currently, you are not doing what I outlined and that's why you should invest your time into using a library that takes care of encryption so you don't roll out your own, insecure implementation. I'm deliberately not posting the code required for this encryption to work from fear that someone will use it, instead of library that I linked. Always use libraries made by other people if you have no idea what you're doing.
In case someone needs it, and yes you can add salt using openssl_pbkdf2
$ciphertext_b64 = "";
$plaintext = "hello alice";
$password = "XhcwO1NNI1Xi43EVAtVPS1vknOGgsgIu16OmUAtzlGoHtaPYWwLqxxAEHcBbocWiaYYtBSlgvkn5rVBu";
$salt = "CK4OGOAtec0zgbNoCK4OGOAtec0zgbNoCK4OGOAtec0zgbNoCK4OGOAtec0zgbNo";
$iv = "LQjFLCU3sAVplBC3";
$iterations = 1000;
$keyLength = 32;
$prepared_key = openssl_pbkdf2($password, $salt, $keyLength, $iterations, "sha256");
$ciphertext_b64 = base64_encode(openssl_encrypt($plaintext,"AES-256-CBC", $prepared_key,OPENSSL_RAW_DATA, $iv));
echo $ciphertext_b64 . "<br/>";
$plaintext = openssl_decrypt(base64_decode($ciphertext_b64),"AES-256-CBC", $prepared_key,OPENSSL_RAW_DATA, $iv);
echo $plaintext . "<br/>";

How to stretch user password for encryption in PHP?

I thought I understood this, but my program won't decrypt and says the key is wrong, so I realize I need help. I thought the algorithm went:
Encryption:
Get user password P
Call hash_pbkdf2 to stretch P into a key K_pass_1
Call another key-stretching algorithm (I don't know which, haven't done this yet) to turn K_pass_1 into K_auth_1
Encrypt data with K_auth_1 K_pass_1
Decryption:
Get user password P
Call hash_pbkdf2 to stretch P into a key K_pass_2
as above
Decrypt data with K_auth_2 K_pass_2
Is that not correct? (Edit: It turns out it is, but that was too high level - my problem was more specific.)
Edit: Here is my code:
<?php
$GLOBALS['key_size'] = 32; // 256 bits
class WeakCryptographyException extends Exception {
public function errorMessage() {
$errorMsg = 'Error on line '.$this->getLine().' in '.$this->getFile()
.': <b>'.$this->getMessage().'</b>There was a problem creating strong pseudo-random bytes: system may be broken or old.';
return $errorMsg;
}
}
class FailedCryptographyException extends Exception {
public function errorMessage() {
$errorMsg = 'Error on line '.$this->getLine().' in '.$this->getFile()
.': <b>'.$this->getMessage().'</b>There was a problem with encryption/decryption.';
return $errorMsg;
}
}
class InvalidHashException extends Exception {
public function errorMessage() {
$errorMsg = 'Error on line '.$this->getLine().' in '.$this->getFile()
.': <b>'.$this->getMessage().'</b>Password verification failed.';
return $errorMsg;
}
}
function generate_key_from_password($password) {
$iterations = 100000;
$salt = openssl_random_pseudo_bytes($GLOBALS['key_size'], $strong);
$output = hash_pbkdf2("sha256", $password, $salt, $iterations, $GLOBALS['key_size'], true);
if ($strong) {
return $output;
} else {
// system did not use a cryptographically strong algorithm to produce the pseudo-random bytes
throw new WeakCryptographyException();
}
}
/** Encrypts the input data with Authenticated Encryption. We specifically use
* openssl_encrypt($data, 'AES-256-CBC', $encryption_key, OPENSSL_RAW_DATA, $iv), where $iv is a 256-bit nonce
* generated with openssl_random_pseudo_bytes. Then we hash the output with bcrypt and prepend the hash and iv to
* the ciphertext to create an 'authenticated ciphertext' that can be fed directly into the my_decrypt method.
*
* #param $data string; The data to be encrypted
* #param $encryption_key string; A 256-bit key (which PHP reads as a string of characters)
* #return string The authenticated ciphertext, with the format: $hash . $iv . $ciphertext
* #throws FailedCryptographyException If there are errors during encryption
* #throws WeakCryptographyException If the openssl_random_pseudo_bytes method fails to use a cryptographically strong
* algorithm to produce pseudo-random bytes.
*
* Note that in creating a hash for the ciphertext, we use bcrypt instead of sha2. In particular, the difference in lines is:
* bcrypt: password_hash($ciphertext, PASSWORD_DEFAULT);
* sha2: hash_hmac('sha256', $ciphertext, $auth_key, true);
*
* And we chose this despite the fact that sha2 is the only acceptable hashing algorithm for NIST, because:
* 1. bcrypt is also widely considered a cryptographically secure hashing algorithm.
* 2. sha2 is not supported by PHP 5's password_hash method and bcrypt is.
* 3. PHP's password_verify method uses a hash created by the password_hash method and compares hashes in a way that is
* safe against timing attacks. There is no known way to make this comparison for other hashes in PHP.
*/
function my_openssl_encrypt($data, $encryption_key) {
$iv_size = 16; // 128 bits to match the block size for AES
$iv = openssl_random_pseudo_bytes($iv_size, $strong);
if (!$strong) {
// system did not use a cryptographically strong algorithm to produce the bytes, don't consider them pseudo-random
throw new WeakCryptographyException();
}
$ciphertext = openssl_encrypt(
$data, // data
'AES-256-CBC', // cipher and mode
$encryption_key, // secret key
OPENSSL_RAW_DATA, // options: we use openssl padding
$iv // initialisation vector
);
if (!$ciphertext) {
$errormes = "";
while ($msg = openssl_error_string())
$errormes .= $msg . "<br />";
throw new FailedCryptographyException($errormes);
}
$auth = password_hash($ciphertext, PASSWORD_DEFAULT);
$auth_enc_name = $auth . $iv . $ciphertext;
return $auth_enc_name;
}
/** Decrypts a ciphertext encrypted with the method my_openssl_encrypt. First checks if the hash of the ciphertext
* matches the hash supplied in the input ciphertext, then decrypts the message if so. We specifically use
* openssl_decrypt($enc_name, 'AES-256-CBC', $encryption_key, OPENSSL_RAW_DATA, $iv), where $iv is a 256-bit nonce
* stored with the ciphertext.
*
* #param $ciphertext string; An authenticated ciphertext produced by my_openssl_encrypt
* #param $encryption_key string; A 256-bit key (which PHP reads as a string of characters)
* #return string The decrypted plaintext
* #throws FailedCryptographyException If there are errors during decryption
* #throws InvalidHashException If the password hash doesn't match the stored hash (this will almost always happen when
* any bits in the ciphertext are changed)
*/
function my_openssl_decrypt($ciphertext, $encryption_key) {
// verification
$auth = substr($ciphertext, 0, 60);
$iv = substr($ciphertext, 60, 16);
$enc_name = substr($ciphertext, 76);
if (password_verify($enc_name, $auth)) {
// perform decryption
$output = openssl_decrypt(
$enc_name,
'AES-256-CBC',
$encryption_key,
OPENSSL_RAW_DATA,
$iv
);
if (!$output) {
$errormes = "";
while ($msg = openssl_error_string())
$errormes .= $msg . "<br />";
throw new FailedCryptographyException($errormes);
}
return $output;
} else {
throw new InvalidHashException();
}
}
// Testing
function testEnc($message)
{
$encryption_key = generate_key_from_password("123456");
$auth_ciphertext = my_openssl_encrypt($message, $encryption_key);
$encryption_key = generate_key_from_password("123456");
$plaintext = my_openssl_decrypt($auth_ciphertext, $encryption_key);
echo "<p>Original message: " . $message .
"</p><p>Encryption (hex): " . bin2hex($auth_ciphertext) .
"</p><p>Plaintext: " . $plaintext . "</p>";
echo "<p>Bytes of input: " . (strlen($message) * 2) .
"<br />Bytes of ciphertext: " . (strlen($auth_ciphertext) * 2) . "</p>";
}
echo '<p>Test 1: ';
testEnc('Hello World');
echo '</p>';
The problem is in the function:
function generate_key_from_password($password)
the line:
$salt = openssl_random_pseudo_bytes($GLOBALS['key_size'], $strong);
The same salt needs to be used in order to derive the same key.
A salt needs to be created outside of the generate_key_from_password function and passed in and it needs to be the same salt for encryption and decryption. This is usually done by creating a salt in the encryption function, passing it into the PBKDF2 function and prepending the salt to the encrypted output in the same manner as the iv. Then the same salt is available to the decryption function.
It is little things like this that make using encryption securely difficult. See RNCryptor-php and RNCryptor-Spec for an example that also includes authentication, the iteration count and a version.
Skip step 3, it is not necessary.
Make sure the key and iv are exactly the correct length. Insure you are using CBC mode and PKCS#7 (or PKCS#5).
Use the optional 5th parameter $length for hash_pbkdf2():
var_dump(hash_pbkdf2("sha256", "foobar", "salty", 100));
// string(64) "5d808ee6539c7d0437e857a586c844900bf0969d1af70aea4c3848550d9038ab"
var_dump(hash_pbkdf2("sha256", "foobar", "salty", 100, 32));
// string(32) "5d808ee6539c7d0437e857a586c84490"
var_dump(hash_pbkdf2("sha256", "foobar", "salty", 100, 128));
// string(128) "5d808ee6539c7d0437e857a586c844900bf0969d1af70aea4c3848550d9038abb2853bf0cf24c9d010555394f958fa647a04b232f993c35916977b4ef5a57dcc"
You probably also want raw output, so read the above-linked doc page for the method and caveats to that.

how to decrypt the crypt("name")

how to decrypt the crypt("name")
You can't. From the documentation:
Note: There is no decrypt function, since crypt() uses a one-way algorithm.
Reading documentation helps ;)
crypt is one way hashing, you can't decrypt it.
If you want to compare it against another string you could crypt that too and then compare the two crypted strings.
crypt — One-way string hashing
use two way hashing
try with mcrypt
tutorial
Since crypt() produces a hash decrypting is not possible. If you need to guess the original data ("name") you can use a combination of a brute force algorithm and a huge dictionary.
I have find an example for mcrypt and create the two functions, for text or for binary files:
function MyDecrypt($input,$key){
/* Open module, and create IV */
$td = mcrypt_module_open('des', '', 'ecb', '');
$key = substr($key, 0, mcrypt_enc_get_key_size($td));
$iv_size = mcrypt_enc_get_iv_size($td);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
/* Initialize encryption handle */
if (mcrypt_generic_init($td, $key, $iv) != -1) {
/* 2 Reinitialize buffers for decryption */
mcrypt_generic_init($td, $key, $iv);
$p_t = mdecrypt_generic($td, $input);
return $p_t;
/* 3 Clean up */
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
}
} // end function Decrypt()
function MyCrypt($input, $key){
/* Open module, and create IV */
$td = mcrypt_module_open('des', '', 'ecb', '');
$key = substr($key, 0, mcrypt_enc_get_key_size($td));
$iv_size = mcrypt_enc_get_iv_size($td);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
/* Initialize encryption handle */
if (mcrypt_generic_init($td, $key, $iv) != -1) {
/* 1 Encrypt data */
$c_t = mcrypt_generic($td, $input);
mcrypt_generic_deinit($td);
return $c_t;
/* 3 Clean up */
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
}
}
For Example Crypt a string :
$original_text = "Hello world !";
$password = "abc123";
echo '<p>Original_text: '.$original_text.'</p>';
$crypted_text = MyCrypt($original_text,$password);
echo '<p>Crypted_text: '.$crypted_text.'</p>';
$decrypted_text= MyDecrypt($crypted_text,$password);
echo '<p>Decrypted_text: '.$decrypted_text.'</p>';
echo '<p>And if I try with a wrong password?</p>';
$wrong_decrypted_text= MyDecrypt($crypted_text,"wrong_pw");
echo '<p>Decrypted with wrong password: '.$wrong_decrypted_text.'</p>';
I hope helpful
You can't truly decrypt it, because there are (infinitely) many strings such that crypt($input) == crypt("name") -- but you can, via brute-force trial-and-error, find some of those strings.
If you know or suspect that the original string is a short dictionary word, and you find a short dictionary word that produces the same output, chances are you have "decrypted" the original string.
md5 and many weaker hash functions are attacked in this way routinely.
<?php
$hashed_password = crypt('mypassword'); // let the salt be automatically generated
/* You should pass the entire results of crypt() as the salt for comparing a
password, to avoid problems when different hashing algorithms are used. (As
it says above, standard DES-based password hashing uses a 2-character salt,
but MD5-based hashing uses 12.) */
if (hash_equals($hashed_password, crypt($user_input, $hashed_password))) {
echo "Password verified!";
}
?>

Categories