I am creating a PHP application which can be installed on user's server but the administration is hosted on my server.
I need to store the password, username, db_name, host and port of database of the user. I think I should store in database (MySQL), but I don't know how to do securely.
Can you tell me please how can I store external passwords securely?
Thank you!
You need to have one secure key setup on main server (where database settings are stored) for example the password is b#auty! and on the other machine decrypt function with the same key to decrypt password. Now you can secure any string simply by
//url encrpytography
function encrypt($val, $key = 'b#auty!') {
$keySalt = $key;
$query = base64_encode(urlencode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, md5($keySalt), $val, MCRYPT_MODE_CBC, md5(md5($keySalt))))); //this line of code encrypt the query string
return $query;
}
function decrypt($val, $key = 'b#auty!') {
$keySalt = $key; // same as used in encryptLink function
$queryString = rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, md5($keySalt), urldecode(base64_decode($val)), MCRYPT_MODE_CBC, md5(md5($keySalt))), "\0"); //this line of code decrypt the query string
//CHECK IF RETURN IS REAL LETTERS
if (preg_match('/^[a-z0-9 .\-]+$/i', $queryString)) {
} else {
$queryString = '';
}
return $queryString;
}
$encrypt = encrypt("string");
echo $encrypt;
$decrypt = decrypt($encrypt);
echo $decrypt
There is obviously more than one way to handle passwords in data stores. If you are very concerned that your password stores are never compromised, I highly recommend you look at this information:
http://www.openwall.com/articles/PHP-Users-Passwords
I always use this methodology now because it's as secure as one can get. It's free, and I have no affiliation with the author or his operation.
Related
I'm currently working on an application that makes use of the prestashop webservice. This means that the application i'm building is an extension of an existing prestashop application. The connection between both applications is through the prestashop webservice
Currently i'm trying to create a login script for thecustomers. The email and password are obtained from the database through the webservice and i'm able to filter the inputs with the existing row's. So when filling in login#test.com. The filter will only obtain the row with that email address.
The problem i'm having is with the password. Prestashop uses a _COOKIE_KEY_ together with anmd5() to encrypt passwords. See this link for more information: link
So i've been trying some different things for a while to check the inputted password with the customers password but i haven't found the solution yet.
Take a look at the code below:
<?php
require_once('./PSWebServiceLibrary.php');
/**
* get information from PrestaShop
*/
$webService = new PrestaShopWebservice($url, $key, $debug);
define('_COOKIE_KEY_', '...');
$email = "login#test.nl";
define('password', "test");
$md5passwd = md5(_COOKIE_KEY_ . password);
$opt = array(
"resource" => "customers",
"display" => "[email , passwd]",
"filter[email]" => "$email"
);
$optPass = array(
"resource" => "customers",
"display" => "[email]",
"filter[email]" => "$email",
"filter[passwd]" => "$md5passwd"
);
$jsonPass = ($webService->get( $optPass ));
//json encode it
$jsonPasswd = json_encode($jsonPass);
echo($jsonPasswd);
if(password_verify($md5passwd, $jsonPasswd)) {
echo "password is valid";
} else {
echo "password is not valid";
}
$jsonUrl = ($webService->get( $opt ));
//json encode it
$json = json_encode($jsonUrl);
echo($json);
As you can see i've been trying out things like the password_verify and the md5() but i can't quite get it. So is there anyone who has done this or who knows how to create a correct login script on the prestashop webservice?
Update -- 12/1/2017
So after doing some research i've come up with a new way of checking the user input. First the code checks the email and if it's true it will continue with checking the password input. But the problem i'm having is with the password and the password encryption of prestashop. I'm not able to compare the two hashes together. The first hash would be the hash from the database were the second hash is the user input password. The input would need a hash() function from prestashop. But i can't quite get to the right hash sequence of prestashop.
I've searched all over the internet for this but couldn't find a decent solution for logging in through the prestashop webservice. The script i've created for logging in is shown below.
require_once('./PSWebServiceLibrary.php');
/**
* get information from PrestaShop
*/
$webService = new PrestaShopWebservice($url, $key, $debug);
$COOKIE_KEY = '_key';
$email = $_REQUEST['email'];
$password = md5('_key' . $_REQUEST['password']);
// The database hash for testing (random)
$passwordString = '$2y$10$UsYrIFQUOr5LBUZBoqSdxODuhbToEc.2QEqfAVB1r\/fhO5EfOyO96';
$opt = array(
'resource' => 'customers',
'filter[email]' => '['.$email.']',
'display' => '[email,lastname,firstname, passwd]'
);
$result = ($webService->get( $opt ));
$json = json_encode($result);
$optUser = array(
'resource' => 'customers',
'filter[email]' => '['.$email.']',
'display' => '[email,lastname,firstname,passwd]'
);
$resultUser = ($webService->get( $optUser ));
$userResult = json_encode($resultUser);
// Check the email
function hasEmail($string, $email)
{
return strpos($string, $email) !== false;
}
// Check the Password
function hasPassword($string, $password)
{
return strpos($string, $password) !== false;
}
if(hasEmail($userResult, $email) == true and hasPassword($userResult, $password) == true) {
session_start();
$_SESSION['user'] = $email;
// redirect is kut.
echo
'<html>
<head>
<meta content="text/html; charset=utf-8">
</head>
</html>';
} else {
// Here, we use single quotes for PHP and double quotes for JavaScript
echo '<script type="text/javascript">';
echo 'alert("Wrong username or password!")';
echo '</script>';
}
Small second question: How would i be able to run a -> go to url in the success statement, Currently the echo "<script></script>"; isn't working and since the header() can't be used i'm having some trouble redirecting on succes.
As always, Thanks in advance!
To generate the cookie key prestashop uses:
array('_COOKIE_KEY_', Tools::passwdGen(56)),
array('_COOKIE_IV_', Tools::passwdGen(8)),
So that cookie key is different everytime. In order to verify if the password is good you should get existing password from database and compare with your user-submitted password:
//CHECK IF THE GIVEN EMAIL MATCHES A ROW IN OUR LEGACY TABLE AND RETRIEVES THE LEGACY PASSWORD
$resultZC = Db::getInstance()->getRow('
SELECT `password`
FROM `zc_legacy_passwords`
WHERE `email` = \''.pSQL($email).'\'
AND `updated` = 0');
if (!$resultZC)
return false; //<- EMAIL NOT FOUND IN NONE OF THE TABLES, SO IT IS AN INVALID LOGIN
//ENCRYPTS THE GIVEN PASSWORD IN ZEN-CART / OSCOMMERCE FORMAT
$salt = substr($resultZC['password'], strrpos($resultZC['password'],':')+1, 2);
$ZCpassword = md5($salt . $passwd) . ':' . $salt;
if ($ZCpassword != $resultZC['password'])
return false; //<- WRONG ZEN-CART/OSCOMMERCE PASSWORD GIVEN
This is the part that you're asking for:
//ENCRYPTS THE GIVEN PASSWORD IN ZEN-CART / OSCOMMERCE FORMAT
$salt = substr($resultZC['password'], strrpos($resultZC['password'],':')+1, 2);
$ZCpassword = md5($salt . $passwd) . ':' . $salt;
where $resultZC['password'] is the password stored in the database and, $passwd is your password
if PrestaShop version is 1.6, the function to encrypt customer passwords is Tools::encrypt($passwd). This method just do this:
return md5(_COOKIE_KEY_.$passwd);
So knowing the _COOKIE_KEY_ you must be able to generate the hash.
_COOKIER_KEY_ is defined in config/settings.inc.php
If PrestaShop version is 1.7, Tools is not used and maybe md5(_COOKIE_KEY_.$passwd) will not match. It is used crypto from Symfony.
However, I guess PrestaShop webservice must have something to check users, in Customer class there is the method getByEmail(...) that is used in both 1.6 and 1.7 versions.
Regards.
I'm learning how to hash passwords using PHP for obvious security reasons. I have the two following functions for setting and checking hashes:
function password_encrypt($password)
{
$hash_format = "$2y$10$"; //blowfish
$salt_length = 22;
$unique_random_string = md5(uniqid(mt_rand(), true));
$base64_string = base64_encode($unique_random_string);
$modified_base_64_string = str_replace('+', '.', $base64_string);
$salt = substr($modified_base64_string, 0, $length);
$format_and_salt = $hash_format . $salt;
$hash = crypt($password, $format_and_salt);
echo $hash;
return $hash;
}
function password_check($password, $existing_hash)
{
$hash = crypt($password, $existing_hash);
if($hash === $existing_hash)
{
return true;
}
else
{
return false;
}
}
My issue is that every time I hash a password and send it off to the database it just gets stored as *0. I don't know where the *0 is coming from. Is there something wrong with what I'm doing?
Basically when I use it I just grab the entered password from $_POST and encrypt it using password_encrypt(). But it seems like it always comes out the other side as *0.
Short and Correct Answer
Don't roll your own cryptography, use an implementation that has already been vetted by security experts. For PHP 5.5 and above, this means password_hash() and password_verify(). For earlier versions of PHP, you can use password_compat (if your PHP version is older than 5.4, shame on you for running unsupported versions of PHP).
The Crypto Self-Education Answer
If you're dead set on writing your own cryptography code (for the learning experience, etc.), you must promise to never deploy your unproven implementation and make anything depend on it for security. Otherwise, just use password_hash() and password_verify() and stop reading right now.
There are a lot of problems with your code. Let's go down the line systematically, starting with password_encrypt(). (By the way, you aren't encrypting a password, you're hashing it.)
$unique_random_string = md5(uniqid(mt_rand(), true));
Yeah, NO. This is not a cryptographically secure way to generate a salt for a bcrypt hash. You should take a look at how password_compat implements this feature. Alternatively, see how random_compat generates random bytes for cryptographic purposes. (Also, please tell whomever advised you to use that one-liner to generate random strings that they are playing with fire.)
$unique_random_string = random_bytes(17);
Moving on...
$modified_base_64_string = str_replace('+', '.', $base64_string);
$salt = substr($modified_base64_string, 0, $length);
You forgot an underscore in the second variable name. Also, there is no variable named $length. These lines, corrected, look like so:
$modified_base_64_string = str_replace('+', '.', $base64_string);
$salt = substr($modified_base_64_string, 0, $salt_length);
Next,
$hash = crypt($password, $format_and_salt);
echo $hash;
return $hash;
Please don't leave your echo statements in your example code. Also, you should check for errors and throw an exception so your application doesn't chug happily along if bcrypt hashing fails.
$hash = crypt($password, $format_and_salt);
if ($hash === '*0') {
throw new Exception('Password hashing unsuccessful.');
}
return $hash;
Whew, almost done. Let's look at your password_check() function now:
if($hash === $existing_hash)
{
return true;
}
else
{
return false;
}
Don't compare cryptographic outputs (password hashes, MACs, etc.) using == or ===, or else you invite timing attacks into your application. Use hash_equals() for PHP 5.6, or a hash_equals() polyfill for earlier versions of PHP.
return hash_equals($hash, $existing_hash);
I hope this was an educational experience. Cryptography is hard, don't reinvent the wheel.
I'm developing an API to let my users access to files stored on another server.
Let's call my two servers, server 1 and server 2!
server 1 is the server im hosting my web site, and
server 2 is the server im storing my files!
My site is basically Javascript based one, so I will be using Javascript to post data to API when user needs to access files which are stored on server 2.
when users requests to access files, the data will be posted to API URL via Javascript! API is made of PHP. Using that PHP script(API) on server 1, I will made another request to server 2 asking for files so there will be another PHP script(API) on server 2.
I need to know how should I do this authentication between two servers as server 2 has no access to user details on server 1?
I hope to do that like this, I can use the method which is used by most payment gateways.
When API on server 2 received a request with some unique data of the user , post back those unique data through SSL to server 1 API and match them with user data in the database, then post back result through SSL to server 2 so then server 2 knows file request is a genuine request.
In this case what kind of user data/credentials server 1 API should post to server 2 and server 2 API should post back to server 1? and which user data should be matched with the data in the database? like user ID, session, cookies, ip, time stamp, ect!
Any clear and described answer would be nice! Thanks.
I would go with this:
user initiates action, javascript asks Server 1 (ajax) for request for file on Server 2
Server 1 creates URL using hash_hmac with data: file, user ID, user secret
when clicking that URL (server2.com/?file=FILE&user_id=ID&hash=SHA_1_HASH) server 2 asks server 1 for validation (sends file, user_id and hash)
server 1 does the validation, sends response to server 2
server 2 pushes file or sends 403 HTTP response
This way, server 2 only needs to consume API of server 1, server 1 has all the logic.
Pseudocode for hash and url creation:
// getHash($userId, $file) method
$user = getUser($userId);
$hash = hash_hmac('sha1', $userId . $file, $user->getSecret());
// getUrl($userId, $file) method
return sprintf('http://server2.com/get-file?file=%1&user_id=%2&hash=%3',
$userId,
$file,
$security->getHash($userId, $file)
);
Pseudocode for validation:
$hash = $security->getHash($_GET['id'], $_GET['file']);
if ($hash === $_GET['hash']) {
// All is good
}
Edit: getHash() method accepts user ID and file (ID or string, what ever suits your needs). With that data, it produces a hash, using hash_hmac method. For the secret parameter of hash_hmac function, users "secret key" is used. That key would be stored together with users data in the db table. It would be generated with mt_rand or even something stronger as reading /dev/random or using something like https://stackoverflow.com/a/16478556/691850.
A word of advice, use mod_xsendfile on server 2 (if it is Apache) to push files.
Introduction
You can use 2 simple method
Authentication Token
Signed Request
You can also combine both of them by using Token for authentication and using signature to verify integrity of the message sent
Authentication Token
If you are going to consider matching any identification in the database perhaps you can consider creating authentication token rather than user ID, session, cookies, ip, time stamp, etc! as suggested.
Create a random token and save to Database
$token = bin2hex(mcrypt_create_iv(64, MCRYPT_DEV_URANDOM));
This can be easily generated
You can guaranteed it more difficult to guess unlike password
It can easily be deleted if compromised and re generate another key
Signed Request
The concept is simple, For each file uploaded must meat a specific signature crated using a random generated key just like the token for each specific user
This can easily be implemented with HMAC with hash_hmac_file function
Combine Both Authentication & Signed Request
Here is a simple Prof of concept
Server 1
/**
* This should be stored securly
* Only known to User
* Unique to each User
* Eg : mcrypt_create_iv(32, MCRYPT_DEV_URANDOM);
*/
$key = "d767d183315656d90cce5c8a316c596c971246fbc48d70f06f94177f6b5d7174";
$token = "3380cb5229d4737ebe8e92c1c2a90542e46ce288901da80fe8d8c456bace2a9e";
$url = "http://server 2/run.php";
// Start File Upload Manager
$request = new FileManager($key, $token);
// Send Multiple Files
$responce = $request->send($url, [
"file1" => __DIR__ . "/a.png",
"file2" => __DIR__ . "/b.css"
]);
// Decode Responce
$json = json_decode($responce->data, true);
// Output Information
foreach($json as $file) {
printf("%s - %s \n", $file['name'], $file['msg']);
}
Output
temp\14-a.png - OK
temp\14-b.css - OK
Server 2
// Where to store the files
$tmpDir = __DIR__ . "/temp";
try {
$file = new FileManager($key, $token);
echo json_encode($file->recive($tmpDir), 128);
} catch (Exception $e) {
echo json_encode([
[
"name" => "Execption",
"msg" => $e->getMessage(),
"status" => 0
]
], 128);
}
Class Used
class FileManager {
private $key;
function __construct($key, $token) {
$this->key = $key;
$this->token = $token;
}
function send($url, $files) {
$post = [];
// Convert to array fromat
$files = is_array($files) ? $files : [
$files
];
// Build Post Request
foreach($files as $name => $file) {
$file = realpath($file);
if (! (is_file($file) || is_readable($file))) {
throw new InvalidArgumentException("Invalid File");
}
// Add File
$post[$name] = "#" . $file;
// Sign File
$post[$name . "-sign"] = $this->sign($file);
}
// Start Curl ;
$ch = curl_init($url);
$options = [
CURLOPT_HTTPHEADER => [
"X-TOKEN:" . $this->token
],
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_POST => count($post),
CURLOPT_POSTFIELDS => $post
];
curl_setopt_array($ch, $options);
// Get Responce
$responce = [
"data" => curl_exec($ch),
"error" => curl_error($ch),
"error" => curl_errno($ch),
"info" => curl_getinfo($ch)
];
curl_close($ch);
return (object) $responce;
}
function recive($dir) {
if (! isset($_SERVER['HTTP_X_TOKEN'])) {
throw new ErrorException("Missing Security Token");
}
if ($_SERVER['HTTP_X_TOKEN'] !== $this->token) {
throw new ErrorException("Invalid Security Token");
}
if (! isset($_FILES)) {
throw new ErrorException("File was not uploaded");
}
$responce = [];
foreach($_FILES as $name => $file) {
$responce[$name]['status'] = 0;
// check if file is uploaded
if ($file['error'] == UPLOAD_ERR_OK) {
// Check for signatire
if (isset($_POST[$name . '-sign']) && $_POST[$name . '-sign'] === $this->sign($file['tmp_name'])) {
$path = $dir . DIRECTORY_SEPARATOR . $file['name'];
$x = 0;
while(file_exists($path)) {
$x ++;
$path = $dir . DIRECTORY_SEPARATOR . $x . "-" . $file['name'];
}
// Move File to temp folder
move_uploaded_file($file['tmp_name'], $path);
$responce[$name]['name'] = $path;
$responce[$name]['sign'] = $_POST[$name . '-sign'];
$responce[$name]['status'] = 1;
$responce[$name]['msg'] = "OK";
} else {
$responce[$name]['msg'] = sprintf("Invalid File Signature");
}
} else {
$responce[$name]['msg'] = sprintf("Upload Error : %s" . $file['error']);
}
}
return $responce;
}
private function sign($file) {
return hash_hmac_file("sha256", $file, $this->key);
}
}
Other things to consider
For better security you can consider the follow
IP Lock down
File Size Limit
File Type Validation
Public-Key Cryptography
Changing Date Based token generation
Conclusion
The sample class can be extended in so many ways and rather than use URL you can consider a proper json RCP solution
A long enough, single-use, short-lived, random generated key should suffice in this case.
Client requests for a file to Server 1
Server 1 confirms login information and generates a long single-use key and sends it to the user. Server 1 keeps track of this key and matches it with an actual file on Server 2.
Client sends a request to Server 2 along with the key
Server 2 contacts Server 1 and submits the key
Server 1 returns a file path if the key is valid. The key is invalidated (destroyed).
Server 2 sends the file to the client
Server 1 invalidates the key after say 30 seconds, even if it didn't receive a confirmation request from Server 2. Your front-end should account for this case and retry the process a couple of times before returning an error.
I do not think there is a point in sending cookie/session information along, this information can be brute-forced just like the random key.
A 1024-bit long key sounds more than reasonable. This entropy can be obtained with a string of less than 200 alphanumeric characters.
For the absolute best security you would need some communication from server 2 to server 1, to double check if the request is valid. Although this communication could be minimal, its still communication and thus slows down the proces.
If you could live with a marginally less secure solution, I would suggest the following.
Server 1 requestfile.php:
<?php
//check login
if (!$loggedon) {
die('You need to be logged on');
}
$dataKey = array();
$uniqueKey = 'fgsdjk%^347JH$#^%&5ghjksc'; //choose whatever you want.
//check file
$file = isset($_GET['file']) ? $_GET['file'] : '';
if (empty($file)) {
die('Invalid request');
}
//add user data to create a reasonably unique fingerprint.
//It will mostlikely be the same for people in the same office with the same browser, thats mainly where the security drop comes from.
//I double check if all variables are set just to be sure. Most of these will never be missing.
if (isset($_SERVER['HTTP_USER_AGENT'])) {
$dataKey[] = $_SERVER['HTTP_USER_AGENT'];
}
if (isset($_SERVER['REMOTE_ADDR'])) {
$dataKey[] = $_SERVER['REMOTE_ADDR'];
}
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
$dataKey[] = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
}
if (isset($_SERVER['HTTP_ACCEPT_ENCODING'])) {
$dataKey[] = $_SERVER['HTTP_ACCEPT_ENCODING'];
}
if (isset($_SERVER['HTTP_ACCEPT'])) {
$dataKey[] = $_SERVER['HTTP_ACCEPT'];
}
//also add the unique key
$dataKey[] = $uniqueKey;
//add the file
$dataKey[] = $file;
//add a timestamp. Since the request will be a different times, dont use the exact second
//make sure its added last
$dataKey[] = date('YmdHi');
//create a hash
$hash = md5(implode('-', $dataKey));
//send to server 2
header('Location: https://server2.com/download.php?file='.urlencode($file).'&key='.$hash);
?>
On server 2 you will do almost the same.
<?php
$valid = false;
$dataKey = array();
$uniqueKey = 'fgsdjk%^347JH$#^%&5ghjksc'; //same as on server one
//check file
$file = isset($_GET['file']) ? $_GET['file'] : '';
if (empty($file)) {
die('Invalid request');
}
//check key
$key = isset($_GET['key']) ? $_GET['key'] : '';
if (empty($key)) {
die('Invalid request');
}
//add user data to create a reasonably unique fingerprint.
if (isset($_SERVER['HTTP_USER_AGENT'])) {
$dataKey[] = $_SERVER['HTTP_USER_AGENT'];
}
if (isset($_SERVER['REMOTE_ADDR'])) {
$dataKey[] = $_SERVER['REMOTE_ADDR'];
}
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
$dataKey[] = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
}
if (isset($_SERVER['HTTP_ACCEPT_ENCODING'])) {
$dataKey[] = $_SERVER['HTTP_ACCEPT_ENCODING'];
}
if (isset($_SERVER['HTTP_ACCEPT'])) {
$dataKey[] = $_SERVER['HTTP_ACCEPT'];
}
//also add the unique key
$dataKey[] = $uniqueKey;
//add the file
$dataKey[] = $file;
//add a timestamp. Since the request will be a different times, dont use the exact second
//keep the request time in a variable
$time = time();
$dataKey[] = date('YmdHi', $time);
//create a hash
$hash = md5(implode('-', $dataKey));
if ($hash == $key) {
$valid = true;
} else {
//perhaps the request to server one was made at 2013-06-26 14:59 and the request to server 2 come in at 2013-06-26 15:00
//It would still fail when the request to server 1 and 2 are more then one minute apart, but I think thats an acceptable margin. You could always adjust for more margin though.
//drop the current time
$requesttime = array_pop($dataKey);
//go back one minute
$time -= 60;
//add the time again
$dataKey[] = date('YmdHi', $time);
//create a hash
$hash = md5(implode('-', $dataKey));
if ($hash == $key) {
$valid = true;
}
}
if ($valid!==true) {
die('Invalid request');
}
//all is ok. Put the code to download the file here
?>
You can restrict access to server2. Only server1 will be able to send request to server2. You can do this by whitelisting ip of server1 on server side or using .htaccess file. In php you can do by checking request generated ip and validate it with server1 ip.
Also you can write a algorithm which generates a unique number. Using that algorithm generate a number on server1 and send it to server2 in request. On server2 check if that number is generated by algorithm and if yes then request is valid.
I'd go with a simple symetric encryption, where server 1 encodes the date and the authenticated user using a key known only by server 1 and server 2, sending it to the client who cant read it, but can send it to server 2 as a sort of ticket to authenticate himself. The date is important to not let any client use the same "ticket" over the time. But at least one of the servers must know which user have access to which files, so unless you use dedicated folders or access groups you must keep the user and file infos together.
For authentification with Dovecot, I use SSHA256 hashes but I have no clue how to validate a given password against the existing hash. The following PHP functions (found them in the web) are used to create the SSHA256 hash:
function ssha256($pw) {
$salt = make_salt();
return "{SSHA256}" . base64_encode( hash('sha256', $pw . $salt, true ) . $salt );
}
function make_salt() {
$len = 4;
$bytes = array();
for ($i = 0; $i < $len; $i++ ) {
$bytes[] = rand(1,255);
}
$salt_str = '';
foreach ($bytes as $b) {
$salt_str .= pack('C', $b);
}
return $salt_str;
}
Example output: {SSHA256}lGq49JTKmBC49AUrk7wLyQVmeZ7cGl/V13A9QbY4RVKchckL
Do I have to extract the salt, but how?
I totally lost the way for solving the problem, has anyone a hint for this?
Thanks to everyone for helping!
Oh and sorry, I have to use SSHA256, because Dovecot 1.2.15 supports only those schemes:
CRYPT MD5 MD5-CRYPT SHA SHA1 SHA256 SMD5 SSHA SSHA256 PLAIN CLEARTEXT CRAM-MD5 HMAC-MD5 DIGEST-MD5 PLAIN-MD4 PLAIN-MD5 LDAP-MD5 LANMAN NTLM OTP SKEY RPA
You should not be using the SHA family for password hashing. They are fast and designed for hashing files at speed. You need pashword hashing to be expensive. Use bcrypt, PHPass or just use this class, which I rolled myself (but not until you learn to pick holes in it):
class PassHash {
public static function rand_str($length) {
$total = $length % 2;
$output = "";
if ($total !== 0) {
$count = floor($length / 2);
$output .= ".";
} else $count = $length / 2;
$bytes = openssl_random_pseudo_bytes($count);
$output .= bin2hex($bytes);
// warning: prepending with a dot if the length is odd.
// this can be very dangerous. no clue why you'd want your
// bcrypt salt to do this, but /shrug
return $output;
}
// 2y is an exploit fix, and an improvement over 2a. Only available in 5.4.0+
public static function hash($input) {
return crypt($input, "$2y$13$" . self::rand_str(22));
}
// legacy support, add exception handling and fall back to <= 5.3.0
public static function hash_weak($input) {
return crypt($input, "$2a$13$" . self::rand_str(22));
}
public static function compare($input, $hash) {
return (crypt($input, $hash) === $hash);
}
}
You have to hash the plaintext given and compare that hash against one you have stored. The salts are stored in the hashes, and should be random. If you like, add a pepper. You should also make the workrate variable, so that you can change the workrate at any moment when needed and still have your system work.
If, like you say, you have no way of implementing this, you can unpack the hash as follows:
function unpack_hash($hash) {
$hash = base64_decode($hash);
$split = str_split($hash, 64);
return array("salt" => $split[1], "hash" => $split[0]);
This is because SHA256 is 256 bits, or 64 hex characters. You can just always assume the first 64 chars are the hash
You need to store the salt along with the hashed value.
When you need to validate the password, you simply calculate the hash again with the user input password + the stored salt. If the hashes match, the user entered the correct password.
For your format, use base64_decode first, the last 4 bytes of the result will be the salt.
I recently began work on a project and it contains the following function to hash passwords :
function hash_password($password) {
$account_id = $this->account_id;
/*
* Cook up some randomness
*/
$password = str_rot13($password);
$random_chars = "1%#)(d%6^".md5($password)."&H1%#)(d%6^&HB(D{}*&$##$#FEFWB".md5($password)."``~~+_+_O(Ed##fvdfgRG:B>";
$salt = $account_id;
$salt = ((int)$salt * 123456789) * 1000;
$salt_len = strlen($salt);
for($i=0; $i <= $salt_len; $i++) {
$salt .= $random_chars[$i];
}
$salt = str_repeat($salt, 3);
return hash('sha256', base64_encode($password.$salt.$password), false);
}
*$account_id is unique to each user account.
My question is : Is this function any more secure than doing something as simple as :
$salt = sha1($account_id);
$hash = hash('sha256', base64_encode($password.$salt), false);
Cheers!
Using the account ID as a salt is probably not a good idea - if someone can steal your hashed passwords, then they can probably get the account ID's too. Having a more convoluted hash in code in this instance is therefore probably more secure, provided that the code is also well protected. Using a known random string as the salt in the code means that someone would have to hack both your data and your code in order to attack passwords - that has to be better than just having to attack the database alone.