Hashing + salting emails used in authentication - good or bad practice? - django

Is hashing + salting of email addresses and usernames not a normal thing to do? Like in a data breach you wouldn't be able to know who is registrered to the service since every field is hashed + salted? Is there any cons to this since there isnt much about it on the internet?

It is not standard practice to salt and hash usernames / email addresses.
It is true that an attacker will be unable to identify the stored usernames/ email addresses if salted and hashed prior to storage. In fact, nobody will be able to access the usernames/ emails (including authorized users like the system administrator).
Sounds secure, so why is this a problem?
When a user attempts to login, they will send a username (or email) and password. Since each salt is unique to that specific username, the only way to associate the username/email with the stored hash username/email is to test every salt + hash combination until either a match is found or every entry is tried / rejected.
While this might be possible for a database consisting of few users, it is infeasible in practice because hashing is computationally demanding. Imagine waiting hours or days for a login service to compute every salt+hash combination only to find the username was simply misspelled.
Additionally, if you are salting usernames then how will you prevent duplicates?
Usernames must be unique. If you salt the username then you have no way of preventing multiple users from using the same username.
What other methods can a developer employ to protect information?
The most obvious solution is database encryption. While this is a bit outside the scope of your question, Wikipedia has a good article covering this topic.

It is not common to hash and salt usernames and email addresses for several reasons:
Usability
If your email address was hashed, the website could not display your email address as the value is hashed.
Security
From a security standpoint, you wouldn't gain much by hashing your email address. Imagine if somebody had access to your email address, they could use the same hashing algorithm and find your credentials easily by iterating through the database. That is why some website have implemented a separate login authenticating name and display name. The login name remains hashed (unsalted) while the display name is stored unhashed. Unless the attacker has your login name/address, there is no way of compromising your data. In this particular case, hashing of email addresses guarantees full security of your data.

Related

How to authenticate with encrypted email as username?

In my Django web-app, I would like the user to authenticate itself with an encrypted email address that would simply be the username. Due to the existing GDPR regulations in my country, I have to encrypt e-mail addresses and by doing it with the help of Python Cryptography and Fernet functions, each string is different after encryption, even if two strings are encrypted with one and the same key. Is it possible to authenticate the user without errors in such a situation? If this is possible, where can I read a little more about it?
EDIT: Maybe I incorrectly specified: Django uses username and password for authentication, if the encrypted email is username, when logging in, the user will enter the email when logging in, i.e. harry#example.com. The database keeps an encrypted version of this email, so when using authenticate(request, username, password), it will look for a user with the username harry#example.com, not the encrypted version. If at this point I would like to decrypt the user's e-mail from the database and compare it with the e-mail that the user entered when logging in, app would probably has to decrypt all e-mails in the database, and then check if and which one is harry#example.com and here, in my opinion, it becomes quite problematic, because I have the impression that it is a not good solution in terms of time and server load. Is there any other way that I will be able to compare the e-mail entered when logging in and the encrypted e-mail in the database?
Here is a good lesson on how to use python cryptography https://www.geeksforgeeks.org/how-to-encrypt-and-decrypt-strings-in-python/
As for GDPR, the user can enter their email but you should encrypt it on the store, then decrypt it when you want to use it. Make sure that your secret is hidden. If someone gets access to your database and your secret, the encryption is as good as if it's not there.
You should not be comparing the encrypted strings, you should decrypt the email and compare it to the email that is currently entered. Comparing hashes should only be done with hashing, not encryption. If you don't want to have access to the user's email, you should consider hashing instead of encrypting.
There's a good read here How do I encrypt and decrypt a string in python?. To know the how-to around what you need. Plus, you described the solution quite well, so take a look at the following packages from the Django community which achieve what you are looking for:
https://github.com/orcasgit/django-fernet-fields/
https://github.com/orcasgit/django-fernet-fields/blob/master/fernet_fields/fields.py#L117 It includes an Encrypted email field
https://github.com/patowc/django-encrypted-field

AWS Cognito Workflow: Using email alias for primary username

So I am trying to get my head around AWS Cognito but I have hit some walls.
So, right now I can register an account, and verify it and sign in. Simple enough. The edge cases are where my walls are.
Here's the info I have so far:
username's cannot be changed once created
I am using UUIDs as my username values
email is marked as an alias, which in Cognito terms means I can use it to sign in with in addition to username.
if email is chosen as an alias, per the docs, the same value cannot be used as the username (http://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html#user-pool-settings-aliases):
If email is selected as an alias, a username cannot match a valid email format. Similarly, if phone number is selected as an alias, a username that matches a valid phone number pattern will not be accepted by the service for that user pool.
The email address can ONLY be used to sign in once the account has been verified (http://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html#user-pool-settings-aliases)
Phone numbers and email addresses only become active aliases for a user after the phone numbers and email addresses have been verified. We therefore recommend that you choose automatic verification of email addresses and phone numbers if you choose to use them as aliases.
Here in lies my edge case.
If a user signs up, but does NOT immediately verify:
they get called away
maybe the app crashes
they lose connectivity
their battery dies
they force quit
app get's accidentally deleted.
In their mind they have signed up just not verified their account. At this point it effectively leaves no way to verify their account they thought they signed up for. I guess it could be solved with messaging:
"Warning your account will not be created until you verify your email address." or something along those lines. Anyway...
They can't attempt to sign in as they won't know the UUID that was randomly assigned as their username.
Even if that wasn't the case, they provided their email address as their username. From the user's POV they would have no idea what their username could even be since they only entered their email address.
The best they could hope for is to try to sign up again. (Assuming they read the verification warning above) In this case now Cognito potentially has abandoned unconfirmed accounts piling up.
"Piling up" may be too strong a phrase, this is likely a pretty fringe case.
Now the plus side is, since they have not "verified" their email they can sign up again with the same email address since the email doesn't get uniquely constrained until it's verified. If someone tries to verify an address that has already been verified they get a AliasExistsException. This actually brings up an interesting point which I just tested as well.
I can register with an email address, then verify that email address so the account becomes confirmed. I can then turn right around and sign up with the same email address and I don't get an official AWS error until I try go to verify that account with the duplicate email address. There isn't any way to surface this error earlier? I guess the expectation is that it's on the developer to write a verification service in the Pre-Signup Trigger:
This trigger is invoked when a user submits their information to sign up, allowing you to perform custom validation to accept or deny the sign up request.
To sum up, and to restate the question:
It seems to be required, practically speaking, that when using an email address with Cognito a Pre-Signup Lambda is required to ensure an account with an email doesn't already exist since the AWS Exception won't be handled until a verification attempt is made.
Is my assumption here correct? By required here I think it's pretty reasonable to let a user know an email address is not available as soon as possible. For example:
John Doe : jdoe#gmail.com
Jane Doe : jdoe#gmail.com
You are correct.
Another solution is to create a lambda (not triggered by preSignUp) and called whenever the user finished typing into the email field. And getting a response "This email is already used" or "This email is available" before even sending the sign-up event.
Referring the first part of your question. If the user does not immediately verify their email. You probably mean confirmation by code. I prefer using confirmation by link sent to email which avoids this problem.
Knowing that this is an old question, here's a solution for posterity... I am using generated UUIDs for usernames, just like you, undisclosed to the user.
When the user wants to confirm the code at a later time (or perhaps ask to resend it), he doesn't know the username but he does know the email address that he registered with...
You can search for Cognito users with a certain email (or any other attribute) using ListUsers with a filter like email = "user#signupemail.com".
Once you find the user, you can access their username via response.Users[0].Username, and use it to confirm the account.

Trying to implement a passwordless authentication using Redis and random tokens

I want to implement a passwordless email based authentication (like in medium.com!), now I want to verify if this logic flow is secure or not:
User submits his email
Server generates a random token, sets to Redis a new value where the KEY is the token, the VALUE is the email or the corresponding user id, also make the key expires within an hour or some soon future value (within tens of minutes or hours), send a link including the token to the user email (i.e. example.com/login/token/{TOKEN})
User visits his inbox and press on that link
Server checks for {TOKEN} key, if it exists, authenticate the user and redirect to homepage, if not redirect to some error page.
Is this approach secure, is there something hidden I can't figure out yet?
Also Note that this method authenticate the user with only GET methods.
I use Django/Python, so if there is some package doing that, what is it?
Also is it relevant and secure for new user registrations/password-change/other user checks? Is there impact in case of DDoS attacks like making my server spam random emails, consuming the server using having a big Redis memory consumption in short time?
For token generation, I will use some value generated by random.SystemRandom().choice('abcdefghijklmnopqrstuvwxyz0123456789')
long enough to be hard to guess ( 60-80 characters) and it is valid for a short time like I mentioned.
Yes, I have used it in production quite successfully at my company. However, the use case is a bit different, as we use it for inter-service authentication once user has logged in using the password.
Spamming of random email addresses using your service is quite a real possibility in this scheme and you would have to put in checks like Captcha etc. to ensure this does not happen.
For actual implementation refer to this link:
Is there a way to configure Django Rest Framework to store token information in Redis rather than the Database?

What is the best way to store password in database when API call requires sending of password in plain text?

I'm writing a web app in Django that is interfacing with the Read it Later List API (http://readitlaterlist.com/api/docs/). However, all of the API calls require sending the username and password with each request. In order to do this, it seems that I need to store the user's password in plain text in my database. Am I missing something or is that the only approach possible in this case? Is there some way to store an encrypted password in my database, but yet be able to send a decrypted version of it when making the API call? If not, then are there any best practices that I should be following to safe-guard the user's password?
I'm pretty sure that this can't be the only service requiring sending password in plain-text. I'm interested in knowing how developers deal with these sort of services in general. I'm new to web development, so any help or pointers is appreciated.
do your users have to log into your website to use it? if you also are making use of a password authentication scheme, you could piggy back on top of that. Use the login password for your site as a cipherkey in a symmetric key cipher to encrypt the api password. then you need only store a hash of the users password (to your own site) and an encrypted password for the remote api.
Never save password in plain text. You can encrypt and decrypt the password but the problem is that the key you use to do the encryption and decryption will generally be accessible to anyone who has gained access to your server so it's not secure.
An alternative is to ask them to enter their password and save it in an encrypted cookie, or session variable or something else that will expire when they have logged out of your app. This has the drawback of them having to enter their password every time they user your app.

Django - auth user with the email domain and no password

I have a request to alter a standard Django login of email and password to allow any user login without a password, but only if they are on a certain domain ... e.g. somebody#example.com ... where the user is allowed in due to them being on the correct domain.
Any suggestions?
Assuming that 'being on the correct domain' means they have an email address for the relevant domain, you could write a custom authentication backend that
looks to check that there is a single user with that email address (and not more than one, which will also mean updating registration flows to ensure email addresses are guaranteed unique, plus possibly checking your DB for duplicates already, just in case)
gets that User and splits off the domain of their email address to check it against a list/whatever of allowed no-password-required domains
return the User from your custom auth backend as if the normal password check had been satisfied, even though it was never checked with check_password(). The Django docs and various djangosnippets.org snippets show how to do this.
In addition:
you will have to use a new/overridden authentication Form class for the admin login view that doesn't require a password field (but still shows it for non-special logins), so that it doesn't complain if there is no password entered.
And finally:
get religion, if you don't already have it
pray to your G/god(s) that no one else learns that the site that will allow no-password authentication with an email address, and especially that they don't also get hold of the email address(es) in question, particularly if your site holds ANY personal data about third parties or has to be PCI-DSS compliant etc, etc.
strongly consider saying 'No' to your client/user/manager/whoever requested this, for the reason immediately above. Passwords are used for a reason.
Or, finally, finally:
skip all of the above and tell your client/user/manager about some of the various password storage tools out there - eg this and this
If your user has an openid with the email somebody#example.com then you can use an OpenId solution (say Django-openid; there are others too) to verify his identity and allow him access.
If that is unlikely, then you'll need to find a custom way of ensuring that the user is who he claims to be.
Somehow like this:
if cleaned_data['email'].endswith('#example.com'):
user = None
try:
user = User.objects.get(email = cleaned_data['email'])
except:
pass
if user:
login(request, user)
Your concept allows everyone knowing or guessing one of the affected email-addresses to login without using a password!
Best regards!