mirror of
https://github.com/GLEGram/GLEGram-iOS.git
synced 2026-04-24 11:56:18 +02:00
4647310322
Based on Swiftgram 12.5 (Telegram iOS 12.5). All GLEGram features ported and organized in GLEGram/ folder. Features: Ghost Mode, Saved Deleted Messages, Content Protection Bypass, Font Replacement, Fake Profile, Chat Export, Plugin System, and more. See CHANGELOG_12.5.md for full details.
98 lines
3.8 KiB
Swift
98 lines
3.8 KiB
Swift
import Foundation
|
|
import Security
|
|
import CryptoKit
|
|
|
|
/// Key derivation for local encryption (key not stored — derived from app identity).
|
|
private func deriveLocalKey() -> SymmetricKey {
|
|
let salt = "sg_local_v1_7f3a9e"
|
|
let seed = (Bundle.main.bundleIdentifier ?? "sg") + salt
|
|
let hash = SHA256.hash(data: Data(Array(seed.utf8)))
|
|
return SymmetricKey(data: hash)
|
|
}
|
|
|
|
/// AES-256-GCM encrypt before Keychain write. Protects against Keychain-substitution tweaks.
|
|
private func encryptForStorage(_ plaintext: Data) -> Data? {
|
|
let key = deriveLocalKey()
|
|
let nonce = AES.GCM.Nonce()
|
|
guard let sealed = try? AES.GCM.seal(plaintext, using: key, nonce: nonce),
|
|
let combined = sealed.combined else { return nil }
|
|
return combined
|
|
}
|
|
|
|
/// AES-256-GCM decrypt after Keychain read.
|
|
private func decryptFromStorage(_ ciphertext: Data) -> Data? {
|
|
let key = deriveLocalKey()
|
|
guard let sealed = try? AES.GCM.SealedBox(combined: ciphertext),
|
|
let decrypted = try? AES.GCM.open(sealed, using: key) else { return nil }
|
|
return decrypted
|
|
}
|
|
|
|
/// Keychain + AES-256-GCM. Data is encrypted before Keychain; substitution tweaks get ciphertext only.
|
|
private enum SupportersSecureStorage {
|
|
private static let service = "sg_glegram_secure"
|
|
private static let accessibility = kSecAttrAccessibleWhenUnlockedThisDeviceOnly
|
|
|
|
static func getData(account: String) -> Data? {
|
|
let query: [String: Any] = [
|
|
kSecClass as String: kSecClassGenericPassword,
|
|
kSecAttrService as String: service,
|
|
kSecAttrAccount as String: account,
|
|
kSecReturnData as String: true,
|
|
kSecMatchLimit as String: kSecMatchLimitOne
|
|
]
|
|
var result: AnyObject?
|
|
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
guard status == errSecSuccess, let ciphertext = result as? Data else {
|
|
return nil
|
|
}
|
|
return decryptFromStorage(ciphertext)
|
|
}
|
|
|
|
static func setData(_ plaintext: Data, account: String) -> Bool {
|
|
guard let ciphertext = encryptForStorage(plaintext) else { return false }
|
|
let query: [String: Any] = [
|
|
kSecClass as String: kSecClassGenericPassword,
|
|
kSecAttrService as String: service,
|
|
kSecAttrAccount as String: account,
|
|
kSecAttrAccessible as String: accessibility
|
|
]
|
|
var status = SecItemCopyMatching(query as CFDictionary, nil)
|
|
if status == errSecSuccess {
|
|
let updateQuery: [String: Any] = [kSecValueData as String: ciphertext]
|
|
status = SecItemUpdate(query as CFDictionary, updateQuery as CFDictionary)
|
|
return status == errSecSuccess
|
|
} else if status == errSecItemNotFound {
|
|
var addQuery = query
|
|
addQuery[kSecValueData as String] = ciphertext
|
|
status = SecItemAdd(addQuery as CFDictionary, nil)
|
|
return status == errSecSuccess
|
|
}
|
|
return false
|
|
}
|
|
|
|
static func delete(account: String) -> Bool {
|
|
let query: [String: Any] = [
|
|
kSecClass as String: kSecClassGenericPassword,
|
|
kSecAttrService as String: service,
|
|
kSecAttrAccount as String: account
|
|
]
|
|
let status = SecItemDelete(query as CFDictionary)
|
|
return status == errSecSuccess || status == errSecItemNotFound
|
|
}
|
|
}
|
|
|
|
func supportersSecureLoadJSON(account: String) -> [String: Any]? {
|
|
guard let data = SupportersSecureStorage.getData(account: account),
|
|
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
|
|
return nil
|
|
}
|
|
return json
|
|
}
|
|
|
|
func supportersSecureSaveJSON(_ dict: [String: Any], account: String) -> Bool {
|
|
guard let data = try? JSONSerialization.data(withJSONObject: dict) else {
|
|
return false
|
|
}
|
|
return SupportersSecureStorage.setData(data, account: account)
|
|
}
|