Files
2024-08-12 10:49:20 +08:00

336 lines
8.8 KiB
Swift
Executable File

//
// HMAC.swift
// Cryptor
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
import CommonCrypto
#elseif os(Linux)
import OpenSSL
#endif
///
/// Calculates a cryptographic Hash-Based Message Authentication Code (HMAC).
///
public class HMAC: Updatable {
///
/// Enumerates available algorithms.
///
public enum Algorithm {
/// Message Digest 5
case md5
/// Secure Hash Algorithm 1
case sha1
/// Secure Hash Algorithm 2 224-bit
case sha224
/// Secure Hash Algorithm 2 256-bit
case sha256
/// Secure Hash Algorithm 2 384-bit
case sha384
/// Secure Hash Algorithm 2 512-bit
case sha512
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
static let fromNative: [CCHmacAlgorithm: Algorithm] = [
CCHmacAlgorithm(kCCHmacAlgSHA1): .sha1,
CCHmacAlgorithm(kCCHmacAlgSHA1): .md5,
CCHmacAlgorithm(kCCHmacAlgSHA256): .sha256,
CCHmacAlgorithm(kCCHmacAlgSHA384): .sha384,
CCHmacAlgorithm(kCCHmacAlgSHA512): .sha512,
CCHmacAlgorithm(kCCHmacAlgSHA224): .sha224
]
static func fromNativeValue(nativeAlg: CCHmacAlgorithm) -> Algorithm? {
return fromNative[nativeAlg]
}
func nativeValue() -> CCHmacAlgorithm {
switch self {
case .sha1:
return CCHmacAlgorithm(kCCHmacAlgSHA1)
case .md5:
return CCHmacAlgorithm(kCCHmacAlgMD5)
case .sha224:
return CCHmacAlgorithm(kCCHmacAlgSHA224)
case .sha256:
return CCHmacAlgorithm(kCCHmacAlgSHA256)
case .sha384:
return CCHmacAlgorithm(kCCHmacAlgSHA384)
case .sha512:
return CCHmacAlgorithm(kCCHmacAlgSHA512)
}
}
#elseif os(Linux)
func nativeValue() -> OpaquePointer? {
switch self {
case .sha1:
return .init(EVP_sha1())
case .md5:
return .init(EVP_md5())
case .sha224:
return .init(EVP_sha224())
case .sha256:
return .init(EVP_sha256())
case .sha384:
return .init(EVP_sha384())
case .sha512:
return .init(EVP_sha512())
}
}
#endif
///
/// Obtains the digest length produced by this algorithm (in bytes).
///
public func digestLength() -> Int {
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
switch self {
case .sha1:
return Int(CC_SHA1_DIGEST_LENGTH)
case .md5:
return Int(CC_MD5_DIGEST_LENGTH)
case .sha224:
return Int(CC_SHA224_DIGEST_LENGTH)
case .sha256:
return Int(CC_SHA256_DIGEST_LENGTH)
case .sha384:
return Int(CC_SHA384_DIGEST_LENGTH)
case .sha512:
return Int(CC_SHA512_DIGEST_LENGTH)
}
#elseif os(Linux)
switch self {
case .sha1:
return Int(SHA_DIGEST_LENGTH)
case .md5:
return Int(MD5_DIGEST_LENGTH)
case .sha224:
return Int(SHA224_DIGEST_LENGTH)
case .sha256:
return Int(SHA256_DIGEST_LENGTH)
case .sha384:
return Int(SHA384_DIGEST_LENGTH)
case .sha512:
return Int(SHA512_DIGEST_LENGTH)
}
#endif
}
}
/// Context
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
typealias Context = UnsafeMutablePointer<CCHmacContext>
#elseif os(Linux)
typealias Context = OpaquePointer?
#endif
/// Status of the calculation
public internal(set) var status: Status = .success
#if os(Linux)
private let context = HMAC_CTX_new_wrapper()
#else
private let context = Context.allocate(capacity: 1)
#endif
private var algorithm: Algorithm
// MARK: Lifecycle Methods
///
/// Creates a new HMAC instance with the specified algorithm and key.
///
/// - Parameters:
/// - algorithm: Selects the algorithm
/// - keyBuffer: Specifies pointer to the key
/// - keyByteCount: Number of bytes on keyBuffer
///
init(using algorithm: Algorithm, keyBuffer: UnsafeRawPointer, keyByteCount: Int) {
self.algorithm = algorithm
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
CCHmacInit(context, algorithm.nativeValue(), keyBuffer, size_t(keyByteCount))
#elseif os(Linux)
HMAC_Init_wrapper(context, keyBuffer, Int32(keyByteCount), .make(optional: algorithm.nativeValue()))
#endif
}
///
/// Creates a new HMAC instance with the specified algorithm and key.
///
/// - Parameters:
/// - algorithm: Selects the algorithm
/// - key: Specifies the key as Data
///
public init(using algorithm: Algorithm, key: Data) {
self.algorithm = algorithm
#if swift(>=5.0)
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
key.withUnsafeBytes() {
CCHmacInit(context, algorithm.nativeValue(), $0.baseAddress, size_t(key.count))
}
#elseif os(Linux)
_ = key.withUnsafeBytes() {
HMAC_Init_wrapper(context, $0.baseAddress, Int32(key.count), .make(optional: algorithm.nativeValue()))
}
#endif
#else
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
key.withUnsafeBytes() { (buffer: UnsafePointer<UInt8>) in
CCHmacInit(context, algorithm.nativeValue(), buffer, size_t(key.count))
}
#elseif os(Linux)
_ = key.withUnsafeBytes() { (buffer: UnsafePointer<UInt8>) in
HMAC_Init_wrapper(context, buffer, Int32(key.count), .make(optional: algorithm.nativeValue()))
}
#endif
#endif
}
///
/// Creates a new HMAC instance with the specified algorithm and key.
///
/// - Parameters:
/// - algorithm: Selects the algorithm
/// - key: Specifies the key as NSData
///
public init(using algorithm: Algorithm, key: NSData) {
self.algorithm = algorithm
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
CCHmacInit(context, algorithm.nativeValue(), key.bytes, size_t(key.length))
#elseif os(Linux)
HMAC_Init_wrapper(context, key.bytes, Int32(key.length), .make(optional: algorithm.nativeValue()))
#endif
}
///
/// Creates a new HMAC instance with the specified algorithm and key.
///
/// - Parameters:
/// - algorithm: Selects the algorithm
/// - key: Specifies the key as byte array.
///
public init(using algorithm: Algorithm, key: [UInt8]) {
self.algorithm = algorithm
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
CCHmacInit(context, algorithm.nativeValue(), key, size_t(key.count))
#elseif os(Linux)
HMAC_Init_wrapper(context, key, Int32(key.count), .make(optional: algorithm.nativeValue()))
#endif
}
///
/// Creates a new HMAC instance with the specified algorithm and key string.
/// The key string is converted to bytes using UTF8 encoding.
///
/// - Parameters:
/// - algorithm: Selects the algorithm
/// - key: Specifies the key as String
///
public init(using algorithm: Algorithm, key: String) {
self.algorithm = algorithm
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
CCHmacInit(context, algorithm.nativeValue(), key, size_t(key.lengthOfBytes(using: String.Encoding.utf8)))
#elseif os(Linux)
HMAC_Init_wrapper(context, key, Int32(key.utf8.count), .make(optional: algorithm.nativeValue()))
#endif
}
///
/// Cleanup
///
deinit {
#if os(Linux)
HMAC_CTX_free_wrapper(.make(optional: context))
#else
#if swift(>=4.1)
context.deallocate()
#else
context.deallocate(capacity: 1)
#endif
#endif
}
// MARK: Public Methods
///
/// Updates the calculation of the HMAC with the contents of a buffer.
///
/// - Parameter buffer: Update buffer
///
/// - Returns: The 'in-progress' calculated HMAC
///
public func update(from buffer: UnsafeRawPointer, byteCount: size_t) -> Self? {
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
CCHmacUpdate(context, buffer, byteCount)
#elseif os(Linux)
HMAC_Update(context, buffer.assumingMemoryBound(to: UInt8.self), byteCount)
#endif
return self
}
///
/// Finalizes the HMAC calculation
///
/// - Returns: The final calculated HMAC
///
public func final() -> [UInt8] {
var hmac = Array<UInt8>(repeating: 0, count:algorithm.digestLength())
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
CCHmacFinal(context, &hmac)
#elseif os(Linux)
var length: UInt32 = 0
HMAC_Final(context, &hmac, &length)
#endif
return hmac
}
}