1131 lines
39 KiB
Swift
1131 lines
39 KiB
Swift
import Foundation
|
|
import CommonCrypto
|
|
import Security
|
|
|
|
public struct NativeRsaBundle {
|
|
public let publicKey: String
|
|
public let privateKey: String
|
|
|
|
public init(publicKey: String, privateKey: String) {
|
|
self.publicKey = publicKey
|
|
self.privateKey = privateKey
|
|
}
|
|
}
|
|
|
|
public enum NativeCipherCore {
|
|
public static func digestHex(kind: String, plainText: String) throws -> String {
|
|
let input = Data(plainText.utf8)
|
|
let digest: Data
|
|
switch normalize(kind) {
|
|
case "MD5":
|
|
digest = hashData(input, length: Int(CC_MD5_DIGEST_LENGTH)) { buffer, bytes, count in
|
|
_ = CC_MD5(bytes, CC_LONG(count), buffer.bindMemory(to: UInt8.self).baseAddress)
|
|
}
|
|
case "SHA1":
|
|
digest = hashData(input, length: Int(CC_SHA1_DIGEST_LENGTH)) { buffer, bytes, count in
|
|
_ = CC_SHA1(bytes, CC_LONG(count), buffer.bindMemory(to: UInt8.self).baseAddress)
|
|
}
|
|
case "SHA256":
|
|
digest = hashData(input, length: Int(CC_SHA256_DIGEST_LENGTH)) { buffer, bytes, count in
|
|
_ = CC_SHA256(bytes, CC_LONG(count), buffer.bindMemory(to: UInt8.self).baseAddress)
|
|
}
|
|
case "SHA512":
|
|
digest = hashData(input, length: Int(CC_SHA512_DIGEST_LENGTH)) { buffer, bytes, count in
|
|
_ = CC_SHA512(bytes, CC_LONG(count), buffer.bindMemory(to: UInt8.self).baseAddress)
|
|
}
|
|
default:
|
|
throw cipherError("不支持的摘要算法")
|
|
}
|
|
return digest.hexString()
|
|
}
|
|
|
|
public static func hmacHex(kind: String, secretText: String, plainText: String) throws -> String {
|
|
let algorithm: CCHmacAlgorithm
|
|
let digestLength: Int
|
|
switch normalize(kind) {
|
|
case "SHA1":
|
|
algorithm = CCHmacAlgorithm(kCCHmacAlgSHA1)
|
|
digestLength = Int(CC_SHA1_DIGEST_LENGTH)
|
|
case "SHA256":
|
|
algorithm = CCHmacAlgorithm(kCCHmacAlgSHA256)
|
|
digestLength = Int(CC_SHA256_DIGEST_LENGTH)
|
|
case "SHA512":
|
|
algorithm = CCHmacAlgorithm(kCCHmacAlgSHA512)
|
|
digestLength = Int(CC_SHA512_DIGEST_LENGTH)
|
|
default:
|
|
throw cipherError("不支持的HMAC算法")
|
|
}
|
|
|
|
let keyData = Data(secretText.utf8)
|
|
let inputData = Data(plainText.utf8)
|
|
var output = Data(count: digestLength)
|
|
output.withUnsafeMutableBytes { outputBuffer in
|
|
keyData.withUnsafeBytes { keyBuffer in
|
|
inputData.withUnsafeBytes { inputBuffer in
|
|
CCHmac(
|
|
algorithm,
|
|
keyBuffer.baseAddress,
|
|
keyData.count,
|
|
inputBuffer.baseAddress,
|
|
inputData.count,
|
|
outputBuffer.baseAddress
|
|
)
|
|
}
|
|
}
|
|
}
|
|
return output.hexString()
|
|
}
|
|
|
|
public static func encodeBase64(plainText: String) -> String {
|
|
Data(plainText.utf8).base64EncodedString()
|
|
}
|
|
|
|
public static func decodeBase64(encodedText: String) throws -> String {
|
|
guard let data = Data(base64Encoded: encodedText) else {
|
|
throw cipherError("Base64无效")
|
|
}
|
|
guard let text = String(data: data, encoding: .utf8) else {
|
|
throw cipherError("文本解码失败")
|
|
}
|
|
return text
|
|
}
|
|
|
|
public static func encodeCodec(codec: String, plainText: String) throws -> String {
|
|
let input = Data(plainText.utf8)
|
|
switch normalize(codec) {
|
|
case "UTF8":
|
|
return plainText
|
|
case "HEX":
|
|
return input.hexString()
|
|
case "BASE64":
|
|
return input.base64EncodedString()
|
|
case "BASE64URL":
|
|
return input.base64EncodedString()
|
|
.replacingOccurrences(of: "+", with: "-")
|
|
.replacingOccurrences(of: "/", with: "_")
|
|
.replacingOccurrences(of: "=+$", with: "", options: .regularExpression)
|
|
case "LATIN1":
|
|
return latin1String(from: input)
|
|
case "UTF16":
|
|
return utf16CodecString(from: input)
|
|
default:
|
|
throw cipherError("不支持的编码类型")
|
|
}
|
|
}
|
|
|
|
public static func decodeCodec(codec: String, encodedText: String) throws -> String {
|
|
switch normalize(codec) {
|
|
case "UTF8":
|
|
return encodedText
|
|
case "HEX":
|
|
return try utf8String(from: Data(hexString: encodedText))
|
|
case "BASE64":
|
|
return try decodeBase64(encodedText: encodedText)
|
|
case "BASE64URL":
|
|
var normalized = encodedText.replacingOccurrences(of: "-", with: "+")
|
|
.replacingOccurrences(of: "_", with: "/")
|
|
while normalized.count % 4 != 0 {
|
|
normalized.append("=")
|
|
}
|
|
return try decodeBase64(encodedText: normalized)
|
|
case "LATIN1":
|
|
return try utf8String(from: dataFromLatin1String(encodedText))
|
|
case "UTF16":
|
|
return try utf8String(from: dataFromUtf16CodecString(encodedText))
|
|
default:
|
|
throw cipherError("不支持的编码类型")
|
|
}
|
|
}
|
|
|
|
public static func aesEncryptText(secretText: String, plainText: String, mode: String, padding: String, ivText: String?, keyLength: Int?) throws -> String {
|
|
let cipherBytes = try aesEncryptBytes(
|
|
secretBytes: Array(Data(secretText.utf8)),
|
|
plainBytes: Array(Data(plainText.utf8)),
|
|
mode: mode,
|
|
padding: padding,
|
|
ivBytes: ivText.map { Array(Data($0.utf8)) },
|
|
keyLength: keyLength
|
|
)
|
|
return Data(cipherBytes).base64EncodedString()
|
|
}
|
|
|
|
public static func aesDecryptText(secretText: String, cipherText: String, mode: String, padding: String, ivText: String?, keyLength: Int?) throws -> String {
|
|
guard let cipherData = Data(base64Encoded: cipherText) else {
|
|
throw cipherError("Base64无效")
|
|
}
|
|
let plainBytes = try aesDecryptBytes(
|
|
secretBytes: Array(Data(secretText.utf8)),
|
|
cipherBytes: Array(cipherData),
|
|
mode: mode,
|
|
padding: padding,
|
|
ivBytes: ivText.map { Array(Data($0.utf8)) },
|
|
keyLength: keyLength
|
|
)
|
|
guard let text = String(data: Data(plainBytes), encoding: .utf8) else {
|
|
throw cipherError("文本解码失败")
|
|
}
|
|
return text
|
|
}
|
|
|
|
public static func aesEncryptBytes(secretBytes: [UInt8], plainBytes: [UInt8], mode: String, padding: String, ivBytes: [UInt8]?, keyLength: Int?) throws -> [UInt8] {
|
|
let config = try makeAesConfig(mode: mode, padding: padding, keyLength: keyLength)
|
|
let key = try normalizeKey(secretBytes, to: config.keyLength)
|
|
let iv = try normalizeIV(ivBytes, blockSize: kCCBlockSizeAES128, mode: config.mode)
|
|
let plainData = Data(plainBytes)
|
|
let padded = try applyPaddingIfNeeded(plainData, blockSize: kCCBlockSizeAES128, padding: config.padding, mode: config.mode)
|
|
let encrypted = try cryptWithMode(
|
|
operation: CCOperation(kCCEncrypt),
|
|
algorithm: CCAlgorithm(kCCAlgorithmAES),
|
|
mode: config.mode.ccMode,
|
|
options: config.mode.ccOptions,
|
|
key: key,
|
|
iv: iv,
|
|
input: padded
|
|
)
|
|
return Array(encrypted)
|
|
}
|
|
|
|
public static func aesDecryptBytes(secretBytes: [UInt8], cipherBytes: [UInt8], mode: String, padding: String, ivBytes: [UInt8]?, keyLength: Int?) throws -> [UInt8] {
|
|
let config = try makeAesConfig(mode: mode, padding: padding, keyLength: keyLength)
|
|
let key = try normalizeKey(secretBytes, to: config.keyLength)
|
|
let iv = try normalizeIV(ivBytes, blockSize: kCCBlockSizeAES128, mode: config.mode)
|
|
let decrypted = try cryptWithMode(
|
|
operation: CCOperation(kCCDecrypt),
|
|
algorithm: CCAlgorithm(kCCAlgorithmAES),
|
|
mode: config.mode.ccMode,
|
|
options: config.mode.ccOptions,
|
|
key: key,
|
|
iv: iv,
|
|
input: Data(cipherBytes)
|
|
)
|
|
let plainData = try removePaddingIfNeeded(decrypted, blockSize: kCCBlockSizeAES128, padding: config.padding, mode: config.mode)
|
|
return Array(plainData)
|
|
}
|
|
|
|
public static func tripleDesEncryptText(secretText: String, plainText: String, mode: String, ivText: String?) throws -> String {
|
|
let cipherData = try tripleDesTransform(
|
|
operation: CCOperation(kCCEncrypt),
|
|
secretBytes: Array(Data(secretText.utf8)),
|
|
input: try applyPadding(Data(plainText.utf8), blockSize: kCCBlockSize3DES, padding: .pkcs7),
|
|
modeName: mode,
|
|
ivBytes: ivText.map { Array(Data($0.utf8)) }
|
|
)
|
|
return cipherData.base64EncodedString()
|
|
}
|
|
|
|
public static func tripleDesDecryptText(secretText: String, cipherText: String, mode: String, ivText: String?) throws -> String {
|
|
guard let cipherData = Data(base64Encoded: cipherText) else {
|
|
throw cipherError("Base64无效")
|
|
}
|
|
let plainData = try tripleDesTransform(
|
|
operation: CCOperation(kCCDecrypt),
|
|
secretBytes: Array(Data(secretText.utf8)),
|
|
input: cipherData,
|
|
modeName: mode,
|
|
ivBytes: ivText.map { Array(Data($0.utf8)) }
|
|
)
|
|
let unpadded = try removePadding(plainData, blockSize: kCCBlockSize3DES, padding: .pkcs7)
|
|
guard let text = String(data: unpadded, encoding: .utf8) else {
|
|
throw cipherError("文本解码失败")
|
|
}
|
|
return text
|
|
}
|
|
|
|
public static func desEncryptText(secretText: String, plainText: String, mode: String, padding: String, ivText: String?) throws -> String {
|
|
let config = try makeDesConfig(mode: mode, padding: padding)
|
|
let key = try normalizeKey(Array(Data(secretText.utf8)), to: kCCKeySizeDES)
|
|
let iv = try normalizeIV(ivText.map { Array(Data($0.utf8)) }, blockSize: kCCBlockSizeDES, mode: config.mode)
|
|
let padded = try applyPaddingIfNeeded(Data(plainText.utf8), blockSize: kCCBlockSizeDES, padding: config.padding, mode: config.mode)
|
|
let encrypted = try cryptWithMode(
|
|
operation: CCOperation(kCCEncrypt),
|
|
algorithm: CCAlgorithm(kCCAlgorithmDES),
|
|
mode: config.mode.ccMode,
|
|
options: config.mode.ccOptions,
|
|
key: key,
|
|
iv: iv,
|
|
input: padded
|
|
)
|
|
return encrypted.base64EncodedString()
|
|
}
|
|
|
|
public static func desDecryptText(secretText: String, cipherText: String, mode: String, padding: String, ivText: String?) throws -> String {
|
|
guard let cipherData = Data(base64Encoded: cipherText) else {
|
|
throw cipherError("Base64无效")
|
|
}
|
|
let config = try makeDesConfig(mode: mode, padding: padding)
|
|
let key = try normalizeKey(Array(Data(secretText.utf8)), to: kCCKeySizeDES)
|
|
let iv = try normalizeIV(ivText.map { Array(Data($0.utf8)) }, blockSize: kCCBlockSizeDES, mode: config.mode)
|
|
let decrypted = try cryptWithMode(
|
|
operation: CCOperation(kCCDecrypt),
|
|
algorithm: CCAlgorithm(kCCAlgorithmDES),
|
|
mode: config.mode.ccMode,
|
|
options: config.mode.ccOptions,
|
|
key: key,
|
|
iv: iv,
|
|
input: cipherData
|
|
)
|
|
let plainData = try removePaddingIfNeeded(decrypted, blockSize: kCCBlockSizeDES, padding: config.padding, mode: config.mode)
|
|
return try utf8String(from: plainData)
|
|
}
|
|
|
|
public static func rc4EncryptToHex(secretText: String, plainText: String) throws -> String {
|
|
let cipher = try rc4Transform(secret: Data(secretText.utf8), input: Data(plainText.utf8))
|
|
return cipher.hexString()
|
|
}
|
|
|
|
public static func rc4DecryptFromHex(secretText: String, cipherHexText: String) throws -> String {
|
|
let cipher = try Data(hexString: cipherHexText)
|
|
let plain = try rc4Transform(secret: Data(secretText.utf8), input: cipher)
|
|
guard let text = String(data: plain, encoding: .utf8) else {
|
|
throw cipherError("文本解码失败")
|
|
}
|
|
return text
|
|
}
|
|
|
|
public static func createRsaBundle(keySize: Int) throws -> NativeRsaBundle {
|
|
let normalizedKeySize = try normalizeRsaKeySize(keySize)
|
|
let attributes: [String: Any] = [
|
|
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
|
|
kSecAttrKeySizeInBits as String: normalizedKeySize,
|
|
kSecPrivateKeyAttrs as String: [
|
|
kSecAttrIsPermanent as String: false
|
|
],
|
|
kSecPublicKeyAttrs as String: [
|
|
kSecAttrIsPermanent as String: false
|
|
]
|
|
]
|
|
|
|
var error: Unmanaged<CFError>?
|
|
guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else {
|
|
throw securityError(error, fallback: "RSA生成失败")
|
|
}
|
|
guard let publicKey = SecKeyCopyPublicKey(privateKey) else {
|
|
throw cipherError("RSA公钥生成失败")
|
|
}
|
|
|
|
guard let privateData = SecKeyCopyExternalRepresentation(privateKey, &error) as Data? else {
|
|
throw securityError(error, fallback: "导出私钥失败")
|
|
}
|
|
guard let publicPkcs1 = SecKeyCopyExternalRepresentation(publicKey, &error) as Data? else {
|
|
throw securityError(error, fallback: "导出公钥失败")
|
|
}
|
|
|
|
let publicPem = pemString(header: "PUBLIC KEY", body: wrapRsaPublicKeyToSpki(publicPkcs1))
|
|
let privatePem = pemString(header: "RSA PRIVATE KEY", body: privateData)
|
|
return NativeRsaBundle(publicKey: publicPem, privateKey: privatePem)
|
|
}
|
|
|
|
public static func rsaEncrypt(publicKey: String, plainText: String, outputKind: String?) throws -> String {
|
|
let key = try makeRsaPublicKey(from: publicKey)
|
|
let plainData = Data(plainText.utf8)
|
|
let encrypted = try rsaChunkedTransform(key: key, data: plainData, encrypting: true)
|
|
return try encodeCipherOutput(encrypted, kind: outputKind)
|
|
}
|
|
|
|
public static func rsaDecrypt(privateKey: String, cipherText: String, outputKind: String?) throws -> String {
|
|
let key = try makeRsaPrivateKey(from: privateKey)
|
|
let cipherData = try decodeCipherInput(cipherText, kind: outputKind)
|
|
let decrypted = try rsaChunkedTransform(key: key, data: cipherData, encrypting: false)
|
|
guard let text = String(data: decrypted, encoding: .utf8) else {
|
|
throw cipherError("文本解码失败")
|
|
}
|
|
return text
|
|
}
|
|
}
|
|
|
|
private enum NativeAesMode {
|
|
case ecb
|
|
case cbc
|
|
case cfb
|
|
case ctr
|
|
case ctrGladman
|
|
case ofb
|
|
|
|
var ccMode: CCMode {
|
|
switch self {
|
|
case .ecb:
|
|
return CCMode(kCCModeECB)
|
|
case .cbc:
|
|
return CCMode(kCCModeCBC)
|
|
case .cfb:
|
|
return CCMode(kCCModeCFB)
|
|
case .ctr, .ctrGladman:
|
|
return CCMode(kCCModeCTR)
|
|
case .ofb:
|
|
return CCMode(kCCModeOFB)
|
|
}
|
|
}
|
|
|
|
var ccOptions: CCModeOptions {
|
|
switch self {
|
|
case .ctr:
|
|
return CCModeOptions(kCCModeOptionCTR_BE)
|
|
case .ctrGladman:
|
|
// CommonCrypto 未单独提供 Gladman 计数器模式,这里映射为标准 CTR 兼容实现。
|
|
return CCModeOptions(kCCModeOptionCTR_BE)
|
|
default:
|
|
return CCModeOptions(0)
|
|
}
|
|
}
|
|
|
|
var needsBlockAlignmentWithoutPadding: Bool {
|
|
switch self {
|
|
case .ecb, .cbc:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
var usesIV: Bool {
|
|
switch self {
|
|
case .ecb:
|
|
return false
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
private enum NativePadding {
|
|
case pkcs7
|
|
case ansiX923
|
|
case iso10126
|
|
case iso97971
|
|
case none
|
|
case zero
|
|
}
|
|
|
|
private struct AesConfig {
|
|
let mode: NativeAesMode
|
|
let padding: NativePadding
|
|
let keyLength: Int
|
|
}
|
|
|
|
private struct DesConfig {
|
|
let mode: NativeAesMode
|
|
let padding: NativePadding
|
|
}
|
|
|
|
private func makeAesConfig(mode: String, padding: String, keyLength: Int?) throws -> AesConfig {
|
|
let aesMode: NativeAesMode
|
|
switch normalize(mode) {
|
|
case "ECB":
|
|
aesMode = .ecb
|
|
case "CBC":
|
|
aesMode = .cbc
|
|
case "CFB":
|
|
aesMode = .cfb
|
|
case "CTR":
|
|
aesMode = .ctr
|
|
case "CTRGLADMAN":
|
|
aesMode = .ctrGladman
|
|
case "OFB":
|
|
aesMode = .ofb
|
|
default:
|
|
throw cipherError("不支持的AES模式")
|
|
}
|
|
|
|
let aesPadding: NativePadding
|
|
switch normalize(padding) {
|
|
case "PKCS7":
|
|
aesPadding = .pkcs7
|
|
case "ANSI_X923":
|
|
aesPadding = .ansiX923
|
|
case "ISO_10126":
|
|
aesPadding = .iso10126
|
|
case "ISO_97971":
|
|
aesPadding = .iso97971
|
|
case "NONE":
|
|
aesPadding = .none
|
|
case "ZERO":
|
|
aesPadding = .zero
|
|
default:
|
|
throw cipherError("不支持的AES填充")
|
|
}
|
|
|
|
return AesConfig(mode: aesMode, padding: aesPadding, keyLength: try normalizeAesKeyLength(keyLength))
|
|
}
|
|
|
|
private func makeDesConfig(mode: String, padding: String) throws -> DesConfig {
|
|
let desMode: NativeAesMode
|
|
switch normalize(mode) {
|
|
case "ECB":
|
|
desMode = .ecb
|
|
case "CBC":
|
|
desMode = .cbc
|
|
case "CFB":
|
|
desMode = .cfb
|
|
case "CTR", "CTRGLADMAN":
|
|
desMode = .ctr
|
|
case "OFB":
|
|
desMode = .ofb
|
|
default:
|
|
throw cipherError("不支持的DES模式")
|
|
}
|
|
|
|
let desPadding: NativePadding
|
|
switch normalize(padding) {
|
|
case "PKCS7":
|
|
desPadding = .pkcs7
|
|
case "ANSI_X923":
|
|
desPadding = .ansiX923
|
|
case "ISO_10126":
|
|
desPadding = .iso10126
|
|
case "ISO_97971":
|
|
desPadding = .iso97971
|
|
case "NONE":
|
|
desPadding = .none
|
|
case "ZERO":
|
|
desPadding = .zero
|
|
default:
|
|
throw cipherError("不支持的DES填充")
|
|
}
|
|
|
|
return DesConfig(mode: desMode, padding: desPadding)
|
|
}
|
|
|
|
private func normalize(_ value: String) -> String {
|
|
value.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
|
|
}
|
|
|
|
private func normalizeAesKeyLength(_ keyLength: Int?) throws -> Int {
|
|
guard let keyLength else {
|
|
return kCCKeySizeAES256
|
|
}
|
|
switch keyLength {
|
|
case 16, 24, 32:
|
|
return keyLength
|
|
case 128, 192, 256:
|
|
return keyLength / 8
|
|
default:
|
|
throw cipherError("AES密钥长度无效")
|
|
}
|
|
}
|
|
|
|
private func normalizeRsaKeySize(_ keySize: Int) throws -> Int {
|
|
if keySize == 1024 || keySize == 2048 || keySize == 3072 || keySize == 4096 {
|
|
return keySize
|
|
}
|
|
throw cipherError("RSA密钥长度无效")
|
|
}
|
|
|
|
private func normalizeKey(_ secretBytes: [UInt8], to length: Int) throws -> Data {
|
|
if secretBytes.isEmpty {
|
|
throw cipherError("密钥不能为空")
|
|
}
|
|
var key = Data(secretBytes)
|
|
if key.count > length {
|
|
key = key.prefix(length)
|
|
} else if key.count < length {
|
|
key.append(Data(repeating: 0, count: length - key.count))
|
|
}
|
|
return key
|
|
}
|
|
|
|
private func normalizeIV(_ ivBytes: [UInt8]?, blockSize: Int, mode: NativeAesMode) throws -> Data? {
|
|
guard mode.usesIV else {
|
|
return nil
|
|
}
|
|
var iv = Data(ivBytes ?? [])
|
|
if iv.count > blockSize {
|
|
iv = iv.prefix(blockSize)
|
|
} else if iv.count < blockSize {
|
|
iv.append(Data(repeating: 0, count: blockSize - iv.count))
|
|
}
|
|
return iv
|
|
}
|
|
|
|
private func applyPaddingIfNeeded(_ data: Data, blockSize: Int, padding: NativePadding, mode: NativeAesMode) throws -> Data {
|
|
if padding == .none && !mode.needsBlockAlignmentWithoutPadding {
|
|
return data
|
|
}
|
|
return try applyPadding(data, blockSize: blockSize, padding: padding)
|
|
}
|
|
|
|
private func removePaddingIfNeeded(_ data: Data, blockSize: Int, padding: NativePadding, mode: NativeAesMode) throws -> Data {
|
|
if padding == .none && !mode.needsBlockAlignmentWithoutPadding {
|
|
return data
|
|
}
|
|
return try removePadding(data, blockSize: blockSize, padding: padding)
|
|
}
|
|
|
|
private func applyPadding(_ data: Data, blockSize: Int, padding: NativePadding) throws -> Data {
|
|
switch padding {
|
|
case .none:
|
|
if data.count % blockSize != 0 {
|
|
throw cipherError("数据长度无效")
|
|
}
|
|
return data
|
|
case .pkcs7:
|
|
let remain = data.count % blockSize
|
|
let actualPad = remain == 0 ? blockSize : blockSize - remain
|
|
return data + Data(repeating: UInt8(actualPad), count: actualPad)
|
|
case .ansiX923:
|
|
let padCount = blockSize - (data.count % blockSize)
|
|
let actualPad = padCount == 0 ? blockSize : padCount
|
|
var result = data
|
|
if actualPad > 1 {
|
|
result.append(Data(repeating: 0, count: actualPad - 1))
|
|
}
|
|
result.append(UInt8(actualPad))
|
|
return result
|
|
case .iso10126:
|
|
let padCount = blockSize - (data.count % blockSize)
|
|
let actualPad = padCount == 0 ? blockSize : padCount
|
|
var result = data
|
|
if actualPad > 1 {
|
|
var random = Data(count: actualPad - 1)
|
|
let status = random.withUnsafeMutableBytes { SecRandomCopyBytes(kSecRandomDefault, actualPad - 1, $0.baseAddress!) }
|
|
if status != errSecSuccess {
|
|
throw cipherError("随机填充失败")
|
|
}
|
|
result.append(random)
|
|
}
|
|
result.append(UInt8(actualPad))
|
|
return result
|
|
case .iso97971:
|
|
var result = data
|
|
result.append(0x80)
|
|
let remain = result.count % blockSize
|
|
if remain != 0 {
|
|
result.append(Data(repeating: 0, count: blockSize - remain))
|
|
}
|
|
return result
|
|
case .zero:
|
|
let remain = data.count % blockSize
|
|
if remain == 0 {
|
|
return data
|
|
}
|
|
return data + Data(repeating: 0, count: blockSize - remain)
|
|
}
|
|
}
|
|
|
|
private func removePadding(_ data: Data, blockSize: Int, padding: NativePadding) throws -> Data {
|
|
switch padding {
|
|
case .none:
|
|
if data.count % blockSize != 0 {
|
|
throw cipherError("数据长度无效")
|
|
}
|
|
return data
|
|
case .pkcs7:
|
|
guard let last = data.last else { return data }
|
|
let pad = Int(last)
|
|
guard pad > 0, pad <= blockSize, pad <= data.count else {
|
|
throw cipherError("填充无效")
|
|
}
|
|
let tail = data.suffix(pad)
|
|
if tail.allSatisfy({ $0 == last }) {
|
|
return Data(data.dropLast(pad))
|
|
}
|
|
throw cipherError("填充无效")
|
|
case .ansiX923:
|
|
guard let last = data.last else { return data }
|
|
let pad = Int(last)
|
|
guard pad > 0, pad <= blockSize, pad <= data.count else {
|
|
throw cipherError("填充无效")
|
|
}
|
|
if pad > 1 && !data.dropLast().suffix(pad - 1).allSatisfy({ $0 == 0 }) {
|
|
throw cipherError("填充无效")
|
|
}
|
|
return Data(data.dropLast(pad))
|
|
case .iso10126:
|
|
guard let last = data.last else { return data }
|
|
let pad = Int(last)
|
|
guard pad > 0, pad <= blockSize, pad <= data.count else {
|
|
throw cipherError("填充无效")
|
|
}
|
|
return Data(data.dropLast(pad))
|
|
case .iso97971:
|
|
var index = data.endIndex
|
|
while index > data.startIndex {
|
|
index = data.index(before: index)
|
|
let value = data[index]
|
|
if value == 0x80 {
|
|
return Data(data[..<index])
|
|
}
|
|
if value != 0 {
|
|
throw cipherError("填充无效")
|
|
}
|
|
}
|
|
throw cipherError("填充无效")
|
|
case .zero:
|
|
var output = data
|
|
while output.last == 0 {
|
|
output.removeLast()
|
|
}
|
|
return output
|
|
}
|
|
}
|
|
|
|
private func cryptWithMode(operation: CCOperation, algorithm: CCAlgorithm, mode: CCMode, options: CCModeOptions, key: Data, iv: Data?, input: Data) throws -> Data {
|
|
var cryptor: CCCryptorRef?
|
|
let status = key.withUnsafeBytes { keyBuffer in
|
|
ivDataPointer(iv) { ivPointer in
|
|
CCCryptorCreateWithMode(
|
|
operation,
|
|
mode,
|
|
algorithm,
|
|
CCPadding(ccNoPadding),
|
|
ivPointer,
|
|
keyBuffer.baseAddress,
|
|
key.count,
|
|
nil,
|
|
0,
|
|
0,
|
|
options,
|
|
&cryptor
|
|
)
|
|
}
|
|
}
|
|
|
|
guard status == kCCSuccess, let cryptor else {
|
|
throw cipherError("加解密初始化失败")
|
|
}
|
|
defer { CCCryptorRelease(cryptor) }
|
|
|
|
var output = Data(count: input.count + kCCBlockSizeAES128)
|
|
var moved = 0
|
|
var finalMoved = 0
|
|
let updateStatus = output.withUnsafeMutableBytes { outputBuffer in
|
|
input.withUnsafeBytes { inputBuffer in
|
|
CCCryptorUpdate(
|
|
cryptor,
|
|
inputBuffer.baseAddress,
|
|
input.count,
|
|
outputBuffer.baseAddress,
|
|
output.count,
|
|
&moved
|
|
)
|
|
}
|
|
}
|
|
guard updateStatus == kCCSuccess else {
|
|
throw cipherError("加解密失败")
|
|
}
|
|
|
|
let finalStatus = output.withUnsafeMutableBytes { outputBuffer in
|
|
CCCryptorFinal(
|
|
cryptor,
|
|
outputBuffer.baseAddress?.advanced(by: moved),
|
|
output.count - moved,
|
|
&finalMoved
|
|
)
|
|
}
|
|
guard finalStatus == kCCSuccess else {
|
|
throw cipherError("加解密失败")
|
|
}
|
|
output.removeSubrange((moved + finalMoved)..<output.count)
|
|
return output
|
|
}
|
|
|
|
private func ivDataPointer<T>(_ iv: Data?, _ body: (UnsafeRawPointer?) -> T) -> T {
|
|
guard let iv else {
|
|
return body(nil)
|
|
}
|
|
return iv.withUnsafeBytes { body($0.baseAddress) }
|
|
}
|
|
|
|
private func tripleDesTransform(operation: CCOperation, secretBytes: [UInt8], input: Data, modeName: String, ivBytes: [UInt8]?) throws -> Data {
|
|
let mode: NativeAesMode
|
|
switch normalize(modeName) {
|
|
case "ECB":
|
|
mode = .ecb
|
|
case "CBC":
|
|
mode = .cbc
|
|
default:
|
|
throw cipherError("不支持的3DES模式")
|
|
}
|
|
let key = try normalizeKey(secretBytes, to: kCCKeySize3DES)
|
|
let iv = try normalizeIV(ivBytes, blockSize: kCCBlockSize3DES, mode: mode)
|
|
return try cryptWithMode(
|
|
operation: operation,
|
|
algorithm: CCAlgorithm(kCCAlgorithm3DES),
|
|
mode: mode.ccMode,
|
|
options: CCModeOptions(0),
|
|
key: key,
|
|
iv: iv,
|
|
input: input
|
|
)
|
|
}
|
|
|
|
private func rc4Transform(secret: Data, input: Data) throws -> Data {
|
|
if secret.isEmpty {
|
|
throw cipherError("密钥不能为空")
|
|
}
|
|
var output = Data(count: input.count + kCCBlockSizeRC2)
|
|
var moved = 0
|
|
let status = output.withUnsafeMutableBytes { outputBuffer in
|
|
secret.withUnsafeBytes { keyBuffer in
|
|
input.withUnsafeBytes { inputBuffer in
|
|
CCCrypt(
|
|
CCOperation(kCCEncrypt),
|
|
CCAlgorithm(kCCAlgorithmRC4),
|
|
CCOptions(0),
|
|
keyBuffer.baseAddress,
|
|
secret.count,
|
|
nil,
|
|
inputBuffer.baseAddress,
|
|
input.count,
|
|
outputBuffer.baseAddress,
|
|
output.count,
|
|
&moved
|
|
)
|
|
}
|
|
}
|
|
}
|
|
guard status == kCCSuccess else {
|
|
throw cipherError("RC4处理失败")
|
|
}
|
|
output.removeSubrange(moved..<output.count)
|
|
return output
|
|
}
|
|
|
|
private func hashData(_ input: Data, length: Int, body: (UnsafeMutableRawBufferPointer, UnsafeRawPointer?, Int) -> Void) -> Data {
|
|
var output = Data(count: length)
|
|
output.withUnsafeMutableBytes { outputBuffer in
|
|
input.withUnsafeBytes { inputBuffer in
|
|
body(outputBuffer, inputBuffer.baseAddress, input.count)
|
|
}
|
|
}
|
|
return output
|
|
}
|
|
|
|
private func utf8String(from data: Data) throws -> String {
|
|
guard let text = String(data: data, encoding: .utf8) else {
|
|
throw cipherError("文本解码失败")
|
|
}
|
|
return text
|
|
}
|
|
|
|
private func latin1String(from data: Data) -> String {
|
|
var output = ""
|
|
output.reserveCapacity(data.count)
|
|
for byte in data {
|
|
if let scalar = UnicodeScalar(Int(byte)) {
|
|
output.append(Character(scalar))
|
|
}
|
|
}
|
|
return output
|
|
}
|
|
|
|
private func dataFromLatin1String(_ text: String) -> Data {
|
|
var bytes = [UInt8]()
|
|
bytes.reserveCapacity(text.count)
|
|
for value in text.unicodeScalars {
|
|
bytes.append(UInt8(value.value & 0xFF))
|
|
}
|
|
return Data(bytes)
|
|
}
|
|
|
|
private func utf16CodecString(from data: Data) -> String {
|
|
if data.isEmpty {
|
|
return ""
|
|
}
|
|
var units = [UInt16]()
|
|
units.reserveCapacity((data.count + 1) / 2)
|
|
var index = data.startIndex
|
|
while index < data.endIndex {
|
|
let high = UInt16(data[index]) << 8
|
|
let nextIndex = data.index(after: index)
|
|
let low: UInt16 = nextIndex < data.endIndex ? UInt16(data[nextIndex]) : 0
|
|
units.append(high | low)
|
|
index = data.index(index, offsetBy: 2, limitedBy: data.endIndex) ?? data.endIndex
|
|
}
|
|
return String(utf16CodeUnits: units, count: units.count)
|
|
}
|
|
|
|
private func dataFromUtf16CodecString(_ text: String) -> Data {
|
|
let units = Array(text.utf16)
|
|
var bytes = [UInt8]()
|
|
bytes.reserveCapacity(units.count * 2)
|
|
for unit in units {
|
|
bytes.append(UInt8((unit >> 8) & 0xFF))
|
|
bytes.append(UInt8(unit & 0xFF))
|
|
}
|
|
return Data(bytes)
|
|
}
|
|
|
|
private func makeRsaPublicKey(from pem: String) throws -> SecKey {
|
|
let body = try decodePemBody(from: pem)
|
|
let raw = pem.contains("BEGIN PUBLIC KEY") ? try unwrapSubjectPublicKeyInfo(body) : body
|
|
let attributes: [String: Any] = [
|
|
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
|
|
kSecAttrKeyClass as String: kSecAttrKeyClassPublic,
|
|
kSecAttrKeySizeInBits as String: rsaBitLength(fromPkcs1: raw)
|
|
]
|
|
var error: Unmanaged<CFError>?
|
|
guard let key = SecKeyCreateWithData(raw as CFData, attributes as CFDictionary, &error) else {
|
|
throw securityError(error, fallback: "公钥无效")
|
|
}
|
|
return key
|
|
}
|
|
|
|
private func makeRsaPrivateKey(from pem: String) throws -> SecKey {
|
|
let body = try decodePemBody(from: pem)
|
|
let raw = pem.contains("BEGIN PRIVATE KEY") && !pem.contains("BEGIN RSA PRIVATE KEY") ? try unwrapPkcs8PrivateKey(body) : body
|
|
let attributes: [String: Any] = [
|
|
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
|
|
kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
|
|
kSecAttrKeySizeInBits as String: rsaBitLength(fromPrivateKey: raw)
|
|
]
|
|
var error: Unmanaged<CFError>?
|
|
guard let key = SecKeyCreateWithData(raw as CFData, attributes as CFDictionary, &error) else {
|
|
throw securityError(error, fallback: "私钥无效")
|
|
}
|
|
return key
|
|
}
|
|
|
|
private func rsaChunkedTransform(key: SecKey, data: Data, encrypting: Bool) throws -> Data {
|
|
let algorithm: SecKeyAlgorithm = .rsaEncryptionPKCS1
|
|
guard SecKeyIsAlgorithmSupported(key, encrypting ? .encrypt : .decrypt, algorithm) else {
|
|
throw cipherError(encrypting ? "RSA加密不可用" : "RSA解密不可用")
|
|
}
|
|
|
|
let blockSize = SecKeyGetBlockSize(key)
|
|
let chunkSize = encrypting ? blockSize - 11 : blockSize
|
|
if chunkSize <= 0 {
|
|
throw cipherError("RSA块大小无效")
|
|
}
|
|
|
|
var result = Data()
|
|
var offset = 0
|
|
while offset < data.count {
|
|
let next = min(offset + chunkSize, data.count)
|
|
let chunk = data.subdata(in: offset..<next)
|
|
var error: Unmanaged<CFError>?
|
|
let transformed: Data?
|
|
if encrypting {
|
|
transformed = SecKeyCreateEncryptedData(key, algorithm, chunk as CFData, &error) as Data?
|
|
} else {
|
|
transformed = SecKeyCreateDecryptedData(key, algorithm, chunk as CFData, &error) as Data?
|
|
}
|
|
guard let transformed else {
|
|
throw securityError(error, fallback: encrypting ? "RSA加密失败" : "RSA解密失败")
|
|
}
|
|
result.append(transformed)
|
|
offset = next
|
|
}
|
|
return result
|
|
}
|
|
|
|
private func encodeCipherOutput(_ data: Data, kind: String?) throws -> String {
|
|
switch normalize(kind ?? "base64") {
|
|
case "BASE64":
|
|
return data.base64EncodedString()
|
|
case "HEX":
|
|
return data.hexString()
|
|
default:
|
|
throw cipherError("RSA输出格式无效")
|
|
}
|
|
}
|
|
|
|
private func decodeCipherInput(_ text: String, kind: String?) throws -> Data {
|
|
switch normalize(kind ?? "base64") {
|
|
case "BASE64":
|
|
guard let data = Data(base64Encoded: text) else {
|
|
throw cipherError("Base64无效")
|
|
}
|
|
return data
|
|
case "HEX":
|
|
return try Data(hexString: text)
|
|
default:
|
|
throw cipherError("RSA输入格式无效")
|
|
}
|
|
}
|
|
|
|
private func decodePemBody(from pem: String) throws -> Data {
|
|
let lines = pem
|
|
.components(separatedBy: .newlines)
|
|
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
|
.filter { !$0.isEmpty && !$0.hasPrefix("-----BEGIN") && !$0.hasPrefix("-----END") }
|
|
let joined = lines.joined()
|
|
guard let data = Data(base64Encoded: joined) else {
|
|
throw cipherError("PEM无效")
|
|
}
|
|
return data
|
|
}
|
|
|
|
private func pemString(header: String, body: Data) -> String {
|
|
let content = body.base64EncodedString()
|
|
let lines = stride(from: 0, to: content.count, by: 64).map { start -> String in
|
|
let startIndex = content.index(content.startIndex, offsetBy: start)
|
|
let endIndex = content.index(startIndex, offsetBy: min(64, content.count - start))
|
|
return String(content[startIndex..<endIndex])
|
|
}
|
|
return "-----BEGIN \(header)-----\n\(lines.joined(separator: "\n"))\n-----END \(header)-----"
|
|
}
|
|
|
|
private func wrapRsaPublicKeyToSpki(_ pkcs1: Data) -> Data {
|
|
let algorithmIdentifier = Data([0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00])
|
|
let bitString = asn1Wrap(tag: 0x03, content: Data([0x00]) + pkcs1)
|
|
return asn1Wrap(tag: 0x30, content: algorithmIdentifier + bitString)
|
|
}
|
|
|
|
private func unwrapSubjectPublicKeyInfo(_ der: Data) throws -> Data {
|
|
let sequence = try readAsn1Element(from: der, at: 0, expectedTag: 0x30)
|
|
var cursor = sequence.contentStartIndex
|
|
let algorithm = try readAsn1Element(from: der, at: cursor, expectedTag: 0x30)
|
|
cursor = algorithm.endIndex
|
|
let bitString = try readAsn1Element(from: der, at: cursor, expectedTag: 0x03)
|
|
let bitStringData = der.subdata(in: bitString.contentStartIndex..<bitString.endIndex)
|
|
guard let first = bitStringData.first, first == 0x00 else {
|
|
throw cipherError("PEM无效")
|
|
}
|
|
return Data(bitStringData.dropFirst())
|
|
}
|
|
|
|
private func unwrapPkcs8PrivateKey(_ der: Data) throws -> Data {
|
|
let sequence = try readAsn1Element(from: der, at: 0, expectedTag: 0x30)
|
|
var cursor = sequence.contentStartIndex
|
|
let version = try readAsn1Element(from: der, at: cursor, expectedTag: 0x02)
|
|
cursor = version.endIndex
|
|
let algorithm = try readAsn1Element(from: der, at: cursor, expectedTag: 0x30)
|
|
cursor = algorithm.endIndex
|
|
let octetString = try readAsn1Element(from: der, at: cursor, expectedTag: 0x04)
|
|
return der.subdata(in: octetString.contentStartIndex..<octetString.endIndex)
|
|
}
|
|
|
|
private struct Asn1Element {
|
|
let contentStartIndex: Int
|
|
let endIndex: Int
|
|
}
|
|
|
|
private func readAsn1Element(from data: Data, at index: Int, expectedTag: UInt8) throws -> Asn1Element {
|
|
guard index < data.count, data[index] == expectedTag else {
|
|
throw cipherError("PEM无效")
|
|
}
|
|
let lengthInfo = try readAsn1Length(from: data, at: index + 1)
|
|
let contentStart = lengthInfo.nextIndex
|
|
let end = contentStart + lengthInfo.length
|
|
guard end <= data.count else {
|
|
throw cipherError("PEM无效")
|
|
}
|
|
return Asn1Element(contentStartIndex: contentStart, endIndex: end)
|
|
}
|
|
|
|
private func readAsn1Length(from data: Data, at index: Int) throws -> (length: Int, nextIndex: Int) {
|
|
guard index < data.count else {
|
|
throw cipherError("PEM无效")
|
|
}
|
|
let first = data[index]
|
|
if first & 0x80 == 0 {
|
|
return (Int(first), index + 1)
|
|
}
|
|
let byteCount = Int(first & 0x7F)
|
|
guard byteCount > 0, index + byteCount < data.count else {
|
|
throw cipherError("PEM无效")
|
|
}
|
|
var length = 0
|
|
for offset in 0..<byteCount {
|
|
length = (length << 8) | Int(data[index + 1 + offset])
|
|
}
|
|
return (length, index + 1 + byteCount)
|
|
}
|
|
|
|
private func asn1Wrap(tag: UInt8, content: Data) -> Data {
|
|
var output = Data([tag])
|
|
output.append(asn1LengthBytes(content.count))
|
|
output.append(content)
|
|
return output
|
|
}
|
|
|
|
private func asn1LengthBytes(_ length: Int) -> Data {
|
|
if length < 0x80 {
|
|
return Data([UInt8(length)])
|
|
}
|
|
var value = length
|
|
var bytes: [UInt8] = []
|
|
while value > 0 {
|
|
bytes.insert(UInt8(value & 0xFF), at: 0)
|
|
value >>= 8
|
|
}
|
|
return Data([0x80 | UInt8(bytes.count)] + bytes)
|
|
}
|
|
|
|
private func rsaBitLength(fromPkcs1 data: Data) -> Int {
|
|
if let modulus = try? extractRsaModulus(fromPublicPkcs1: data) {
|
|
return modulusBitLength(modulus)
|
|
}
|
|
return max(1024, data.count * 8)
|
|
}
|
|
|
|
private func rsaBitLength(fromPrivateKey data: Data) -> Int {
|
|
if let modulus = try? extractRsaModulus(fromPrivatePkcs1: data) {
|
|
return modulusBitLength(modulus)
|
|
}
|
|
return max(1024, data.count * 8)
|
|
}
|
|
|
|
private func extractRsaModulus(fromPublicPkcs1 data: Data) throws -> Data {
|
|
let sequence = try readAsn1Element(from: data, at: 0, expectedTag: 0x30)
|
|
let integer = try readAsn1Element(from: data, at: sequence.contentStartIndex, expectedTag: 0x02)
|
|
return trimmedInteger(data.subdata(in: integer.contentStartIndex..<integer.endIndex))
|
|
}
|
|
|
|
private func extractRsaModulus(fromPrivatePkcs1 data: Data) throws -> Data {
|
|
let sequence = try readAsn1Element(from: data, at: 0, expectedTag: 0x30)
|
|
var cursor = sequence.contentStartIndex
|
|
let version = try readAsn1Element(from: data, at: cursor, expectedTag: 0x02)
|
|
cursor = version.endIndex
|
|
let modulus = try readAsn1Element(from: data, at: cursor, expectedTag: 0x02)
|
|
return trimmedInteger(data.subdata(in: modulus.contentStartIndex..<modulus.endIndex))
|
|
}
|
|
|
|
private func trimmedInteger(_ data: Data) -> Data {
|
|
var result = data
|
|
while result.count > 1 && result.first == 0x00 {
|
|
result.removeFirst()
|
|
}
|
|
return result
|
|
}
|
|
|
|
private func modulusBitLength(_ modulus: Data) -> Int {
|
|
guard let first = modulus.first else {
|
|
return 0
|
|
}
|
|
var bits = modulus.count * 8
|
|
var mask: UInt8 = 0x80
|
|
while mask > 0, first & mask == 0 {
|
|
bits -= 1
|
|
mask >>= 1
|
|
}
|
|
return bits
|
|
}
|
|
|
|
private func cipherError(_ message: String) -> NSError {
|
|
NSError(domain: "NativeCipherCore", code: -1, userInfo: [NSLocalizedDescriptionKey: message])
|
|
}
|
|
|
|
private func securityError(_ error: Unmanaged<CFError>?, fallback: String) -> NSError {
|
|
if let error {
|
|
let message = CFErrorCopyDescription(error.takeRetainedValue()) as String
|
|
return cipherError(message.isEmpty ? fallback : message)
|
|
}
|
|
return cipherError(fallback)
|
|
}
|
|
|
|
private extension Data {
|
|
init(hexString: String) throws {
|
|
let cleaned = hexString.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
guard cleaned.count % 2 == 0 else {
|
|
throw cipherError("Hex无效")
|
|
}
|
|
var output = Data(capacity: cleaned.count / 2)
|
|
var index = cleaned.startIndex
|
|
while index < cleaned.endIndex {
|
|
let next = cleaned.index(index, offsetBy: 2)
|
|
let pair = cleaned[index..<next]
|
|
guard let value = UInt8(pair, radix: 16) else {
|
|
throw cipherError("Hex无效")
|
|
}
|
|
output.append(value)
|
|
index = next
|
|
}
|
|
self = output
|
|
}
|
|
|
|
func hexString() -> String {
|
|
map { String(format: "%02x", $0) }.joined()
|
|
}
|
|
}
|