2.9 KiB
Password hashing
Password management is critical for good user security and doesn't need to cost a lot of effort. No software is perfect. Even if your software is perfect, other software on the same server likely isn't. Good password encryption security prevents users' passwords from leaking out in case of a hypothetical future data breach.
For password hashing Vapor supports PBKDF2 and BCrypt.
We recommend using BCrypt over PBKDF2 for almost all scenarios. Whilst PBKDF2 is a proven standard, it's much more easily brute-forced than BCrypt and is less future-proof.
BCrypt
BCrypt is an algorithm specifically designed for password hashing. It's easy to store and verify.
Deriving a key
Unlike PBKDF2 you don't need to generate and store a salt, that's part of the BCrypt hashing and verification process.
The output is a combination of the BCrypt "cost" factor, salt and resulting hash. Meaning that the derived output contains all information necessary for verification, simplifying the database access.
let result: Data = try BCrypt.make(message: "MyPassword")
guard try BCrypt.verify(message: "MyPassword", matches: result) else {
fatalError("This never triggers, since the verification process will always be successful for the same password and conditions")
}
The default cost factor is 12, based on the official recommendations.
Storing the derived key as a String
BCrypt always outputs valid ASCII/UTF-8 for the resulting hash.
This means you can convert the output Data to a String as such:
guard let string = String(bytes: result, encoding: .utf8) else {
// This must never trigger
}
PBKDF2
PBKDF2 is an algorithm that is almost always (and in Vapor, exclusively) used with HMAC for message authentication.
PBKDF2 can be paired up with any hashing algorithm and is simple to implement. PBKDF2 is used all over the world through the WPA2 standard, securing WiFi connections. But we still recommend PBKDF2 above any normal hashing function.
For PBKDF2 you also select the Hash using generics.
Deriving a key
In the following example:
passwordis either aStringorData- The
saltisData - Iterations is defaulted to
10_000iterations - The keySize is equivalent to 1 hash's length.
// Generate a random salt
let salt: Data = OSRandom().data(count: 32)
let hash = try PBKDF2<SHA256>.derive(fromPassword: password, salt: salt)
You can optionally configure PBKDF2 to use a different iteration count and output keysize.
// Iterates 20'000 times and outputs 100 bytes
let hash = try PBKDF2<SHA256>.derive(fromPassword: password, salt: salt, iterating: 20_000, derivedKeyLength: 100)
Storing the results
When you're storing the PBKDF2 results, be sure to also store the Salt. Without the original salt, iteration count and other parameters you cannot reproduce the same hash for validation or authentication.