Search code examples
iosswifthashphpass

phpass hash similar functionality in swift


My wordpress backend is using phpass hash algorithm and giving me phpass using web service. In ios end in swift I am trying to generate same phpass hash in swift. Below are codes in swift and php. Both have same input but output is different. So question is that how can i get same output. Am I missing anything?

Php code :

<?php
function phpassHash($password, $salt,$iterations){
    $hash = hash('md5', $salt.$password, TRUE);
    for($i = 0; $i < $iterations; $i++){
        $hash = hash('md5', $hash.$password, TRUE);
    }
    return $hash;
}
$result = phpassHash("a_test_password","MsdvACyA", 8192);
echo bin2hex($result); 
?>

Swift code :

func md5(string: String) -> String {
        var digest = [UInt8](count: Int(CC_MD5_DIGEST_LENGTH), repeatedValue: 0)
        if let data = string.dataUsingEncoding(NSUTF8StringEncoding) {
            CC_MD5(data.bytes, CC_LONG(data.length), &digest)
        }
        var digestHex = ""
        for index in 0..<Int(CC_MD5_DIGEST_LENGTH) {
            digestHex += String(format: "%02x", digest[index])
        }

        return digestHex
    }



func phpassHash(password: String, salt: String, iterations: Int) -> String {
        var hash = md5(salt+password)
        for _ in 0..<iterations {
            hash = md5(hash+password)
        }
        return hash;
    }

Solution

  • You were too eager to convert the bytes array into String in your md5 function. As an example, this changes the meaning of the integer 0x47 to the string 47. Your first call to md5() returns the correct hash, but if you md5() that again, it will go wrong since it's a string now instead of an array of bytes as in PHP. Notice that in PHP, you call bin2hex at the very last step.

    Since the CC_MD5 function in CommonCrypt like to deal with array of bytes, keep everything as bytes and writer wrapper if necessary.

    First, let's define some helper functions:

    extension String {
        // Return the bytes that make up the string according to UTF-8 encoding
        var bytes: [UInt8] {
            get {
                return self.cStringUsingEncoding(NSUTF8StringEncoding)!
                    .dropLast() // C strings are null-terminated so we need to drop the last byte
                    .map { UInt8(bitPattern: $0) }
            }
        }
    }
    
    // Convert an array of bytes to a hex string
    func toHexString(bytes: [UInt8]) -> String {
        return bytes.map { String(format: "%02x", $0) }.joinWithSeparator("")
    }
    

    Now you can write your hash fucntions:

    // Allow you to quickly hash a string. We don't really use it here
    func md5(string: String) -> [UInt8] {
        return md5(string.bytes)
    }
    
    func md5(bytes: [UInt8]) -> [UInt8] {
        var digest = [UInt8](count: Int(CC_MD5_DIGEST_LENGTH), repeatedValue: 0)
    
        CC_MD5(bytes, CC_LONG(bytes.count), &digest)
        return digest
    }
    
    func phpassHash(password: String, salt: String, iterations: Int) -> [UInt8] {
        let passwordBytes = password.bytes
        let saltBytes     = salt.bytes
    
        var hash = md5(saltBytes + passwordBytes)
        for _ in 0..<iterations {
            hash = md5(hash + passwordBytes)
        }
    
        return hash
    }
    
    let password   = "a_test_password"
    let salt       = "MsdvACyA"
    let iterations = 8192
    let assHash    = phpassHash(password, salt: salt, iterations: iterations)
    
    print(toHexString(assHash)) // 42a89278a28860f223a10fdb43b5d4b2