I am setting up a new PHP app and would like to learn to salt and secure user password. I am unsure about which step during registration I need to do this at. Also, do I need to change my login forms as well?
if(isset($_POST['submit'])){
//protect and then add the posted data to variables
$username = protect($_POST['username']);
$password = protect($_POST['password']);
$passconf = protect($_POST['passconf']);
$email = protect($_POST['email']);
//check to see if any of the boxes were not filled in
if(!$username || !$password || !$passconf || !$email){
//if any weren't display the error message
echo "<center>You need to fill in all of the required filds!</center>";
}else{
//if all were filled in continue checking
//Check if the wanted username is more than 32 or less than 3 charcters long
if(strlen($username) > 32 || strlen($username) < 3){
//if it is display error message
echo "<center>Your <b>Username</b> must be between 3 and 32 characters long!</center>";
}else{
//if not continue checking
//select all the rows from out users table where the posted username matches the username stored
$res = mysql_query("SELECT * FROM `users` WHERE `username` = '".$username."'");
$num = mysql_num_rows($res);
//check if theres a match
if($num == 1){
//if yes the username is taken so display error message
echo "<center>The <b>Username</b> you have chosen is already taken!</center>";
}else{
//otherwise continue checking
//check if the password is less than 5 or more than 32 characters long
if(strlen($password) < 5 || strlen($password) > 32){
//if it is display error message
echo "<center>Your <b>Password</b> must be between 5 and 32 characters long!</center>";
}else{
//else continue checking
//check if the password and confirm password match
if($password != $passconf){
//if not display error message
echo "<center>The <b>Password</b> you supplied did not math the confirmation password!</center>";
}else{
//otherwise continue checking
//Set the format we want to check out email address against
$checkemail = "/^[a-z0-9]+([_\\.-][a-z0-9]+)*#([a-z0-9]+([\.-][a-z0-9]+)*)+\\.[a-z]{2,}$/i";
//check if the formats match
if(!preg_match($checkemail, $email)){
//if not display error message
echo "<center>The <b>E-mail</b> is not valid, must be name#server.tld!</center>";
}else{
//if they do, continue checking
//select all rows from our users table where the emails match
$res1 = mysql_query("SELECT * FROM `users` WHERE `email` = '".$email."'");
$num1 = mysql_num_rows($res1);
//if the number of matchs is 1
if($num1 == 1){
//the email address supplied is taken so display error message
echo "<center>The <b>E-mail</b> address you supplied is already taken</center>";
}else{
//finally, otherwise register there account
//time of register (unix)
$registerTime = date('U');
//make a code for our activation key
$code = md5($username).$registerTime;
//insert the row into the database
$res2 = mysql_query("INSERT INTO `users` (`username`, `password`, `email`, `rtime`) VALUES('".$username."','".$password."','".$email."','".$registerTime."')");
//send the email with an email containing the activation link to the supplied email address
You absolutely must read this article: Enough with the rainbow tables.
Summary: If you're not using BCrypt, you're doing it wrong. No ifs, no buts. (This also means that all the suggestions to use MD5 or SHA-1 or SHA-512 or anything else are wrong too.)
As for when you do it, it should be sometime before you insert it into the DB but after you check it for errors.
Some suggestions though.
Instead of nesting the ifs during error checking so that if username fails, password doesn't get checked, and if password fails, passconf doesn't get checked try something like this:
$errors = array();
if(strlen($username) > 32 || strlen($username) < 3)
{
$errors['username'] = "Username must be between 3 and 32 characters.";
}
else
{
$res = mysql_query("SELECT * FROM `users` WHERE `username` = '".$username."'");
$num = mysql_num_rows($res);
if($num == 1)
{
$errors['username'] = "Username already exists!";
}
}
if(strlen($password) < 5 || strlen($password) > 32)
{
$errors['password'] = "Password must be between 5 and 32 characters.";
}
else if($password != $confpass)
{
$errors['password'] = "Passwords do not match.";
}
etc. etc. etc. so that each field is checked and errors returned if there are any. Then you do something like this at the end:
if(!count($errors)) //or if(count($errors) == 0)
{
//code to process login/registration/whatever Do password hashing here.
}
else
{
//There were errors, do something else
}
This way you get all errors, so you can tell the user everything that's wrong with their input at once, and the code isn't as deeply nested.
Also, the people having the flame war on what hashing algorithm to use above, just ignore them unless you're trying to create a US Government or Corporate application. No attackers will care enough to actually attack otherwise, unless your application gets popular enough to warrant an attack. It is important that you hash it in some way though.
SECURITY IS HARD. Don't do it yourself but let the exports figure it out. You could read there specs/implementations(if open):
openid
google friend connect
facebook connect
twitter single sign in
just to name a few options.
Related
I'm trying to check if the entered username already exists or if the entered username is the current username.
I've Googled various SO questions but none seem to check if the current username is the submitted one.
The problem with the following code; it doesn't matter if the username is taken or not, it will still let you save.
$stmt = $engine->runQuery("SELECT user_name, user_email FROM users WHERE user_name=:username OR user_email=:email");
$stmt->execute(array(':username'=>$username, ':email'=>$email));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if(strtolower($row['user_name']) == strtolower($username) || $username !== $row['user_name']) {
$engine->authapi(false, 'Sorry, username is already taken. Please choose a different one.');
} elseif(strtolower($row['user_email']) == strtolower($email) && $email !== $_SESSION['user_email']) {
$engine->authapi(false, 'Email is already registered. You cannot use the same emails for multiple accounts.');
} else {
// save
}
How can I make it so it checks if the username is taken or not, and at the same time check if the submitted username is the current username (if so, let the user save)?
Actually, there are several issues in your code.
1) Your SQL. You can fetch more than one row here, for example you have two entries in your database, username: maio290, e-mail: a#foo.bar and username: maio291, e-mail: b#foo.bar. Now your user enteres username: maio290 and e-mail: b#foo.bar which will result in two entries selected. Most likely an edge case, but a valid one.
2) Your if: You're comparing strtolower($row['user_name']) == strtolower($username) OR $username !== $row['user_name']) - the second one doesn't make any sense with your error. Since that means: "hey, your user is not in our database, please take a different one" Also, the first comparision could be a lot nicer with using strcasecmp.
I would really split these two options, since it's a lot better to read and you don't have the problem with two selectable rows. Also, you let your database handle the comparision.
Therefore I would write the code like that:
<?PHP
// Select if username is taken
$stmt = $engine->runQuery("SELECT user_name FROM users WHERE user_name=:username");
$stmt->execute(array(':username'=>$username));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if(count($row) != 0)
{
$engine->authapi(false, 'Sorry, username is already taken. Please choose a different one.');
// I would actually return here, so we wouldn't need an else
}
else
{
// check if e-mail is registred
$stmt = $engine->runQuery("SELECT user_email FROM users WHERE user_email=:email");
$stmt->execute(array(':email'=>$email));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if(count($row) != 0)
{
$engine->authapi(false, 'Email is already registered. You cannot use the same emails for multiple accounts.');
}
else
{
// store
}
}
?>
I am trying to re-learn my web development skills for a current application project I am working on. I have verified my PHP back-end is getting the data passed to it from the HTML/JS form on the front-end. But when I submit the form the webpage does not mention any errors. Everything looks fine until I look at the database and see nothing was added. I do not see the echo at the end either that data was successfully added, or that it failed to add data.
register.php
<?php
include "conn.php";
if(empty($_POST['firstName']) || empty($_POST['lastName']) || empty($_POST['email']) || empty($_POST['password']) || empty($_POST['pin'])) {
echo "<br>Error: Please fill out all required fields.";
} else if($_POST['password'] != $_POST['confirmPassword']) {
echo "<br>Error: Passwords do not match.";
} else if($_POST['pin'] != $_POST['confirmPin']) {
echo "<br>Error: Pin codes do not match.";
} else if(strlen($_POST['password']) > 20 || strlen($_POST['password']) < 8) {
echo "<br>Error: Passwords must be between 8 and 20 characters in length.";
} else if(strlen($_POST['pin']) != 4) {
echo "<br>Error: Pin codes must be a maximum of 4 characters.";
} else if(strlen($_POST['firstName']) > 50 && strlen($_POST['lastName']) > 50 && strlen($_POST['email']) > 50) {
echo "<br>Error: First Name, Last Name, and E-mail fields must be shorter than 50 characters in length.";
} else if(!filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) {
echo "<br>Error: You must use a valid e-mail address.";
} else {
echo "<br><br>Successfully checked all data!";
$options = [
'cost' => 12,
];
$hash = password_hash($_POST['password'], PASSWORD_BCRYPT, $options);
$numericPin = (int)$_POST['pin'];
$currentDateTime = date("Y-m-d H:i:s");
$stmt = $conn->prepare("INSERT INTO account (firstName, lastName, email, password, pin, registerDate) VALUES (:firstName, :lastName, :email, :password, :pin, :registerDate)");
$stmt->bindParam(":firstName", $_POST['firstName']);
$stmt->bindParam(":lastName", $_POST['lastName']);
$stmt->bindParam(":email", $_POST['email']);
$stmt->bindParam(":password", $hash);
$stmt->bindParam(":pin", $numericPin);
$stmt->bindParam(":registerdate", $currentDateTime);
if($stmt->execute()) {
echo "<br><br>Data added successfully";
} else {
echo "<br><br>Failed to add data";
}
}
?>
MySQL DB Structure:
MySQL DB Structure Picture Link
You have a misspelled placeholder
":registerdate"
You have this in the SQL
$stmt = $conn->prepare("INSERT INTO account (firstName, lastName, email, password, pin, registerDate) VALUES (:firstName, :lastName, :email, :password, :pin, :registerDate)");
Specifically :registerDate vs :registerdate, casing matters here.
A few other things:
change include to require ask youself will the script work without it, well no. Then it needs to be require. Include won't give you an error when it fails to find the file.
Change your validation to help your end users
$errors = [];
if(empty($_POST['firstName']) || empty($_POST['lastName']) || empty($_POST['email']) || empty($_POST['password']) || empty($_POST['pin']))
$errors[] ="<br>Error: Please fill out all required fields.";
if($_POST['password'] != $_POST['confirmPassword'])
$errors[] ="<br>Error: Passwords do not match.";
if($_POST['pin'] != $_POST['confirmPin']) ....
Then count the array
if(count($errors) > 0 ){
echo implode("\n", $errors);
}else{
//--- do insert
}
This way you can show them all the errors at once. Otherwise they have to submit, correct, submit, correct etc etc... This will get very frustrating very quickly and the last thing you want to do is frustrate your users, they may just leave. I have and will leave sites that don't show me the errors right away, I would rather find a site offering what I want that is properly build then deal with one that can't even do proper validation. It makes me wonder what else cant be trusted on the site. (but that is me and I have experience in this thing).
Naming Conventions (opinion):
I always name DB fields all lowercase with spaces replaced with _. This reduces a lot of these kinds of issues because, is it registerdate, registerDate or RegisterDate.
There are a few other things related to table names that I do, but I won't bore you with all that.
Cheers!
So just to add the answer here, it was simple. I added a try/catch block around where the statement was being executed (and where the script was dying). Found that there was some kind of duplicate placeholder usage or mismatch. Then I double checked my placeholders and found :registerDate was bound as :registerdate. Fixed and boom, adding to database. Thanks all for helping!
This question already has answers here:
How to prevent duplicate usernames when people register?
(4 answers)
Closed 6 months ago.
I am trying to check if the email or password is taken. When I type in a taken username, it says username taken, if I type in a taken email, it says email taken but if I type a taken Email AND Username, it says "Good" instead of "Username and email taken." Does anyone know why it isn't working?
$userSql = "SELECT * FROM members WHERE username='$username'";
$emailSql = "SELECT * FROM members WHERE email='$email'";
$result = mysql_query($userSql);
$result2 = mysql_query($emailSql);
$count = mysql_num_rows($result);
$count2 = mysql_num_rows($result2);
if (!empty($first_name) && !empty($last_name) && !empty($email) && !empty($username) && !empty($password)) {
if ($count != 1) {
echo "<p style=\"color: red\">Email taken, try another. You may already have an account</p>";
}
else if ($count2 != 1) {
echo "<p style=\"color: red\">Username taken, try another. You may already have an account</p>";
}
else if ($count != 1 && $count2 != 1) {
echo "<p style=\"color: red\">Username and email taken, try another. You may already have an account</p>";
}
else {
echo "<p>Good</p>";
}
It's really frustation because I have no idea why it wouldn't work.
What you should do is set a constraint in your database for unique usernames and e-mail addresses. Then, try to do an insert and catch the exception when it fails. Otherwise, you could have a condition where nearly simultaneous users try to register at the same time, and between your SELECT and INSERT statements, the username or e-mail address might be used by someone else.
ALTER TABLE `members` ADD UNIQUE INDEX `email` (`email`);
You also have a real serious problem with SQL injection. Never concatenate data directly into a query, or you risk having the data getting confused with the command. The data must be escaped. The proper way to handle this is with prepared/parameterized queries which are available with PDO.
I have created this php login script. I was wondering weather it was secure and if not how could I improve it.
PHP Script
<?php
include_once ("ConnectToMySql.php");
session_start();
$username = $_POST['username'];
$username = mysql_real_escape_string($username);
$password = $_POST['password'];
$password = sha1($password);
$query = "SELECT password FROM users WHERE username = '$username';";
$result = mysql_query($query);
if(mysql_num_rows($result) < 1)
{
echo "This Username Is Not Registered!";
exit;
}
if(mysql_num_rows($result) == 1)
{
if ($password == $result)
{
echo "Logged In!";
}
else echo "Wrong Password!";
}
?>
Thanks
A first tip could be to show a common error for both invalid login cases: invalid username or password. That way an eventual attacker wouldn't know if the username is valid or not.
You could also make a single query matching both username and password. You would probably need more user information (to store in session?), so it would be a good idea to select those fields instead of the password (e.g. id, name).
Regarding the hashed password stored in the database, you could add a SALT to improve security.
http://en.wikipedia.org/wiki/Salt_%28cryptography%29
What I would do is change the query to the following:
"SELECT COUNT(*) FROM users WHERE username = '$username' AND password='$password';"
That way, you don't have to check if the password is correct afterwards (and you don't have to pass the sensitive data as well), you only have to check if the numbers of rows returned equal 1 and you can produce a single error message for both username/password.
In a login system, how can you tell if the user has entered the password incorrectly? Do you perform two SQL queries, one to find the username, and then one to find the username and matching (salted+hashed etc) password? I'm asking this because If the user entered the password incorrectly, I want to update the failed_login_attempts column I have.
If you perform two queries wouldn't that increase overhead?
If you did a query like this, how would you tell if the password entered was correct or not, or whether the username doesn't exist:
SELECT * FROM author
WHERE username = '$username'
AND password = '$password'
LIMIT 1
( ^ NB: I'm keeping it simple, will use hash and salt, and will sanitize input in real one.)
Something like this:
$user = perform_Query() // get username and password?
if ($user['username'] == $username && $user['password'] == $password)
{
return $user;
}
elseif($user['username'] == $username && $user['password'] !== $password)
{ // here the password doesn't match
// update failed_login_attemps += 1
}
You're overthinking it. Only one query is required:
SELECT * FROM author WHERE username = '$username';
Then do:
if ($user['password'] == saltedHash($password,$user['salt'])) {
return "successful";
}
else {
return "failed";
}
Username must be unique. Otherwise this won't work. I would advise against making username non-unique because it causes a lot of other headaches apart from this.
If you perform two queries wouldn't that increase overhead?
I'd say it doesn't matter really. Many complex web frameworks issue dozens or hundreds of queries per request. One more or less won't change things much.
I think it's really up to preference. Fetching the whole user row, and then checking the password on PHP side makes the most sense as far as I can see, because you then already have the ID you need to update the failed_logins column.
What we do is perform one query to find the user (based on userid) and we select the UserId, PwSalt, and PwHash.
If no user is found then we know it is an invalid username.
If the user is found, we hash the password and compare it to the pwHash from the query. If the hash doesn't match we update the failed login attempts.
In your code, $user will be empty if username or password is incorrect
SELECT password = '$password' AS is_legit, *
FROM author
WHERE username = '$username'
LIMIT 1
$user = perform_Query() // get username and password?
// $user will be empty if the username is incorrect
$user_exists = $user.length > 0;
// to make sure we don't address a
// non-existent array element
if($user_exists && $user['password'] == $password){
return $user;
}
elseif($user_exists && $user['password'] !== $password)
{ // here the password doesn't match, but the user does
// update failed_login_attemps += 1
// be sure to let the user know that the penetration
// attempt is halfway complete
}
else{
// F4il
}
The way (and many others) create login systems is like so:
On Registration
create a unique hash and store along with username,password
On Login
Pull username,password,hash from database
Use the clause WHERE username = '$username'
If theres 1 row, the username is correct
build a compiled hash with hash($post_pass,$user_hash) and compare with $user_pass
Also if you return anything at 1 point of your method anything after would not be run, so
if ($user['username'] == $username && $user['password'] == $password)
{
return $user;
}
elseif($user['username'] == $username && $user['password'] !== $password)
{ // here the password doesn't match
// update failed_login_attemps += 1
}
can be modified to
if ($user['username'] == $username && $user['password'] == $password)
{
return $user;
}
return false
because if ($user['username'] == $username && $user['password'] == $password) is met, then the return would be executed there for the false would not be executed.
Hope this helps.