在移动应用开发中,安全性是至关重要的考虑因素。iOS平台提供了丰富的安全框架和机制,帮助开发者构建安全可靠的应用程序。本文将深入探讨iOS安全编程的核心技术,包括数据加密、身份验证、网络安全、应用防护等关键领域的实现方法和最佳实践。
数据加密与保护
对称加密实现
@interface SymmetricEncryption : NSObject
+ (NSData *)encryptData:(NSData *)data withKey:(NSData *)key algorithm:(CCAlgorithm)algorithm;
+ (NSData *)decryptData:(NSData *)encryptedData withKey:(NSData *)key algorithm:(CCAlgorithm)algorithm;
+ (NSData *)generateRandomKey:(size_t)keyLength;
+ (NSData *)encryptString:(NSString *)string withPassword:(NSString *)password;
+ (NSString *)decryptDataToString:(NSData *)encryptedData withPassword:(NSString *)password;
@end
@implementation SymmetricEncryption
+ (NSData *)encryptData:(NSData *)data withKey:(NSData *)key algorithm:(CCAlgorithm)algorithm {
if (!data || !key) return nil;
size_t keyLength;
switch (algorithm) {
case kCCAlgorithmAES128:
keyLength = kCCKeySizeAES128;
break;
case kCCAlgorithmAES:
keyLength = kCCKeySizeAES256;
break;
case kCCAlgorithmDES:
keyLength = kCCKeySizeDES;
break;
default:
return nil;
}
if (key.length != keyLength) {
NSLog(@"Invalid key length for algorithm");
return nil;
}
// 生成随机IV
size_t blockSize = (algorithm == kCCAlgorithmAES128 || algorithm == kCCAlgorithmAES) ? kCCBlockSizeAES128 : kCCBlockSizeDES;
NSMutableData *iv = [NSMutableData dataWithLength:blockSize];
SecRandomCopyBytes(kSecRandomDefault, blockSize, iv.mutableBytes);
// 计算输出缓冲区大小
size_t bufferSize = data.length + blockSize;
NSMutableData *encryptedData = [NSMutableData dataWithLength:bufferSize];
size_t numBytesEncrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(
kCCEncrypt,
algorithm,
kCCOptionPKCS7Padding,
key.bytes,
keyLength,
iv.bytes,
data.bytes,
data.length,
encryptedData.mutableBytes,
bufferSize,
&numBytesEncrypted
);
if (cryptStatus != kCCSuccess) {
NSLog(@"Encryption failed with status: %d", cryptStatus);
return nil;
}
// 将IV和加密数据组合
NSMutableData *result = [NSMutableData dataWithData:iv];
[result appendBytes:encryptedData.bytes length:numBytesEncrypted];
return [result copy];
}
+ (NSData *)decryptData:(NSData *)encryptedData withKey:(NSData *)key algorithm:(CCAlgorithm)algorithm {
if (!encryptedData || !key) return nil;
size_t keyLength;
size_t blockSize;
switch (algorithm) {
case kCCAlgorithmAES128:
keyLength = kCCKeySizeAES128;
blockSize = kCCBlockSizeAES128;
break;
case kCCAlgorithmAES:
keyLength = kCCKeySizeAES256;
blockSize = kCCBlockSizeAES128;
break;
case kCCAlgorithmDES:
keyLength = kCCKeySizeDES;
blockSize = kCCBlockSizeDES;
break;
default:
return nil;
}
if (key.length != keyLength || encryptedData.length <= blockSize) {
NSLog(@"Invalid key length or encrypted data size");
return nil;
}
// 提取IV
NSData *iv = [encryptedData subdataWithRange:NSMakeRange(0, blockSize)];
NSData *cipherData = [encryptedData subdataWithRange:NSMakeRange(blockSize, encryptedData.length - blockSize)];
// 解密
size_t bufferSize = cipherData.length + blockSize;
NSMutableData *decryptedData = [NSMutableData dataWithLength:bufferSize];
size_t numBytesDecrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(
kCCDecrypt,
algorithm,
kCCOptionPKCS7Padding,
key.bytes,
keyLength,
iv.bytes,
cipherData.bytes,
cipherData.length,
decryptedData.mutableBytes,
bufferSize,
&numBytesDecrypted
);
if (cryptStatus != kCCSuccess) {
NSLog(@"Decryption failed with status: %d", cryptStatus);
return nil;
}
return [NSData dataWithBytes:decryptedData.bytes length:numBytesDecrypted];
}
+ (NSData *)generateRandomKey:(size_t)keyLength {
NSMutableData *key = [NSMutableData dataWithLength:keyLength];
OSStatus status = SecRandomCopyBytes(kSecRandomDefault, keyLength, key.mutableBytes);
if (status != errSecSuccess) {
NSLog(@"Failed to generate random key");
return nil;
}
return [key copy];
}
+ (NSData *)encryptString:(NSString *)string withPassword:(NSString *)password {
if (!string || !password) return nil;
// 从密码派生密钥
NSData *salt = [self generateRandomKey:16]; // 16字节盐值
NSData *key = [self deriveKeyFromPassword:password salt:salt keyLength:kCCKeySizeAES256];
if (!key) return nil;
NSData *stringData = [string dataUsingEncoding:NSUTF8StringEncoding];
NSData *encryptedData = [self encryptData:stringData withKey:key algorithm:kCCAlgorithmAES];
if (!encryptedData) return nil;
// 将盐值和加密数据组合
NSMutableData *result = [NSMutableData dataWithData:salt];
[result appendData:encryptedData];
return [result copy];
}
+ (NSString *)decryptDataToString:(NSData *)encryptedData withPassword:(NSString *)password {
if (!encryptedData || !password || encryptedData.length <= 16) return nil;
// 提取盐值
NSData *salt = [encryptedData subdataWithRange:NSMakeRange(0, 16)];
NSData *cipherData = [encryptedData subdataWithRange:NSMakeRange(16, encryptedData.length - 16)];
// 从密码派生密钥
NSData *key = [self deriveKeyFromPassword:password salt:salt keyLength:kCCKeySizeAES256];
if (!key) return nil;
NSData *decryptedData = [self decryptData:cipherData withKey:key algorithm:kCCAlgorithmAES];
if (!decryptedData) return nil;
return [[NSString alloc] initWithData:decryptedData encoding:NSUTF8StringEncoding];
}
+ (NSData *)deriveKeyFromPassword:(NSString *)password salt:(NSData *)salt keyLength:(size_t)keyLength {
NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
NSMutableData *derivedKey = [NSMutableData dataWithLength:keyLength];
int result = CCKeyDerivationPBKDF(
kCCPBKDF2,
passwordData.bytes,
passwordData.length,
salt.bytes,
salt.length,
kCCPRFHmacAlgSHA256,
10000, // 迭代次数
derivedKey.mutableBytes,
keyLength
);
if (result != kCCSuccess) {
NSLog(@"Key derivation failed");
return nil;
}
return [derivedKey copy];
}
@end
生物识别认证
Touch ID和Face ID集成
@interface BiometricAuthentication : NSObject
+ (BOOL)isBiometricAuthenticationAvailable;
+ (LABiometryType)availableBiometryType;
+ (void)authenticateWithReason:(NSString *)reason
completion:(void (^)(BOOL success, NSError *error))completion;
+ (void)authenticateWithReason:(NSString *)reason
fallback:(NSString *)fallbackTitle
completion:(void (^)(BOOL success, NSError *error))completion;
+ (BOOL)canEvaluatePolicy:(LAPolicy)policy error:(NSError **)error;
+ (void)evaluateAccessControl:(SecAccessControlRef)accessControl
operation:(NSString *)operation
completion:(void (^)(BOOL success, NSError *error))completion;
@end
@implementation BiometricAuthentication
+ (BOOL)isBiometricAuthenticationAvailable {
LAContext *context = [[LAContext alloc] init];
NSError *error = nil;
BOOL canEvaluate = [context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
error:&error];
if (error) {
NSLog(@"Biometric authentication error: %@", error.localizedDescription);
}
return canEvaluate;
}
+ (LABiometryType)availableBiometryType {
if (@available(iOS 11.0, *)) {
LAContext *context = [[LAContext alloc] init];
NSError *error = nil;
if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]) {
return context.biometryType;
}
}
return LABiometryTypeNone;
}
+ (void)authenticateWithReason:(NSString *)reason
completion:(void (^)(BOOL success, NSError *error))completion {
LAContext *context = [[LAContext alloc] init];
// 设置本地化字符串
context.localizedFallbackTitle = @"使用密码";
context.localizedCancelTitle = @"取消";
NSError *error = nil;
if (![context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(NO, error);
});
return;
}
[context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:reason
reply:^(BOOL success, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(success, error);
});
}];
}
+ (void)authenticateWithReason:(NSString *)reason
fallback:(NSString *)fallbackTitle
completion:(void (^)(BOOL success, NSError *error))completion {
LAContext *context = [[LAContext alloc] init];
if (fallbackTitle) {
context.localizedFallbackTitle = fallbackTitle;
}
[context evaluatePolicy:LAPolicyDeviceOwnerAuthentication
localizedReason:reason
reply:^(BOOL success, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(success, error);
});
}];
}
+ (BOOL)canEvaluatePolicy:(LAPolicy)policy error:(NSError **)error {
LAContext *context = [[LAContext alloc] init];
return [context canEvaluatePolicy:policy error:error];
}
+ (void)evaluateAccessControl:(SecAccessControlRef)accessControl
operation:(NSString *)operation
completion:(void (^)(BOOL success, NSError *error))completion {
LAContext *context = [[LAContext alloc] init];
[context evaluateAccessControl:accessControl
operation:LAAccessControlOperationUseItem
localizedReason:operation
reply:^(BOOL success, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(success, error);
});
}];
}
@end
安全存储管理器
@interface SecureStorageManager : NSObject
+ (instancetype)sharedManager;
- (BOOL)storeSecureData:(NSData *)data forKey:(NSString *)key requireBiometric:(BOOL)requireBiometric;
- (void)retrieveSecureDataForKey:(NSString *)key
completion:(void (^)(NSData *data, NSError *error))completion;
- (BOOL)deleteSecureDataForKey:(NSString *)key;
- (void)storeCredentials:(NSString *)username
password:(NSString *)password
service:(NSString *)service
requireBiometric:(BOOL)requireBiometric
completion:(void (^)(BOOL success, NSError *error))completion;
- (void)retrieveCredentialsForService:(NSString *)service
completion:(void (^)(NSString *username, NSString *password, NSError *error))completion;
@end
@implementation SecureStorageManager
+ (instancetype)sharedManager {
static SecureStorageManager *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (BOOL)storeSecureData:(NSData *)data forKey:(NSString *)key requireBiometric:(BOOL)requireBiometric {
if (!data || !key) return NO;
CFErrorRef error = NULL;
SecAccessControlRef accessControl = NULL;
if (requireBiometric) {
accessControl = SecAccessControlCreateWithFlags(
kCFAllocatorDefault,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
kSecAccessControlBiometryAny,
&error
);
if (error) {
NSLog(@"Failed to create access control: %@", (__bridge NSError *)error);
CFRelease(error);
return NO;
}
}
NSMutableDictionary *query = [@{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrAccount: key,
(__bridge id)kSecAttrService: @"SecureStorage",
(__bridge id)kSecValueData: data
} mutableCopy];
if (accessControl) {
query[(__bridge id)kSecAttrAccessControl] = (__bridge id)accessControl;
} else {
query[(__bridge id)kSecAttrAccessible] = (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly;
}
// 删除现有项目
[self deleteSecureDataForKey:key];
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
if (accessControl) {
CFRelease(accessControl);
}
if (status != errSecSuccess) {
NSLog(@"Failed to store secure data. Status: %d", (int)status);
return NO;
}
return YES;
}
- (void)retrieveSecureDataForKey:(NSString *)key
completion:(void (^)(NSData *data, NSError *error))completion {
if (!key || !completion) return;
NSDictionary *query = @{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrAccount: key,
(__bridge id)kSecAttrService: @"SecureStorage",
(__bridge id)kSecReturnData: @YES,
(__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne
};
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
CFDataRef data = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&data);
dispatch_async(dispatch_get_main_queue(), ^{
if (status == errSecSuccess) {
completion((__bridge_transfer NSData *)data, nil);
} else {
NSError *error = [NSError errorWithDomain:@"SecureStorageError"
code:status
userInfo:@{NSLocalizedDescriptionKey: @"Failed to retrieve secure data"}];
completion(nil, error);
}
});
});
}
- (BOOL)deleteSecureDataForKey:(NSString *)key {
if (!key) return NO;
NSDictionary *query = @{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrAccount: key,
(__bridge id)kSecAttrService: @"SecureStorage"
};
OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);
if (status != errSecSuccess && status != errSecItemNotFound) {
NSLog(@"Failed to delete secure data. Status: %d", (int)status);
return NO;
}
return YES;
}
- (void)storeCredentials:(NSString *)username
password:(NSString *)password
service:(NSString *)service
requireBiometric:(BOOL)requireBiometric
completion:(void (^)(BOOL success, NSError *error))completion {
if (!username || !password || !service) {
if (completion) {
NSError *error = [NSError errorWithDomain:@"SecureStorageError"
code:-1
userInfo:@{NSLocalizedDescriptionKey: @"Invalid parameters"}];
completion(NO, error);
}
return;
}
// 创建凭据字典
NSDictionary *credentials = @{
@"username": username,
@"password": password
};
NSError *jsonError = nil;
NSData *credentialsData = [NSJSONSerialization dataWithJSONObject:credentials
options:0
error:&jsonError];
if (jsonError) {
if (completion) {
completion(NO, jsonError);
}
return;
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
BOOL success = [self storeSecureData:credentialsData
forKey:service
requireBiometric:requireBiometric];
dispatch_async(dispatch_get_main_queue(), ^{
if (completion) {
NSError *error = success ? nil : [NSError errorWithDomain:@"SecureStorageError"
code:-2
userInfo:@{NSLocalizedDescriptionKey: @"Failed to store credentials"}];
completion(success, error);
}
});
});
}
- (void)retrieveCredentialsForService:(NSString *)service
completion:(void (^)(NSString *username, NSString *password, NSError *error))completion {
if (!service || !completion) return;
[self retrieveSecureDataForKey:service completion:^(NSData *data, NSError *error) {
if (error) {
completion(nil, nil, error);
return;
}
NSError *jsonError = nil;
NSDictionary *credentials = [NSJSONSerialization JSONObjectWithData:data
options:0
error:&jsonError];
if (jsonError) {
completion(nil, nil, jsonError);
return;
}
NSString *username = credentials[@"username"];
NSString *password = credentials[@"password"];
completion(username, password, nil);
}];
}
@end
网络安全
SSL/TLS证书验证
@interface NetworkSecurityManager : NSObject <NSURLSessionDelegate>
+ (instancetype)sharedManager;
- (NSURLSession *)createSecureSessionWithPinning:(BOOL)enablePinning;
- (BOOL)validateCertificateChain:(SecTrustRef)serverTrust forHost:(NSString *)host;
- (BOOL)validateCertificatePinning:(SecTrustRef)serverTrust;
- (NSData *)sha256HashOfCertificate:(SecCertificateRef)certificate;
@property (nonatomic, strong) NSSet<NSString *> *pinnedCertificateHashes;
@property (nonatomic, assign) BOOL allowInvalidCertificates;
@property (nonatomic, assign) BOOL validatesDomainName;
@end
@implementation NetworkSecurityManager
+ (instancetype)sharedManager {
static NetworkSecurityManager *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (instancetype)init {
self = [super init];
if (self) {
_allowInvalidCertificates = NO;
_validatesDomainName = YES;
// 示例:预设的证书哈希值(实际使用时应该是你的服务器证书的哈希)
_pinnedCertificateHashes = [NSSet setWithArray:@[
@"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // 替换为实际的证书哈希
@"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=" // 备用证书哈希
]];
}
return self;
}
- (NSURLSession *)createSecureSessionWithPinning:(BOOL)enablePinning {
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
// 设置超时
config.timeoutIntervalForRequest = 30.0;
config.timeoutIntervalForResource = 60.0;
// 设置缓存策略
config.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
// 创建会话
NSURLSession *session = [NSURLSession sessionWithConfiguration:config
delegate:enablePinning ? self : nil
delegateQueue:nil];
return session;
}
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler {
NSString *authMethod = challenge.protectionSpace.authenticationMethod;
if ([authMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
NSString *host = challenge.protectionSpace.host;
SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
// 验证证书链
BOOL isValidChain = [self validateCertificateChain:serverTrust forHost:host];
// 验证证书固定
BOOL isValidPinning = [self validateCertificatePinning:serverTrust];
if (isValidChain && isValidPinning) {
NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
} else {
NSLog(@"Certificate validation failed for host: %@", host);
completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil);
}
} else {
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
}
}
- (BOOL)validateCertificateChain:(SecTrustRef)serverTrust forHost:(NSString *)host {
if (!serverTrust) return NO;
// 如果允许无效证书,跳过验证
if (self.allowInvalidCertificates) {
return YES;
}
// 设置验证策略
SecPolicyRef policy = SecPolicyCreateSSL(true, (__bridge CFStringRef)host);
SecTrustSetPolicies(serverTrust, policy);
CFRelease(policy);
// 执行验证
SecTrustResultType result;
OSStatus status = SecTrustEvaluate(serverTrust, &result);
if (status != errSecSuccess) {
NSLog(@"Certificate evaluation failed with status: %d", (int)status);
return NO;
}
// 检查验证结果
BOOL isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
if (!isValid) {
NSLog(@"Certificate validation failed with result: %d", result);
}
return isValid;
}
- (BOOL)validateCertificatePinning:(SecTrustRef)serverTrust {
if (!serverTrust || self.pinnedCertificateHashes.count == 0) {
return YES; // 如果没有设置固定证书,跳过验证
}
CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust);
for (CFIndex i = 0; i < certificateCount; i++) {
SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i);
NSData *certificateHash = [self sha256HashOfCertificate:certificate];
if (certificateHash) {
NSString *hashString = [certificateHash base64EncodedStringWithOptions:0];
if ([self.pinnedCertificateHashes containsObject:hashString]) {
return YES;
}
}
}
NSLog(@"Certificate pinning validation failed");
return NO;
}
- (NSData *)sha256HashOfCertificate:(SecCertificateRef)certificate {
if (!certificate) return nil;
CFDataRef certificateData = SecCertificateCopyData(certificate);
if (!certificateData) return nil;
NSData *data = (__bridge NSData *)certificateData;
NSMutableData *hash = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];
CC_SHA256(data.bytes, (CC_LONG)data.length, hash.mutableBytes);
CFRelease(certificateData);
return [hash copy];
}
@end
安全网络请求管理
@interface SecureNetworkManager : NSObject
+ (instancetype)sharedManager;
- (void)performSecureRequest:(NSURLRequest *)request
completion:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completion;
- (NSURLRequest *)createSecureRequestWithURL:(NSURL *)url
method:(NSString *)method
body:(NSData *)body
headers:(NSDictionary *)headers;
- (void)uploadData:(NSData *)data
toURL:(NSURL *)url
completion:(void (^)(NSData *responseData, NSError *error))completion;
- (void)downloadFromURL:(NSURL *)url
completion:(void (^)(NSURL *fileURL, NSError *error))completion;
@property (nonatomic, strong) NSString *apiKey;
@property (nonatomic, strong) NSString *userAgent;
@property (nonatomic, assign) NSTimeInterval requestTimeout;
@end
@implementation SecureNetworkManager
+ (instancetype)sharedManager {
static SecureNetworkManager *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (instancetype)init {
self = [super init];
if (self) {
_requestTimeout = 30.0;
_userAgent = [NSString stringWithFormat:@"SecureApp/1.0 (%@; iOS %@)",
[[UIDevice currentDevice] model],
[[UIDevice currentDevice] systemVersion]];
}
return self;
}
- (void)performSecureRequest:(NSURLRequest *)request
completion:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completion {
if (!request || !completion) return;
// 创建安全会话
NSURLSession *session = [[NetworkSecurityManager sharedManager] createSecureSessionWithPinning:YES];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
NSLog(@"Network request failed: %@", error.localizedDescription);
completion(nil, response, error);
return;
}
// 验证响应
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode >= 200 && httpResponse.statusCode < 300) {
completion(data, response, nil);
} else {
NSError *httpError = [NSError errorWithDomain:@"HTTPError"
code:httpResponse.statusCode
userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"HTTP Error %ld", (long)httpResponse.statusCode]}];
completion(nil, response, httpError);
}
} else {
completion(data, response, nil);
}
});
}];
[task resume];
}
- (NSURLRequest *)createSecureRequestWithURL:(NSURL *)url
method:(NSString *)method
body:(NSData *)body
headers:(NSDictionary *)headers {
if (!url) return nil;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = method ?: @"GET";
request.timeoutInterval = self.requestTimeout;
// 设置默认头部
[request setValue:self.userAgent forHTTPHeaderField:@"User-Agent"];
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
// 添加API密钥
if (self.apiKey) {
[request setValue:self.apiKey forHTTPHeaderField:@"X-API-Key"];
}
// 添加自定义头部
for (NSString *key in headers) {
[request setValue:headers[key] forHTTPHeaderField:key];
}
// 设置请求体
if (body && [method isEqualToString:@"POST"] || [method isEqualToString:@"PUT"]) {
request.HTTPBody = body;
[request setValue:[NSString stringWithFormat:@"%lu", (unsigned long)body.length]
forHTTPHeaderField:@"Content-Length"];
}
// 添加时间戳和随机数(防重放攻击)
NSTimeInterval timestamp = [[NSDate date] timeIntervalSince1970];
NSString *nonce = [[NSUUID UUID] UUIDString];
[request setValue:[NSString stringWithFormat:@"%.0f", timestamp] forHTTPHeaderField:@"X-Timestamp"];
[request setValue:nonce forHTTPHeaderField:@"X-Nonce"];
return [request copy];
}
- (void)uploadData:(NSData *)data
toURL:(NSURL *)url
completion:(void (^)(NSData *responseData, NSError *error))completion {
if (!data || !url || !completion) return;
NSURLRequest *request = [self createSecureRequestWithURL:url
method:@"POST"
body:data
headers:@{@"Content-Type": @"application/octet-stream"}];
[self performSecureRequest:request completion:^(NSData *responseData, NSURLResponse *response, NSError *error) {
completion(responseData, error);
}];
}
- (void)downloadFromURL:(NSURL *)url
completion:(void (^)(NSURL *fileURL, NSError *error))completion {
if (!url || !completion) return;
NSURLSession *session = [[NetworkSecurityManager sharedManager] createSecureSessionWithPinning:YES];
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:url
completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
completion(nil, error);
return;
}
// 移动文件到安全位置
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *fileName = [NSString stringWithFormat:@"download_%@", [[NSUUID UUID] UUIDString]];
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:fileName];
NSURL *destinationURL = [NSURL fileURLWithPath:filePath];
NSError *moveError = nil;
BOOL success = [[NSFileManager defaultManager] moveItemAtURL:location
toURL:destinationURL
error:&moveError];
if (success) {
completion(destinationURL, nil);
} else {
completion(nil, moveError);
}
});
}];
[downloadTask resume];
}
@end
应用防护与反调试
反调试检测
@interface AntiDebuggingManager : NSObject
+ (BOOL)isDebuggerAttached;
+ (BOOL)isJailbroken;
+ (BOOL)isRunningOnSimulator;
+ (void)enableAntiDebuggingProtection;
+ (BOOL)detectDynamicLibraryInjection;
+ (BOOL)detectCodeInjection;
+ (void)obfuscateString:(NSString *)string;
@end
@implementation AntiDebuggingManager
+ (BOOL)isDebuggerAttached {
// 方法1:检查ptrace
int mib[4];
struct kinfo_proc info;
size_t size = sizeof(info);
mib[0] = CTL_KERN;
mib[1] = KERN_PROC;
mib[2] = KERN_PROC_PID;
mib[3] = getpid();
if (sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0) != 0) {
return NO;
}
// 检查P_TRACED标志
BOOL isDebugged = (info.kp_proc.p_flag & P_TRACED) != 0;
// 方法2:检查调试器端口
mach_port_t port = MACH_PORT_NULL;
if (task_get_exception_ports(mach_task_self(), EXC_MASK_ALL, NULL, NULL, NULL, NULL, NULL) == KERN_SUCCESS) {
// 如果能获取异常端口,可能有调试器
}
// 方法3:检查父进程
pid_t ppid = getppid();
if (ppid != 1) { // 正常情况下父进程应该是launchd (PID 1)
char pathbuf[PROC_PIDPATHINFO_MAXSIZE];
if (proc_pidpath(ppid, pathbuf, sizeof(pathbuf)) > 0) {
NSString *parentPath = [NSString stringWithUTF8String:pathbuf];
if ([parentPath containsString:@"debugserver"] || [parentPath containsString:@"lldb"]) {
isDebugged = YES;
}
}
}
return isDebugged;
}
+ (BOOL)isJailbroken {
// 检查常见的越狱文件和目录
NSArray *jailbreakPaths = @[
@"/Applications/Cydia.app",
@"/Library/MobileSubstrate/MobileSubstrate.dylib",
@"/bin/bash",
@"/usr/sbin/sshd",
@"/etc/apt",
@"/private/var/lib/apt/",
@"/private/var/lib/cydia",
@"/private/var/mobile/Library/SBSettings/Themes",
@"/Library/MobileSubstrate/DynamicLibraries/LiveClock.plist",
@"/System/Library/LaunchDaemons/com.ikey.bbot.plist",
@"/System/Library/LaunchDaemons/com.saurik.Cydia.Startup.plist",
@"/var/cache/apt",
@"/var/lib/apt",
@"/var/lib/cydia",
@"/usr/libexec/sftp-server",
@"/usr/bin/sshd"
];
for (NSString *path in jailbreakPaths) {
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
return YES;
}
}
// 检查是否能写入系统目录
NSString *testPath = @"/private/test_jailbreak.txt";
NSError *error;
[@"test" writeToFile:testPath atomically:YES encoding:NSUTF8StringEncoding error:&error];
if (!error) {
[[NSFileManager defaultManager] removeItemAtPath:testPath error:nil];
return YES;
}
// 检查URL Scheme
if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"cydia://package/com.example.package"]]) {
return YES;
}
// 检查动态库
uint32_t count = _dyld_image_count();
for (uint32_t i = 0; i < count; i++) {
const char *name = _dyld_get_image_name(i);
if (name) {
NSString *imageName = [NSString stringWithUTF8String:name];
if ([imageName containsString:@"MobileSubstrate"] ||
[imageName containsString:@"Substrate"] ||
[imageName containsString:@"Cycript"]) {
return YES;
}
}
}
return NO;
}
+ (BOOL)isRunningOnSimulator {
#if TARGET_IPHONE_SIMULATOR
return YES;
#else
return NO;
#endif
}
+ (void)enableAntiDebuggingProtection {
// 禁用ptrace
ptrace(PT_DENY_ATTACH, 0, 0, 0);
// 设置信号处理器
signal(SIGINT, SIG_IGN);
signal(SIGTERM, SIG_IGN);
signal(SIGKILL, SIG_IGN);
// 定期检查调试器
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_async(queue, ^{
while (YES) {
if ([self isDebuggerAttached]) {
// 检测到调试器,执行保护措施
exit(0); // 或者其他保护措施
}
sleep(1);
}
});
}
+ (BOOL)detectDynamicLibraryInjection {
uint32_t count = _dyld_image_count();
// 记录预期的系统库数量(这个值需要根据实际情况调整)
static uint32_t expectedLibraryCount = 0;
if (expectedLibraryCount == 0) {
expectedLibraryCount = count;
return NO;
}
// 如果库数量显著增加,可能有注入
if (count > expectedLibraryCount + 5) {
return YES;
}
// 检查可疑的库名
for (uint32_t i = 0; i < count; i++) {
const char *name = _dyld_get_image_name(i);
if (name) {
NSString *imageName = [NSString stringWithUTF8String:name];
NSArray *suspiciousLibraries = @[
@"FridaGadget",
@"frida",
@"cycript",
@"substrate",
@"substitute"
];
for (NSString *suspicious in suspiciousLibraries) {
if ([imageName.lowercaseString containsString:suspicious.lowercaseString]) {
return YES;
}
}
}
}
return NO;
}
+ (BOOL)detectCodeInjection {
// 检查内存中的可疑模式
vm_address_t address = 0;
vm_size_t size = 0;
vm_region_basic_info_data_64_t info;
mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;
mach_port_t object_name;
while (vm_region_64(mach_task_self(), &address, &size, VM_REGION_BASIC_INFO_64,
(vm_region_info_t)&info, &count, &object_name) == KERN_SUCCESS) {
// 检查可执行内存区域
if (info.protection & VM_PROT_EXECUTE) {
// 这里可以添加更复杂的代码完整性检查
// 例如计算关键函数的哈希值并与预期值比较
}
address += size;
}
return NO;
}
+ (void)obfuscateString:(NSString *)string {
// 简单的字符串混淆示例
// 实际应用中应该使用更复杂的混淆技术
const char *cString = [string UTF8String];
char *obfuscated = malloc(strlen(cString) + 1);
for (int i = 0; i < strlen(cString); i++) {
obfuscated[i] = cString[i] ^ 0xAA; // 简单的XOR混淆
}
obfuscated[strlen(cString)] = '\0';
// 使用混淆后的字符串...
free(obfuscated);
}
@end
代码完整性验证
@interface CodeIntegrityChecker : NSObject
+ (BOOL)verifyApplicationIntegrity;
+ (NSString *)calculateExecutableHash;
+ (BOOL)verifyCodeSignature;
+ (BOOL)checkApplicationBundle;
+ (void)performRuntimeIntegrityCheck;
@end
@implementation CodeIntegrityChecker
+ (BOOL)verifyApplicationIntegrity {
// 检查代码签名
if (![self verifyCodeSignature]) {
NSLog(@"Code signature verification failed");
return NO;
}
// 检查应用包完整性
if (![self checkApplicationBundle]) {
NSLog(@"Application bundle integrity check failed");
return NO;
}
// 计算并验证可执行文件哈希
NSString *currentHash = [self calculateExecutableHash];
NSString *expectedHash = @"YOUR_EXPECTED_HASH_HERE"; // 预期的哈希值
if (![currentHash isEqualToString:expectedHash]) {
NSLog(@"Executable hash verification failed");
return NO;
}
return YES;
}
+ (NSString *)calculateExecutableHash {
NSString *executablePath = [[NSBundle mainBundle] executablePath];
NSData *executableData = [NSData dataWithContentsOfFile:executablePath];
if (!executableData) {
return nil;
}
NSMutableData *hash = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];
CC_SHA256(executableData.bytes, (CC_LONG)executableData.length, hash.mutableBytes);
// 转换为十六进制字符串
NSMutableString *hashString = [NSMutableString string];
const unsigned char *bytes = hash.bytes;
for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) {
[hashString appendFormat:@"%02x", bytes[i]];
}
return [hashString copy];
}
+ (BOOL)verifyCodeSignature {
SecStaticCodeRef staticCode = NULL;
OSStatus status;
// 获取当前应用的静态代码引用
status = SecStaticCodeCreateWithPath((__bridge CFURLRef)[[NSBundle mainBundle] bundleURL],
kSecCSDefaultFlags, &staticCode);
if (status != errSecSuccess) {
NSLog(@"Failed to create static code reference: %d", (int)status);
return NO;
}
// 验证代码签名
status = SecStaticCodeCheckValidity(staticCode, kSecCSDefaultFlags, NULL);
CFRelease(staticCode);
if (status != errSecSuccess) {
NSLog(@"Code signature validation failed: %d", (int)status);
return NO;
}
return YES;
}
+ (BOOL)checkApplicationBundle {
NSBundle *mainBundle = [NSBundle mainBundle];
// 检查Info.plist
NSDictionary *infoPlist = [mainBundle infoDictionary];
if (!infoPlist) {
return NO;
}
// 检查Bundle ID
NSString *bundleID = [mainBundle bundleIdentifier];
NSString *expectedBundleID = @"com.yourcompany.yourapp"; // 替换为实际的Bundle ID
if (![bundleID isEqualToString:expectedBundleID]) {
return NO;
}
// 检查关键文件是否存在
NSArray *criticalFiles = @[
@"Info.plist",
@"PkgInfo"
];
for (NSString *fileName in criticalFiles) {
NSString *filePath = [mainBundle pathForResource:fileName.stringByDeletingPathExtension
ofType:fileName.pathExtension];
if (!filePath || ![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
return NO;
}
}
return YES;
}
+ (void)performRuntimeIntegrityCheck {
// 定期执行完整性检查
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_async(queue, ^{
while (YES) {
if (![self verifyApplicationIntegrity]) {
// 检测到完整性问题,执行保护措施
dispatch_async(dispatch_get_main_queue(), ^{
// 可以显示警告或退出应用
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"安全警告"
message:@"检测到应用完整性问题"
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *exitAction = [UIAlertAction actionWithTitle:@"退出"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
exit(0);
}];
[alert addAction:exitAction];
// 显示警告(需要获取当前视图控制器)
// [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alert animated:YES completion:nil];
});
break;
}
sleep(60); // 每分钟检查一次
}
});
}
@end
安全编程最佳实践
安全开发指南
@interface SecurityBestPractices : NSObject
// 安全的数据处理
+ (void)secureDataHandlingExample;
// 安全的网络通信
+ (void)secureNetworkCommunicationExample;
// 安全的用户输入处理
+ (NSString *)sanitizeUserInput:(NSString *)input;
// 安全的文件操作
+ (BOOL)secureFileOperationWithData:(NSData *)data fileName:(NSString *)fileName;
// 安全的内存管理
+ (void)secureMemoryManagementExample;
@end
@implementation SecurityBestPractices
+ (void)secureDataHandlingExample {
// 1. 敏感数据加密存储
NSString *sensitiveData = @"用户的敏感信息";
NSString *password = @"用户密码";
// 使用对称加密
NSData *encryptedData = [SymmetricEncryption encryptString:sensitiveData withPassword:password];
// 存储到Keychain而不是UserDefaults
[[SecureStorageManager sharedManager] storeSecureData:encryptedData
forKey:@"sensitive_data"
requireBiometric:YES];
// 2. 清除内存中的敏感数据
// 使用安全的字符串清除方法
[self secureStringClear:sensitiveData];
[self secureStringClear:password];
}
+ (void)secureNetworkCommunicationExample {
// 1. 使用HTTPS和证书固定
NSURL *url = [NSURL URLWithString:@"https://api.example.com/data"];
NSDictionary *requestData = @{
@"user_id": @"12345",
@"timestamp": @([[NSDate date] timeIntervalSince1970])
};
NSError *jsonError;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:requestData
options:0
error:&jsonError];
if (!jsonError) {
NSURLRequest *request = [[SecureNetworkManager sharedManager]
createSecureRequestWithURL:url
method:@"POST"
body:jsonData
headers:@{@"Content-Type": @"application/json"}];
[[SecureNetworkManager sharedManager] performSecureRequest:request
completion:^(NSData *data, NSURLResponse *response, NSError *error) {
if (!error) {
// 处理响应数据
NSLog(@"Request successful");
} else {
NSLog(@"Request failed: %@", error.localizedDescription);
}
}];
}
}
+ (NSString *)sanitizeUserInput:(NSString *)input {
if (!input) return nil;
// 1. 移除危险字符
NSCharacterSet *dangerousChars = [NSCharacterSet characterSetWithCharactersInString:@"<>\"'&"];
NSString *sanitized = [[input componentsSeparatedByCharactersInSet:dangerousChars] componentsJoinedByString:@""];
// 2. 限制长度
NSUInteger maxLength = 1000;
if (sanitized.length > maxLength) {
sanitized = [sanitized substringToIndex:maxLength];
}
// 3. 验证格式(例如邮箱格式)
if ([self isValidEmail:sanitized]) {
return sanitized;
}
return nil;
}
+ (BOOL)isValidEmail:(NSString *)email {
NSString *emailRegex = @"[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}";
NSPredicate *emailPredicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", emailRegex];
return [emailPredicate evaluateWithObject:email];
}
+ (BOOL)secureFileOperationWithData:(NSData *)data fileName:(NSString *)fileName {
if (!data || !fileName) return NO;
// 1. 验证文件名安全性
if ([fileName containsString:@"../"] || [fileName containsString:@"~"]) {
NSLog(@"Unsafe file name detected");
return NO;
}
// 2. 使用应用沙盒目录
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:fileName];
// 3. 设置文件保护级别
NSError *error;
BOOL success = [data writeToFile:filePath options:NSDataWritingFileProtectionComplete error:&error];
if (success) {
// 4. 设置文件属性
NSDictionary *attributes = @{
NSFileProtectionKey: NSFileProtectionComplete
};
[[NSFileManager defaultManager] setAttributes:attributes
ofItemAtPath:filePath
error:&error];
}
if (error) {
NSLog(@"File operation failed: %@", error.localizedDescription);
return NO;
}
return success;
}
+ (void)secureMemoryManagementExample {
// 1. 使用安全的内存分配
size_t size = 1024;
void *secureBuffer = malloc(size);
if (secureBuffer) {
// 2. 初始化内存
memset(secureBuffer, 0, size);
// 使用内存...
// 3. 安全清除内存
memset_s(secureBuffer, size, 0, size);
// 4. 释放内存
free(secureBuffer);
secureBuffer = NULL;
}
// 5. 避免在栈上存储敏感数据
// 不好的做法:
// char password[100] = "sensitive_password";
// 好的做法:使用堆内存并及时清除
char *password = malloc(100);
strcpy(password, "sensitive_password");
// 使用密码...
// 清除并释放
memset_s(password, 100, 0, 100);
free(password);
password = NULL;
}
+ (void)secureStringClear:(NSString *)string {
// 注意:NSString是不可变的,这个方法主要用于演示
// 实际应用中应该使用NSMutableString或C字符串
if ([string isKindOfClass:[NSMutableString class]]) {
NSMutableString *mutableString = (NSMutableString *)string;
// 用随机字符覆盖
for (NSUInteger i = 0; i < mutableString.length; i++) {
[mutableString replaceCharactersInRange:NSMakeRange(i, 1)
withString:[NSString stringWithFormat:@"%c", arc4random() % 256]];
}
// 清空字符串
[mutableString setString:@""];
}
}
@end
总结
iOS安全编程是一个复杂而重要的主题,涉及多个层面的安全考虑:
核心安全原则
- 数据保护:使用强加密算法保护敏感数据,合理使用Keychain服务
- 身份验证:集成生物识别认证,提供多层身份验证机制
- 网络安全:实施SSL/TLS证书验证和证书固定,防范中间人攻击
- 应用防护:实现反调试和代码完整性检查,防范逆向工程
- 安全开发:遵循安全编程最佳实践,建立安全的开发流程
实施建议
- 分层防护:不要依赖单一的安全措施,建立多层防护体系
- 持续监控:实施运行时安全检查和异常监控
- 定期更新:及时更新安全策略和防护措施
- 安全测试:进行渗透测试和安全审计
- 用户教育:提高用户的安全意识
通过系统性地实施这些安全措施,可以显著提高iOS应用的安全性,保护用户数据和应用完整性。安全是一个持续的过程,需要在整个应用生命周期中不断关注和改进。
非对称加密实现
@interface AsymmetricEncryption : NSObject
+ (BOOL)generateKeyPairWithKeySize:(NSUInteger)keySize
publicKey:(SecKeyRef *)publicKey
privateKey:(SecKeyRef *)privateKey;
+ (NSData *)encryptData:(NSData *)data withPublicKey:(SecKeyRef)publicKey;
+ (NSData *)decryptData:(NSData *)encryptedData withPrivateKey:(SecKeyRef)privateKey;
+ (NSData *)signData:(NSData *)data withPrivateKey:(SecKeyRef)privateKey;
+ (BOOL)verifySignature:(NSData *)signature forData:(NSData *)data withPublicKey:(SecKeyRef)publicKey;
+ (NSData *)exportPublicKey:(SecKeyRef)publicKey;
+ (SecKeyRef)importPublicKeyFromData:(NSData *)keyData;
@end
@implementation AsymmetricEncryption
+ (BOOL)generateKeyPairWithKeySize:(NSUInteger)keySize
publicKey:(SecKeyRef *)publicKey
privateKey:(SecKeyRef *)privateKey {
NSDictionary *keyPairAttributes = @{
(__bridge id)kSecAttrKeyType: (__bridge id)kSecAttrKeyTypeRSA,
(__bridge id)kSecAttrKeySizeInBits: @(keySize),
(__bridge id)kSecPrivateKeyAttrs: @{
(__bridge id)kSecAttrIsPermanent: @NO,
(__bridge id)kSecAttrApplicationTag: [@"com.app.privatekey" dataUsingEncoding:NSUTF8StringEncoding]
},
(__bridge id)kSecPublicKeyAttrs: @{
(__bridge id)kSecAttrIsPermanent: @NO,
(__bridge id)kSecAttrApplicationTag: [@"com.app.publickey" dataUsingEncoding:NSUTF8StringEncoding]
}
};
OSStatus status = SecKeyGeneratePair((__bridge CFDictionaryRef)keyPairAttributes, publicKey, privateKey);
if (status != errSecSuccess) {
NSLog(@"Key pair generation failed with status: %d", (int)status);
return NO;
}
return YES;
}
+ (NSData *)encryptData:(NSData *)data withPublicKey:(SecKeyRef)publicKey {
if (!data || !publicKey) return nil;
size_t blockSize = SecKeyGetBlockSize(publicKey);
size_t maxPlaintextLength = blockSize - 11; // PKCS#1 padding
if (data.length > maxPlaintextLength) {
NSLog(@"Data too large for RSA encryption. Max size: %zu bytes", maxPlaintextLength);
return nil;
}
NSMutableData *encryptedData = [NSMutableData dataWithLength:blockSize];
size_t encryptedLength = blockSize;
OSStatus status = SecKeyEncrypt(
publicKey,
kSecPaddingPKCS1,
data.bytes,
data.length,
encryptedData.mutableBytes,
&encryptedLength
);
if (status != errSecSuccess) {
NSLog(@"Encryption failed with status: %d", (int)status);
return nil;
}
return [NSData dataWithBytes:encryptedData.bytes length:encryptedLength];
}
+ (NSData *)decryptData:(NSData *)encryptedData withPrivateKey:(SecKeyRef)privateKey {
if (!encryptedData || !privateKey) return nil;
size_t blockSize = SecKeyGetBlockSize(privateKey);
NSMutableData *decryptedData = [NSMutableData dataWithLength:blockSize];
size_t decryptedLength = blockSize;
OSStatus status = SecKeyDecrypt(
privateKey,
kSecPaddingPKCS1,
encryptedData.bytes,
encryptedData.length,
decryptedData.mutableBytes,
&decryptedLength
);
if (status != errSecSuccess) {
NSLog(@"Decryption failed with status: %d", (int)status);
return nil;
}
return [NSData dataWithBytes:decryptedData.bytes length:decryptedLength];
}
+ (NSData *)signData:(NSData *)data withPrivateKey:(SecKeyRef)privateKey {
if (!data || !privateKey) return nil;
// 计算SHA-256哈希
NSMutableData *hash = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];
CC_SHA256(data.bytes, (CC_LONG)data.length, hash.mutableBytes);
size_t blockSize = SecKeyGetBlockSize(privateKey);
NSMutableData *signature = [NSMutableData dataWithLength:blockSize];
size_t signatureLength = blockSize;
OSStatus status = SecKeyRawSign(
privateKey,
kSecPaddingPKCS1SHA256,
hash.bytes,
hash.length,
signature.mutableBytes,
&signatureLength
);
if (status != errSecSuccess) {
NSLog(@"Signing failed with status: %d", (int)status);
return nil;
}
return [NSData dataWithBytes:signature.bytes length:signatureLength];
}
+ (BOOL)verifySignature:(NSData *)signature forData:(NSData *)data withPublicKey:(SecKeyRef)publicKey {
if (!signature || !data || !publicKey) return NO;
// 计算SHA-256哈希
NSMutableData *hash = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];
CC_SHA256(data.bytes, (CC_LONG)data.length, hash.mutableBytes);
OSStatus status = SecKeyRawVerify(
publicKey,
kSecPaddingPKCS1SHA256,
hash.bytes,
hash.length,
signature.bytes,
signature.length
);
return status == errSecSuccess;
}
+ (NSData *)exportPublicKey:(SecKeyRef)publicKey {
if (!publicKey) return nil;
CFDataRef keyData = NULL;
OSStatus status = SecItemExport(publicKey, kSecFormatOpenSSL, kSecItemPemArmour, NULL, &keyData);
if (status != errSecSuccess) {
NSLog(@"Public key export failed with status: %d", (int)status);
return nil;
}
NSData *result = (__bridge_transfer NSData *)keyData;
return result;
}
+ (SecKeyRef)importPublicKeyFromData:(NSData *)keyData {
if (!keyData) return NULL;
SecExternalFormat format = kSecFormatOpenSSL;
SecExternalItemType itemType = kSecItemTypePublicKey;
CFArrayRef items = NULL;
OSStatus status = SecItemImport(
(__bridge CFDataRef)keyData,
NULL,
&format,
&itemType,
kSecItemPemArmour,
NULL,
NULL,
&items
);
if (status != errSecSuccess || !items || CFArrayGetCount(items) == 0) {
NSLog(@"Public key import failed with status: %d", (int)status);
if (items) CFRelease(items);
return NULL;
}
SecKeyRef publicKey = (SecKeyRef)CFArrayGetValueAtIndex(items, 0);
CFRetain(publicKey);
CFRelease(items);
return publicKey;
}
@end
Keychain服务管理
Keychain访问封装
@interface KeychainManager : NSObject
+ (BOOL)storePassword:(NSString *)password forAccount:(NSString *)account service:(NSString *)service;
+ (NSString *)retrievePasswordForAccount:(NSString *)account service:(NSString *)service;
+ (BOOL)updatePassword:(NSString *)newPassword forAccount:(NSString *)account service:(NSString *)service;
+ (BOOL)deletePasswordForAccount:(NSString *)account service:(NSString *)service;
+ (BOOL)storeData:(NSData *)data forKey:(NSString *)key accessGroup:(NSString *)accessGroup;
+ (NSData *)retrieveDataForKey:(NSString *)key accessGroup:(NSString *)accessGroup;
+ (NSArray<NSDictionary *> *)allKeychainItems;
+ (BOOL)clearAllKeychainItems;
@end
@implementation KeychainManager
+ (BOOL)storePassword:(NSString *)password forAccount:(NSString *)account service:(NSString *)service {
if (!password || !account || !service) return NO;
NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *query = @{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrAccount: account,
(__bridge id)kSecAttrService: service,
(__bridge id)kSecValueData: passwordData,
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly
};
// 首先尝试删除现有项目
[self deletePasswordForAccount:account service:service];
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
if (status != errSecSuccess) {
NSLog(@"Failed to store password. Status: %d", (int)status);
return NO;
}
return YES;
}
+ (NSString *)retrievePasswordForAccount:(NSString *)account service:(NSString *)service {
if (!account || !service) return nil;
NSDictionary *query = @{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrAccount: account,
(__bridge id)kSecAttrService: service,
(__bridge id)kSecReturnData: @YES,
(__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne
};
CFDataRef passwordData = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&passwordData);
if (status != errSecSuccess) {
if (status != errSecItemNotFound) {
NSLog(@"Failed to retrieve password. Status: %d", (int)status);
}
return nil;
}
NSString *password = [[NSString alloc] initWithData:(__bridge_transfer NSData *)passwordData
encoding:NSUTF8StringEncoding];
return password;
}
+ (BOOL)updatePassword:(NSString *)newPassword forAccount:(NSString *)account service:(NSString *)service {
if (!newPassword || !account || !service) return NO;
NSData *passwordData = [newPassword dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *query = @{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrAccount: account,
(__bridge id)kSecAttrService: service
};
NSDictionary *attributesToUpdate = @{
(__bridge id)kSecValueData: passwordData
};
OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)query,
(__bridge CFDictionaryRef)attributesToUpdate);
if (status != errSecSuccess) {
NSLog(@"Failed to update password. Status: %d", (int)status);
return NO;
}
return YES;
}
+ (BOOL)deletePasswordForAccount:(NSString *)account service:(NSString *)service {
if (!account || !service) return NO;
NSDictionary *query = @{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrAccount: account,
(__bridge id)kSecAttrService: service
};
OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);
if (status != errSecSuccess && status != errSecItemNotFound) {
NSLog(@"Failed to delete password. Status: %d", (int)status);
return NO;
}
return YES;
}
+ (BOOL)storeData:(NSData *)data forKey:(NSString *)key accessGroup:(NSString *)accessGroup {
if (!data || !key) return NO;
NSMutableDictionary *query = [@{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrAccount: key,
(__bridge id)kSecAttrService: @"SecureDataStorage",
(__bridge id)kSecValueData: data,
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly
} mutableCopy];
if (accessGroup) {
query[(__bridge id)kSecAttrAccessGroup] = accessGroup;
}
// 删除现有项目
[self deleteDataForKey:key accessGroup:accessGroup];
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
if (status != errSecSuccess) {
NSLog(@"Failed to store data. Status: %d", (int)status);
return NO;
}
return YES;
}
+ (NSData *)retrieveDataForKey:(NSString *)key accessGroup:(NSString *)accessGroup {
if (!key) return nil;
NSMutableDictionary *query = [@{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrAccount: key,
(__bridge id)kSecAttrService: @"SecureDataStorage",
(__bridge id)kSecReturnData: @YES,
(__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne
} mutableCopy];
if (accessGroup) {
query[(__bridge id)kSecAttrAccessGroup] = accessGroup;
}
CFDataRef data = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&data);
if (status != errSecSuccess) {
if (status != errSecItemNotFound) {
NSLog(@"Failed to retrieve data. Status: %d", (int)status);
}
return nil;
}
return (__bridge_transfer NSData *)data;
}
+ (BOOL)deleteDataForKey:(NSString *)key accessGroup:(NSString *)accessGroup {
if (!key) return NO;
NSMutableDictionary *query = [@{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrAccount: key,
(__bridge id)kSecAttrService: @"SecureDataStorage"
} mutableCopy];
if (accessGroup) {
query[(__bridge id)kSecAttrAccessGroup] = accessGroup;
}
OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);
if (status != errSecSuccess && status != errSecItemNotFound) {
NSLog(@"Failed to delete data. Status: %d", (int)status);
return NO;
}
return YES;
}
+ (NSArray<NSDictionary *> *)allKeychainItems {
NSDictionary *query = @{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecReturnAttributes: @YES,
(__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitAll
};
CFArrayRef items = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&items);
if (status != errSecSuccess) {
if (status != errSecItemNotFound) {
NSLog(@"Failed to retrieve keychain items. Status: %d", (int)status);
}
return @[];
}
return (__bridge_transfer NSArray *)items;
}
+ (BOOL)clearAllKeychainItems {
NSDictionary *query = @{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword
};
OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);
if (status != errSecSuccess && status != errSecItemNotFound) {
NSLog(@"Failed to clear keychain items. Status: %d", (int)status);
return NO;
}
return YES;
}
@end
上一篇 下一篇