import CommonCrypto import CryptoKit import Foundation // MARK: - BigUInt struct BigUInt { private(set) var words: [UInt32] // little-endian (index 0 = least significant) static let zero = BigUInt(words: [0]) static let one = BigUInt(words: [1]) init(words: [UInt32]) { self.words = words normalize() } init(decimal string: String) { var result = BigUInt.zero let ten = BigUInt(words: [10]) for ch in string { guard let d = ch.wholeNumberValue else { continue } result = result.multiplied(by: ten) result = result.adding(BigUInt(words: [UInt32(d)])) } self = result } private mutating func normalize() { while words.count > 1 && words.last == 0 { words.removeLast() } } var isZero: Bool { words.count == 1 && words[0] == 0 } var isOdd: Bool { words[0] & 1 == 1 } var bitWidth: Int { let n = words.count guard n > 0 else { return 0 } let top = words[n - 1] if top == 0 { return max(0, (n - 1) * 32) } return (n - 1) * 32 + (32 - top.leadingZeroBitCount) } func bit(_ index: Int) -> Bool { let word = index / 32 let bit = index % 32 guard word < words.count else { return false } return (words[word] >> bit) & 1 == 1 } // MARK: Comparison static func < (lhs: BigUInt, rhs: BigUInt) -> Bool { if lhs.words.count != rhs.words.count { return lhs.words.count < rhs.words.count } for i in stride(from: lhs.words.count - 1, through: 0, by: -1) { if lhs.words[i] != rhs.words[i] { return lhs.words[i] < rhs.words[i] } } return false } static func == (lhs: BigUInt, rhs: BigUInt) -> Bool { lhs.words == rhs.words } // MARK: Addition func adding(_ other: BigUInt) -> BigUInt { let maxLen = max(words.count, other.words.count) var result = [UInt32](repeating: 0, count: maxLen + 1) var carry: UInt64 = 0 for i in 0..> 32 } result[maxLen] = UInt32(carry) return BigUInt(words: result) } // MARK: Subtraction (self >= other assumed) func subtracting(_ other: BigUInt) -> BigUInt { var result = words var borrow: Int64 = 0 for i in 0.. BigUInt { let n = words.count let m = other.words.count var result = [UInt32](repeating: 0, count: n + m) for i in 0..> 32 } result[i + m] += UInt32(carry) } return BigUInt(words: result) } // MARK: Modulo (binary shift-subtract) func modulo(_ divisor: BigUInt) -> BigUInt { if divisor.isZero { return .zero } if self < divisor { return self } var remainder = BigUInt.zero let totalBits = bitWidth for i in stride(from: totalBits - 1, through: 0, by: -1) { remainder = remainder.shiftedLeft1() if bit(i) { remainder.words[0] |= 1 } if !(remainder < divisor) { remainder = remainder.subtracting(divisor) } } return remainder } private func shiftedLeft1() -> BigUInt { var result = [UInt32](repeating: 0, count: words.count + 1) var carry: UInt32 = 0 for i in 0..> 32) } result[words.count] = carry return BigUInt(words: result) } // MARK: ModPow — right-to-left binary func modPow(exp: BigUInt, mod: BigUInt) -> BigUInt { if mod == .one { return .zero } var result = BigUInt.one var base = modulo(mod) var e = exp while !e.isZero { if e.isOdd { result = result.multiplied(by: base).modulo(mod) } e = e.shiftedRight1() base = base.multiplied(by: base).modulo(mod) } return result } private func shiftedRight1() -> BigUInt { var result = words var borrow: UInt32 = 0 for i in stride(from: result.count - 1, through: 0, by: -1) { let new = (result[i] >> 1) | (borrow << 31) borrow = result[i] & 1 result[i] = new } return BigUInt(words: result) } // MARK: Decimal String (O(n) per digit via UInt64 carry) var decimalString: String { if isZero { return "0" } var digits: [Character] = [] var remaining = words while !(remaining.count == 1 && remaining[0] == 0) { var rem: UInt64 = 0 for i in stride(from: remaining.count - 1, through: 0, by: -1) { let cur = (rem << 32) | UInt64(remaining[i]) remaining[i] = UInt32(cur / 10) rem = cur % 10 } digits.append(Character(String(rem))) while remaining.count > 1 && remaining.last == 0 { remaining.removeLast() } } return String(digits.reversed()) } } // MARK: - MibCrypto enum MibCrypto { static let defaultKey = "8M3L9SBF1AC4FRE56788M3L9SBF1AC4FRE5678" // DH exponent A private static let dhA = BigUInt(decimal: "1563516802667282387226490351799736881442299778484610378722158765594241028592123324764949712696577") // DH modulus P private static let dhP = BigUInt(decimal: "2410312426921032588552076022197566074856950548502459942654116941958108831682612228890093858261341614673227141477904012196503648957050582631942730706805009223062734745341073406696246014589361659774041027169249453200378729434170325843778659198143763193776859869524088940195577346119843545301547043747207749969763750084308926339295559968882457872412993810129130294592999947926365264059284647209730384947211681434464714438488520940127459844288859336526896320919633919") // Pre-computed: 2^A mod P (sent to server in every DH key exchange) static let cmod = "2301533261465719294935473752300816828871347570489984010719302185643154930578030858802510266223676458248149736055131586081546441805822467879511077637292661430926459449504165353261883687521672133746481188567585259995381323120449147180627189549882525129015625540437713248150022924125681681822594907090716721855161928482983925605401801685462643917325004120294159320617793870605573541358655167992228254033496751384810855107557385690184061912872019448968632814193704749" // Derives session key: Base64(SHA256(smod^A mod P as decimal string)) static func deriveSessionKey(_ smod: String) -> String { let smodInt = BigUInt(decimal: smod) let shared = smodInt.modPow(exp: dhA, mod: dhP) let secretStr = shared.decimalString let sha = SHA256.hash(data: Data(secretStr.utf8)) return Data(sha).base64EncodedString() } // MARK: Blowfish/ECB/PKCS7 Encrypt static func encrypt(_ json: [String: Any], key: String) throws -> String { guard let jsonData = try? JSONSerialization.data(withJSONObject: json) else { throw MibError.encryptionFailed } let keyBytes = key.data(using: .isoLatin1) ?? Data(key.utf8) let encrypted = try blowfishProcess(data: jsonData, key: keyBytes, operation: CCOperation(kCCEncrypt)) return encrypted.base64EncodedString() } static func decrypt(_ base64: String, key: String) throws -> [String: Any] { guard let cipherData = Data(base64Encoded: base64, options: .ignoreUnknownCharacters) else { throw MibError.decryptionFailed } let keyBytes = key.data(using: .isoLatin1) ?? Data(key.utf8) let plainData = try blowfishProcess(data: cipherData, key: keyBytes, operation: CCOperation(kCCDecrypt)) guard let json = try? JSONSerialization.jsonObject(with: plainData) as? [String: Any] else { throw MibError.decryptionFailed } return json } private static func blowfishProcess(data: Data, key: Data, operation: CCOperation) throws -> Data { var outLen = 0 let bufLen = data.count + kCCBlockSizeBlowfish var outBuf = [UInt8](repeating: 0, count: bufLen) let status = data.withUnsafeBytes { dataPtr in key.withUnsafeBytes { keyPtr in CCCrypt( operation, CCAlgorithm(kCCAlgorithmBlowfish), CCOptions(kCCOptionECBMode | kCCOptionPKCS7Padding), keyPtr.baseAddress, key.count, nil, dataPtr.baseAddress, data.count, &outBuf, bufLen, &outLen ) } } guard status == kCCSuccess else { throw operation == CCOperation(kCCEncrypt) ? MibError.encryptionFailed : MibError.decryptionFailed } return Data(outBuf[0.. String { sha256Upper(password) } static func sha256Upper(_ input: String) -> String { let hash = SHA256.hash(data: Data(input.utf8)) return hash.map { String(format: "%02X", $0) }.joined() } // pgf03 = SHA256(clientSalt + SHA256(passwordHash + userSalt)) static func computePgf03(passwordHash: String, userSalt: String, clientSalt: String) -> String { let inner = sha256Upper(passwordHash + userSalt) return sha256Upper(clientSalt + inner) } }