Repository: zulip/zulip

Issue:
Limitation on password length?
I tried to use a 128 characters password on my Zulip profile. The password was accepted when I set it up, but then I was constantly getting rejected logins. I reset it three times via the 'forgot password' functionality.

When shortening it to 49 characters, it was accepted (like the 128 chards one) but then also functional (I could log in).

This would suggest a limit on the password length, which is a red light form a security perspective (there should be no limit because there is not reason for having one when the password is stored correctly). Is that the case?


Discussion:
I hope you discussed this in the [forum](https://chat.zulip.org/) first, @wsw70?
@PawBud no I did not. Is it expected to raise a bug / issue in the community first before (or instead) of here?

If so I will move it there. (not sure by what you mean when you say that you hope)
Well keep this issue open and go to the community page. thank you 😄
Hello @zulip/server-authentication members, this issue was labeled with the "area: authentication" label, so you may want to check it out!

<!-- areaLabelAddition -->
It would appear we set a maximum password length of 100 characters.

```
class RegistrationForm(forms.Form):                                                                                    
    MAX_PASSWORD_LENGTH = 100                                                                                          
    full_name = forms.CharField(max_length=UserProfile.MAX_NAME_LENGTH)                                                
    # The required-ness of the password field gets overridden if it isn't                                              
    # actually required for a realm                                                                                    
    password = forms.CharField(widget=forms.PasswordInput, max_length=MAX_PASSWORD_LENGTH)                             
    realm_subdomain = forms.CharField(max_length=Realm.MAX_REALM_SUBDOMAIN_LENGTH, required=False)                     
```

From https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#maximum-password-lengths:
```
Maximum Password Lengths
Some hashing algorithms such as Bcrypt have a maximum length for the input, which is 72 characters for most implementations (there are some reports that other implementations have lower maximum lengths, but none have been identified at the time of writing). Where Bcrypt is used, a maximum length of 64 characters should be enforced on the input, as this provides a sufficiently high limit, while still allowing for string termination issues and not revealing that the application uses Bcrypt.

Additionally, due to how computationally expensive modern hashing functions are, if a user can supply very long passwords then there is a potential denial of service vulnerability, such as the one published in Django in 2013.

In order to protect against both of these issues, a maximum password length should be enforced. This should be 64 characters for Bcrypt (due to limitations in the algorithm and implementations), and between 64 and 128 characters for other algorithms.
```

It seems like 100 characters is potentially reasonable; maybe 128 would feel more standard.  But I think there's still a bug here; the login and registration flows seem to be using a different truncation approach for too-long passwords (or something).  Needs investigation.

Thanks @timabbott for the response and the reminder about bcrypt (I was pre-hashing passwords in the implementation above 72 chars, which is a good or bad idea depending on the population and their passwords, but anyways).

> It seems like 100 characters is potentially reasonable

It is. What is indeed problematic as you are saying  is that a >100 chars password is accepted but truncated - and that truncated version is then stored as the actual password. Which means that a check at login time will always fail for such passwords.
@zulipbot claim
On a sidenote, I've noticed that after I reset the password for a user in the development environment, if I try to reset it again the email containing the reset link won't be logged to `http://localhost:9991/emails/`. Is this intended?

Resolving patch:
diff --git a/zerver/forms.py b/zerver/forms.py
--- a/zerver/forms.py
+++ b/zerver/forms.py
@@ -1,6 +1,6 @@
 from django import forms
 from django.conf import settings
-from django.contrib.auth import authenticate
+from django.contrib.auth import authenticate, password_validation
 from django.contrib.auth.forms import SetPasswordForm, AuthenticationForm, \
     PasswordResetForm
 from django.core.exceptions import ValidationError
@@ -194,6 +194,20 @@ class RealmCreationForm(forms.Form):
                                          email_is_not_disposable])
 
 class LoggingSetPasswordForm(SetPasswordForm):
+    new_password1 = forms.CharField(
+        label=_("New password"),
+        widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
+        strip=False,
+        help_text=password_validation.password_validators_help_text_html(),
+        max_length=RegistrationForm.MAX_PASSWORD_LENGTH,
+    )
+    new_password2 = forms.CharField(
+        label=_("New password confirmation"),
+        strip=False,
+        widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
+        max_length=RegistrationForm.MAX_PASSWORD_LENGTH,
+    )
+
     def clean_new_password1(self) -> str:
         new_password = self.cleaned_data['new_password1']
         if not check_password_strength(new_password):


Test patch: