Skip to content

Commit 7709e23

Browse files
author
Utngy Pisal
committed
Initial commit
1 parent a060bc7 commit 7709e23

21 files changed

+1911
-6
lines changed
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
//
2+
// AESCipher.swift
3+
// MobileBanking
4+
//
5+
// Created by Pisal on 6/22/22.
6+
// Copyright © 2022 WB Finance. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import CommonCrypto
11+
12+
public class AESCipher {
13+
14+
private var aesKeySizeInBytes: Int!
15+
private var aesMode: String!
16+
private var keyIterationCount: UInt32!
17+
18+
public init(aesKeySizeInBytes: Int,
19+
aesMode: String,
20+
keyIterationCount: Int = 100
21+
) {
22+
self.aesKeySizeInBytes = aesKeySizeInBytes
23+
self.aesMode = aesMode
24+
self.keyIterationCount = UInt32(keyIterationCount)
25+
}
26+
27+
public func encrypt(password: String, message: String) -> AESResult {
28+
let iv = randomizeBytes(count: kCCBlockSizeAES128)
29+
let salt = randomizeBytes(count: 8)
30+
let aesKey = aesKey(forPassword: password, salt: salt)
31+
let data = message.data(using: .utf8)!
32+
33+
let ciphertext = crypt(
34+
operation: kCCEncrypt,
35+
algorithm: kCCAlgorithmAES,
36+
options: kCCOptionPKCS7Padding,
37+
key: aesKey,
38+
initializationVector: iv,
39+
dataIn: data)
40+
41+
return AESResult(
42+
salt: salt.toBase64String(),
43+
iv: iv.toBase64String(),
44+
encodedData: ciphertext!.toBase64String()
45+
)
46+
}
47+
48+
public func decrypt(password: String, salt: Data, iv: Data, base64CipherText: Data) -> NSDictionary {
49+
let aesKey = aesKey(forPassword: password, salt: salt)
50+
let decryptedData = crypt(operation: kCCDecrypt,
51+
algorithm: kCCAlgorithmAES,
52+
options: kCCOptionPKCS7Padding,
53+
key: aesKey,
54+
initializationVector: iv,
55+
dataIn: base64CipherText
56+
)
57+
58+
let str = String(decoding: decryptedData!, as: UTF8.self)
59+
return str.toDictionary()! as NSDictionary
60+
}
61+
62+
63+
//========================================================================================
64+
//MARK:- Internal
65+
//========================================================================================
66+
private func randomizeBytes(count: Int) -> Data {
67+
let bytes = UnsafeMutableRawPointer.allocate(byteCount: count, alignment: 1)
68+
defer { bytes.deallocate() }
69+
let status = SecRandomCopyBytes(nil, count, bytes)
70+
assert(status == kCCSuccess)
71+
return Data(bytes: bytes, count: count)
72+
}
73+
74+
private func aesKey(
75+
forPassword password: String,
76+
salt: Data
77+
) -> Data {
78+
let derivedKey = NSMutableData(length: aesKeySizeInBytes * 2)!
79+
let result = CCKeyDerivationPBKDF(
80+
CCPBKDFAlgorithm(kCCPBKDF2),
81+
password,
82+
password.lengthOfBytes(using: .utf8),
83+
salt.bytes,
84+
salt.count,
85+
CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA1),
86+
self.keyIterationCount,
87+
derivedKey.mutableBytes,
88+
derivedKey.length
89+
)
90+
assert(result == kCCSuccess)
91+
return derivedKey.copy() as! Data
92+
}
93+
94+
private func crypt(
95+
operation: Int,
96+
algorithm: Int,
97+
options: Int,
98+
key: Data,
99+
initializationVector iv: Data,
100+
dataIn: Data
101+
) -> Data? {
102+
return key.withUnsafeBytes { keyUnsafeRawBufferPointer in
103+
return dataIn.withUnsafeBytes { dataInUnsafeRawBufferPointer in
104+
return iv.withUnsafeBytes { ivUnsafeRawBufferPointer in
105+
// Give the data out some breathing room for PKCS7's padding.
106+
let dataOutSize: Int = dataIn.count + kCCBlockSizeAES128 * 2
107+
let dataOut = UnsafeMutableRawPointer.allocate(byteCount: dataOutSize,
108+
alignment: 1)
109+
defer { dataOut.deallocate() }
110+
var dataOutMoved: Int = 0
111+
let status = CCCrypt(
112+
CCOperation(operation),
113+
CCAlgorithm(algorithm),
114+
CCOptions(options),
115+
keyUnsafeRawBufferPointer.baseAddress,
116+
key.count,
117+
ivUnsafeRawBufferPointer.baseAddress,
118+
dataInUnsafeRawBufferPointer.baseAddress,
119+
dataIn.count,
120+
dataOut,
121+
dataOutSize,
122+
&dataOutMoved
123+
)
124+
guard status == kCCSuccess else { return nil }
125+
return Data(bytes: dataOut, count: dataOutMoved)
126+
}
127+
}
128+
}
129+
}
130+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//
2+
// File.swift
3+
//
4+
//
5+
// Created by Pisal on 8/19/22.
6+
//
7+
8+
import Foundation
9+
10+
public struct AESResult : Codable {
11+
let salt: String
12+
let iv: String
13+
let encodedData: String
14+
15+
enum CodingKeys: String, CodingKey {
16+
case salt
17+
case iv
18+
case encodedData
19+
}
20+
}
21+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//
2+
// Data+Extensions.swift
3+
// HybridEncryption
4+
//
5+
// Created by Pisal on 8/19/22.
6+
//
7+
8+
import Foundation
9+
import CommonCrypto
10+
11+
public extension Data {
12+
func toBase64String() -> String {
13+
return self.base64EncodedString(options: .lineLength64Characters)
14+
}
15+
16+
mutating func appendString(string: String) {
17+
append(string.data(using: .utf8)!)
18+
}
19+
20+
public var bytes: Array<UInt8> {
21+
Array(self)
22+
}
23+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//
2+
// String+Extensions.swift
3+
// HybridEncryption
4+
//
5+
// Created by Pisal on 8/19/22.
6+
//
7+
8+
import Foundation
9+
10+
11+
public extension String {
12+
public func toDictionary() -> [String:AnyObject]? {
13+
if let data = self.data(using: String.Encoding.utf8) {
14+
do {
15+
let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String:AnyObject]
16+
return json
17+
} catch {}
18+
}
19+
return nil
20+
}
21+
22+
public func toBase64() -> String {
23+
return Data(self.utf8).base64EncodedString()
24+
}
25+
26+
public var md5: String {
27+
return HMAC.hash(input: self, algo: HMACAlgo.MD5)
28+
}
29+
30+
public var sha1: String {
31+
return HMAC.hash(input: self, algo: HMACAlgo.SHA1)
32+
}
33+
34+
public var sha224: String {
35+
return HMAC.hash(input: self, algo: HMACAlgo.SHA224)
36+
}
37+
38+
public var sha256: String {
39+
return HMAC.hash(input: self, algo: HMACAlgo.SHA256)
40+
}
41+
42+
public var sha384: String {
43+
return HMAC.hash(input: self, algo: HMACAlgo.SHA384)
44+
}
45+
46+
public var sha512: String {
47+
return HMAC.hash(input: self, algo: HMACAlgo.SHA512)
48+
}
49+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
//
2+
// Mmac.swift
3+
// HybridEncryption
4+
//
5+
// Created by Pisal on 8/19/22.
6+
//
7+
8+
import Foundation
9+
import CommonCrypto
10+
11+
public struct HMAC {
12+
13+
static func hash(input: String, algo: HMACAlgo) -> String {
14+
if let stringData = input.data(using: .utf8, allowLossyConversion: false) {
15+
return hexStringFromData(input: digest(input: stringData as NSData, algo: algo))
16+
}
17+
return ""
18+
}
19+
20+
private static func digest(input : NSData, algo: HMACAlgo) -> NSData {
21+
let digestLength = algo.digestLength()
22+
var hash = [UInt8](repeating: 0, count: digestLength)
23+
switch algo {
24+
case .MD5:
25+
CC_MD5(input.bytes, UInt32(input.length), &hash)
26+
break
27+
case .SHA1:
28+
CC_SHA1(input.bytes, UInt32(input.length), &hash)
29+
break
30+
case .SHA224:
31+
CC_SHA224(input.bytes, UInt32(input.length), &hash)
32+
break
33+
case .SHA256:
34+
CC_SHA256(input.bytes, UInt32(input.length), &hash)
35+
break
36+
case .SHA384:
37+
CC_SHA384(input.bytes, UInt32(input.length), &hash)
38+
break
39+
case .SHA512:
40+
CC_SHA512(input.bytes, UInt32(input.length), &hash)
41+
break
42+
}
43+
return NSData(bytes: hash, length: digestLength)
44+
}
45+
46+
private static func hexStringFromData(input: NSData) -> String {
47+
var bytes = [UInt8](repeating: 0, count: input.length)
48+
input.getBytes(&bytes, length: input.length)
49+
50+
var hexString = ""
51+
for byte in bytes {
52+
hexString += String(format:"%02x", UInt8(byte))
53+
}
54+
55+
return hexString
56+
}
57+
}
58+
59+
enum HMACAlgo {
60+
case MD5, SHA1, SHA224, SHA256, SHA384, SHA512
61+
62+
func digestLength() -> Int {
63+
var result: CInt = 0
64+
switch self {
65+
case .MD5:
66+
result = CC_MD5_DIGEST_LENGTH
67+
case .SHA1:
68+
result = CC_SHA1_DIGEST_LENGTH
69+
case .SHA224:
70+
result = CC_SHA224_DIGEST_LENGTH
71+
case .SHA256:
72+
result = CC_SHA256_DIGEST_LENGTH
73+
case .SHA384:
74+
result = CC_SHA384_DIGEST_LENGTH
75+
case .SHA512:
76+
result = CC_SHA512_DIGEST_LENGTH
77+
}
78+
return Int(result)
79+
}
80+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//
2+
// HttpFriendlyResult.swift
3+
//
4+
//
5+
// Created by Pisal on 8/19/22.
6+
//
7+
8+
import Foundation
9+
10+
public struct HttpFriendlyResult : Codable {
11+
/// RSA-encrypted AES password of request body
12+
let requestPassword: String
13+
14+
/// IV that was used while encrypting the raw data
15+
let iv: String
16+
17+
/// salt that was used while encrypting the raw data
18+
let salt: String
19+
20+
/// RSA-encrypted AES password for peer/server to encrypt the response
21+
let responsePassword: String
22+
23+
/// AES-encrypted data
24+
let encryptedData: String
25+
26+
/// RSA-encrypted SHA512 hash of the raw data
27+
let signature: String
28+
29+
enum CodingKeys: String, CodingKey {
30+
case requestPassword
31+
case iv
32+
case salt
33+
case responsePassword
34+
case encryptedData
35+
case signature
36+
}
37+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//
2+
// HybridCipherResult.swift
3+
//
4+
//
5+
// Created by Pisal on 8/19/22.
6+
//
7+
8+
import Foundation
9+
10+
public struct HybridCipherResult : Codable {
11+
12+
/**
13+
* Should be saved for decrypting the encrypted response from server
14+
*/
15+
let rawResponsePassword: String
16+
17+
/**
18+
* Suitable for sending to server via REST API
19+
*/
20+
let httpParams: HttpFriendlyResult
21+
22+
enum CodingKeys: String, CodingKey {
23+
case rawResponsePassword
24+
case httpParams
25+
}
26+
}

0 commit comments

Comments
 (0)