Add more secure password storage via salt value and hmac

This commit is contained in:
Matt Wright
2012-07-18 13:27:30 -04:00
parent 1c606a242a
commit 68dd972bfa
8 changed files with 43 additions and 10 deletions
+1 -1
View File
@@ -114,7 +114,7 @@ using SQLAlchemy.::
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), unique=True)
password = db.Column(db.String(120))
password = db.Column(db.String(255))
remember_token = db.Column(db.String(255))
active = db.Column(db.Boolean())
authentication_token = db.Column(db.String(255))
+2 -2
View File
@@ -136,7 +136,7 @@ def create_sqlalchemy_app(auth_config=None, register_blueprint=True):
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), unique=True)
password = db.Column(db.String(120))
password = db.Column(db.String(255))
remember_token = db.Column(db.String(255))
last_login_at = db.Column(db.DateTime())
current_login_at = db.Column(db.DateTime())
@@ -180,7 +180,7 @@ def create_mongoengine_app(auth_config=None):
class User(db.Document, UserMixin):
email = db.StringField(unique=True, max_length=255)
password = db.StringField(required=True, max_length=120)
password = db.StringField(required=True, max_length=255)
remember_token = db.StringField(max_length=255)
last_login_at = db.DateTimeField()
current_login_at = db.DateTimeField()
+6 -2
View File
@@ -21,7 +21,7 @@ from werkzeug.local import LocalProxy
from . import views, exceptions
from .confirmable import requires_confirmation
from .utils import config_value as cv, get_config
from .utils import config_value as cv, get_config, verify_password
# Convenient references
_security = LocalProxy(lambda: current_app.extensions['security'])
@@ -32,6 +32,8 @@ _default_config = {
'URL_PREFIX': None,
'FLASH_MESSAGES': True,
'PASSWORD_HASH': 'plaintext',
'PASSWORD_SALT': None,
'PASSWORD_HMAC': False,
'AUTH_URL': '/auth',
'LOGOUT_URL': '/logout',
'REGISTER_URL': '/register',
@@ -308,7 +310,9 @@ class AuthenticationProvider(object):
raise exceptions.BadCredentialsError('Account requires confirmation')
# compare passwords
if _security.pwd_context.verify(password, user.password):
if verify_password(password, user.password,
salt=_security.password_salt,
use_hmac=_security.password_hmac):
return user
# bad match
+4 -1
View File
@@ -113,7 +113,10 @@ class UserDatastore(object):
pw = kwargs['password']
if not pwd_context.identify(pw):
kwargs['password'] = pwd_context.encrypt(pw)
pwd_hash = utils.encrypt_password(pw,
salt=_security.password_salt,
use_hmac=_security.password_hmac)
kwargs['password'] = pwd_hash
kwargs['remember_token'] = utils.get_remember_token(kwargs['email'],
kwargs['password'])
+3 -1
View File
@@ -77,7 +77,9 @@ def _check_http_auth():
except UserNotFoundError:
return False
rv = _security.pwd_context.verify(auth.password, user.password)
rv = utils.verify_password(auth.password, user.password,
salt=_security.password_salt,
use_hmac=_security.password_hmac)
if rv:
identity_changed.send(current_app._get_current_object(),
+5 -2
View File
@@ -16,7 +16,7 @@ from werkzeug.local import LocalProxy
from .exceptions import ResetPasswordError, UserNotFoundError
from .signals import password_reset, password_reset_requested, \
reset_instructions_sent
from .utils import send_mail, get_max_age, md5, get_message
from .utils import send_mail, get_max_age, md5, get_message, encrypt_password
# Convenient references
@@ -85,7 +85,10 @@ def reset_by_token(token, password):
if md5(user.password) != data[1]:
raise UserNotFoundError()
user.password = _security.pwd_context.encrypt(password)
user.password = encrypt_password(password,
salt=_security.password_salt,
use_hmac=_security.password_hmac)
print user.password
_datastore._save_model(user)
send_password_reset_notice(user)
+19 -1
View File
@@ -11,6 +11,7 @@
import base64
import hashlib
import hmac
import os
from contextlib import contextmanager
from datetime import datetime, timedelta
@@ -25,6 +26,23 @@ from .signals import user_registered, password_reset_requested
# Convenient references
_security = LocalProxy(lambda: current_app.extensions['security'])
_pwd_context = LocalProxy(lambda: _security.pwd_context)
def get_hmac(msg, salt=None, digestmod=None):
digestmod = digestmod or hashlib.sha512
return base64.b64encode(hmac.new(salt, msg, digestmod).digest())
def verify_password(password, password_hash, salt=None, use_hmac=False):
hmac_value = get_hmac(password, salt) if use_hmac else password
return _pwd_context.verify(hmac_value, password_hash)
def encrypt_password(password, salt=None, use_hmac=False):
hmac_value = get_hmac(password, salt) if use_hmac else password
return _pwd_context.encrypt(hmac_value)
def md5(data):
return hashlib.md5(data).hexdigest()
@@ -202,4 +220,4 @@ def capture_reset_password_requests(reset_password_sent_at=None):
try:
yield reset_requests
finally:
password_reset_requested.disconnect(_on)
password_reset_requested.disconnect(_on)
+3
View File
@@ -157,6 +157,9 @@ class DefaultSecurityTests(SecurityTest):
class ConfiguredSecurityTests(SecurityTest):
AUTH_CONFIG = {
'SECURITY_PASSWORD_HASH': 'bcrypt',
'SECURITY_PASSWORD_SALT': 'so-salty',
'SECURITY_PASSWORD_HMAC': True,
'SECURITY_REGISTERABLE': True,
'SECURITY_AUTH_URL': '/custom_auth',
'SECURITY_LOGOUT_URL': '/custom_logout',