Implement bank transfer app flows
This commit is contained in:
@@ -0,0 +1,289 @@
|
||||
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..<maxLen {
|
||||
let a: UInt64 = i < words.count ? UInt64(words[i]) : 0
|
||||
let b: UInt64 = i < other.words.count ? UInt64(other.words[i]) : 0
|
||||
let sum = a + b + carry
|
||||
result[i] = UInt32(sum & 0xFFFFFFFF)
|
||||
carry = sum >> 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..<result.count {
|
||||
let b: Int64 = i < other.words.count ? Int64(other.words[i]) : 0
|
||||
let diff = Int64(result[i]) - b - borrow
|
||||
if diff < 0 {
|
||||
result[i] = UInt32(bitPattern: Int32(truncatingIfNeeded: diff &+ 0x1_0000_0000))
|
||||
borrow = 1
|
||||
} else {
|
||||
result[i] = UInt32(diff)
|
||||
borrow = 0
|
||||
}
|
||||
}
|
||||
return BigUInt(words: result)
|
||||
}
|
||||
|
||||
// MARK: Multiplication
|
||||
|
||||
func multiplied(by other: BigUInt) -> BigUInt {
|
||||
let n = words.count
|
||||
let m = other.words.count
|
||||
var result = [UInt32](repeating: 0, count: n + m)
|
||||
for i in 0..<n {
|
||||
var carry: UInt64 = 0
|
||||
for j in 0..<m {
|
||||
let prod = UInt64(words[i]) * UInt64(other.words[j]) + UInt64(result[i + j]) + carry
|
||||
result[i + j] = UInt32(prod & 0xFFFFFFFF)
|
||||
carry = prod >> 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..<words.count {
|
||||
let shifted = (UInt64(words[i]) << 1) | UInt64(carry)
|
||||
result[i] = UInt32(shifted & 0xFFFFFFFF)
|
||||
carry = UInt32(shifted >> 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..<outLen])
|
||||
}
|
||||
|
||||
// MARK: SHA-256 helpers
|
||||
|
||||
static func hashPassword(_ password: String) -> 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user