Explorar o código

First attempt to implement HMAC-based key derivation function (#532)

* Add HMAC-base key derivation function and corresponding test fixtures

* [HKDF] update README.md
Alexey Komnin %!s(int64=7) %!d(string=hai) anos
pai
achega
76771ae3b8

+ 8 - 0
CryptoSwift.xcodeproj/project.pbxproj

@@ -86,6 +86,8 @@
 		75EC52B81EE8B83D0048EB3B /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75EC52781EE8B6CA0048EB3B /* Utils.swift */; };
 		75EC52B91EE8B83D0048EB3B /* ZeroPadding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75EC52791EE8B6CA0048EB3B /* ZeroPadding.swift */; };
 		E3FD2D531D6B81CE00A9F35F /* Error+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3FD2D511D6B813C00A9F35F /* Error+Extension.swift */; };
+		E6200E141FB9A7AE00258382 /* HKDF.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6200E131FB9A7AE00258382 /* HKDF.swift */; };
+		E6200E171FB9B68C00258382 /* HKDFTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6200E151FB9B67C00258382 /* HKDFTests.swift */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -236,6 +238,8 @@
 		75EC527A1EE8B6CA0048EB3B /* CryptoSwift.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CryptoSwift.h; sourceTree = "<group>"; };
 		75EDCB811DAC4CA400D270E0 /* LinuxMain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LinuxMain.swift; path = Tests/LinuxMain.swift; sourceTree = SOURCE_ROOT; };
 		E3FD2D511D6B813C00A9F35F /* Error+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Error+Extension.swift"; sourceTree = "<group>"; };
+		E6200E131FB9A7AE00258382 /* HKDF.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HKDF.swift; sourceTree = "<group>"; };
+		E6200E151FB9B67C00258382 /* HKDFTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HKDFTests.swift; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -291,6 +295,7 @@
 				754BE46719693E190098E6F3 /* DigestTests.swift */,
 				75100F8E19B0BC890005C5F5 /* Poly1305Tests.swift */,
 				758A94271A65C59200E46135 /* HMACTests.swift */,
+				E6200E151FB9B67C00258382 /* HKDFTests.swift */,
 				757DA2541A4ED408002BA3EF /* AESTests.swift */,
 				750CC3EA1DC0CACE0096BE6E /* BlowfishTests.swift */,
 				757DA2581A4ED4D7002BA3EF /* ChaCha20Tests.swift */,
@@ -347,6 +352,7 @@
 				75EC52511EE8B6CA0048EB3B /* DigestType.swift */,
 				75EC52521EE8B6CA0048EB3B /* Foundation */,
 				75EC525C1EE8B6CA0048EB3B /* Generics.swift */,
+				E6200E131FB9A7AE00258382 /* HKDF.swift */,
 				75EC525D1EE8B6CA0048EB3B /* HMAC.swift */,
 				75EC52611EE8B6CA0048EB3B /* MD5.swift */,
 				75EC52621EE8B6CA0048EB3B /* NoPadding.swift */,
@@ -540,6 +546,7 @@
 			files = (
 				75EC52861EE8B8170048EB3B /* CFB.swift in Sources */,
 				75EC52901EE8B81A0048EB3B /* Collection+Extension.swift in Sources */,
+				E6200E141FB9A7AE00258382 /* HKDF.swift in Sources */,
 				75EC529F1EE8B8230048EB3B /* HMAC.swift in Sources */,
 				75EC52B91EE8B83D0048EB3B /* ZeroPadding.swift in Sources */,
 				75EC529E1EE8B8230048EB3B /* Generics.swift in Sources */,
@@ -612,6 +619,7 @@
 				75482EA41CB310B7001F66A5 /* PBKDF.swift in Sources */,
 				758A94291A65C67400E46135 /* HMACTests.swift in Sources */,
 				75100F8F19B0BC890005C5F5 /* Poly1305Tests.swift in Sources */,
+				E6200E171FB9B68C00258382 /* HKDFTests.swift in Sources */,
 				753B33011DAB84D600D06422 /* RandomBytesSequenceTests.swift in Sources */,
 				754BE46819693E190098E6F3 /* DigestTests.swift in Sources */,
 				E3FD2D531D6B81CE00A9F35F /* Error+Extension.swift in Sources */,

+ 11 - 0
README.md

@@ -64,6 +64,7 @@ Good mood
 #### Password-Based Key Derivation Function
 - [PBKDF1](http://tools.ietf.org/html/rfc2898#section-5.1) (Password-Based Key Derivation Function 1)
 - [PBKDF2](http://tools.ietf.org/html/rfc2898#section-5.2) (Password-Based Key Derivation Function 2)
+- [HKDF](https://tools.ietf.org/html/rfc5869) (HMAC-based Extract-and-Expand Key Derivation Function)
 
 #### Data padding
 - PKCS#5
@@ -171,6 +172,7 @@ See: [Package.swift - manual](http://blog.krzyzanowskim.com/2016/08/09/package-s
 * [Digest (MD5, SHA...)](#calculate-digest)
 * [Message authenticators (HMAC...)](#message-authenticators-1)
 * [Password-Based Key Derivation Function (PBKDF2, ...)](#password-based-key-derivation-functions)
+* [HMAC-based Key Derivation Function (HKDF)](#hmac-based-key-derivation-function)
 * [Data Padding](#data-padding)
 * [ChaCha20](#chacha20)
 * [Rabbit](#rabbit)
@@ -287,6 +289,15 @@ let salt: Array<UInt8> = Array("nacllcan".utf8)
 try PKCS5.PBKDF2(password: password, salt: salt, iterations: 4096, variant: .sha256).calculate()
 ```
 
+##### HMAC-based Key Derivation Function
+
+```swift
+let password: Array<UInt8> = Array("s33krit".utf8)
+let salt: Array<UInt8> = Array("nacllcan".utf8)
+
+try HKDF(password: password, salt: salt, variant: .sha256).calculate()
+```
+
 ##### Data Padding
     
 Some content-encryption algorithms assume the input length is a multiple of `k` octets, where `k` is greater than one. For such algorithms, the input shall be padded.

+ 87 - 0
Sources/CryptoSwift/HKDF.swift

@@ -0,0 +1,87 @@
+//
+//  HKDF.swift
+//  CryptoSwift
+//
+//  Copyright (C) 2014-2017 Marcin Krzyżanowski <marcin@krzyzanowskim.com>
+//  This software is provided 'as-is', without any express or implied warranty.
+//
+//  In no event will the authors be held liable for any damages arising from the use of this software.
+//
+//  Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
+//
+//  - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required.
+//  - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
+//  - This notice may not be removed or altered from any source or binary distribution.
+//
+
+//  https://www.ietf.org/rfc/rfc5869.txt
+//
+
+#if os(Linux) || os(Android) || os(FreeBSD)
+    import Glibc
+#else
+    import Darwin
+#endif
+    
+/// A key derivation function.
+///
+/// HKDF   - HMAC-based Extract-and-Expand Key Derivation Function.
+public struct HKDF {
+    
+    public enum Error: Swift.Error {
+        case invalidInput
+        case derivedKeyTooLong
+    }
+    
+    private let numBlocks: Int // l
+    private let dkLen: Int
+    private let info: Array<UInt8>
+    fileprivate let prk: Array<UInt8>
+    fileprivate let variant: HMAC.Variant
+    
+    /// - parameters:
+    ///   - variant: hash variant
+    ///   - salt: optional salt (if not provided, it is set to a sequence of variant.digestLength zeros)
+    ///   - info: optional context and application specific information
+    ///   - keyLength: intended length of derived key
+    public init(password: Array<UInt8>, salt: Array<UInt8>? = nil, info: Array<UInt8>? = nil, keyLength: Int? = nil /* dkLen */, variant: HMAC.Variant = .sha256) throws {
+        guard !password.isEmpty else {
+            throw Error.invalidInput
+        }
+        
+        let dkLen = keyLength ?? variant.digestLength
+        let keyLengthFinal = Double(dkLen)
+        let hLen = Double(variant.digestLength)
+        let numBlocks = Int(ceil(keyLengthFinal / hLen)) // l = ceil(keyLength / hLen)
+        guard numBlocks <= 255 else {
+            throw Error.derivedKeyTooLong
+        }
+        
+        /// HKDF-Extract(salt, password) -> PRK
+        ///  - PRK - a pseudo-random key; it is used by calculate()
+        self.prk = try HMAC(key: salt ?? [], variant: variant).authenticate(password)
+        self.info = info ?? []
+        self.variant = variant
+        self.dkLen = dkLen
+        self.numBlocks = numBlocks
+    }
+    
+    public func calculate() throws -> Array<UInt8> {
+        let hmac = HMAC(key: self.prk, variant: self.variant)
+        var ret = Array<UInt8>()
+        ret.reserveCapacity(self.numBlocks * self.variant.digestLength)
+        var value = Array<UInt8>()
+        for i in 1...self.numBlocks {
+            value.append(contentsOf: self.info)
+            value.append(UInt8(i))
+            
+            let bytes = try hmac.authenticate(value)
+            ret.append(contentsOf: bytes)
+            
+            /// update value to use it as input for next iteration
+            value = bytes
+        }
+        return Array(ret.prefix(dkLen))
+    }
+}
+

+ 91 - 0
Tests/CryptoSwiftTests/HKDFTests.swift

@@ -0,0 +1,91 @@
+//
+//  HKDFTests.swift
+//  CryptoSwift
+//
+//  Created by Alexey Komnin on 11/13/17.
+//  Copyright © 2017 Marcin Krzyzanowski. All rights reserved.
+//
+
+import XCTest
+@testable import CryptoSwift
+
+class HKDFTests: XCTestCase {
+    
+    /// All test cases are implemented with regard to RFC 5869
+    /// https://www.ietf.org/rfc/rfc5869.txt
+    
+    func testHKDF1() {
+        let password = Array<UInt8>(hex: "0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b")
+        let salt = Array<UInt8>(hex: "0x000102030405060708090a0b0c")
+        let info = Array<UInt8>(hex: "0xf0f1f2f3f4f5f6f7f8f9")
+        let keyLength = 42
+        let variant = HMAC.Variant.sha256
+        let reference = Array<UInt8>(hex: "0x3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865")
+        
+        XCTAssertEqual(reference, try HKDF(password: password, salt: salt, info: info, keyLength: keyLength, variant: variant).calculate())
+    }
+    
+    func testHKDF2() {
+        let password = Array<UInt8>(hex: "0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f")
+        let salt = Array<UInt8>(hex: "0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf")
+        let info = Array<UInt8>(hex: "0xb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff")
+        let keyLength = 82
+        let variant = HMAC.Variant.sha256
+        let reference = Array<UInt8>(hex: "0xb11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71cc30c58179ec3e87c14c01d5c1f3434f1d87")
+        
+        XCTAssertEqual(reference, try HKDF(password: password, salt: salt, info: info, keyLength: keyLength, variant: variant).calculate())
+    }
+    
+    func testHKDF3() {
+        let password = Array<UInt8>(hex: "0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b")
+        let keyLength = 42
+        let variant = HMAC.Variant.sha256
+        let reference = Array<UInt8>(hex: "0x8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8")
+        
+        XCTAssertEqual(reference, try HKDF(password: password, keyLength: keyLength, variant: variant).calculate())
+    }
+    
+    func testHKDF4() {
+        let password = Array<UInt8>(hex: "0x0b0b0b0b0b0b0b0b0b0b0b")
+        let salt = Array<UInt8>(hex: "0x000102030405060708090a0b0c")
+        let info = Array<UInt8>(hex: "0xf0f1f2f3f4f5f6f7f8f9")
+        let keyLength = 42
+        let variant = HMAC.Variant.sha1
+        let reference = Array<UInt8>(hex: "0x085a01ea1b10f36933068b56efa5ad81a4f14b822f5b091568a9cdd4f155fda2c22e422478d305f3f896")
+        
+        XCTAssertEqual(reference, try HKDF(password: password, salt: salt, info: info, keyLength: keyLength, variant: variant).calculate())
+    }
+    
+    func testHKDF5() {
+        let password = Array<UInt8>(hex: "0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f")
+        let salt = Array<UInt8>(hex: "0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf")
+        let info = Array<UInt8>(hex: "0xb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff")
+        let keyLength = 82
+        let variant = HMAC.Variant.sha1
+        let reference = Array<UInt8>(hex: "0x0bd770a74d1160f7c9f12cd5912a06ebff6adcae899d92191fe4305673ba2ffe8fa3f1a4e5ad79f3f334b3b202b2173c486ea37ce3d397ed034c7f9dfeb15c5e927336d0441f4c4300e2cff0d0900b52d3b4")
+        
+        XCTAssertEqual(reference, try HKDF(password: password, salt: salt, info: info, keyLength: keyLength, variant: variant).calculate())
+    }
+    
+    func testHKDF6() {
+        let password = Array<UInt8>(hex: "0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b")
+        let keyLength = 42
+        let variant = HMAC.Variant.sha1
+        let reference = Array<UInt8>(hex: "0x0ac1af7002b3d761d1e55298da9d0506b9ae52057220a306e07b6b87e8df21d0ea00033de03984d34918")
+        
+        XCTAssertEqual(reference, try HKDF(password: password, keyLength: keyLength, variant: variant).calculate())
+    }
+
+    static func allTests() -> [(String, (HKDFTests) -> () -> Void)] {
+        let tests = [
+            ("Basic test case with SHA-256", testHKDF1),
+            ("Test with SHA-256 and longer inputs/outputs", testHKDF2),
+            ("Test with SHA-256 and zero-length salt/info", testHKDF3),
+            ("Basic test case with SHA-1", testHKDF4),
+            ("Test with SHA-1 and longer inputs/outputs", testHKDF5),
+            ("Test with SHA-1 and zero-length salt/info", testHKDF6)
+        ]
+
+        return tests
+    }
+}