I'm trying to compare the hash of a password, but when I compare it I get hieroglyphs and can't match whether it's true or false.
analog php function:
/*
* Split hash into pieces
* ([0] = ??, [1] = master key, [2] = salt len, [3] = salt, [4] = iteration count, [5] = salt position, [6] = ??, [7] == ??, [8] == ??)
*/
$passHashArray = explode('$', $passHash);
/*
* Combine passphrase and salt
*/
$passToHash = $testPassphrase.hex2bin($passHashArray[3]);
/*
* Hash $passToHash $passHasArray[4] times with SHA512
*/
for($i = 0; $i < $passHashArray[4]; $i++){
$passToHash = hash('SHA512', $passToHash, true);
}
/*
* Get Key and Iv from $passToHash for final encryption
*/
$key = substr($passToHash, 0, 32);
$iv = substr($passToHash, 32, 16);
/*
* final passphrase encryption
*/
if(in_array('aes-256-cbc', openssl_get_cipher_methods())){
if(openssl_decrypt(hex2bin($passHashArray[1]), 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv)){
echo 'password correct';
}else{
echo 'decrypt failed';
}
}
go function:
var passwordHash = "64$718eadbd49dbee69e2b3e5f9659c361129cc07199d421d01892694477331ad8a$16$dce01545e0c918e7$76012$2$00$2$00"
var password = "12345678910"
func main() {
var passwordHashArray = strings.Split(passwordHash, "$")
/*
* Convert to hex to bin passphrase and salt
*/
hex2Bin, err := hex.DecodeString(passwordHashArray[3])
if err != nil {
log.Printf("error hex decode string password hash array: %s", err)
}
/*
* Combine passphrase and salt
*/
passwordToHash := strings.Join([]string{ password, string(hex2Bin)}, "")
/*
* Hash $passToHash $passHasArray[4] times with SHA512
*/
intVar, err := strconv.Atoi(passwordHashArray[4])
if err != nil {
log.Printf("error password hash array string to int: %s", err)
}
passwordToHashBinary := make([]byte, 32)
passwordToHashBinary = hashSHA512([]byte(passwordToHash))
for i := 1; i < intVar; i++ {
passwordToHashBinary = hashSHA512(passwordToHashBinary)
}
/*
* Get Key and Iv from $passToHash for final encryption
*/
var encKeyDecoded = make([]byte, 32)
copy(encKeyDecoded, passwordToHashBinary[:32])
var ivDecoded = make([]byte, 16)
copy(ivDecoded, passwordToHashBinary[32:48])
cipherTextDecoded, err := hex.DecodeString(passwordHashArray[1])
if err != nil {
log.Printf("error hex decode string password hash array: %s", err)
}
results, err := decrypt(cipherTextDecoded, encKeyDecoded, ivDecoded)
if err != nil {
log.Printf("error result decode password: %s", err)
}
log.Printf("%s", string(results))
log.Printf("%x", string(results))
}
func decrypt(cipherTextDecoded []byte, encKeyDecoded []byte, ivDecoded []byte) ([]byte, error) {
block, err := aes.NewCipher(encKeyDecoded)
if err != nil {
return nil, err
}
if len(cipherTextDecoded) < aes.BlockSize {
return nil, fmt.Errorf("ciphertext too short")
}
if len(cipherTextDecoded)%aes.BlockSize != 0 {
return nil, fmt.Errorf("ciphertext is not a multiple of the block size")
}
mode := cipher.NewCBCDecrypter(block, ivDecoded)
mode.CryptBlocks(cipherTextDecoded, cipherTextDecoded)
return cipherTextDecoded, nil
}
func hashSHA512(crypto []byte) []byte {
hash := sha512.New()
hash.Write(crypto)
sha := hash.Sum(nil)
return sha
}
it is worth noting that in php the password is displayed correctly, but on the go I get a line like:
���Pʎ&L�t→]��f�►►►►►►►►►►►►►►►►
First of all, I don't understand where it comes from:
►►►►►►►►►►►►►►►►
How can I check if a password is valid in golang?
if () {good} else {bad}
out php:
https://onecompiler.com/php/3xqvgkhbr
out go:
https://go.dev/play/p/HUxoD29fM4c
i never tried using AES on my site to store password but here's how i do it i'm using PDO prepared statement and bcrypt
$read_username = $pdo->prepare("SELECT * FROM users WHERE username = :username LIMIT 1");
$read_username->execute([':username' => $username]);
if ($read_username->rowCount() > === 1) {
$row = $write_account->fetch(PDO::FETCH_ASSOC);
$read_username = null; // close connection we already got what we need
$pdo = null; // close connection we already got what we need
$stored_hash = $row['password']; // bind the hash stored on db as $stored_hash
if (password_verify($password, $stored_hash)) { // compare user input to $stored_hash
$_SESSION['username'] = $username . bin2hex(random_bytes(12));
header('location: index.php');
die("ACCESS GRANTED!");
} else {
array_push($errors, "Incorrect password!");
}
} else {
array_push($errors, "Account does not exist!");
}
also your if else concern is easy it goes like this
$hotdog = 123;
if ($hotdog == 123) {
echo "hotdog";
} else {
echo "not hotdog";
}
Related
Recently I have been trying to implement my own security on a log in script I stumbled upon on the internet. After struggling of trying to learn how to make my own script to generate a salt for each user, I stumbled upon password_hash.
From what I understand (based off of the reading on this page), salt is already generated in the row when you use password_hash. Is this true?
Another question I had was, wouldn't it be smart to have 2 salts? One directly in the file and one in the DB? That way, if someone compromises your salt in the DB, you still have the one directly in the file? I read on here that storing salts is never a smart idea, but it always confused me what people meant by that.
Using password_hash is the recommended way to store passwords. Don't separate them to DB and files.
Let's say we have the following input:
$password = $_POST['password'];
You first hash the password by doing this:
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
Then see the output:
var_dump($hashed_password);
As you can see it's hashed. (I assume you did those steps).
Now you store this hashed password in your database, ensuring your password column is large enough to hold the hashed value (at least 60 characters or longer). When a user asks to log them in, you check the password input with this hash value in the database, by doing this:
// Query the database for username and password
// ...
if(password_verify($password, $hashed_password)) {
// If the password inputs matched the hashed password in the database
// Do something, you know... log them in.
}
// Else, Redirect them back to the login page.
Official Reference
Yes you understood it correctly, the function password_hash() will generate a salt on its own, and includes it in the resulting hash-value. Storing the salt in the database is absolutely correct, it does its job even if known.
// Hash a new password for storing in the database.
// The function automatically generates a cryptographically safe salt.
$hashToStoreInDb = password_hash($_POST['password'], PASSWORD_DEFAULT);
// Check if the hash of the entered login password, matches the stored hash.
// The salt and the cost factor will be extracted from $existingHashFromDb.
$isPasswordCorrect = password_verify($_POST['password'], $existingHashFromDb);
The second salt you mentioned (the one stored in a file), is actually a pepper or a server side key. If you add it before hashing (like the salt), then you add a pepper. There is a better way though, you could first calculate the hash, and afterwards encrypt (two-way) the hash with a server-side key. This gives you the possibility to change the key when necessary.
In contrast to the salt, this key should be kept secret. People often mix it up and try to hide the salt, but it is better to let the salt do its job and add the secret with a key.
Yes, it's true. Why do you doubt the php faq on the function? :)
The result of running password_hash() has has four parts:
the algorithm used
parameters
salt
actual password hash
So as you can see, the hash is a part of it.
Sure, you could have an additional salt for an added layer of security, but I honestly think that's overkill in a regular php application. The default bcrypt algorithm is good, and the optional blowfish one is arguably even better.
There is a distinct lack of discussion on backwards and forwards compatibility that is built in to PHP's password functions. Notably:
Backwards Compatibility: The password functions are essentially a well-written wrapper around crypt(), and are inherently backwards-compatible with crypt()-format hashes, even if they use obsolete and/or insecure hash algorithms.
Forwards Compatibilty: Inserting password_needs_rehash() and a bit of logic into your authentication workflow can keep you your hashes up to date with current and future algorithms with potentially zero future changes to the workflow. Note: Any string that does not match the specified algorithm will be flagged for needing a rehash, including non-crypt-compatible hashes.
Eg:
class FakeDB {
public function __call($name, $args) {
printf("%s::%s(%s)\n", __CLASS__, $name, json_encode($args));
return $this;
}
}
class MyAuth {
protected $dbh;
protected $fakeUsers = [
// old crypt-md5 format
1 => ['password' => '$1$AVbfJOzY$oIHHCHlD76Aw1xmjfTpm5.'],
// old salted md5 format
2 => ['password' => '3858f62230ac3c915f300c664312c63f', 'salt' => 'bar'],
// current bcrypt format
3 => ['password' => '$2y$10$3eUn9Rnf04DR.aj8R3WbHuBO9EdoceH9uKf6vMiD7tz766rMNOyTO']
];
public function __construct($dbh) {
$this->dbh = $dbh;
}
protected function getuser($id) {
// just pretend these are coming from the DB
return $this->fakeUsers[$id];
}
public function authUser($id, $password) {
$userInfo = $this->getUser($id);
// Do you have old, turbo-legacy, non-crypt hashes?
if( strpos( $userInfo['password'], '$' ) !== 0 ) {
printf("%s::legacy_hash\n", __METHOD__);
$res = $userInfo['password'] === md5($password . $userInfo['salt']);
} else {
printf("%s::password_verify\n", __METHOD__);
$res = password_verify($password, $userInfo['password']);
}
// once we've passed validation we can check if the hash needs updating.
if( $res && password_needs_rehash($userInfo['password'], PASSWORD_DEFAULT) ) {
printf("%s::rehash\n", __METHOD__);
$stmt = $this->dbh->prepare('UPDATE users SET pass = ? WHERE user_id = ?');
$stmt->execute([password_hash($password, PASSWORD_DEFAULT), $id]);
}
return $res;
}
}
$auth = new MyAuth(new FakeDB());
for( $i=1; $i<=3; $i++) {
var_dump($auth->authuser($i, 'foo'));
echo PHP_EOL;
}
Output:
MyAuth::authUser::password_verify
MyAuth::authUser::rehash
FakeDB::prepare(["UPDATE users SET pass = ? WHERE user_id = ?"])
FakeDB::execute([["$2y$10$zNjPwqQX\/RxjHiwkeUEzwOpkucNw49yN4jjiRY70viZpAx5x69kv.",1]])
bool(true)
MyAuth::authUser::legacy_hash
MyAuth::authUser::rehash
FakeDB::prepare(["UPDATE users SET pass = ? WHERE user_id = ?"])
FakeDB::execute([["$2y$10$VRTu4pgIkGUvilTDRTXYeOQSEYqe2GjsPoWvDUeYdV2x\/\/StjZYHu",2]])
bool(true)
MyAuth::authUser::password_verify
bool(true)
As a final note, given that you can only re-hash a user's password on login you should consider "sunsetting" insecure legacy hashes to protect your users. By this I mean that after a certain grace period you remove all insecure [eg: bare MD5/SHA/otherwise weak] hashes and have your users rely on your application's password reset mechanisms.
Class Password full code:
Class Password {
public function __construct() {}
/**
* Hash the password using the specified algorithm
*
* #param string $password The password to hash
* #param int $algo The algorithm to use (Defined by PASSWORD_* constants)
* #param array $options The options for the algorithm to use
*
* #return string|false The hashed password, or false on error.
*/
function password_hash($password, $algo, array $options = array()) {
if (!function_exists('crypt')) {
trigger_error("Crypt must be loaded for password_hash to function", E_USER_WARNING);
return null;
}
if (!is_string($password)) {
trigger_error("password_hash(): Password must be a string", E_USER_WARNING);
return null;
}
if (!is_int($algo)) {
trigger_error("password_hash() expects parameter 2 to be long, " . gettype($algo) . " given", E_USER_WARNING);
return null;
}
switch ($algo) {
case PASSWORD_BCRYPT :
// Note that this is a C constant, but not exposed to PHP, so we don't define it here.
$cost = 10;
if (isset($options['cost'])) {
$cost = $options['cost'];
if ($cost < 4 || $cost > 31) {
trigger_error(sprintf("password_hash(): Invalid bcrypt cost parameter specified: %d", $cost), E_USER_WARNING);
return null;
}
}
// The length of salt to generate
$raw_salt_len = 16;
// The length required in the final serialization
$required_salt_len = 22;
$hash_format = sprintf("$2y$%02d$", $cost);
break;
default :
trigger_error(sprintf("password_hash(): Unknown password hashing algorithm: %s", $algo), E_USER_WARNING);
return null;
}
if (isset($options['salt'])) {
switch (gettype($options['salt'])) {
case 'NULL' :
case 'boolean' :
case 'integer' :
case 'double' :
case 'string' :
$salt = (string)$options['salt'];
break;
case 'object' :
if (method_exists($options['salt'], '__tostring')) {
$salt = (string)$options['salt'];
break;
}
case 'array' :
case 'resource' :
default :
trigger_error('password_hash(): Non-string salt parameter supplied', E_USER_WARNING);
return null;
}
if (strlen($salt) < $required_salt_len) {
trigger_error(sprintf("password_hash(): Provided salt is too short: %d expecting %d", strlen($salt), $required_salt_len), E_USER_WARNING);
return null;
} elseif (0 == preg_match('#^[a-zA-Z0-9./]+$#D', $salt)) {
$salt = str_replace('+', '.', base64_encode($salt));
}
} else {
$salt = str_replace('+', '.', base64_encode($this->generate_entropy($required_salt_len)));
}
$salt = substr($salt, 0, $required_salt_len);
$hash = $hash_format . $salt;
$ret = crypt($password, $hash);
if (!is_string($ret) || strlen($ret) <= 13) {
return false;
}
return $ret;
}
/**
* Generates Entropy using the safest available method, falling back to less preferred methods depending on support
*
* #param int $bytes
*
* #return string Returns raw bytes
*/
function generate_entropy($bytes){
$buffer = '';
$buffer_valid = false;
if (function_exists('mcrypt_create_iv') && !defined('PHALANGER')) {
$buffer = mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM);
if ($buffer) {
$buffer_valid = true;
}
}
if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) {
$buffer = openssl_random_pseudo_bytes($bytes);
if ($buffer) {
$buffer_valid = true;
}
}
if (!$buffer_valid && is_readable('/dev/urandom')) {
$f = fopen('/dev/urandom', 'r');
$read = strlen($buffer);
while ($read < $bytes) {
$buffer .= fread($f, $bytes - $read);
$read = strlen($buffer);
}
fclose($f);
if ($read >= $bytes) {
$buffer_valid = true;
}
}
if (!$buffer_valid || strlen($buffer) < $bytes) {
$bl = strlen($buffer);
for ($i = 0; $i < $bytes; $i++) {
if ($i < $bl) {
$buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255));
} else {
$buffer .= chr(mt_rand(0, 255));
}
}
}
return $buffer;
}
/**
* Get information about the password hash. Returns an array of the information
* that was used to generate the password hash.
*
* array(
* 'algo' => 1,
* 'algoName' => 'bcrypt',
* 'options' => array(
* 'cost' => 10,
* ),
* )
*
* #param string $hash The password hash to extract info from
*
* #return array The array of information about the hash.
*/
function password_get_info($hash) {
$return = array('algo' => 0, 'algoName' => 'unknown', 'options' => array(), );
if (substr($hash, 0, 4) == '$2y$' && strlen($hash) == 60) {
$return['algo'] = PASSWORD_BCRYPT;
$return['algoName'] = 'bcrypt';
list($cost) = sscanf($hash, "$2y$%d$");
$return['options']['cost'] = $cost;
}
return $return;
}
/**
* Determine if the password hash needs to be rehashed according to the options provided
*
* If the answer is true, after validating the password using password_verify, rehash it.
*
* #param string $hash The hash to test
* #param int $algo The algorithm used for new password hashes
* #param array $options The options array passed to password_hash
*
* #return boolean True if the password needs to be rehashed.
*/
function password_needs_rehash($hash, $algo, array $options = array()) {
$info = password_get_info($hash);
if ($info['algo'] != $algo) {
return true;
}
switch ($algo) {
case PASSWORD_BCRYPT :
$cost = isset($options['cost']) ? $options['cost'] : 10;
if ($cost != $info['options']['cost']) {
return true;
}
break;
}
return false;
}
/**
* Verify a password against a hash using a timing attack resistant approach
*
* #param string $password The password to verify
* #param string $hash The hash to verify against
*
* #return boolean If the password matches the hash
*/
public function password_verify($password, $hash) {
if (!function_exists('crypt')) {
trigger_error("Crypt must be loaded for password_verify to function", E_USER_WARNING);
return false;
}
$ret = crypt($password, $hash);
if (!is_string($ret) || strlen($ret) != strlen($hash) || strlen($ret) <= 13) {
return false;
}
$status = 0;
for ($i = 0; $i < strlen($ret); $i++) {
$status |= (ord($ret[$i]) ^ ord($hash[$i]));
}
return $status === 0;
}
}
Recently I have been trying to implement my own security on a log in script I stumbled upon on the internet. After struggling of trying to learn how to make my own script to generate a salt for each user, I stumbled upon password_hash.
From what I understand (based off of the reading on this page), salt is already generated in the row when you use password_hash. Is this true?
Another question I had was, wouldn't it be smart to have 2 salts? One directly in the file and one in the DB? That way, if someone compromises your salt in the DB, you still have the one directly in the file? I read on here that storing salts is never a smart idea, but it always confused me what people meant by that.
Using password_hash is the recommended way to store passwords. Don't separate them to DB and files.
Let's say we have the following input:
$password = $_POST['password'];
You first hash the password by doing this:
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
Then see the output:
var_dump($hashed_password);
As you can see it's hashed. (I assume you did those steps).
Now you store this hashed password in your database, ensuring your password column is large enough to hold the hashed value (at least 60 characters or longer). When a user asks to log them in, you check the password input with this hash value in the database, by doing this:
// Query the database for username and password
// ...
if(password_verify($password, $hashed_password)) {
// If the password inputs matched the hashed password in the database
// Do something, you know... log them in.
}
// Else, Redirect them back to the login page.
Official Reference
Yes you understood it correctly, the function password_hash() will generate a salt on its own, and includes it in the resulting hash-value. Storing the salt in the database is absolutely correct, it does its job even if known.
// Hash a new password for storing in the database.
// The function automatically generates a cryptographically safe salt.
$hashToStoreInDb = password_hash($_POST['password'], PASSWORD_DEFAULT);
// Check if the hash of the entered login password, matches the stored hash.
// The salt and the cost factor will be extracted from $existingHashFromDb.
$isPasswordCorrect = password_verify($_POST['password'], $existingHashFromDb);
The second salt you mentioned (the one stored in a file), is actually a pepper or a server side key. If you add it before hashing (like the salt), then you add a pepper. There is a better way though, you could first calculate the hash, and afterwards encrypt (two-way) the hash with a server-side key. This gives you the possibility to change the key when necessary.
In contrast to the salt, this key should be kept secret. People often mix it up and try to hide the salt, but it is better to let the salt do its job and add the secret with a key.
Yes, it's true. Why do you doubt the php faq on the function? :)
The result of running password_hash() has has four parts:
the algorithm used
parameters
salt
actual password hash
So as you can see, the hash is a part of it.
Sure, you could have an additional salt for an added layer of security, but I honestly think that's overkill in a regular php application. The default bcrypt algorithm is good, and the optional blowfish one is arguably even better.
There is a distinct lack of discussion on backwards and forwards compatibility that is built in to PHP's password functions. Notably:
Backwards Compatibility: The password functions are essentially a well-written wrapper around crypt(), and are inherently backwards-compatible with crypt()-format hashes, even if they use obsolete and/or insecure hash algorithms.
Forwards Compatibilty: Inserting password_needs_rehash() and a bit of logic into your authentication workflow can keep you your hashes up to date with current and future algorithms with potentially zero future changes to the workflow. Note: Any string that does not match the specified algorithm will be flagged for needing a rehash, including non-crypt-compatible hashes.
Eg:
class FakeDB {
public function __call($name, $args) {
printf("%s::%s(%s)\n", __CLASS__, $name, json_encode($args));
return $this;
}
}
class MyAuth {
protected $dbh;
protected $fakeUsers = [
// old crypt-md5 format
1 => ['password' => '$1$AVbfJOzY$oIHHCHlD76Aw1xmjfTpm5.'],
// old salted md5 format
2 => ['password' => '3858f62230ac3c915f300c664312c63f', 'salt' => 'bar'],
// current bcrypt format
3 => ['password' => '$2y$10$3eUn9Rnf04DR.aj8R3WbHuBO9EdoceH9uKf6vMiD7tz766rMNOyTO']
];
public function __construct($dbh) {
$this->dbh = $dbh;
}
protected function getuser($id) {
// just pretend these are coming from the DB
return $this->fakeUsers[$id];
}
public function authUser($id, $password) {
$userInfo = $this->getUser($id);
// Do you have old, turbo-legacy, non-crypt hashes?
if( strpos( $userInfo['password'], '$' ) !== 0 ) {
printf("%s::legacy_hash\n", __METHOD__);
$res = $userInfo['password'] === md5($password . $userInfo['salt']);
} else {
printf("%s::password_verify\n", __METHOD__);
$res = password_verify($password, $userInfo['password']);
}
// once we've passed validation we can check if the hash needs updating.
if( $res && password_needs_rehash($userInfo['password'], PASSWORD_DEFAULT) ) {
printf("%s::rehash\n", __METHOD__);
$stmt = $this->dbh->prepare('UPDATE users SET pass = ? WHERE user_id = ?');
$stmt->execute([password_hash($password, PASSWORD_DEFAULT), $id]);
}
return $res;
}
}
$auth = new MyAuth(new FakeDB());
for( $i=1; $i<=3; $i++) {
var_dump($auth->authuser($i, 'foo'));
echo PHP_EOL;
}
Output:
MyAuth::authUser::password_verify
MyAuth::authUser::rehash
FakeDB::prepare(["UPDATE users SET pass = ? WHERE user_id = ?"])
FakeDB::execute([["$2y$10$zNjPwqQX\/RxjHiwkeUEzwOpkucNw49yN4jjiRY70viZpAx5x69kv.",1]])
bool(true)
MyAuth::authUser::legacy_hash
MyAuth::authUser::rehash
FakeDB::prepare(["UPDATE users SET pass = ? WHERE user_id = ?"])
FakeDB::execute([["$2y$10$VRTu4pgIkGUvilTDRTXYeOQSEYqe2GjsPoWvDUeYdV2x\/\/StjZYHu",2]])
bool(true)
MyAuth::authUser::password_verify
bool(true)
As a final note, given that you can only re-hash a user's password on login you should consider "sunsetting" insecure legacy hashes to protect your users. By this I mean that after a certain grace period you remove all insecure [eg: bare MD5/SHA/otherwise weak] hashes and have your users rely on your application's password reset mechanisms.
Class Password full code:
Class Password {
public function __construct() {}
/**
* Hash the password using the specified algorithm
*
* #param string $password The password to hash
* #param int $algo The algorithm to use (Defined by PASSWORD_* constants)
* #param array $options The options for the algorithm to use
*
* #return string|false The hashed password, or false on error.
*/
function password_hash($password, $algo, array $options = array()) {
if (!function_exists('crypt')) {
trigger_error("Crypt must be loaded for password_hash to function", E_USER_WARNING);
return null;
}
if (!is_string($password)) {
trigger_error("password_hash(): Password must be a string", E_USER_WARNING);
return null;
}
if (!is_int($algo)) {
trigger_error("password_hash() expects parameter 2 to be long, " . gettype($algo) . " given", E_USER_WARNING);
return null;
}
switch ($algo) {
case PASSWORD_BCRYPT :
// Note that this is a C constant, but not exposed to PHP, so we don't define it here.
$cost = 10;
if (isset($options['cost'])) {
$cost = $options['cost'];
if ($cost < 4 || $cost > 31) {
trigger_error(sprintf("password_hash(): Invalid bcrypt cost parameter specified: %d", $cost), E_USER_WARNING);
return null;
}
}
// The length of salt to generate
$raw_salt_len = 16;
// The length required in the final serialization
$required_salt_len = 22;
$hash_format = sprintf("$2y$%02d$", $cost);
break;
default :
trigger_error(sprintf("password_hash(): Unknown password hashing algorithm: %s", $algo), E_USER_WARNING);
return null;
}
if (isset($options['salt'])) {
switch (gettype($options['salt'])) {
case 'NULL' :
case 'boolean' :
case 'integer' :
case 'double' :
case 'string' :
$salt = (string)$options['salt'];
break;
case 'object' :
if (method_exists($options['salt'], '__tostring')) {
$salt = (string)$options['salt'];
break;
}
case 'array' :
case 'resource' :
default :
trigger_error('password_hash(): Non-string salt parameter supplied', E_USER_WARNING);
return null;
}
if (strlen($salt) < $required_salt_len) {
trigger_error(sprintf("password_hash(): Provided salt is too short: %d expecting %d", strlen($salt), $required_salt_len), E_USER_WARNING);
return null;
} elseif (0 == preg_match('#^[a-zA-Z0-9./]+$#D', $salt)) {
$salt = str_replace('+', '.', base64_encode($salt));
}
} else {
$salt = str_replace('+', '.', base64_encode($this->generate_entropy($required_salt_len)));
}
$salt = substr($salt, 0, $required_salt_len);
$hash = $hash_format . $salt;
$ret = crypt($password, $hash);
if (!is_string($ret) || strlen($ret) <= 13) {
return false;
}
return $ret;
}
/**
* Generates Entropy using the safest available method, falling back to less preferred methods depending on support
*
* #param int $bytes
*
* #return string Returns raw bytes
*/
function generate_entropy($bytes){
$buffer = '';
$buffer_valid = false;
if (function_exists('mcrypt_create_iv') && !defined('PHALANGER')) {
$buffer = mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM);
if ($buffer) {
$buffer_valid = true;
}
}
if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) {
$buffer = openssl_random_pseudo_bytes($bytes);
if ($buffer) {
$buffer_valid = true;
}
}
if (!$buffer_valid && is_readable('/dev/urandom')) {
$f = fopen('/dev/urandom', 'r');
$read = strlen($buffer);
while ($read < $bytes) {
$buffer .= fread($f, $bytes - $read);
$read = strlen($buffer);
}
fclose($f);
if ($read >= $bytes) {
$buffer_valid = true;
}
}
if (!$buffer_valid || strlen($buffer) < $bytes) {
$bl = strlen($buffer);
for ($i = 0; $i < $bytes; $i++) {
if ($i < $bl) {
$buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255));
} else {
$buffer .= chr(mt_rand(0, 255));
}
}
}
return $buffer;
}
/**
* Get information about the password hash. Returns an array of the information
* that was used to generate the password hash.
*
* array(
* 'algo' => 1,
* 'algoName' => 'bcrypt',
* 'options' => array(
* 'cost' => 10,
* ),
* )
*
* #param string $hash The password hash to extract info from
*
* #return array The array of information about the hash.
*/
function password_get_info($hash) {
$return = array('algo' => 0, 'algoName' => 'unknown', 'options' => array(), );
if (substr($hash, 0, 4) == '$2y$' && strlen($hash) == 60) {
$return['algo'] = PASSWORD_BCRYPT;
$return['algoName'] = 'bcrypt';
list($cost) = sscanf($hash, "$2y$%d$");
$return['options']['cost'] = $cost;
}
return $return;
}
/**
* Determine if the password hash needs to be rehashed according to the options provided
*
* If the answer is true, after validating the password using password_verify, rehash it.
*
* #param string $hash The hash to test
* #param int $algo The algorithm used for new password hashes
* #param array $options The options array passed to password_hash
*
* #return boolean True if the password needs to be rehashed.
*/
function password_needs_rehash($hash, $algo, array $options = array()) {
$info = password_get_info($hash);
if ($info['algo'] != $algo) {
return true;
}
switch ($algo) {
case PASSWORD_BCRYPT :
$cost = isset($options['cost']) ? $options['cost'] : 10;
if ($cost != $info['options']['cost']) {
return true;
}
break;
}
return false;
}
/**
* Verify a password against a hash using a timing attack resistant approach
*
* #param string $password The password to verify
* #param string $hash The hash to verify against
*
* #return boolean If the password matches the hash
*/
public function password_verify($password, $hash) {
if (!function_exists('crypt')) {
trigger_error("Crypt must be loaded for password_verify to function", E_USER_WARNING);
return false;
}
$ret = crypt($password, $hash);
if (!is_string($ret) || strlen($ret) != strlen($hash) || strlen($ret) <= 13) {
return false;
}
$status = 0;
for ($i = 0; $i < strlen($ret); $i++) {
$status |= (ord($ret[$i]) ^ ord($hash[$i]));
}
return $status === 0;
}
}
I am working on a legacy code written in PHP that I have to migrate to GO. We are required to store encrypted data, and decrypt on demand.
Because the legacy code will still have to run until everything is migrated to GO, I need to use the same encrypt / decrypt methods in GO too.
I have been strugling with this issue for the last 10 hours. I have tried (I think) all the Stackoverflow suggestions + many others.
The servers are running OpenSSL 1.1.1a. I have found and tested something helpful, but without success:
https://dequeue.blogspot.com/2014/11/decrypting-something-encrypted-with.html
I do not have to much knowledge about encryption in general, but I can understand it's basics.
So, if there is anyone kind enough to give me some suggestions ... please.
PHP legacy code
var $encryption_key = 'XXX';
var $cipher = 'aes-256-cbc';
function encrypt($text) {
global $encryption_key, $cipher;
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($cipher));
$data = openssl_encrypt($text, $cipher, $encryption_key, OPENSSL_RAW_DATA, $iv);
return base64_encode($iv . $data);
}
function decrypt($text) {
global $encryption_key, $cipher;
$data = base64_decode($text);
$iv_len = openssl_cipher_iv_length($cipher);
$iv = substr($data, 0, $iv_len);
$data = substr($data, $iv_len);
return openssl_decrypt($data, $cipher, $encryption_key, OPENSSL_RAW_DATA, $iv);
}
Last code tested in GO for decrypting data (ignore the block below and scroll for updated code)
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/md5"
"encoding/base64"
"fmt"
)
func main(){
result, _ := DecryptString(`password`, `U2FsdGVkX1+ywYxveBnekSnx6ZP25nyPsWHS3oqcuTo=`)
fmt.Printf("Decrypted string is: %s", result)
}
var openSSLSaltHeader string = "Salted_" // OpenSSL salt is always this string + 8 bytes of actual salt
type OpenSSLCreds struct {
key []byte
iv []byte
}
// Decrypt string that was encrypted using OpenSSL and AES-256-CBC
func DecryptString(passphrase, encryptedBase64String string) ([]byte, error) {
data, err := base64.StdEncoding.DecodeString(encryptedBase64String)
if err != nil {
return nil, err
}
saltHeader := data[:aes.BlockSize]
if string(saltHeader[:7]) != openSSLSaltHeader {
return nil, fmt.Errorf("Does not appear to have been encrypted with OpenSSL, salt header missing.")
}
salt := saltHeader[8:]
creds, err := extractOpenSSLCreds([]byte(passphrase), salt)
if err != nil {
return nil, err
}
return decrypt(creds.key, creds.iv, data)
}
func decrypt(key, iv, data []byte) ([]byte, error) {
if len(data) == 0 || len(data)%aes.BlockSize != 0 {
return nil, fmt.Errorf("bad blocksize(%v), aes.BlockSize = %v\n", len(data), aes.BlockSize)
}
c, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
cbc := cipher.NewCBCDecrypter(c, iv)
cbc.CryptBlocks(data[aes.BlockSize:], data[aes.BlockSize:])
out, err := pkcs7Unpad(data[aes.BlockSize:], aes.BlockSize)
if out == nil {
return nil, err
}
return out, nil
}
// openSSLEvpBytesToKey follows the OpenSSL (undocumented?) convention for extracting the key and IV from passphrase.
// It uses the EVP_BytesToKey() method which is basically:
// D_i = HASH^count(D_(i-1) || password || salt) where || denotes concatentaion, until there are sufficient bytes available
// 48 bytes since we're expecting to handle AES-256, 32bytes for a key and 16bytes for the IV
func extractOpenSSLCreds(password, salt []byte) (OpenSSLCreds, error) {
m := make([]byte, 48)
prev := []byte{}
for i := 0; i < 3; i++ {
prev = hash(prev, password, salt)
copy(m[i*16:], prev)
}
return OpenSSLCreds{key: m[:32], iv: m[32:]}, nil
}
func hash(prev, password, salt []byte) []byte {
a := make([]byte, len(prev)+len(password)+len(salt))
copy(a, prev)
copy(a[len(prev):], password)
copy(a[len(prev)+len(password):], salt)
return md5sum(a)
}
func md5sum(data []byte) []byte {
h := md5.New()
h.Write(data)
return h.Sum(nil)
}
// pkcs7Unpad returns slice of the original data without padding.
func pkcs7Unpad(data []byte, blocklen int) ([]byte, error) {
if blocklen <= 0 {
return nil, fmt.Errorf("invalid blocklen %d", blocklen)
}
if len(data)%blocklen != 0 || len(data) == 0 {
return nil, fmt.Errorf("invalid data len %d", len(data))
}
padlen := int(data[len(data)-1])
if padlen > blocklen || padlen == 0 {
return nil, fmt.Errorf("invalid padding")
}
pad := data[len(data)-padlen:]
for i := 0; i < padlen; i++ {
if pad[i] != byte(padlen) {
return nil, fmt.Errorf("invalid padding")
}
}
return data[:len(data)-padlen], nil
}
The code written in GO should be able to decrypt data encrypted in PHP. The only result that I get is an error: Error: Does not appear to have been encrypted with OpenSSL, salt header missing.
UPDATE: Go code updated
The code below almost works. IV, key, cipher binary text are all the same as in PHP, but the final result is still encrypted (or wrongly decrypted)
package main
import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"fmt"
)
func main() {
var stringToDecode string = "base64_encode(SOME_PHP_ENCRYPTED_USING_THE_PHP_CODE_ABOVE)"
var cipherKey = []byte("myprivatekey")
content, err := base64.StdEncoding.DecodeString(stringToDecode)
if err != nil {
fmt.Printf("Error: %s", err)
}
fmt.Println("Cipher key: ", string(cipherKey))
fmt.Println("Cipher key length: ", len(cipherKey))
cipherText := content
cipherBlock, err := aes.NewCipher(cipherKey)
if err != nil {
panic(err)
}
iv := cipherText[:aes.BlockSize]
fmt.Println("iv:", base64.StdEncoding.EncodeToString(iv))
fmt.Println("Cipher text:", string(cipherText[aes.BlockSize:]))
cipherText = cipherText[aes.BlockSize:]
fmt.Println("Cipher text binary: ", string(cipherText))
if len(cipherText)%aes.BlockSize != 0 {
panic(fmt.Sprintf("Cipher text (len=%d) is not a multiple of the block size (%d)", len(cipherText), aes.BlockSize))
}
mode := cipher.NewCBCDecrypter(cipherBlock, iv)
mode.CryptBlocks(cipherText, cipherText)
// The output is not decrypted as expected
fmt.Printf("The result: %s\n", string(cipherText))
}
There is a decryption and signature interface. I want to move from PHP to Golang. The PHP function is as follows:
function getSignature($param){
if (is_string($param)) {
$file_private = 'file.p12';
if (!$cert_store = file_get_contents($file_private)) {
return "Error: Unable to read the cert file\n";
}
$signature = "";
$algo = "sha256WithRSAEncryption";
$password = "PASSWORD";
$private_key_file = openssl_pkcs12_read($cert_store, $cert_info, $password);
if ($private_key_file)
{
$private_key = $cert_info['pkey'];
openssl_sign($param, $signature, $private_key, $algo);
return htmlentities(base64_encode($signature));
}
}
return false;
}
I want to use golang to achieve.
How can I convert into golang?
SOLVED
This is what actually my code in golang:
func Sign(privateKey *rsa.PrivateKey, data string) (string, error) {
h := crypto.SHA256.New()
h.Write([]byte(data))
hashed := h.Sum(nil)
sign, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hashed)
if err != nil {
return "", err
}
return base64.RawURLEncoding.EncodeToString(sign), err
}
func read_keys() {
b, err := ioutil.ReadFile("file.p12")
if err != nil {
fmt.Println(err)
}
password := "PASSWORD"
privk, _, err := pkcs12.Decode(b, password)
if err != nil {
fmt.Println(err)
}
pv := privk.(*rsa.PrivateKey)
sign, _ := Sign(pv, "Your String Data")
fmt.Print(sign)
}
this is the package you're looking for
data, err := ioutil.ReadFile(*in)
if err != nil {
log.Fatal(err)
}
privateKey, certificate, err := pkcs12.Decode(data, *password)
if err != nil {
log.Fatal(err)
}
pv := privateKey.(*rsa.PrivateKey)
signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey, rypto.SHA256, hash)
if err != nil {
log.Fatal(err)
}
I am using AES encryption in Go and PHP. But both the languages does not encrypt/decrypt each other ciphertext. Following i have tried in php
class Crypto {
private $encryptKey = "keyforencryption";
private $iv = 'ivusedforencrypt';
private $blocksize = 16;
public function encrypt($toEncrypt){
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CFB);
//$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
return base64_encode($this->iv . mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $this->encryptKey, $toEncrypt, MCRYPT_MODE_CFB, $this->iv));
}
public function decrypt($toDecrypt){
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CFB);//$this->blocksize;
$toDecrypt = base64_decode($toDecrypt);
return rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $this->encryptKey, substr($toDecrypt, $iv_size), MCRYPT_MODE_CFB, substr($toDecrypt, 0, $iv_size)));
}
}
$c = new Crypto();
echo "Encrypted : ".$e = $c->encrypt("test");
echo "<br/>Decrypted : ".$c->decrypt($e);
output : aXZ1c2VkZm9yZW5jcnlwdDpdZEinU2rB
and this one in Go with AES
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"errors"
"fmt"
"io"
"log"
)
func main() {
key := []byte("keyforencryption")
plaintext := []byte("test")
fmt.Printf("%s\n", plaintext)
ciphertext, err := encrypt(key, plaintext)
if err != nil {
log.Fatal(err)
}
b := base64.StdEncoding.EncodeToString(ciphertext)
fmt.Printf("Encrypted text : %s\n", b)
result, err := decrypt(key, b)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Decrypted Text : %s\n", result)
}
func encrypt(key, text []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
//b := base64.StdEncoding.EncodeToString(text)
ciphertext := make([]byte, aes.BlockSize+len(text))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
cfb := cipher.NewCFBEncrypter(block, iv)
cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(text))
return ciphertext, nil
}
func decrypt(key []byte, text1 string) ([]byte, error) {
text, _ := base64.StdEncoding.DecodeString(string(text1))
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
if len(text) < aes.BlockSize {
return nil, errors.New("ciphertext too short")
}
iv := text[:aes.BlockSize]
text = text[aes.BlockSize:]
cfb := cipher.NewCFBDecrypter(block, iv)
cfb.XORKeyStream(text, text)
b := base64.StdEncoding.EncodeToString(text)
data, err := base64.StdEncoding.DecodeString(string(b))
if err != nil {
return nil, err
}
return data, nil
}
output : ZVnhCXjIvtGKBdqvjwHRZKcVy34=
any help would be appreciable.
CFB mode has an issue, this will work in CBC mode
class Crypto {
private $encryptKey = "keyforencryption";
public function encrypt($toEncrypt){
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
return base64_encode($iv . mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $this->encryptKey, $toEncrypt, MCRYPT_MODE_CBC, $iv));
}
public function decrypt($toDecrypt){
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
echo "<br/>".$toDecrypt = base64_decode($toDecrypt);
return rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $this->encryptKey, substr($toDecrypt, $iv_size), MCRYPT_MODE_CBC, substr($toDecrypt, 0, $iv_size)));
}
}
$c = new Crypto();
echo "Encrypted : ".$e = $c->encrypt("test123");
echo "<br/>Decrypted : ".$c->decrypt($e);
and this one in golang
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"fmt"
"io"
"bytes"
)
func main() {
e:= cbcEncrypt()
fmt.Printf("Encrypted String : %s\n", e)
d:= cbcDecrypt(e)
fmt.Printf("Decrypted String : %s\n", d)
}
func cbcDecrypt(text1 string) []byte{
key := []byte("keyforencryption")
ciphertext, _ := base64.StdEncoding.DecodeString(string(text1))
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
// include it at the beginning of the ciphertext.
if len(ciphertext) < aes.BlockSize {
panic("ciphertext too short")
}
iv := ciphertext[:aes.BlockSize]
ciphertext = ciphertext[aes.BlockSize:]
// CBC mode always works in whole blocks.
if len(ciphertext)%aes.BlockSize != 0 {
panic("ciphertext is not a multiple of the block size")
}
mode := cipher.NewCBCDecrypter(block, iv)
// CryptBlocks can work in-place if the two arguments are the same.
mode.CryptBlocks(ciphertext, ciphertext)
ciphertext = PKCS5UnPadding(ciphertext)
return ciphertext
}
func cbcEncrypt() string{
key := []byte("keyforencryption")
plaintext := []byte("testssssss")
plaintext = PKCS5Padding(plaintext, 16)
// CBC mode works on blocks so plaintexts may need to be padded to the
// next whole block. For an example of such padding, see
// https://tools.ietf.org/html/rfc5246#section-6.2.3.2. Here we'll
// assume that the plaintext is already of the correct length.
if len(plaintext)%aes.BlockSize != 0 {
panic("plaintext is not a multiple of the block size")
}
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
// The IV needs to be unique, but not secure. Therefore it's common to
// include it at the beginning of the ciphertext.
ciphertext := make([]byte, aes.BlockSize+len(plaintext))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
panic(err)
}
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(ciphertext[aes.BlockSize:], plaintext)
// It's important to remember that ciphertexts must be authenticated
// (i.e. by using crypto/hmac) as well as being encrypted in order to
// be secure.
return base64.StdEncoding.EncodeToString(ciphertext)
}
func PKCS5Padding(src []byte, blockSize int) []byte {
padding := blockSize - len(src)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(src, padtext...)
}
func PKCS5UnPadding(src []byte) []byte {
length := len(src)
unpadding := int(src[length-1])
return src[:(length - unpadding)]
}
this should work
Also use padding for encoding and unpadding for decode in php.
function pkcs5_pad($text)
{
$blocksize = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$pad = $blocksize - (strlen($text) % $blocksize);
return $text . str_repeat(chr($pad), $pad);
}
function pkcs5_unpad($text)
{
$block = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$pad = ord($text[($len = strlen($text)) - 1]);
$len = strlen($text);
$pad = ord($text[$len-1]);
return substr($text, 0, strlen($text) - $pad);
}