Better crypto for storing passwords
authorRogdham <contact@rogdham.net>
Thu, 30 Aug 2012 14:40:02 +0000 (16:40 +0200)
committerRogdham <contact@rogdham.net>
Thu, 30 Aug 2012 14:53:33 +0000 (16:53 +0200)
Instead of hash(passwd), store hash(SALT, key, passwd) where:
 - SALT is application-specific
 - key is random and changed each time passwd changes

To login as admin the first time, go and see /login/1/victory

main.py
schema.sql

diff --git a/main.py b/main.py
index c3ebd37..50a8452 100755 (executable)
--- a/main.py
+++ b/main.py
@@ -15,11 +15,12 @@ import smtplib
 import string
 
 DATABASE = '/tmp/cavote.db'
+PASSWD_SALT = 'change this value to some random chars!'
 SECRET_KEY = '{J@uRKO,xO-PK7B,jF?>iHbxLasF9s#zjOoy=+:'
 DEBUG = True
 TITLE = u"Cavote FFDN"
 EMAIL = '"' + TITLE + '"' + ' <' + u"cavote@ffdn.org" + '>'
-VERSION = "cavote 0.1.0"
+VERSION = "cavote 0.1.1"
 SMTP_SERVER = "10.33.33.30"
 PATTERNS = {u'Oui/Non': [u'Oui', u'Non'], u'Oui/Non/Blanc': [u'Oui', u'Non', u'Blanc'], u'Oui/Non/Peut-être': [u'Oui', u'Non', u'Peut-être']}
 
@@ -57,8 +58,17 @@ def init_db():
 #----------------
 # Login / Logout
 
-def valid_login(username, password):
-    return query_db('select * from users where email = ? and password = ?', [username, crypt(password)], one=True)
+def valid_login(email, password):
+    # get user key
+    user_key = query_db('select key from users where email = ?', (email,),
+            one=True)
+    if not user_key:
+        # no such user
+        return None
+    user_key = user_key['key']
+    # try password
+    return query_db('select * from users where email = ? and password = ?',
+            [email, crypt(password, user_key)], one=True)
 
 def connect_user(user):
     session['user'] = user
@@ -68,8 +78,12 @@ def connect_user(user):
 def disconnect_user():
     session.pop('user', None)
 
-def crypt(passwd):
-    return hashlib.sha1(passwd).hexdigest() 
+def crypt(passwd, user_key):
+    # the per-user salt should not be stored in the db
+    # storing the passwd... but this is better than nothing
+    per_user_salt = hashlib.sha1(user_key).hexdigest()
+    salt_passwd = '%s%s%s' % (app.config['PASSWD_SALT'], per_user_salt, passwd)
+    return hashlib.sha1(salt_passwd).hexdigest()
 
 def keygen():
     return hashlib.sha1(os.urandom(24)).hexdigest()
@@ -116,7 +130,7 @@ def password_lost():
         if user is None:
             flash('Cet utilisateur n\'existe pas !', 'error')
         else:
-            key = keygen()
+            key = 'v%s' % keygen() # start with v: valid key
             g.db.execute('update users set key = ? where id = ?', [key, user['id']])
             g.db.commit()
             link = request.url_root + url_for('login_key', userid=user['id'], key=key)
@@ -145,12 +159,10 @@ def password_lost():
 @app.route('/login/<userid>/<key>')
 def login_key(userid, key):
     user = query_db('select * from users where id = ? and key = ?', [userid, key], one=True)
-    if user is None or user['key'] == "invalid":
+    if user is None or user['key'][0] != "v":
         abort(404)
     else:
         connect_user(user)
-        g.db.execute('update users set key = "invalid" where id = ?', [user['id']])
-        g.db.commit()
         flash(u"Veuillez mettre à jour votre mot de passe", 'info')
         return redirect(url_for('user_password', userid=user['id']))
 
@@ -193,7 +205,12 @@ def user_password(userid):
         abort(401)
     if request.method == 'POST':
         if request.form['password'] == request.form['password2']:
-            g.db.execute('update users set password = ? where id = ?', [crypt(request.form['password']), session['user']['id']])
+            # new (invalid) key
+            key = 'i%s' % keygen() # start with i: invalid key
+            print "\n\nchange key for %s\n" % key # FIXME TMP
+            g.db.execute('update users set password = ?, key = ? where id = ?',
+                    [crypt(request.form['password'], key),
+                        key, session['user']['id']])
             g.db.commit()
             flash(u'Votre mot de passe a été mis à jour.', 'success')
         else:
@@ -231,13 +248,15 @@ def admin_user_add():
             if query_db('select * from users where email=?', [request.form['email']], one=True) is None:
                 if request.form['username']:
                     if query_db('select * from users where name=?', [request.form['username']], one=True) is None:
-                        password = keygen()
                         admin = 0
                         if 'admin' in request.form.keys():
                             admin = 1
-                        key = keygen()
+                        key = 'v%s' % keygen()
                         g.db.execute('insert into users (email, name, organization, password, is_admin, key) values (?, ?, ?, ?, ?, ?)',
-                                [request.form['email'], request.form['username'], request.form['organization'], password, admin, key])
+                                [request.form['email'],
+                                    request.form['username'],
+                                    request.form['organization'],
+                                    '', admin, key])
                         g.db.commit()
                         user = query_db('select * from users where email = ?', [request.form["email"]], one=True)
                         if user:
index 700c2a3..88a542b 100644 (file)
@@ -83,9 +83,10 @@ create table user_choice (
 
 -- Test data
 
-INSERT INTO users (id, email, password, name, organization, is_admin, key) VALUES (1, "admin@admin.fr", "d033e22ae348aeb5660fc2140aec35850c4da997", "Toto (admin) Tata", "World corp", 1, "test"); -- mdp = admin
+INSERT INTO users (id, email, password, name, organization, is_admin, key)
+VALUES (1, "admin@admin.fr", "", "Toto (admin) Tata", "World corp", 1, "victory");
+-- to login, go to /login/1/victory
 INSERT INTO groups (id, name, system) VALUES (1, "Tous", 1);
 INSERT INTO groups (name) VALUES ("CA");
 INSERT INTO groups (name) VALUES ("Membres");
 INSERT INTO user_group (id_user, id_group) VALUES(1, 1);
-