// Swift iOS - SECURE: Proper mobile cryptography without hardcoded secrets
import Foundation
import CryptoKit
import Security
import CommonCrypto
class SecureMobileCrypto {
private let keychain = Keychain()
private let serverConfig: ServerConfiguration
init(serverConfig: ServerConfiguration) {
self.serverConfig = serverConfig
}
// Generate device-specific encryption key
private func getOrCreateDeviceKey() throws -> Data {
let keyTag = "com.app.device.encryption.key"
// Try to retrieve existing key from keychain
if let existingKey = keychain.getData(keyTag) {
return existingKey
}
// Generate new device-specific key
var randomBytes = Data(count: 32) // 256-bit key
let result = randomBytes.withUnsafeMutableBytes { bytes in
SecRandomCopyBytes(kSecRandomDefault, 32, bytes.baseAddress!)
}
guard result == errSecSuccess else {
throw CryptoError.keyGenerationFailed
}
// Store in keychain
keychain.setData(randomBytes, forKey: keyTag)
return randomBytes
}
func encryptUserData(_ data: String) throws -> EncryptedData {
let deviceKey = try getOrCreateDeviceKey()
let dataToEncrypt = Data(data.utf8)
// Generate random salt and IV for each encryption
var salt = Data(count: 32)
var iv = Data(count: 16)
_ = salt.withUnsafeMutableBytes { SecRandomCopyBytes(kSecRandomDefault, 32, $0.baseAddress!) }
_ = iv.withUnsafeMutableBytes { SecRandomCopyBytes(kSecRandomDefault, 16, $0.baseAddress!) }
// Derive encryption key using PBKDF2
let derivedKey = try deriveKey(from: deviceKey, salt: salt)
// Encrypt using AES-GCM for authenticated encryption
let sealedBox = try AES.GCM.seal(dataToEncrypt, using: SymmetricKey(data: derivedKey))
return EncryptedData(
ciphertext: sealedBox.ciphertext,
nonce: sealedBox.nonce,
tag: sealedBox.tag,
salt: salt
)
}
func decryptUserData(_ encryptedData: EncryptedData) throws -> String {
let deviceKey = try getOrCreateDeviceKey()
// Derive the same key using stored salt
let derivedKey = try deriveKey(from: deviceKey, salt: encryptedData.salt)
// Reconstruct sealed box and decrypt
let sealedBox = try AES.GCM.SealedBox(
nonce: encryptedData.nonce,
ciphertext: encryptedData.ciphertext,
tag: encryptedData.tag
)
let decryptedData = try AES.GCM.open(sealedBox, using: SymmetricKey(data: derivedKey))
guard let decryptedString = String(data: decryptedData, encoding: .utf8) else {
throw CryptoError.decryptionFailed
}
return decryptedString
}
private func deriveKey(from masterKey: Data, salt: Data) throws -> Data {
var derivedKey = Data(count: 32)
let result = derivedKey.withUnsafeMutableBytes { derivedKeyBytes in
salt.withUnsafeBytes { saltBytes in
masterKey.withUnsafeBytes { masterKeyBytes in
CCKeyDerivationPBKDF(
CCPBKDFAlgorithm(kCCPBKDF2),
masterKeyBytes.baseAddress, masterKey.count,
saltBytes.baseAddress, salt.count,
CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256),
100000, // High iteration count
derivedKeyBytes.baseAddress, 32
)
}
}
}
guard result == kCCSuccess else {
throw CryptoError.keyDerivationFailed
}
return derivedKey
}
func hashPassword(_ password: String) throws -> HashedPassword {
// Generate random salt for each password
var salt = Data(count: 32)
_ = salt.withUnsafeMutableBytes { SecRandomCopyBytes(kSecRandomDefault, 32, $0.baseAddress!) }
let saltedPassword = password + salt.base64EncodedString()
let inputData = Data(saltedPassword.utf8)
let hashed = SHA256.hash(data: inputData)
return HashedPassword(
hash: Data(hashed),
salt: salt,
algorithm: "SHA256+Salt"
)
}
func generateApiSignature(for data: String) async throws -> String {
// Get API signing key from server (never store in app)
let signingKey = try await fetchApiSigningKey()
let key = SymmetricKey(data: signingKey)
let signature = HMAC<SHA256>.authenticationCode(for: Data(data.utf8), using: key)
return Data(signature).base64EncodedString()
}
private func fetchApiSigningKey() async throws -> Data {
// Implement secure key exchange with server
// Use device certificate or OAuth for authentication
let request = URLRequest(url: serverConfig.keyExchangeURL)
// ... implement secure key exchange
throw CryptoError.notImplemented
}
// OAuth implementation without client secrets
func performOAuthFlow() async throws -> OAuthToken {
// Use PKCE (Proof Key for Code Exchange) for mobile OAuth
// Never store client secrets in mobile apps
let codeVerifier = generateCodeVerifier()
let codeChallenge = generateCodeChallenge(from: codeVerifier)
// Implement PKCE OAuth flow
// This eliminates the need for client secrets
throw CryptoError.notImplemented
}
private func generateCodeVerifier() -> String {
var bytes = Data(count: 32)
_ = bytes.withUnsafeMutableBytes { SecRandomCopyBytes(kSecRandomDefault, 32, $0.baseAddress!) }
return bytes.base64URLEncodedString()
}
private func generateCodeChallenge(from verifier: String) -> String {
let challenge = SHA256.hash(data: Data(verifier.utf8))
return Data(challenge).base64URLEncodedString()
}
}
// Supporting data structures
struct EncryptedData {
let ciphertext: Data
let nonce: AES.GCM.Nonce
let tag: Data
let salt: Data
}
struct HashedPassword {
let hash: Data
let salt: Data
let algorithm: String
}
struct ServerConfiguration {
let keyExchangeURL: URL
let baseURL: URL
}
enum CryptoError: Error {
case keyGenerationFailed
case keyDerivationFailed
case decryptionFailed
case notImplemented
}