Nathan Evans' Nemesis of the Moment

Quick and dirty literal port of my PBKDF2 password hash function from C# to F#

Posted in .NET Framework, F# by Nathan B. Evans on March 13, 2014

Now that I’m fully on board the F# bandwagon I’ve found myself wanting to refactor some of my old utility functions that I’ve had for years in C# land. Sure, I could just reference my C# assemblies, and probably should have. But there’s something nice about porting some code over to your shiny new language, if only just as a learning exercise.

module Crypto.Pbkdf2
open System
open System.Security.Cryptography

let private subkeyLength = 32
let private saltSize = 16

/// Hashes a password by a specified number of iterations using the PBKDF2 crypto function.
let hash password iterations =
    use algo = new Rfc2898DeriveBytes(password, saltSize, iterations)
    let salt = algo.Salt
    let bytes = algo.GetBytes(subkeyLength)

    let iters = if BitConverter.IsLittleEndian then BitConverter.GetBytes(iterations) else BitConverter.GetBytes(iterations) |> Array.rev

    let parts = Array.zeroCreate<byte> 54
    Buffer.BlockCopy(salt, 0, parts, 1, saltSize)
    Buffer.BlockCopy(bytes, 0, parts, 17, subkeyLength)
    Buffer.BlockCopy(iters, 0, parts, 50, sizeof<int>)

    Convert.ToBase64String(parts)

/// Hashes a password using 10,000 iterations of the PBKDF2 crypto function.
let fastHash password = hash password 10000

/// Hashes a password using 100,000 iterations of the PBKDF2 crypto function.
let strongHash password = hash password 100000

/// Hashes a password using 300,000 iterations of the PBKDF2 crypto function.
let uberHash password = hash password 300000

/// Verifies a PBKDF2 hashed password with a candidate password.
/// Returns true if the candidate password is correct.
/// The hashed password must have been originally generated by one of the hash functions within this module.
let verify hashedPassword (password:string) =
    let parts = Convert.FromBase64String(hashedPassword)
    if parts.Length <> 54 || parts.[0] <> byte 0 then
        false
    else
        let salt = Array.zeroCreate<byte> saltSize
        Buffer.BlockCopy(parts, 1, salt, 0, saltSize)

        let bytes = Array.zeroCreate<byte> subkeyLength
        Buffer.BlockCopy(parts, 17, bytes, 0, subkeyLength)

        let iters = Array.zeroCreate<byte> sizeof<int>
        Buffer.BlockCopy(parts, 50, iters, 0, sizeof<int>);

        let iters = if BitConverter.IsLittleEndian then iters else iters |> Array.rev

        let iterations = BitConverter.ToInt32(iters, 0)

        use algo = new Rfc2898DeriveBytes(password, salt, iterations)
        let challengeBytes = algo.GetBytes(32)

        match Seq.compareWith (fun a b -> if a = b then 0 else 1) bytes challengeBytes with
        | v when v = 0 -> true
        | _ -> false

Here’s the Gist, if it’s better for you: https://gist.github.com/nbevans/9519088

Sure the code is quite imperative in style, but it is just a utility function and I literally did a “one pass” refactor from the C# code. It’s not really worth giving a second pass just for the sake of making it more pure functional.

This is MIT licensed by the way. I ain’t going to come calling.

Enjoy.

Advertisements
Tagged with: , , ,