Authentication and Password Management

OWASP Secure Coding Practices is a valuable document for programmers to help them to validate if all best practices were followed during project implementation. Authentication and Password Management are critical parts of any system and they are covered in detail from user signup, to credentials storage, password reset and private resources access.

Some guidelines may be grouped for more in-depth details. Plus, source code examples are provided to illustrate the topics.

Rules of Thumb

Let’s start with the rules of thumb: “all authentication controls must be enforced on a trusted system” which usually is the server where the application’s backend is running.

For the sake of system’s simplicity, and to reduce the points of failure, you should utilize standard and tested authentication services. Usually frameworks already have such a module and you’re encouraged to use them as they are developed, maintained, and used by many people behaving as a centralized authentication mechanism. Nevertheless, you should “inspect the code carefully to ensure it is not affected by any malicious code”, and be sure that it follows the best practices.

Resources which require authentication should not perform it themselves. Instead, “redirection to and from the centralized authentication control” should be used. Be careful handling redirection: you should redirect only to local and/or safe resources.

Authentication should not be used only by the application’s users, but also by your own application when it requires “connection to external systems that involve sensitive information or functions”. In these cases, “authentication credentials for accessing services external to the application should be encrypted and stored in a protected location on a trusted system (e.g., the server). The source code is NOT a secure location”.

Communicating authentication data

In this section, “communication” is used in a broader sense, encompassing User Experience (UX) and client-server communication.

Not only is it true that “password entry should be obscured on user’s screen”,
but also the “remember me functionality should be disabled”.

You can accomplish both by using an input field with type="password", and setting the autocomplete attribute to off.

<input type="password" name="passwd" autocomplete="off" />

Authentication credentials should be sent only through encrypted connections (HTTPS). An exception to the encrypted connection may be the temporary passwords associated with email resets.

Remember that requested URLs are usually logged by the HTTP server (access_log), which include the query string. To prevent authentication credentials leakage to HTTP server logs, data should be sent to the server using the HTTP POST method.

xxx.xxx.xxx.xxx - - [27/Feb/2017:01:55:09 +0000] "GET /?username=user&password=70pS3cure/oassw0rd HTTP/1.1" 200 235 "-" "Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:51.0) Gecko/20100101 Firefox/51.0"

A well-designed HTML form for authentication would look like:

<form method="post" action="https://somedomain.com/user/signin" autocomplete="off">
    <input type="hidden" name="csrf" value="CSRF-TOKEN" />

    <label>Username <input type="text" name="username" /></label>
    <label>Password <input type="password" name="password" /></label>

    <input type="submit" value="Submit" />
</form>

When handling authentication errors, your application should not disclose which part of the authentication data was incorrect. Instead of “Invalid username” or “Invalid password”, just use “Invalid username and/or password” interchangeably:

<form method="post" action="https://somedomain.com/user/signin" autocomplete="off">
    <input type="hidden" name="csrf" value="CSRF-TOKEN" />

    <div class="error">
        <p>Invalid username and/or password</p>
    </div>

    <label>Username <input type="text" name="username" /></label>
    <label>Password <input type="password" name="password" /></label>

    <input type="submit" value="Submit" />
</form>

Using a generic message you do not disclose:

  • Who is registered: “Invalid password” means that the username exists.
  • How your system works: “Invalid password” may reveal how your application works, first querying the database for the username and then comparing passwords in-memory.

An example of how to perform authentication data validation (and storage) is available at Validation and Storage section.

After a successful login, the user should be informed about the last successful or unsuccessful access date/time so that he can detect and report suspicious activity. Further information regarding logging can be found in the Error Handling and Logging section of the document. Additionally, it is also recommended to use a constant time comparison function while checking passwords in order to prevent a timing attack. The latter consists of analyzing the difference of time between multiple requests with different inputs. In this case, a standard comparison of the form record == password would return false at the first character that does not match. The closer the submitted password is, the longer the response time. By exploiting that, an attacker could guess the password. Note that even if the record doesn’t exist, we always force the execution of subtle.ConstantTimeCompare with an empty value to compare it to the user input.


Validation and storing authentication data

Validation

The key subject of this section is the “authentication data storage”, since more often than desirable, user account databases are leaked on the Internet. Of course, this is not guaranteed to happen. But in the case of such an event, collateral damages can be avoided if authentication data, especially passwords, are stored properly.

First, let’s be clear that “all authentication controls should fail securely”. We recommend you read all other “Authentication and Password Management” sections, since they cover recommendations about reporting back wrong authentication data and how to handle logging.

One other preliminary recommendation is as follow: for sequential authentication implementations (like Google does nowadays), validation should happen only on the completion of all data input, on a trusted system (e.g. the server).

Storing password securely: the theory

Now let’s discuss storing passwords.

You really don’t need to store passwords, since they are provided by the users (plaintext). But you will need to validate on each authentication whether users are providing the same token.

So, for security reasons, what you need is a “one way” function H, so that for every password p1 and p2, p1 is different from p2, H(p1) is also different from H(p2)1.

Does this sound, or look like math? Pay attention to this last requirement: H should be such a function that there’s no function H⁻¹ so that H⁻¹(H(p1)) is equal to p1. This means that there’s no way back to the original p1, unless you try all possible values of p.

If H is one-way only, what’s the real problem about account leakage?

Well, if you know all possible passwords, you can pre-compute their hashes and then run a rainbow table attack.

Certainly you were already told that passwords are hard to manage from user’s point of view, and that users are not only able to re-use passwords, but they also tend to use something that’s easy to remember, hence somehow guessable.

How can we avoid this?

The point is: if two different users provide the same password p1, we should store a different hashed value. It may sound impossible, but the answer is salt: a pseudo-random unique per user password value which is prepended to p1, so that the resulting hash is computed as follows: H(salt + p1).

So each entry on a passwords store should keep the resulting hash, and the salt itself in plaintext: salt is not required to remain private.

Last recommendations.

The following code-sample shows a basic example of how this works:

package main

import (
    "crypto/rand"
    "crypto/sha256"
    "database/sql"
    "context"
    "fmt"
)

const saltSize = 32

func main() {
    ctx := context.Background()
    email := []byte("[email protected]")
    password := []byte("47;u5:B(95m72;Xq")

    // create random word
    salt := make([]byte, saltSize)
    _, err := rand.Read(salt)
    if err != nil {
        panic(err)
    }

    // let's create SHA256(salt+password)
    hash := sha256.New()
    hash.Write(salt)
    hash.Write(password)
    h := hash.Sum(nil)

    // this is here just for demo purposes
    //
    // fmt.Printf("email   : %s\n", string(email))
    // fmt.Printf("password: %s\n", string(password))
    // fmt.Printf("salt    : %x\n", salt)
    // fmt.Printf("hash    : %x\n", h)

    // you're supposed to have a database connection
    stmt, err := db.PrepareContext(ctx, "INSERT INTO accounts SET hash=?, salt=?, email=?")
    if err != nil {
        panic(err)
    }
    result, err := stmt.ExecContext(ctx, h, salt, email)
    if err != nil {
        panic(err)
    }

}

However, this approach has several flaws and should not be used. It is shown here only to illustrate the theory with a practical example. The next section explains how to correctly salt passwords in real life.

Storing password securely: the practice

One of the most important sayings in cryptography is: never roll your own crypto. By doing so, one can put the entire application at risk. It is a sensitive and complex topic. Hopefully, cryptography provides tools and standards reviewed and approved by experts. It is therefore important to use them instead of trying to re-invent the wheel.

In the case of password storage, the hashing algorithms recommended by OWASP are bcrypt, PDKDF2, Argon2 and scrypt. Those take care of hashing and salting passwords in a robust way. Go authors provide an extended package for cryptography, that is not part of the standard library. It provides robust implementations for most of the aforementioned algorithms. It can be downloaded using go get:

go get golang.org/x/crypto

The following example shows how to use bcrypt, which should be good enough for most of the situations. The advantage of bcrypt is that it is simpler to use, and is therefore less error-prone.

package main

import (
    "database/sql"
    "context"
    "fmt"

    "golang.org/x/crypto/bcrypt"
)

func main() {
    ctx := context.Background()
    email := []byte("[email protected]")
    password := []byte("47;u5:B(95m72;Xq")

    // Hash the password with bcrypt
    hashedPassword, err := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost)
    if err != nil {
        panic(err)
    }

    // this is here just for demo purposes
    //
    // fmt.Printf("email          : %s\n", string(email))
    // fmt.Printf("password       : %s\n", string(password))
    // fmt.Printf("hashed password: %x\n", hashedPassword)

    // you're supposed to have a database connection
    stmt, err := db.PrepareContext(ctx, "INSERT INTO accounts SET hash=?, email=?")
    if err != nil {
        panic(err)
    }
    result, err := stmt.ExecContext(ctx, hashedPassword, email)
    if err != nil {
        panic(err)
    }
}

Bcrypt also provides a simple and secure way to compare a plaintext password with an already hashed password:

 ctx := context.Background()

 // credentials to validate
 email := []byte("[email protected]")
 password := []byte("47;u5:B(95m72;Xq")

// fetch the hashed password corresponding to the provided email
record := db.QueryRowContext(ctx, "SELECT hash FROM accounts WHERE email = ? LIMIT 1", email)

var expectedPassword string
if err := record.Scan(&expectedPassword); err != nil {
    // user does not exist

    // this should be logged (see Error Handling and Logging) but execution
    // should continue
}

if bcrypt.CompareHashAndPassword(password, []byte(expectedPassword)) != nil {
    // passwords do not match

    // passwords mismatch should be logged (see Error Handling and Logging)
    // error should be returned so that a GENERIC message "Sign-in attempt has
    // failed, please check your credentials" can be shown to the user.
}

If you’re not comfortable with password hashing and comparison options/parameters, better delegating the task to specialized third-party package with safe defaults. Always opt for an actively maintained package and remind to check for known issues.

  • passwd - A Go package that provides a safe default abstraction for password hashing and comparison. It has support for original go bcrypt implementation, argon2, scrypt, parameters masking and key’ed (uncrackable) hashes.

Password Policies

Passwords are a historical asset, part of most authentication systems, and are the number one target of attackers.

Quite often some service leaks its users’ database, and despite the leak of email addresses and other personal data, the biggest concern are passwords. Why? Because passwords are not easy to manage and remember. Users not only tend to use weak passwords (e.g. “123456”) they can easily remember, they can also re-use the same password for different services.

If your application sign-in requires a password, the best you can do is to “enforce password complexity requirements, (…) requiring the use of alphabetic as well as numeric and/or special characters)”. Password length should also be enforced: “eight characters is commonly used, but 16 is better or consider the use of multi-word pass phrases”.

Of course, none of the previous guidelines will prevent users from re-using the same password. The best you can do to reduce this bad practice is to “enforce password changes”, and preventing password re-use. “Critical systems may require more frequent changes. The time between resets must be administratively controlled”.

Reset

Even if you’re not applying any extra password policy, users still need to be able to reset their password. Such a mechanism is as critical as signup or sign-in, and you’re encouraged to follow the best practices to be sure your system does not disclose sensitive data and become compromised.

Passwords should be at least one day old before they can be changed”. This way you’ll prevent attacks on password re-use. Whenever using “email based resets, only send email to a pre-registered address with a temporary link/password” which should have a short expiration period.

Whenever a password reset is requested, the user should be notified. The same way, temporary passwords should be changed on the next usage.

A common practice for password reset is the “Security Question”, whose answer was previously configured by the account owner. “Password reset questions should support sufficiently random answers”: asking for “Favorite Book?” may lead to “The Bible” which makes this reset questions undesirable in most cases.

Other guidelines

Authentication is a critical part of any system, therefore you should always employ correct and safe practices. Below are some guidelines to make your authentication system more resilient:

  • Re-authenticate users prior to performing critical operations
  • Use Multi-Factor Authentication for highly sensitive or high value transactional accounts
  • Implement monitoring to identify attacks against multiple user accounts, utilizing the same password. This attack pattern is used to bypass standard lockouts, when user IDs can be harvested or guessed
  • Change all vendor-supplied default passwords and user IDs or disable the associated accounts
  • Enforce account disabling after an established number of invalid login attempts (e.g., five attempts is common). The account must be disabled for a period of time sufficient to discourage brute force guessing of credentials, but not so long as to allow for a denial-of-service attack to be performed
  1. Hashing functions are the subject of Collisions but recommended hashing functions have a really low collisions probability 


Licenses and Attributions


Speak Your Mind

-->