Many people doesn't know about passwords stored in database...
What's the right and safe way to do?
OK! In this short presentation teaches about this.
The pgcrypto extension provides functions to ease hashing and salting passwords.
A Python script was used as example.
2. 2/30
About me
Juliano Atanazio
● Graduated in Computer Science for Business Management (Informática para Gestão de
Negócios), FATEC Zona Sul, São Paulo – SP;
● PostgreSQL DBA;
● Linux admin;
● Instructor (PostgreSQL);
● LPIC-1, LPIC-2 Certified;
● Linux user since 2000;
● Free Software enthusiast;
● Favorite technologies: PostgreSQL, Linux, Python, Shell Script, FreeBSD, etc...;
● Headbanger :) m/
3. 3/30
Plain Text Passwords
● Store passwords (in plain text) of application users in database
is not a good practice;
● The DBA doesn’t need and shouldn’t know the users passwords;
● If your database is invaded, the attacker will have access to user
passwords;
● The unencrypted password should never be stored in the
database.
4. 4/30
Hashing and Salting
Hashing and salting is a technique to store passwords securely:
1. Generate a random string (salt) with a desired algorithm (e. g.
blowfish based);
2. This salt and the plain text password are used to generate the
hash password through hashing process;
3. Hash password is stored in the database;
The password must be encrypted in an irreversible way, that you
can not decrypt it (theoretically).
5. 5/30
pgcrypto
pgcrypto is a extension that provides cryptographic functions for
PostgreSQL.
https://www.postgresql.org/docs/current/static/pgcrypto.html
6. 6/30
pgcrypto
gen_salt and crypt Functions
They are specifically designed for hashing passwords. crypt()
does the hashing and gen_salt() prepares algorithm
parameters for it.
● gen_salt(): Generates a new random salt string for use in
crypt(). The salt string also tells crypt() which algorithm to
use.
● crypt(): It generate hash password through passing the
password (in plain text) and salt as parameters.
7. 7/30
Salting and Hashing: Practice
First, you need to enable the pgcrypto extension in your database.
pgcrypto is a additional supplied module (a contrib module).
Enable the pgcrypto extension:
> CREATE EXTENSION pgcrypto;
9. 9/30
Salting and Hashing: Practice
Test; generate a salt string:
salt_string
-------------------------------
$2a$06$YmO7UZZZxkTWZfT8s7b/GO
> SELECT gen_salt('bf') AS salt_string;
blowfish algorithm
10. 10/30
Salting and Hashing: Practice
Test; generate the password hash with the previous salt string:
pw_hash
--------------------------------------------------------------
$2a$06$YmO7UZZZxkTWZfT8s7b/GOlO0rZpMhU674srD/dbSyplwO/clTZzi
> SELECT
crypt('mypass', '$2a$06$YmO7UZZZxkTWZfT8s7b/GO')
AS pw_hash;
11. 11/30
Salting and Hashing: Practice
Test; comparison with crypt function and previous hash string:
simple_auth_test
------------------
t
> SELECT
crypt('mypass',
'$2a$06$YmO7UZZZxkTWZfT8s7b/GOlO0rZpMhU674srD/dbSyplwO/clTZzi')
=
'$2a$06$YmO7UZZZxkTWZfT8s7b/GOlO0rZpMhU674srD/dbSyplwO/clTZzi'
AS simple_auth_test;
12. 12/30
Salting and Hashing: Practice
Create the table:
> CREATE TABLE tb_user(
username varchar(50) PRIMARY KEY, -- natural primary key
password VARCHAR(72) NOT NULL
);
13. 13/30
Salting and Hashing: Practice
Using CTE* to INSERT new user with password creation:
* CTE = Common Table Expressions
https://www.postgresql.org/docs/current/static/queries-with.html
> WITH x AS (
SELECT
'foo'::text AS user,
'123'::text AS pw,
gen_salt('bf')::text AS salt
)
INSERT INTO tb_user (username, password)
SELECT
x.user,
crypt(x.pw, x.salt) -- password hash
FROM x;
14. 14/30
Salting and Hashing: Practice
Enable expanded display automatically (psql):
Querying username and password in the table:
username | password
----------+--------------------------------------------------------------
foo | $2a$06$RqHcf7F.nUGLkQF1fOea.OLAU0gyz/liF3dO58JWTB0oyVirzUdgK
> x auto
> SELECT username, password FROM tb_user;
16. 16/30
Salting and Hashing: Practice
User authentication test with correct password:
acessed
---------
t
True value (boolean) returned.
> SELECT
crypt('123', password) = password
AS acessed
FROM tb_user
WHERE username = 'foo';
Plain text (password) provided by user
17. 17/30
Salting and Hashing: Practice
User authentication test with wrong password:
acessed
---------
f
False value (boolean) returned.
> SELECT
crypt('1234', password) = password
AS acessed
FROM tb_user
WHERE username = 'foo';
18. 18/30
Application Test: The Code (Python)
Continue
#!/usr/bin/env python3
# _*_ encoding: utf-8 _*_
from argparse import ArgumentParser
from getpass import getpass
from os import system
from psycopg2 import connect
from psycopg2 import Error
from sys import exit
19. 19/30
Application Test: The Code (Python)
Continue
# Argument parser
parser = ArgumentParser()
# Argument help strings
help_d = 'Database'
help_H = 'Hostname or IP address'
help_p = 'Port'
help_u = 'Username'
help_w = 'With password prompt'
21. 21/30
Application Test: The Code (Python)
Continue
# Test if password prompt is required
if args.password:
args.password = getpass('Database user password: ')
else:
args.password = None
# Connection string variable (initially as a list)
conn_str = []
# Take all provided paramaters and make the connection string
for k, v in vars(args).items():
if v:
str_tmp = "{} = '{}'".format(k, v)
conn_str.append(str_tmp)
conn_str = ' '.join(conn_str)
22. 22/30
Application Test: The Code (Python)
Continue
# SQL string for PREPARE command
sql_prepare = """
PREPARE q_user (text, text) AS
SELECT
crypt($2, password) = password
FROM tb_user
WHERE username = $1;
"""
# SQL string for EXECUTE command
sql_execute = "EXECUTE q_user('{}', '{}');"
# When occur authentication error...
def user_pw_error(connection):
print('nError: Invalid user and password combination!')
connection.close()
exit(1)
23. 23/30
Application Test: The Code (Python)
Continue
try:
# Connection
conn = connect(conn_str)
# Cursor creation to execute SQL commands
cursor = conn.cursor()
# Execute the SQL string in database
cursor.execute(sql_prepare)
# Clear Screen
system('clear')
# Get user and password of the application
app_user = input('nApplication user: ')
app_user_pw = getpass('Application user password: ')
# Execute the SQL string in database
cursor.execute(sql_execute.format(app_user, app_user_pw))
24. 24/30
Application Test: The Code (Python)
# The result of the string SQL execution
res = cursor.fetchone()
try:
# User login validation
if res[0]:
print('nAcessed!')
else:
raise
except:
user_pw_error(conn)
except Error as e:
print('nAn error has occurred!')
print(format(e))
exit(1)
# Close the database connection
conn.close()
25. 25/30
Application Test: Execution
$ ./salting.py
A simple test access with correct password:
Application user: foo
Application user password:
Acessed!
26. 26/30
Application Test: Execution
$ ./salting.py
A simple test access with wrong password:
Application user: foo
Application user password:
Error: Invalid user and password combination!
27. 27/30
Conclusion
PostgreSQL has its own mechanisms of encryption passwords
which makes it very independent of the application.
This makes it easier for the application developer, may delegate
such tasks to the database, avoiding technical adjustments in the
application and finally provide a robust solution independent of
programming language.