杨锴
2024-08-14 909e20941e45f8712c012db602034b47da0bfdb0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
//
//  CryptoSwift
//
//  Copyright (C) 2014-2022 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/rfc2898.txt
//
 
#if canImport(Darwin)
import Darwin
#elseif canImport(Glibc)
import Glibc
#elseif canImport(ucrt)
import ucrt
#elseif canImport(WASILibc)
import WASILibc
#endif
 
public extension PKCS5 {
  /// A key derivation function.
  ///
  /// PBKDF2 - Password-Based Key Derivation Function 2. Key stretching technique.
  ///          DK = PBKDF2(PRF, Password, Salt, c, dkLen)
  struct PBKDF2 {
    public enum Error: Swift.Error {
      case invalidInput
      case derivedKeyTooLong
    }
 
    private let salt: Array<UInt8> // S
    fileprivate let iterations: Int // c
    private let numBlocks: Int // l
    private let dkLen: Int
    fileprivate let prf: HMAC
 
    /// - parameters:
    ///   - salt: salt
    ///   - variant: hash variant
    ///   - iterations: iteration count, a positive integer
    ///   - keyLength: intended length of derived key
    ///   - variant: MAC variant. Defaults to SHA256
    public init(password: Array<UInt8>, salt: Array<UInt8>, iterations: Int = 4096 /* c */, keyLength: Int? = nil /* dkLen */, variant: HMAC.Variant = .sha2(.sha256)) throws {
      precondition(iterations > 0)
 
      let prf = HMAC(key: password, variant: variant)
 
      guard iterations > 0 && !salt.isEmpty else {
        throw Error.invalidInput
      }
 
      self.dkLen = keyLength ?? variant.digestLength
      let keyLengthFinal = Double(dkLen)
      let hLen = Double(prf.variant.digestLength)
      if keyLengthFinal > (pow(2, 32) - 1) * hLen {
        throw Error.derivedKeyTooLong
      }
 
      self.salt = salt
      self.iterations = iterations
      self.prf = prf
 
      self.numBlocks = Int(ceil(Double(keyLengthFinal) / hLen)) // l = ceil(keyLength / hLen)
    }
 
    public func calculate() throws -> Array<UInt8> {
      var ret = Array<UInt8>()
      ret.reserveCapacity(self.numBlocks * self.prf.variant.digestLength)
      for i in 1...self.numBlocks {
        // for each block T_i = U_1 ^ U_2 ^ ... ^ U_iter
        if let value = try calculateBlock(self.salt, blockNum: i) {
          ret.append(contentsOf: value)
        }
      }
      return Array(ret.prefix(self.dkLen))
    }
 
    public func callAsFunction() throws -> Array<UInt8> {
      try calculate()
    }
  }
}
 
private extension PKCS5.PBKDF2 {
  func ARR(_ i: Int) -> Array<UInt8> {
    var inti = Array<UInt8>(repeating: 0, count: 4)
    inti[0] = UInt8((i >> 24) & 0xff)
    inti[1] = UInt8((i >> 16) & 0xff)
    inti[2] = UInt8((i >> 8) & 0xff)
    inti[3] = UInt8(i & 0xff)
    return inti
  }
 
  // F (P, S, c, i) = U_1 \xor U_2 \xor ... \xor U_c
  // U_1 = PRF (P, S || INT (i))
  func calculateBlock(_ salt: Array<UInt8>, blockNum: Int) throws -> Array<UInt8>? {
    guard let u1 = try? prf.authenticate(salt + ARR(blockNum)) else { // blockNum.bytes() is slower
      return nil
    }
 
    var u = u1
    var ret = u
    if iterations > 1 {
      // U_2 = PRF (P, U_1) ,
      // U_c = PRF (P, U_{c-1}) .
      for _ in 2...iterations {
        u = try prf.authenticate(u)
        for x in 0..<ret.count {
          ret[x] = ret[x] ^ u[x]
        }
      }
    }
    return ret
  }
}