I'm attempting to use Shiro for Authentication and Authorization for a JSF Web Application I'm building. Unfortunately, I'm still having some difficulty wrapping my head around how it all fits together.
I've been successful (100% using the shiro.ini file) configuring authentication back to a JDBC realm where a test set of credentials are stored. It has worked perfectly for me when credentials are stored in plaintext.
My ultimate goal is to unify an existing credential set in a MySQL database. The passwords are stored as SHA-256 salted hashes. I've spent an entire day reading over the documentation (minus Javadocs) that is available, but I'm still having some difficulty understanding exactly how to set it up.
In an attempt to implement in stages, I've modified my shiro.ini as follows with the intention of simply using SHA-256 hashes:
[main]
dataSource = org.apache.shiro.jndi.JndiObjectFactory
dataSource.resourceName = jdbc/Communicator_dev
jdbcRealm = org.apache.shiro.realm.jdbc.JdbcRealm
jdbcRealm.dataSource = $dataSource
dataSource.resourceRef = true;
jdbcRealm.authenticationQuery = select password from account where site_id = ?
jdbcRealm.userRolesQuery = select user_role from web_roles where site_id = ?
# From https://stackoverflow.com/questions/20742666/shiro-with-jdbc-and-hashed-passwords.
#
passwordService = org.apache.shiro.authc.credential.DefaultPasswordService
#configure the passwordService to use the settings you desire
#...
passwordMatcher = org.apache.shiro.authc.credential.PasswordMatcher
passwordMatcher.passwordService = $passwordService
#...
# Finally, set the matcher on a realm that requires password matching for account authentication:
jdbcRealm.credentialsMatcher = $passwordMatcher
The actual login logic is programmatic in a page backing bean. Here's the simple test source I'm currently using:
// Create auth token
UsernamePasswordToken token = new UsernamePasswordToken(this.siteID, this.password);
// Get the current subject
Subject currentUser = SecurityUtils.getSubject();
// Attempt to login
try {
currentUser.login(token);
} catch (AuthenticationException e) {
System.out.println("Invalid creds.");
return "";
}
return "authenticated.xhtml?faces-redirect=true";
This code works perfectly with plaintext passwords stored in my RDBMS, but now that I've hashed them, It's failing.
From my understanding of the framework, I believe the problem lies with the AuthenticationToken. I know that I need to use a different token to ultimately implement the Salted Hashes stored in my RDBMS, but I'm confused on how to proceed.
(1) I don't want to reinvent the wheel. Does Shiro have something that does this natively? I've checked out Les' links to PasswordMatcher and PasswordService (from link shiro with jdbc and hashed passwords) but this still isn't clear. Do I need to sub-classingPasswordMatcher?
(2) An architecture question: Who actually calls the doCredentialsMatch(..) method? Is it the Realm during the execution of the login(...) method?
(3) The AuthenticationInfo parameter of the doCredentialsMap(...) method .. Is that supplied by the Realm? Since Realms encapsulate the actual security data, is this an object created from, in my case, the SQL queries that return the password from the RDBMS?
Thank you very much for your time! I'm hoping to be able to contribute to the documentation when I get my head wrapped around it all.
Item 1:
I suspect you may be running into this issue involving the "salt style" parameter of the JdbcRealm which defaults to "NO_SALT". This causes hashing to work but if you're adding a salt to your password the realm will be unable to properly match them.
Here are your steps afterwards:
The default query for a COLUMN based salt style is as follows, "select password, password_salt from users where username = ?". If you cannot use that structure you need to provide a new query via your 'shiro.ini' with a similar structure.
jdbcRealm.authenticationQuery=select password, salt_column_here from users where username = ?
Here is a related question.
Item 2: Yes, the realm calls the doCredentialsMatch(..) method.
Item 3: Yes, the realm supplies the AuthenticationInfo to the doCredentialsMatch(..) method.
Related
I found similar replies but nothing really straightforward.
How can AES_DECRYPT be used only for the password field in a query using MySQL extension in NodeJS ?
What I have is as follow:
app.post("/verify",function(req,res){
connection.query('SELECT *, FROM `bosses` where u=? and p=?', [req.body.user,req.body.pass], function (error, results, fields) {
if(results.length){
session.loggedin=1;
res.redirect('/zirkus');
}else{
res.redirect('/soccer');
}
});
I assume that I need to modify the query with something like this:
connection.query('SELECT *, FROM `bosses` where u=? and p=AES_DECRYPT (?, 'ENCRYPTIONKEY')', [req.body.user,req.body.pass], function (error, results, fields) {
but somehow I can't get it to work properly. Should I use a placeholder for the encryption key too ?
EDIT
Thanks for the replies and explanation on why this was generally a bad idea :)
Here is a variation: no decryption password is stored in the code:
connection.query('SELECT *, AES_DECRYPT(p, ?) AS `key` FROM bosses WHERE u = ?', [req.body.pass, req.body.user], function (error, results, fields) {
console.log (req.body.pass + req.body.user )
if(results[0].key){
session.loggedin=1;
res.redirect('/zirkus');
}else{
res.redirect('/soccer');
}
});
});
Here the admin user types the decryption password in the form and if the decryption is successful (the key returns true) it allows the user to log in (without using or saving the password) else access is denied.
I assume that in this solution the only downside are the mysql logs right ?
Answer 1: Don't use encryption for storing user passwords. Use hashing.
There's no reason you need to decrypt user passwords, ever. Instead, when the user logs in, you hash their input with the same hashing function and compare the result to the hash string stored in the database.
Try bcrypt: https://www.npmjs.com/package/bcrypt
Also read https://blog.codinghorror.com/youre-probably-storing-passwords-incorrectly/
Answer 2: I never do encryption or hashing in SQL expressions. The reason is that the if you use the query log, it will contain the plaintext of the sensitive content, as it appears in SQL expressions. It will also be visible in the PROCESSLIST.
Instead, if you need to do encryption or hashing of sensitive content, do it in your application code, and then use the result in SQL statements.
Re your edit:
I assume that in this solution the only downside are the mysql logs right ?
No. The problem is that you're storing the password using reversible encryption. There is no reason to reverse a user password. If I visit a website that offers a "password recovery" feature where they can tell me what my password was (no matter how many other security checks they do), then I know they're storing passwords wrong.
If passwords are stored in a reversible encrypted format, this creates the possibility that someone else other than me can reverse the encryption and read my password. That will never happen with hashing, because you can't reverse hashing to get the original content.
If it is because of the logs ... ?
You could disable the query logs, of course. But there's also other places where the query is visible, such as:
the binary log (if you use statement-based binary logs)
the PROCESSLIST
the performance_schema statement tables
the MySQL network protocol. That is, if you don't use TLS to encrypt the connection between the application and the database, someone could intercept packets on the network and see the plaintext query with the plaintext content.
In your edited example, they could view the user's plaintext decryption key in any of the above contexts.
... why MySQL has this function ...?
There are legitimate uses of encryption other than user passwords. Sometimes you do need to decrypt encrypted content. I'm just talking about user passwords. User passwords can be authenticated without decryption, as I described at the top of this answer. It's covered in the blog I linked to, and also as a chapter in my book SQL Antipatterns Volume 1: Avoiding the Pitfalls of Database Programming.
Another use of encryption and corresponding decryption function in SQL is when you develop code as stored procedures. It would be inconvenient to have to return encrypted data to the client application just to decrypt it, and then send it back to your stored procedures for further processing it.
You have to use doubole quotes for the decryption key or escaping ut
connection.query('SELECT *, FROM `bosses` where u=? and p=AES_DECRYPT (?, "ENCRYPTIONKEY)', [req.body.user,req.body.pass], function (error, results, fields) {
if(results.length){
session.loggedin=1;
res.redirect('/zirkus');
}else{
res.redirect('/soccer');
}
});
But as in every language passwords are usually only stored as hashed values, so that they can't be easily reconstructed, even with the logs. so chelkc for example https://coderrocketfuel.com/article/using-bcrypt-to-hash-and-check-passwords-in-node-js
I am trying to verify user login my matching the input password to the password input by user
My insert query:
insert into login (Emp_id, Emp_Fname, Emp_Lname, Username, Password) values (5, 'TestFName', 'TestLName', 'Test', password('april'));
it stores the password as this value :
*72B46CDA233C759A88BEF81F59F66D78B26B2848
select * from login where password = '*72B46CDA233C759A88BEF81F59F66D78B26B2848'; -- this line shows me the result
select password('april'); -- this returns *72B46CDA233C759A88BEF81F59F66D78B26B2848
select * from login where password = 'password(april)'; -- this returns an empty set
Is there any alternative to this line of code?
I think you need to use:
select * from login where password = password('april');
So, don't quote the whole password function, just the argument to the function.
One cannot safely store passwords with pure SQL commands, instead a dedicated password-hash function of the development language should be used. In PHP this would be the functions password_hash() and password_verify() for the verification of the password.
Even more, MySql's password() function was never intended to be used with user passwords and is deprecated (will be removed in future versions). Have a look at the second note box in the documentation.
The reason why you cannot left the hashing to the SQL command is, that salted password hashes cannot be searched for in the database. The searching has to be done by user name only and afterwards one can verify the found password hash with the user input. A more in-depth explanation you can find in this answer.
https://dev.mysql.com/doc/refman/5.7/en/encryption-functions.html#function_password says:
This function is deprecated as of MySQL 5.7.6 and will be removed in a future MySQL release.
PASSWORD() is used by the authentication system in MySQL Server; you should not use it in your own applications.
That wasn't an idle warning. https://dev.mysql.com/doc/refman/8.0/en/encryption-functions.html#function_password says:
This function was removed in MySQL 8.0.11.
So don't use PASSWORD() — unless you plan to never upgrade to MySQL 8.0.
Besides that, you have some problems in your code.
insert into login (Emp_id, Emp_Fname, Emp_Lname, Username, Password)
values (5, 'TestFName', 'TestLName', 'Test', password('april'));
I wouldn't use password (or any other hashing function) in this way, because you still have the plaintext password in your SQL statement. This ends up getting logged in query logs and statement-based binary logs, so it's a security weakness. That is, anyone who can get access to your logs can inspect the passwords.
Instead, hash the password in your app, and then put the result of that hash into your SQL statement.
Which hashing function you use depends on the language you use to write your application code. #martinstoeckli mentions a couple of functions that are used by PHP developers, but those won't be the same for other programming languages. You don't mention which language you use.
Likewise, when you search for a login that has that password, it works if you search for a specific hash string, but this doesn't work:
select * from login where password = 'password(april)'; -- this returns an empty set
The reason is that you're searching for the string 'password(april)'. Putting an expression in quotes means to use that literal string — it won't execute the function and use the result of it.
Again, you don't want to calculate the hash using SQL anyway. That puts the plaintext password into query logs and is not good for security.
You want to produce the hash string in your app, and then use the hash string in searches, like your first example. But not using the PASSWORD() function — using some application code function.
select * from login where password = '*72B46CDA233C759A88BEF81F59F66D78B26B2848';
(The hash string above is based on your example. It's a hash produced by MySQL's PASSWORD() function, only as strong as a SHA1 hash, which is known to be unsuitable for passwords.)
Actually, my preferred method is not to search for a password at all. Search for the login, and return the password hash string that is stored in the database.
select password from login where user = 'billkarwin'
Then in the application code, compare the hash string you fetched from the database against the re-calculation of the hash string based on the user's input when they're trying to log in.
I am setting up a MySQL connection (in my case PDO but it shouldn't matter) in a REST API.
The REST API uses an internal authentication (username / password). There are multiple user groups accessing the REST API, e.g. customers, IT, backend, customer service. They all use the same MySQL connection in the end because they also use the same end points most of the time.
In the MySQL database I would like to save the user who is responsible for a change in a data set.
I would like to implement this on the MySQL layer through a trigger. So, I have to pass the user information from the REST API to this trigger somehow. There are some MySQL calls like CURRENT_USER() or status that allow to query for meta-information. My idea was to somehow pass additional information in the connection string to MySQL, so that I don't have to use different database users but I am still able to retrieve this information from within the trigger.
I have done some research and don't think it is possible, but since it would facilitate my task a lot, I still wanted to ask on SO if someone did know a solution for my problem.
I would set a session variable on connect.
Thanks to the comment from #Álvaro González for reminding me about running a command on PDO init.
The suggestion of adding data to a temp table isn't necessary. It's just as good to set one or more session variables, assuming you just need a few scalars.
$pdo = new PDO($dsn, $user, $password, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::MYSQL_ATTR_INIT_COMMAND => "SET #myvar = 'myvalue', #myothervar = 'othervalue'"
]);
It's also possible to set session variables at any time after connect, with a call to $pdo->exec().
$pdo->exec("SET #thirdvar = 1234");
You can read session variables in your SQL queries:
$stmt = $pdo->query("SELECT #myvar, #myothervar");
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
print_r($row);
}
You can also read session variables in triggers:
CREATE TRIGGER mytrig BEFORE INSERT ON mytable
FOR EACH ROW
SET NEW.somecolumn = #myvar;
I am on a problem which need a quick solution. I will try to explain it.
I have the table wp_user (from WordPress), where all the members have password encrypted with a wordpress encrypting function. And I have to transfer this table to a new Mysql Database on a new web site working with Codeigniter.
For new members on the new site which works under codeigniter, I use the MD5 function in order to mask password.
But the problem is that the two functions are different, so when an older user try to connect on my new web site, that doesn't work because the passwords don't match ...
So, how I can do for translate the wordpress encrpyting in a normal MD5 password?
Or maybe it's not possible?
Unfortunately you'll have to perform both checks, or just change CI to use the same hashing scheme as WP. That's probably the easier option. If you really want to start using the default CI hashing scheme, you could store both hashes and update the new hash on successful login (when you have the plaintext password).
I'm not familiar with what kind of hashing Wordpress uses for passwords, but I assume that it is secure and irreversible. The hashed passwords cannot be converted to their MD5 equivalent because hashing is a one-way algorithm.
Here is my suggestion for you:
Add a using_md5_flag boolean column to the users table in your new website with default value 0. Copy the passwords over from Wordpress into a column wppassword and also create a column called md5password When users log into the system perform the following code (assumes Datamapper ORM, convert to Active Record if you need to):
$u = new User();
$u->where('username', $this->input->post('username'))->get();
$y = new User();
$y->where('username', $this->input->post('username'));
if($u->using_md5_flag){
/*the flag is 1*/
$y->where('md5password', md5($this->input->post('password')));
$y->get();
if($y->exists()) echo "setting cookie and redirecting to logged in area";
else echo "Wrong credentials!";
}
else{
/*the flag is 0, use wordpress's hashing algorithm (not sure what this is)*/
$y->where('wppassword', wp_hashing_algo($this->input->post('password')));
$y->get();
if($y->exists()){
/*set the using_md5_flag flag so next time they log in it will ignore wp's*/
$y->using_md5_flag = 1;
/*set the new md5 password.*/
$y->md5password = md5($this->input->post('password'));
$y->save();
echo "setting cookie and redirecting to logged in area";
}
else{
echo "Wrong credentials.";
}
}
This code hasn't been tested, I wrote it inside StackOverflow's editor... but It's the method I'd take to perform slow conversion to a more secure hash. Lastly, if you're looking for a really secure hash, check out Bcrypt (phpass), it's more resistant to rainbow table attacks.
Update 1: If you need to use the Phpass library with CodeIgniter, you can find a copy I modified here (I added a constructor). Put this in libraries/Phpass.php You can use the library in your controllers using:
$this->load->library("phpass", array("iteration_count_log2" => 8, "portable_hashes" => FALSE));
$check = $this->phpass->CheckPassword($this->input->post('password'), $u->password);
if($check) /*logged in*/
else /*wrong credentials.*/
When you download the Phpass file it comes with a test.php file which demos how the functions work. I suggest reviewing it.
I've implemented my mail server as dictated here.
It works perfectly fine. My curiousity revolves around entering users into the database and authenticating them
Running:
INSERT INTO users (email, password) VALUES ('sales#example.com', ENCRYPT('password'));
Multiple times will give a different hash for the encrypted password as its utilizing a random salt. I.e. If I enter sales#example.com three times with the same password each hash is different...
My question to this is, how is it that the Postfix server can actually authenticate the password when a user logs in via a mail client?
There isn't any problem per say as it works fine, more just to satisfy my curiosity so I can fully understand whats going on behind the scenes to properly authenticate the encrypted password.
Postfix compares the password from the database to a new encrypt done with the salt(password from db).
to encrypt:
update user set password = ENCRYPT('1234') where id = 1
to check password:
SELECT u.* FROM user u where u.email ='admin#dominio.com'
and ENCRYPT('1234', u.password) = u.password
Read man crypt: it returns the salt in the first two chars of the return value.
So the salt is not lost, you can compare the encrypted string to the result of crypt( 'pass', $first_two_chars_of_encrypted_value ).
You must use ENCRYPT('pass','salt') to force a salt, otherwise the salt is lost forever and you have no way of recovering it. Fairly pointless function without it. It's a terrible function to use, though, because the security is so minimal; use PASSWORD() or OLD_PASSWORD() instead.
ENCRYPT() uses the system crypt(), which may use all or only the first 8 characters, must be printable 7-bit ascii, generally uses 1 round of a DES-based hash, and is completely unportable. Avoid it.