Scrypt hash algorithm for password hashing in Swift 3.0 (iOS)

3.2k views Asked by At

I am trying to find a library to implement password hashing(with salt) using Scrypt algorithm. My question is similar to one already asked in stackoverflow (Hash password in Swift application)

I have found following two libraries in swift and objective c respectively but the hash string generated from these is not matching with the one generated at server.

  1. Swift-Sodium (https://github.com/jedisct1/swift-sodium)
  2. NAChloride (https://github.com/gabriel/NAChloride)

Can someone please help in finding library which can be used for Swift 3.0 iOS application for password hashing with salt.

Thank you.

Regards,

Nagraj Wadgire

2

There are 2 answers

0
Nagraj Wadgire On

I had found answer for my own question, thought of sharing as it will be useful for others.

The server team was using Scrypt library (https://github.com/wg/scrypt) to generate hash string for given password and salt.

After analysing the server side library we came to know that the generated hash string contains following components.

1) Scrypt version ($s0$)

2) params (This is calculated using below formula:

String params = Long.toString(log2(N) << 16L | r << 8 | p, 16))

3) Salt in base64 string format

4) Generated derived key in base64 string format

The Format of final hash string is $s0$params$salt$key

(Refer to this question for more information What's the is maximum length of scrypt output?)

As stated in the question I have used NAChloride library at the client side to generate hash string.

This class contains below method for generating hash string:

open class func scrypt(_ password: Data!, salt: Data!, n N: UInt64, r: UInt32, p: UInt32, length: Int) throws -> Data

In our example we have passed below values:

n= 16,

r= 16,

p= 16,

length (bytes) = 32,

salt = Data(bytes:[0x73, 0x61, 0x6c, 0x74, 0x44, 0x61, 0x74, 0x61,0x73, 0x61, 0x6c, 0x74, 0x44, 0x61, 0x74, 0x61,0x73, 0x61, 0x6c, 0x74, 0x44, 0x61, 0x74, 0x61,0x73, 0x61, 0x6c, 0x74, 0x44, 0x61, 0x74, 0x61])

This method will generate only derived key in 'Data' format, thus I was thinking that it is different when compared to the key generated at server side.

I had to write a logic after generating the derived key to match to the format of the hash string (server side hash string format) generated at the server.

Below is the code written in Swift 3.0 to generate hash string for given password using NAChloride library which internally uses Scrypt hash algorithm:

    func passwordHashingUsingScrypt(password: String) -> String{
    let N = 16
    let r = 16
    let p = 16

    let term1 = Int(log2(Double(N))) << 16
    let term2 = r << 8
    let paramsDecimal = term1 | term2 | p

    let params = String(format:"%2X", paramsDecimal)
    print(params)

    let message = password.data(using:.utf8)!
    let salt = Data(bytes:[0x73, 0x61, 0x6c, 0x74, 0x44, 0x61, 0x74, 0x61,0x73, 0x61, 0x6c, 0x74, 0x44, 0x61, 0x74, 0x61,0x73, 0x61, 0x6c, 0x74, 0x44, 0x61, 0x74, 0x61,0x73, 0x61, 0x6c, 0x74, 0x44, 0x61, 0x74, 0x61])

    let saltBase64String = salt.base64EncodedString()
    print(saltBase64String)

    let hashData = try! NAScrypt.scrypt(message, salt: salt, n: 16, r: 16, p: 16, length: 32)
    let hashBase64String = hashData.base64EncodedString()
    print(hashBase64String)
    let result = saltBase64String+"$"+hashBase64String
    print(result)

    var hashString = String()
    hashString.append("$s0$")
    hashString.append(params)
    hashString.append("$")
    hashString.append(saltBase64String)
    hashString.append("$")
    hashString.append(hashBase64String)
    print(hashString)
    return hashString
}

You can also generate the random salt using below method:

func randomBytes(numberOfBytes:Int) -> [UInt8] {
    var randomBytes = [UInt8](repeating: 0, count: numberOfBytes) // array to hold randoms bytes
    let result = SecRandomCopyBytes(kSecRandomDefault, numberOfBytes, &randomBytes)
    print(result)
    return randomBytes
}

Result:

Password: admin1234<

Hash String: $s0$41010$c2FsdERhdGFzYWx0RGF0YXNhbHREYXRhc2FsdERhdGE=$GrMF1P3VH8YrgUEaOJDVSc4as/XTSWhCbbp4DLie00I=

2
zaph On

Common Crypto contains PBKDF2 which is the NIST recommended password hashing function.

Example:

Password Based Key Derivation 2 (Swift 3)

Password Based Key Derivation can be used both for deriving an encryption key from password text and saving a password for authentication purposes.

There are several hash algorithms that can be used including SHA1, SHA256, SHA512 which are provided by this example code.

The rounds parameter is used to make the calculation slow so that an attacker will have to spend substantial time on each attempt. Typical delay values fall in the 100ms to 500ms, shorter values can be used if there is unacceptable performance.

This example requires Common Crypto
It is necessary to have a bridging header to the project:
#import <CommonCrypto/CommonCrypto.h>
Add the Security.framework to the project.

Parameters:

password     password String  
salt         salt Data  
keyByteCount number of key bytes to generate
rounds       Iteration rounds

returns      Derived key


func pbkdf2SHA1(password: String, salt: Data, keyByteCount: Int, rounds: Int) -> Data? {
    return pbkdf2(hash:CCPBKDFAlgorithm(kCCPRFHmacAlgSHA1), password:password, salt:salt, keyByteCount:keyByteCount, rounds:rounds)
}

func pbkdf2SHA256(password: String, salt: Data, keyByteCount: Int, rounds: Int) -> Data? {
    return pbkdf2(hash:CCPBKDFAlgorithm(kCCPRFHmacAlgSHA256), password:password, salt:salt, keyByteCount:keyByteCount, rounds:rounds)
}

func pbkdf2SHA512(password: String, salt: Data, keyByteCount: Int, rounds: Int) -> Data? {
    return pbkdf2(hash:CCPBKDFAlgorithm(kCCPRFHmacAlgSHA512), password:password, salt:salt, keyByteCount:keyByteCount, rounds:rounds)
}

func pbkdf2(hash :CCPBKDFAlgorithm, password: String, salt: Data, keyByteCount: Int, rounds: Int) -> Data? {
    let passwordData = password.data(using:String.Encoding.utf8)!
    var derivedKeyData = Data(repeating:0, count:keyByteCount)

    let derivationStatus = derivedKeyData.withUnsafeMutableBytes {derivedKeyBytes in
        salt.withUnsafeBytes { saltBytes in

            CCKeyDerivationPBKDF(
                CCPBKDFAlgorithm(kCCPBKDF2),
                password, passwordData.count,
                saltBytes, salt.count,
                hash,
                UInt32(rounds),
                derivedKeyBytes, derivedKeyData.count)
        }
    }
    if (derivationStatus != 0) {
        print("Error: \(derivationStatus)")
        return nil;
    }

    return derivedKeyData
}

Example usage:

let password     = "password"
//let salt       = "saltData".data(using: String.Encoding.utf8)!
let salt         = Data(bytes: [0x73, 0x61, 0x6c, 0x74, 0x44, 0x61, 0x74, 0x61])
let keyByteCount = 16
let rounds       = 100000

let derivedKey = pbkdf2SHA1(password:password, salt:salt, keyByteCount:keyByteCount, rounds:rounds)
print("derivedKey (SHA1): \(derivedKey! as NSData)")

Example Output:

derivedKey (SHA1): <6b9d4fa3 0385d128 f6d196ee 3f1d6dbf>