7

I am trying to validate an encrypted password for login purposes in Pyramid. So that if the user and password match then the system will authorize the user. At the moment I am finding it difficult to write a function to compare passwords when one is encrypted in the database and the the password being entered into Pyramid's login form is unencrypted. Right now, I have no verification occurring in the login view.

I am new to this entire process of working with security measures/code and want to do this right. I was looking at this Auth tutorial, however the encryption in the User class is slightly different and I am using Pyramid's Auth kit. Any guidance on how to do this successfully and smartly would be highly appreciated.

Software: Python 2.7.9, Pyramid 1.5.7, SQLAlchemy 1.0.9


database class:

class User(Base):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    username = Column(String(15), nullable=False, unique=True)
    email = Column(String(300))
    password = Column(String(300), nullable=False)

    def __init__(self, username, password, email):
        self.username = username
        self.password = hashlib.sha224(password).hexdigest()
        self.email = email

    def __repr__(self):
        return "<User(username ='%s', password='%s', email='%s')>" % (self.username, self.password, self.email)

views

@view_config(route_name='login', renderer='templates/login.jinja2')
@forbidden_view_config(renderer='templates/login.jinja2')
def login(request):
    login_url = request.route_url('login')
    referrer = request.url
    if referrer == login_url:
        referrer = '/' # never use the login form itself as came_from
    came_from = request.params.get('came_from', referrer)
    message = ''
    login = ''
    password = ''
    if 'form.submitted' in request.params:
        login = request.params['login']
        password = request.params['password']

        user = api.retrieve_user(login) # need some way to validate password
        if user is not None: # need to check user/password here, redirect if wrong
            headers = remember(request, login)
            return HTTPFound(location = came_from,
                             headers = headers)
            message = 'Failed login'

    return dict(
        message = message,
        url = request.application_url + '/login',
        came_from = came_from,
        login = login,
        password = password,
        )
Mikko Ohtamaa
  • 82,057
  • 50
  • 264
  • 435
thesayhey
  • 938
  • 3
  • 17
  • 38
  • 1
    Please do not store user passwords using sha224, use a proper library that allows you to store passwords securely (such as [passlib](http://pythonhosted.org/passlib/)). – X-Istence Nov 15 '15 at 06:50

2 Answers2

11

Please modify your code, add the excellent passlib library, and use secure password storage using bcrypt as the hashing algorithm.

In your projects setup.py add the following as requirements:

  • bcrypt
  • passlib

And then use the following code snippet for your model:

from passlib.hash import bcrypt

class User(Base):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    username = Column(String(15), nullable=False, unique=True)
    email = Column(String(300))
    password = Column(String(300), nullable=False)

    def __init__(self, username, password, email):
        self.username = username
        self.password = bcrypt.encrypt(password)
        self.email = email

    def validate_password(self, password):
        return bcrypt.verify(password, self.password)

    def __repr__(self):
        return "<User(username ='%s', password='%s', email='%s')>" % (self.username, self.password, self.email)
X-Istence
  • 16,324
  • 6
  • 57
  • 74
  • Thank you for the code! I had no idea. I updated my code to reflect this stronger system. Cheers! – thesayhey Nov 16 '15 at 16:47
  • 1
    It was recently brought to my attention that using a context manager may help, since it allows you to easily switch to a new algorithm when the time comes: https://pythonhosted.org/passlib/lib/passlib.context-tutorial.html#module-passlib.context – X-Istence Nov 16 '15 at 21:51
  • 1
    The code here is now starting throw some issues: `passlib.registry: DEBUG: registered 'bcrypt' handler: passlib.handlers.bcrypt: DEBUG: 'bcrypt' backend lacks '$2$' support` – thesayhey Nov 17 '15 at 22:20
  • Looks like the bcrypt library I recommended doesn't support the salt mechanism used by pass lib. Take a look at these backends supported here and verify if it works with pass lib for that hashing algorithm, or change the salt algorithm: https://pythonhosted.org/passlib/lib/passlib.hash.bcrypt.html#index-0 – X-Istence Nov 19 '15 at 17:13
  • 2
    These days, passlib emits a deprecation warning when using `bcrypt.encrypt()`. We should use `bcrypt.hash()` instead. – Victor Domingos Mar 12 '18 at 12:53
  • Are you sure about the `String(300)` for the `password` column type? From [what I understand](https://stackoverflow.com/a/5882472/1720199) and also what I can observe `len(bcrypt.hash("test"))` this could very well be `String(60)`. – cglacet Jun 04 '20 at 19:35
  • @cglacet: I used the code the original poster had. You'd probably want to use something that is longer than what just `bcrypt` requires so that you can switch algorithms later. Passlib has a lot of functionality around that which will allow you to easily upgrade users to a new hashing scheme in the future. – X-Istence Jun 05 '20 at 20:08
3

WARNING INSECURE CODE FOLLOWS

The code below is NOT a SECURE and SAFE way to store/verify user passwords. Please use a library that provides secure password storage, such as passlib which is specifically designed to securely store passwords.


You hash the user's password in your User.__init__ using self.password = hashlib.sha224(password).hexdigest(). Just use a similar method to validate it:

class User(Base):
    # Your existing code unchanged

    def validate_password(self, password):
        return self.password == hashlib.sha224(password).hexdigest()

And use it in your view:

user = api.retrieve_user(login)
if user is not None and user.validate_password(password):
    # You logic on success
X-Istence
  • 16,324
  • 6
  • 57
  • 74
301_Moved_Permanently
  • 4,007
  • 14
  • 28
  • I was trying to validate without the addition of the ` hash...` code. This was so simple !!! Thank you so much for clarifying. I was going crazy over this. One more question. I notice if the user login is wrong it doesn't show the failed login message. Is that due to the placement of message after the return? Should I move it up? So I can redirect to create an account? – thesayhey Nov 13 '15 at 23:12
  • 1
    @thesayhey Yes, your `message = 'Failed login'` seems to be indented too much. Remove 4 spaces and you should be good. – 301_Moved_Permanently Nov 13 '15 at 23:49
  • Please do not use sha224 directly for the password. Use a proper library such as passlib! – X-Istence Nov 15 '15 at 06:46