From 6b6bca8572c5b9390e0c90c51d2194cb7fc442f1 Mon Sep 17 00:00:00 2001 From: Timo <38291523+lovetodream@users.noreply.github.com> Date: Tue, 29 Oct 2024 11:34:09 +0000 Subject: [PATCH] Replace PBKDF2 implementation with the swift-crypto version (#71) --- Package.resolved | 12 +- Package.swift | 6 +- Sources/OracleNIO/Helper/Crypto.swift | 12 +- Sources/_PBKDF2/PBKDF2.swift | 179 -------------------------- Tests/_PBKDF2Tests/_PBKDF2Tests.swift | 169 ------------------------ 5 files changed, 17 insertions(+), 361 deletions(-) delete mode 100644 Sources/_PBKDF2/PBKDF2.swift delete mode 100644 Tests/_PBKDF2Tests/_PBKDF2Tests.swift diff --git a/Package.resolved b/Package.resolved index 51c4bac..271be3e 100644 --- a/Package.resolved +++ b/Package.resolved @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-crypto.git", "state" : { - "revision" : "21f7878f2b39d46fd8ba2b06459ccb431cdf876c", - "version" : "3.8.1" + "revision" : "8fa345c2081cfbd4851dffff5dd5bed48efe6081", + "version" : "3.9.0" } }, { @@ -59,8 +59,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio.git", "state" : { - "revision" : "f7dc3f527576c398709b017584392fb58592e7f5", - "version" : "2.75.0" + "revision" : "914081701062b11e3bb9e21accc379822621995e", + "version" : "2.76.1" } }, { @@ -77,8 +77,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-transport-services.git", "state" : { - "revision" : "dbace16f126fdcd80d58dc54526c561ca17327d7", - "version" : "1.22.0" + "revision" : "bbd5e63cf949b7db0c9edaf7a21e141c52afe214", + "version" : "1.23.0" } }, { diff --git a/Package.swift b/Package.swift index 57b97fc..a842710 100644 --- a/Package.swift +++ b/Package.swift @@ -17,7 +17,7 @@ let package = Package( .package(url: "https://github.com/apple/swift-nio.git", from: "2.67.0"), .package(url: "https://github.com/apple/swift-nio-transport-services.git", from: "1.21.0"), .package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.27.0"), - .package(url: "https://github.com/apple/swift-crypto.git", from: "3.4.0"), + .package(url: "https://github.com/apple/swift-crypto.git", "3.9.0"..<"5.0.0"), .package(url: "https://github.com/apple/swift-collections.git", from: "1.1.3"), .package(url: "https://github.com/apple/swift-atomics.git", from: "1.2.0"), .package(url: "https://github.com/swift-server/swift-service-lifecycle.git", from: "2.6.0"), @@ -34,8 +34,6 @@ let package = Package( ], path: "Sources/VendoredConnectionPoolModule" ), - .target(name: "_PBKDF2", dependencies: [.product(name: "Crypto", package: "swift-crypto")]), - .testTarget(name: "_PBKDF2Tests", dependencies: ["_PBKDF2"]), .target( name: "OracleNIO", dependencies: [ @@ -49,7 +47,7 @@ let package = Package( .product(name: "Crypto", package: "swift-crypto"), .product(name: "_CryptoExtras", package: "swift-crypto"), .product(name: "ServiceLifecycle", package: "swift-service-lifecycle"), - "_PBKDF2", "_ConnectionPoolModule", + "_ConnectionPoolModule", ], swiftSettings: [ .enableExperimentalFeature("StrictConcurrency=complete"), diff --git a/Sources/OracleNIO/Helper/Crypto.swift b/Sources/OracleNIO/Helper/Crypto.swift index fcf37cf..ad59f3d 100644 --- a/Sources/OracleNIO/Helper/Crypto.swift +++ b/Sources/OracleNIO/Helper/Crypto.swift @@ -15,7 +15,6 @@ import Crypto import RegexBuilder import _CryptoExtras -import _PBKDF2 import struct Foundation.Data @@ -48,8 +47,15 @@ func encryptCBC(_ key: [UInt8], _ plainText: [UInt8], zeros: Bool = false) throw } func getDerivedKey(key: Data, salt: [UInt8], length: Int, iterations: Int) throws -> [UInt8] { - Array( - try PBKDF2.calculate(length: length, password: key, salt: salt, rounds: iterations)) + try KDF.Insecure.PBKDF2.deriveKey( + from: key, + salt: salt, + using: .sha512, + outputByteCount: length, + unsafeUncheckedRounds: iterations + ).withUnsafeBytes { bytes in + Array(bytes) + } } /// Returns a signed version of the given payload (used for Oracle IAM token authentication) in base64 diff --git a/Sources/_PBKDF2/PBKDF2.swift b/Sources/_PBKDF2/PBKDF2.swift deleted file mode 100644 index 81365e1..0000000 --- a/Sources/_PBKDF2/PBKDF2.swift +++ /dev/null @@ -1,179 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the OracleNIO open source project -// -// Copyright (c) 2024 Timo Zacherl and the OracleNIO project authors -// Licensed under Apache License v2.0 -// -// See LICENSE for license information -// See CONTRIBUTORS.md for the list of OracleNIO project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -import Crypto - -import struct Foundation.Data -import protocol Foundation.DataProtocol - -// RFC 2898 Section 5.2 -// -// FromSpec: -// -// PBKDF2 applies a pseudorandom function (see Appendix B.1 for an -// example) to derive keys. The length of the derived key is essentially -// unbounded. (However, the maximum effective search space for the -// derived key may be limited by the structure of the underlying -// pseudorandom function. See Appendix B.1 for further discussion.) -// PBKDF2 is recommended for new applications. -// -// PBKDF2 (P, S, c, dk_len) -// -// Options: PRF underlying pseudorandom function (h_len -// denotes the length in octets of the -// pseudorandom function output) -// -// Input: P password, an octet string -// S salt, an octet string -// c iteration count, a positive integer -// dk_len intended length in octets of the derived -// key, a positive integer, at most -// (2^32 - 1) * h_len -// -// Output: DK derived key, a dk_len-octet string - -// Based on Apple's CommonKeyDerivation, based originally on code by Damien Bergamini. - -/// PBKDF2 is used to generate a key and salt from a password as defined in RFC 2898. -/// -/// This is using a generic `HashFunction` from `swift-crypto`. -/// -/// Example usage: -/// ```swift -/// let result = PBKDF2.hash( -/// length: 32, -/// password: "password".data(using: .utf8)!, -/// salt: "hash".data(using: .utf8)!, -/// rounds: 6 -/// ) -/// print(result) -/// ``` -public struct PBKDF2 { - /// Generates a key from the given password and salt. - /// - Parameters: - /// - length: The size for the generated key. Generally 16 or 32 bytes. Maximum size is `UInt32.max * Hash.Digest.byteCount`. - /// - password: The password used to generate the key, can be empty. - /// - salt: The salt used to generate the key, can be empty. Commonly 8 bytes long. - /// - rounds: Iteration count, must be greater than 9. Common values range from `1_000` to `100_00`. - /// Larger iteration counts improve security by increasing the time required to compute - /// the key. It is common to tune this parameter to achieve approximately 100ms. - /// - Returns: The calculated key. - public static func calculate( - length: Int, password: P, salt: S, rounds: Int - ) throws -> Data { - if rounds < 1 { - throw CryptoKitError.incorrectParameterSize - } - - let dkLength = length - let hLen = Hash.Digest.byteCount - - // FromSpec: - // - // 1. If dk_len > maxInt(u32) * h_len, output "derived key too long" and - // stop. - // - if dkLength / hLen >= UInt32.max { - // Counter starts at 1 and is 32 bit, so if we have to return more blocks, we would overflow - throw CryptoKitError.incorrectParameterSize - } - - // FromSpec: - // - // 2. Let l be the number of h_len-long blocks of bytes in the derived key, - // rounding up, and let r be the number of bytes in the last - // block - // - - let blocksCount = Int((Double(dkLength) / Double(hLen)).rounded(.up)) - var r = dkLength % hLen - if r == 0 { - r = hLen - } - - // FromSpec: - // - // 3. For each block of the derived key apply the function F defined - // below to the password P, the salt S, the iteration count c, and - // the block index to compute the block: - // - // T_1 = F (P, S, c, 1) , - // T_2 = F (P, S, c, 2) , - // ... - // T_l = F (P, S, c, l) , - // - // where the function F is defined as the exclusive-or sum of the - // first c iterates of the underlying pseudorandom function PRF - // applied to the password P and the concatenation of the salt S - // and the block index i: - // - // F (P, S, c, i) = U_1 \xor U_2 \xor ... \xor U_c - // - // where - // - // U_1 = PRF (P, S || INT (i)) , - // U_2 = PRF (P, U_1) , - // ... - // U_c = PRF (P, U_{c-1}) . - // - // Here, INT (i) is a four-octet encoding of the integer i, most - // significant octet first. - // - // 4. Concatenate the blocks and extract the first dk_len octets to - // produce a derived key DK: - // - // DK = T_1 || T_2 || ... || T_l<0..r-1> - - var block = 0 - var dk = Data(repeating: 0, count: dkLength) - let password = Data(password) - while block < blocksCount { - defer { block += 1 } - var prevBlock: Data - var newBlock: Data - - // U_1 = PRF (P, S || INT (i)) - var value = UInt32(block + 1).bigEndian - // Block index starts at 0001 - let blockIndex = withUnsafeBytes(of: &value) { Data($0) } - var ctx = HMAC(key: SymmetricKey(data: password)) - ctx.update(data: salt) - ctx.update(data: blockIndex) - prevBlock = Data(ctx.finalize()) - - // Choose portion of DK to write into (T_n) and initialize - let offset = block * hLen - let blockLen = if block != blocksCount - 1 { hLen } else { r } - var dkBlock = dk[offset..<(offset + blockLen)] - dkBlock = prevBlock[0..(key: SymmetricKey(data: password)) - ctx.update(data: prevBlock) - newBlock = Data(ctx.finalize()) - prevBlock = newBlock - - // F (P, S, c, i) = U_1 \xor U_2 \xor ... \xor U_c - for (j, _) in dkBlock.enumerated() { - dkBlock[j] ^= Data(newBlock)[j] - } - } - - dk[offset..<(offset + blockLen)] = dkBlock - } - - return dk - } -} diff --git a/Tests/_PBKDF2Tests/_PBKDF2Tests.swift b/Tests/_PBKDF2Tests/_PBKDF2Tests.swift deleted file mode 100644 index f64d7be..0000000 --- a/Tests/_PBKDF2Tests/_PBKDF2Tests.swift +++ /dev/null @@ -1,169 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the OracleNIO open source project -// -// Copyright (c) 2024 Timo Zacherl and the OracleNIO project authors -// Licensed under Apache License v2.0 -// -// See LICENSE for license information -// See CONTRIBUTORS.md for the list of OracleNIO project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -import Crypto -import XCTest -import _PBKDF2 - -final class _PBKDF2Tests: XCTestCase { - struct TestCase { - var p: Data - var s: Data - var c: Int - var dkLen: Int - var expected: (sha224: String, sha256: String, sha384: String, sha512: String) - var skip: Bool - - init( - p: String, - s: String, - c: Int, - dkLen: Int, - expected: (sha224: String, sha256: String, sha384: String, sha512: String), - skip: Bool = false - ) { - self.p = p.data(using: .utf8)! - self.s = s.data(using: .utf8)! - self.c = c - self.dkLen = dkLen - self.expected = expected - self.skip = skip - } - } - - // Test inputs from https://www.ietf.org/rfc/rfc6070.txt - static let cases = [ - TestCase( - p: "password", s: "salt", c: 1, dkLen: 20, - expected: ( - "3c198cbdb9464b7857966bd05b7bc92bc1cc4e6e", - "120fb6cffcf8b32c43e7225256c4f837a86548c9", - "c0e14f06e49e32d73f9f52ddf1d0c5c719160923", - "867f70cf1ade02cff3752599a3a53dc4af34c7a6" - )), - TestCase( - p: "password", s: "salt", c: 2, dkLen: 20, - expected: ( - "93200ffa96c5776d38fa10abdf8f5bfc0054b971", - "ae4d0c95af6b46d32d0adff928f06dd02a303f8e", - "54f775c6d790f21930459162fc535dbf04a93918", - "e1d9c16aa681708a45f5c7c4e215ceb66e011a2e" - )), - TestCase( - p: "password", s: "salt", c: 4096, dkLen: 20, - expected: ( - "218c453bf90635bd0a21a75d172703ff6108ef60", - "c5e478d59288c841aa530db6845c4c8d962893a0", - "559726be38db125bc85ed7895f6e3cf574c7a01c", - "d197b1b33db0143e018b12f3d1d1479e6cdebdcc" - )), - TestCase( - p: "password", s: "salt", c: 16_777_216, dkLen: 20, - expected: ( - "b49925184cb4b559f365e94fcafcd4cdb9f7aef4", - "cf81c66fe8cfc04d1f31ecb65dab4089f7f179e8", - "a7fdb349ba2bfa6bf647bb0161bae1320df27e64", - "6180a3ceabab45cc3964112c811e0131bca93a35" - ), skip: true), // takes very long :) - TestCase( - p: "passwordPASSWORDpassword", s: "saltSALTsaltSALTsaltSALTsaltSALTsalt", c: 4096, - dkLen: 25, - expected: ( - "056c4ba438ded91fc14e0594e6f52b87e1f3690c0dc0fbc057", - "348c89dbcbd32b2f32d814b8116e84cf2b17347ebc1800181c", - "819143ad66df9a552559b9e131c52ae6c5c1b0eed18f4d283b", - "8c0511f4c6e597c6ac6315d8f0362e225f3c501495ba23b868" - )), - TestCase( - p: "pass\0word", s: "sa\0lt", c: 4096, dkLen: 16, - expected: ( - "9b4011b641f40a2a500a31d4a392d15c", - "89b69d0516f829893c696226650a8687", - "a3f00ac8657e095f8e0823d232fc60b3", - "9d9e9c4cd21fe4be24d5b8244c759665" - )), - TestCase( - p: "passwd", s: "salt", c: 1, dkLen: 128, - expected: ( - "e55bd77cfc18b012ac6362e22d7cdf77c4b03879a6af51fbf0045bc32a03e7f0d829d26b765bff0ca5873e07a8e85804ff4a17683ed706130d51657456bc0ebd07c35ca0675b3113ad9c33fe48a5eb9e9dc6c6a8cf5cf6de1318b414dbe667bfaeb863ef8399ff4a732520dab4ba82336513a25077ddfc11fc618c11efaf04ae", - "55ac046e56e3089fec1691c22544b605f94185216dde0465e68b9d57c20dacbc49ca9cccf179b645991664b39d77ef317c71b845b1e30bd509112041d3a19783c294e850150390e1160c34d62e9665d659ae49d314510fc98274cc79681968104b8f89237e69b2d549111868658be62f59bd715cac44a1147ed5317c9bae6b2a", - "cd3443723a41cf1460cca9efeede428a8898a82d2ad4d1fc5cca08ed3f4d3cb47a62a70b3cb9ce65dcbfb9fb9d425027a8be69b53e2a22674b0939e5e0a682f76d21f449ad184562a3bc4c519b4d048de6d8e0999fb88770f95e40185e19fc8b68767417ccc064f47a455d045b3bafda7e81b97ad0e4c5581af1aa27871cd5e4", - "c74319d99499fc3e9013acff597c23c5baf0a0bec5634c46b8352b793e324723d55caa76b2b25c43402dcfdc06cdcf66f95b7d0429420b39520006749c51a04ef3eb99e576617395a178ba33214793e48045132928a9e9bf2661769fdc668f31798597aaf6da70dd996a81019726084d70f152baed8aafe2227c07636c6ddece" - )), - TestCase( - p: "Password", s: "NaCl", c: 80_000, dkLen: 128, - expected: ( - "bebbdf809d53fc84531d0abe06679a8c8526fde47b47245634186908335857334a7578543f9241726d845ee8e575105e4a733b5dcaefa7560af3d028eccf95937535918dbaa84269fc0586711e7a5b9dc0d4c28fc7a89469db7ff5829b8fc1ef709d7ef95c6c7db24cece88f7c1408c8e7cee55c84db0eebb8d8e419bb50e17b", - "4ddcd8f60b98be21830cee5ef22701f9641a4418d04c0414aeff08876b34ab56a1d425a1225833549adb841b51c9b3176a272bdebba1d078478f62b397f33c8d62aae85a11cdde829d89cb6ffd1ab0e63a981f8747d2f2f9fe5874165c83c168d2eed1d2d5ca4052dec2be5715623da019b8c0ec87dc36aa751c38f9893d15c3", - "11c198987730fa113458053cd5cc9b51d7024a35f9134f1ee8740923c901aab23bbaea43686981b6e6a9f4130a1401daeeec74060246ebac958f3cfc3c65579b6e3d08b94ade5fc257a6902a0a1664b8dbd5a8ae2af70438931d3f3679abffc7a17770582f1ee413cc0d9914ce5f8143c8a7dc9c43fbc31e3d41b2030fb73c02", - "e6337d6fbeb645c794d4a9b5b75b7b30dac9ac50376a91df1f4460f6060d5addb2c1fd1f84409abacc67de7eb4056e6bb06c2d82c3ef4ccd1bded0f675ed97c65c33d39f81248454327aa6d03fd049fc5cbb2b5e6dac08e8ace996cdc960b1bd4530b7e754773d75f67a733fdb99baf6470e42ffcb753c15c352d4800fb6f9d6" - )), - TestCase( - p: "Password", s: "sa\0lt", c: 4096, dkLen: 256, - expected: ( - "a329a360c825e12e454ad8633a842a06ba1456907770779d1fa4e0b61a5b1c6ce02e71de74ae433bbf14b907690d008d0cab5b01c976c1e627b027a9a809fd001082c809650344ecfcdebdf0d64b92cb1e869bf91b75517ea36918127b1eccc4cac145fb965071292a6dfa388d8ad893d2541f83a0dac1c55d2d90709963b066de985e92974e87b7d8c0e8026d96684bb0425203919b4792962b065e2b2b815ba888b8428ae51f57a74f637a658e27cf5fbc5593e85f775a1f81660850a723e2eb565f30dfc2cf2973ad57ec95b89c0979c7bab81c11d8987540a32badb2f7bbe4ff21a4f0d91dbd911b88ddd928603fd27b0ede994ee99edd2c04667b82067f", - "436c82c6af9010bb0fdb274791934ac7dee21745dd11fb57bb90112ab187c495ad82df776ad7cefb606f34fedca59baa5922a57f3e91bc0e11960da7ec87ed0471b456a0808b60dff757b7d313d4068bf8d337a99caede24f3248f87d1bf16892b70b076a07dd163a8a09db788ae34300ff2f2d0a92c9e678186183622a636f4cbce15680dfea46f6d224e51c299d4946aa2471133a649288eef3e4227b609cf203dba65e9fa69e63d35b6ff435ff51664cbd6773d72ebc341d239f0084b004388d6afa504eee6719a7ae1bb9daf6b7628d851fab335f1d13948e8ee6f7ab033a32df447f8d0950809a70066605d6960847ed436fa52cdfbcf261b44d2a87061", - "cf6f194aaf4e970afea1f41169045029e34759e124a670b5f73053da552a190ad2d7085533b8b22901f0e3caeeb431ba673468f981352dfcbe517699db791777cf52346a460b093c59ea300fb18daee270e2ea8473806da1663cebe7438b51fe56ba832c13d88ad5b2e46404457c34cc6ad8e5cd8707a1acfa737f3617628a5983d8d10fa16a92652cfa736d4610132710a517c216cc3252e6c2b8aae0275d04a49756fa5bf1bb067bc367d1b8c80c3df7dc22ee74b4be4150871624bfdde3f86f5fbd4e0828af7d5a4f01b5605e54471435d827eaecf199db315ae60d1a6350105c0e1a71b40518a4a66ebba4792a511f8f52aeac961ebea215f8fb89ba998b", - "10176fb32cb98cd7bb31e2bb5c8f6e425c103333a2e496058e3fd2bd88f657485c89ef92daa0668316bc23ebd1ef88f6dd14157b2320b5d54b5f26377c5dc279b1dcdec044bd6f91b166917c80e1e99ef861b1d2c7bce1b961178125fb86867f6db489a2eae0022e7bc9cf421f044319fac765d70cb89b45c214590e2ffb2c2b565ab3b9d07571fde0027b1dc57f8fd25afa842c1056dd459af4074d7510a0c020b914a5e202445d4d3f151070589dd6a2554fc506018c4f001df6239643dc86771286ae4910769d8385531bba57544d63c3640b90c98f1445ebdd129475e02086b600f0beb5b05cc6ca9b3633b452b7dad634e9336f56ec4c3ac0b4fe54ced8" - )), - ] - - func testAll() throws { - let total = Self.cases.count - let clock = ContinuousClock() - for (index, testCase) in Self.cases.enumerated() where !testCase.skip { - print(" running: \(index + 1)/\(total)") - let duration = try clock.measure { - let sha256 = try PBKDF2.calculate( - length: testCase.dkLen, password: testCase.p, salt: testCase.s, - rounds: testCase.c) - let sha384 = try PBKDF2.calculate( - length: testCase.dkLen, password: testCase.p, salt: testCase.s, - rounds: testCase.c) - let sha512 = try PBKDF2.calculate( - length: testCase.dkLen, password: testCase.p, salt: testCase.s, - rounds: testCase.c) - XCTAssertEqual(sha256.hexString, testCase.expected.sha256) - XCTAssertEqual(sha384.hexString, testCase.expected.sha384) - XCTAssertEqual(sha512.hexString, testCase.expected.sha512) - } - print("finished: \(index + 1)/\(total) - took: \(duration)") - } - } -} - -let charA = UInt8(UnicodeScalar("a").value) -let char0 = UInt8(UnicodeScalar("0").value) - -private func itoh(_ value: UInt8) -> UInt8 { - return (value > 9) ? (charA + value - 10) : (char0 + value) -} - -extension DataProtocol { - var hexString: String { - let hexLen = self.count * 2 - var hexChars = [UInt8](repeating: 0, count: hexLen) - var offset = 0 - - for _ in self.regions { - for i in self { - hexChars[Int(offset * 2)] = itoh((i >> 4) & 0xF) - hexChars[Int(offset * 2 + 1)] = itoh(i & 0xF) - offset += 1 - } - } - - return String(bytes: hexChars, encoding: .utf8)! - } -}