-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
269 additions
and
110 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
public class HMAC<Variant: Hash> { | ||
/// Authenticates a message using the provided `Hash` algorithm | ||
/// | ||
/// - parameter message: The message to authenticate | ||
/// - parameter key: The key to authenticate with | ||
/// | ||
/// - returns: The authenticated message | ||
public static func authenticate(_ message: [UInt8], withKey key: [UInt8]) -> [UInt8] { | ||
var key = key | ||
|
||
// If it's too long, hash it first | ||
if key.count > Variant.chunkSize { | ||
key = Variant.hash(key) | ||
} | ||
|
||
// Add padding | ||
if key.count < Variant.chunkSize { | ||
key = key + [UInt8](repeating: 0, count: Variant.chunkSize - key.count) | ||
} | ||
|
||
// XOR the information | ||
var outerPadding = [UInt8](repeating: 0x5c, count: Variant.chunkSize) | ||
var innerPadding = [UInt8](repeating: 0x36, count: Variant.chunkSize) | ||
|
||
for i in 0..<key.count { | ||
outerPadding[i] = key[i] ^ outerPadding[i] | ||
} | ||
|
||
for i in 0..<key.count { | ||
innerPadding[i] = key[i] ^ innerPadding[i] | ||
} | ||
|
||
// Hash the information | ||
let innerPaddingHash: [UInt8] = Variant.hash(innerPadding + message) | ||
let outerPaddingHash: [UInt8] = Variant.hash(outerPadding + innerPaddingHash) | ||
|
||
return outerPaddingHash | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
import Foundation | ||
|
||
public enum PBKDF2Error: Error { | ||
case cannotIterateZeroTimes | ||
case cannotDeriveFromPassword([UInt8]) | ||
case cannotDeriveFromSalt([UInt8]) | ||
case keySizeTooBig(Int) | ||
} | ||
|
||
public final class PBKDF2_HMAC<Variant: Hash> { | ||
/// Derives a key from a given set of parameters | ||
/// | ||
/// - parameter password: The password to hash | ||
/// - parameter salt: The random salt that should be unique to the user's credentials, used for preventing Rainbow Tables | ||
/// - parameter iterations: The amount of iterations to use for strengthening the key, higher is stronger/safer but also slower | ||
/// - parameter keySize: The amount of bytes to output | ||
/// | ||
/// - throws: Invalid input bytes for password or salt | ||
/// - throws: Too large amount of key bytes requested | ||
/// - throws: Too little iterations | ||
/// | ||
/// - returns: The derived key bytes | ||
public static func derive(fromPassword password: [UInt8], saltedWith salt: [UInt8], iterating iterations: Int = 10_000, derivedKeyLength keySize: Int? = nil) throws -> [UInt8] { | ||
|
||
// Used to create a block number to append to the salt before deriving | ||
func integerBytes(blockNum block: UInt32) -> [UInt8] { | ||
var bytes = [UInt8](repeating: 0, count: 4) | ||
bytes[0] = UInt8((block >> 24) & 0xFF) | ||
bytes[1] = UInt8((block >> 16) & 0xFF) | ||
bytes[2] = UInt8((block >> 8) & 0xFF) | ||
bytes[3] = UInt8(block & 0xFF) | ||
return bytes | ||
} | ||
|
||
// Authenticated using HMAC with precalculated keys (saves 50% performance) | ||
func authenticate(innerPadding: [UInt8], outerPadding: [UInt8], message: [UInt8]) throws -> [UInt8] { | ||
let innerPaddingHash: [UInt8] = Variant.hash(innerPadding + message) | ||
let outerPaddingHash: [UInt8] = Variant.hash(outerPadding + innerPaddingHash) | ||
|
||
return outerPaddingHash | ||
} | ||
|
||
let keySize = keySize ?? Variant.chunkSize | ||
|
||
// Check input values to be correct | ||
guard iterations > 0 else { | ||
throw PBKDF2Error.cannotIterateZeroTimes | ||
} | ||
|
||
guard password.count > 0 else { | ||
throw PBKDF2Error.cannotDeriveFromPassword(password) | ||
} | ||
|
||
guard salt.count > 0 else { | ||
throw PBKDF2Error.cannotDeriveFromSalt(salt) | ||
} | ||
|
||
guard keySize <= Int(((pow(2,32) as Double) - 1) * Double(Variant.chunkSize)) else { | ||
throw PBKDF2Error.keySizeTooBig(keySize) | ||
} | ||
|
||
// MARK - Precalculate paddings | ||
var password = password | ||
|
||
// If the key is too long, hash it first | ||
if password.count > Variant.chunkSize { | ||
password = Variant.hash(password) | ||
} | ||
|
||
// Add padding | ||
if password.count < Variant.chunkSize { | ||
password = password + [UInt8](repeating: 0, count: Variant.chunkSize - password.count) | ||
} | ||
|
||
// XOR the information | ||
var outerPadding = [UInt8](repeating: 0x5c, count: Variant.chunkSize) | ||
var innerPadding = [UInt8](repeating: 0x36, count: Variant.chunkSize) | ||
|
||
for i in 0..<password.count { | ||
outerPadding[i] = password[i] ^ outerPadding[i] | ||
} | ||
|
||
for i in 0..<password.count { | ||
innerPadding[i] = password[i] ^ innerPadding[i] | ||
} | ||
|
||
// This is where all the processing happens | ||
let blocks = UInt32((keySize + Variant.digestSize - 1) / Variant.digestSize) | ||
var response = [UInt8]() | ||
|
||
// Loop over all blocks | ||
for block in 1...blocks { | ||
let s = salt + integerBytes(blockNum: block) | ||
|
||
// Iterate the first time | ||
var ui = try authenticate(innerPadding: innerPadding, outerPadding: outerPadding, message: s) | ||
var u1 = ui | ||
|
||
// Continue iterating for this block | ||
for _ in 0..<iterations - 1 { | ||
u1 = try authenticate(innerPadding: innerPadding, outerPadding: outerPadding, message: u1) | ||
xor(&ui, u1) | ||
} | ||
|
||
// Append the response to be returned | ||
response.append(contentsOf: ui) | ||
} | ||
|
||
return Array(response[0..<keySize]) | ||
} | ||
|
||
public static func validate(_ password: [UInt8], saltedWith salt: [UInt8], against: [UInt8], iterating iterations: Int) throws -> Bool { | ||
let newHash = try derive(fromPassword: password, saltedWith: salt, iterating: iterations, derivedKeyLength: against.count) | ||
|
||
return newHash == against | ||
} | ||
} | ||
|
||
fileprivate func xor(_ lhs: inout [UInt8], _ rhs: [UInt8]) { | ||
assert(lhs.count == rhs.count) | ||
|
||
for i in 0..<lhs.count { | ||
lhs[i] = lhs[i] ^ rhs[i] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,57 +1,50 @@ | ||
//import XCTest | ||
//import CryptoKitten | ||
// | ||
//class PBKDF2Tests: XCTestCase { | ||
// static var allTests = [ | ||
// ("testValidation", testValidation), | ||
// ("testSHA1", testSHA1), | ||
// ("testMD5", testMD5), | ||
// ("testPerformance", testPerformance), | ||
// ] | ||
// | ||
// func testValidation() throws { | ||
// let result = try PBKDF2<SHA1>.deriveKey(fromPassword: [UInt8]("vapor".utf8), saltedWith: [UInt8]("V4P012".utf8), iteratingTimes: 1000, derivedKeyLength: 10) | ||
// | ||
// XCTAssert(try PBKDF2<SHA1>.validate(password: [UInt8]("vapor".utf8), saltedWith: [UInt8]("V4P012".utf8), against: result, iterating: 1000)) | ||
// } | ||
// | ||
// func testSHA1() throws { | ||
// // Source: PHP/produce_tests.php | ||
// let tests: [(key: String, salt: String, expected: String, iterations: Int)] = [ | ||
// ("password", "longsalt", "1712d0a135d5fcd98f00bb25407035c41f01086a", 1000), | ||
// ("password2", "othersalt", "7a0363dd39e51c2cf86218038ad55f6fbbff6291", 1000), | ||
// ("somewhatlongpasswordstringthatIwanttotest", "1", "8cba8dd99a165833c8d7e3530641c0ecddc6e48c", 1000), | ||
// ("p", "somewhatlongsaltstringthatIwanttotest", "31593b82b859877ea36dc474503d073e6d56a33d", 1000), | ||
// ] | ||
// | ||
// for test in tests { | ||
// let result = try PBKDF2<SHA1>.deriveKey(fromPassword: [UInt8](test.key.utf8), saltedWith: [UInt8](test.salt.utf8), iteratingTimes: test.iterations).hexString.lowercased() | ||
// | ||
// XCTAssertEqual(result, test.expected.lowercased()) | ||
// } | ||
// } | ||
// | ||
// func testMD5() throws { | ||
// // Source: PHP/produce_tests.php | ||
// let tests: [(key: String, salt: String, expected: String, iterations: Int)] = [ | ||
// ("password", "longsalt", "95d6567274c3ed283041d5135c798823", 1000), | ||
// ("password2", "othersalt", "78e4d28875d6f3b92a01dbddc07370f1", 1000), | ||
// ("somewhatlongpasswordstringthatIwanttotest", "1", "c91a23ffd2a352f0f49c6ce64146fc0a", 1000), | ||
// ("p", "somewhatlongsaltstringthatIwanttotest", "4d0297fc7c9afd51038a0235926582bc", 1000), | ||
// ] | ||
// | ||
// for test in tests { | ||
// let result = try PBKDF2<MD5>.deriveKey(fromPassword: [UInt8](test.key.utf8), saltedWith: [UInt8](test.salt.utf8), iteratingTimes: test.iterations).hexString.lowercased() | ||
// | ||
// XCTAssertEqual(result, test.expected.lowercased()) | ||
// } | ||
// } | ||
// | ||
// func testPerformance() { | ||
// // ~0.137 release | ||
// measure { | ||
// _ = try! PBKDF2<SHA1>.deriveKey(fromPassword: [UInt8]("p".utf8), saltedWith: [UInt8]("somewhatlongsaltstringthatIwanttotest".utf8), iteratingTimes: 10_000) | ||
// } | ||
// } | ||
//} | ||
import XCTest | ||
import CryptoKitten | ||
|
||
class PBKDF2Tests: XCTestCase { | ||
static var allTests = [ | ||
("testSHA1", testSHA1), | ||
("testMD5", testMD5), | ||
("testPerformance", testPerformance), | ||
] | ||
|
||
func testSHA1() throws { | ||
// Source: PHP/produce_tests.php | ||
let tests: [(key: String, salt: String, expected: String, iterations: Int)] = [ | ||
("password", "longsalt", "1712d0a135d5fcd98f00bb25407035c41f01086a", 1000), | ||
("password2", "othersalt", "7a0363dd39e51c2cf86218038ad55f6fbbff6291", 1000), | ||
("somewhatlongpasswordstringthatIwanttotest", "1", "8cba8dd99a165833c8d7e3530641c0ecddc6e48c", 1000), | ||
("p", "somewhatlongsaltstringthatIwanttotest", "31593b82b859877ea36dc474503d073e6d56a33d", 1000), | ||
] | ||
|
||
for test in tests { | ||
let result = try PBKDF2_HMAC<SHA1>.derive(fromPassword: [UInt8](test.key.utf8), saltedWith: [UInt8](test.salt.utf8), iterating: test.iterations, derivedKeyLength: SHA1.digestSize).hexString.lowercased() | ||
|
||
XCTAssertEqual(result, test.expected.lowercased()) | ||
} | ||
} | ||
|
||
func testMD5() throws { | ||
// Source: PHP/produce_tests.php | ||
let tests: [(key: String, salt: String, expected: String, iterations: Int)] = [ | ||
("password", "longsalt", "95d6567274c3ed283041d5135c798823", 1000), | ||
("password2", "othersalt", "78e4d28875d6f3b92a01dbddc07370f1", 1000), | ||
("somewhatlongpasswordstringthatIwanttotest", "1", "c91a23ffd2a352f0f49c6ce64146fc0a", 1000), | ||
("p", "somewhatlongsaltstringthatIwanttotest", "4d0297fc7c9afd51038a0235926582bc", 1000), | ||
] | ||
|
||
for test in tests { | ||
let result = try PBKDF2_HMAC<MD5>.derive(fromPassword: [UInt8](test.key.utf8), saltedWith: [UInt8](test.salt.utf8), iterating: test.iterations, derivedKeyLength: MD5.digestSize).hexString.lowercased() | ||
|
||
XCTAssertEqual(result, test.expected.lowercased()) | ||
} | ||
} | ||
|
||
func testPerformance() { | ||
// ~0.137 release | ||
measure { | ||
_ = try! PBKDF2_HMAC<SHA1>.derive(fromPassword: [UInt8]("p".utf8), saltedWith: [UInt8]("somewhatlongsaltstringthatIwanttotest".utf8), iterating: 10_000, derivedKeyLength: SHA1.digestSize) | ||
} | ||
} | ||
} | ||
|
Oops, something went wrong.