I'm building a basic authentication setup similar to how it's used in Vapor's auth-template template (from here). I have everything set up the same way as in the template.
However, I would like to add salt. I can generate a salt for a user upon creation:
static func create(_ req: Request, newUserRequest user: CreateUserRequest) throws -> Future<User.Public> {
return User.query(on: req).filter(\.username == user.username).first().flatMap { existingUser in
guard existingUser == nil else {
throw Abort(.badRequest, reason: "A user with the given username already exists.")
}
guard user.password == user.passwordVerification else {
throw Abort(.badRequest, reason: "Given passwords did not match.")
}
let count = 16
var pw_salt_data = Data(count: count)
let _ = pw_salt_data.withUnsafeMutableBytes { mutableBytes in
SecRandomCopyBytes(kSecRandomDefault, count, mutableBytes)
}
let pw_salt = try BCrypt.hash(pw_salt_data.base64EncodedString())
let pw_hash = try BCrypt.hash(pw_salt + user.password)
return User(id: nil, username: user.username, pw_hash: pw_hash, pw_salt: pw_salt, email: user.email).save(on: req).toPublic()
}
}
But there's no way to retrieve that salt when performing authentication during login:
static func login(_ req: Request) throws -> Future<UserToken> {
let user = try req.requireAuthenticated(User.self)
let token = try UserToken.create(userID: user.requireID())
return token.save(on: req)
}
I want the salt to be randomly generated for each user and stored in the database as a separate column from the hashed password to be used later during authentication.
Is there a standardized way to handle salting a password hash in Vapor 3?
The way it works in Vapor is that each BCrypt hash has a unique salt that’s saved with the password in the database. The BCrypt default functions in Vapor expect this.
If you want to go down a different route have a look at the function to hash a password - that takes a salt. You can then save that in its own field and retrieve and when you verify the password. Honestly I’d say to just use the defaults unless you have a very specific reason not to
You hash the password using BCrypt. BCrypt is already part of the Vapor dependencies.
BCrypt.hash("vapor", cost: 4)
This will hash the string "vapor", using a randomly generated salt, with complexity 4. Choosing the cost is subjective and arbitrary but it is recommended that real-world secure applications should have a cost factor above 10-12. If you don't like the salt being randomly generated by BCrypt, and you want to generate your own salt, you can provide the salt to the hash function, which has the following signature:
public func hash(_ plaintext: LosslessDataConvertible, cost: Int = 12, salt: LosslessDataConvertible? = nil) throws -> String
Documentation says that the salt must be 16-bytes if manually provided. This is a sample hash:
$2a$04$/nqhWqplnughhq6mlKmi8.raprxoG/dczY8kdbOKm.zC5sPu.2IBi
As you can see it includes auxiliary information such as the complexity, the type of the algorithm and the salt, everything necessary to do the verification. If you provided your own salt it will also be part of the final hash and you don't need to separately provide it. You can do verification as written below.
try BCrypt.verify("vapor", created: hashedPasswordSavedInDatabase)