MST

星途 面试题库

面试题:Objective-C中Touch ID与Face ID身份验证的安全优化及与服务器交互

在Objective-C使用Touch ID与Face ID进行身份验证时,从安全角度出发,如何对本地存储的身份验证相关数据进行加密处理?另外,如果需要与服务器交互验证结果,阐述如何设计一个安全可靠的交互流程,防止数据被窃取或篡改。
20.7万 热度难度
编程语言Objective-C

知识考点

AI 面试

面试题答案

一键面试

本地存储数据加密处理

  1. 使用系统加密框架
    • 在iOS开发中,可以使用CommonCrypto框架(在较新系统中也可使用CryptoKit)。例如,使用CommonCrypto中的AES(高级加密标准)算法对本地存储的身份验证相关数据进行加密。示例代码如下(以AES - 256加密为例):
    #import <CommonCrypto/CommonCryptor.h>
    #import <CommonCrypto/CommonKeyDerivation.h>
    #import <Security/Security.h>
    #import <UIKit/UIKit.h>
    #import <Foundation/Foundation.h>
    
    NSData *AESEncrypt(NSData *plainText, NSString *key) {
        char keyPtr[kCCKeySizeAES256 + 1];
        bzero(keyPtr, sizeof(keyPtr));
        [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
    
        NSUInteger dataLength = [plainText length];
        size_t bufferSize = dataLength + kCCBlockSizeAES128;
        void *buffer = malloc(bufferSize);
    
        size_t numBytesEncrypted = 0;
        CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
                                             keyPtr, kCCKeySizeAES256,
                                             NULL /* initialization vector (optional) */,
                                             [plainText bytes], dataLength,
                                             buffer, bufferSize,
                                             &numBytesEncrypted);
        if (cryptStatus == kCCSuccess) {
            return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
        }
        free(buffer);
        return nil;
    }
    
    • 使用CryptoKit框架(iOS 13+),以AES - GCM加密为例:
    #import <CryptoKit/CryptoKit.h>
    NSData *AESEncryptWithCryptoKit(NSData *plainText, NSString *key) {
        let keyData = key.data(using:.utf8)!
        let symetricKey = SymmetricKey(data: keyData)
        let sealedBox = try! AES.GCM.seal(plainText as! Data, using: symetricKey)
        return sealedBox.combined as NSData
    }
    
  2. 密钥管理
    • 密钥不应硬编码在代码中。可以使用iOS的钥匙串(Keychain)来安全存储密钥。例如,使用SecItemAdd等函数将密钥存储到钥匙串中。
    NSDictionary *keychainQuery = @{(id)kSecClass: (id)kSecClassKey,
                                    (id)kSecAttrKeyType: (id)kSecAttrKeyTypeAES,
                                    (id)kSecAttrKeySizeInBits: @256,
                                    (id)kSecValueData: keyData,
                                    (id)kSecAttrApplicationTag: @"com.example.app.key"};
    OSStatus status = SecItemAdd((__bridge CFDictionaryRef)keychainQuery, NULL);
    
    • 读取密钥时使用SecItemCopyMatching函数。
    NSDictionary *keychainQuery = @{(id)kSecClass: (id)kSecClassKey,
                                    (id)kSecAttrKeyType: (id)kSecAttrKeyTypeAES,
                                    (id)kSecAttrApplicationTag: @"com.example.app.key",
                                    (id)kSecReturnData: (id)kCFBooleanTrue};
    CFTypeRef result = NULL;
    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)keychainQuery, &result);
    if (status == errSecSuccess) {
        NSData *keyData = (__bridge_transfer NSData *)result;
    }
    

与服务器交互验证结果的安全流程设计

  1. 使用HTTPS
    • 与服务器交互验证结果时,应使用HTTPS协议。这通过TLS(传输层安全协议)对数据进行加密,防止数据在传输过程中被窃取或篡改。在iOS中,可以使用NSURLSession来发起HTTPS请求。
    NSURL *url = [NSURL URLWithString:@"https://example.com/api/verify"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    [request setHTTPMethod:@"POST"];
    // 设置请求体,包含验证结果等数据
    [request setHTTPBody:postData];
    NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        // 处理响应
    }];
    [task resume];
    
  2. 数据签名
    • 在客户端,使用本地私钥对验证结果数据进行签名。可以使用Security框架中的函数来进行签名操作。
    // 从钥匙串获取私钥
    NSDictionary *privateKeyQuery = @{(id)kSecClass: (id)kSecClassKey,
                                      (id)kSecAttrKeyType: (id)kSecAttrKeyTypeRSA,
                                      (id)kSecAttrApplicationTag: @"com.example.app.privateKey",
                                      (id)kSecReturnRef: (id)kCFBooleanTrue};
    CFTypeRef privateKeyRef = NULL;
    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)privateKeyQuery, &privateKeyRef);
    if (status == errSecSuccess) {
        SecKeyRef privateKey = (__bridge SecKeyRef)privateKeyRef;
        uint8_t signature[256];
        size_t signatureLen = sizeof(signature);
        status = SecKeyRawSign(privateKey, kSecPaddingPKCS1SHA256, [data bytes], (size_t)[data length], signature, &signatureLen);
        if (status == errSecSuccess) {
            NSData *signatureData = [NSData dataWithBytes:signature length:signatureLen];
            // 将签名数据与验证结果一起发送到服务器
        }
    }
    
    • 在服务器端,使用对应的公钥对签名进行验证,确保数据未被篡改。
  3. 令牌(Token)机制
    • 服务器在验证成功后,返回一个包含用户身份信息的令牌(如JWT - JSON Web Token)。客户端在后续与服务器的交互中,将该令牌包含在请求头中。
    • 服务器每次接收到请求时,验证令牌的有效性,例如验证签名、过期时间等。这样可以减少频繁使用身份验证(Touch ID / Face ID)的次数,同时保证交互的安全性。例如,在iOS中设置请求头:
    [request setValue:[NSString stringWithFormat:@"Bearer %@", token] forHTTPHeaderField:@"Authorization"];
    
  4. 防重放攻击
    • 可以在请求中添加时间戳和随机数(Nonce)。客户端生成一个随机数,并附上当前时间戳一起发送给服务器。
    • 服务器记录已接收的随机数和时间戳,对于重复的随机数或超时的时间戳请求进行拒绝,防止攻击者重放之前的有效请求。例如,在客户端生成随机数和时间戳:
    NSUUID *nonce = [NSUUID UUID];
    NSDate *now = [NSDate date];
    NSNumber *timestamp = @((int)[now timeIntervalSince1970]);
    // 将nonce和timestamp添加到请求体或请求头中发送到服务器
    
    • 在服务器端验证:
    # Python示例代码,验证时间戳和随机数
    import time
    received_nonce = request.json.get('nonce')
    received_timestamp = request.json.get('timestamp')
    current_time = int(time.time())
    if received_timestamp < current_time - 60 or received_nonce in used_nonces:
        return jsonify({'error': 'Replay attack detected'}), 403
    used_nonces.add(received_nonce)