前言
iOS系统由于其封闭性其安全系数要比安卓高不少,但是依然有插件可以捕捉到iOS端敏感数据,特别是以plist文件形式进行存储的数据,Plist 文件主要用于存储用户设置及 App 的配置信息,但 App 可能使用 Plist 文件存储明文的用户名、密码 或其它一些个人敏感信息。而保存在 Plist 文件中的二进制格式文件数据则可以使用 Plist 文件编辑器(如 plutil)进行查看或修改,即使在一个没有越狱的设备上,plist 文件内容也可以通过工具 iExplorer 获取。对 于以编码、未加密或弱加密形式存储的敏感信息就可能会导致敏感信息泄露了。这就需要对一些敏感信息做一些本地加密存储和解密读取,其实,对于用户的一些敏感信息,建议最好使用归档解档的方式做本地数据持久化。
简介
最近项目由一家专业的测试公司做了次渗透测试,测试出的主要是数据安全问题,对于攻击者,从iOS端获取到敏感数据主要有以下三种方式:
- 恶意程序
借助iOS系统的安全弱点,攻击者可以设计出一种远程偷取iPhone上文件的恶意程序。
备份
当iPhone连接至iTunes后,如果iPhone信任了所连接的电脑后,iTunes将自动对设备上的所有数据进行备份。通过备份,敏感数据也将会保存到电脑上。因此,攻击者如果可以接触到那台电脑,则可以通过备份文件读取到敏感信息。
物理接触
用户iPhone的丢失或被盗非常常见。在这两种情形下,攻击者都将可以物理接触到设备,并读取设备上存储的敏感信息。
所以,对本地持久化的数据加密显得尤为重要,加密方式有很多种:MD5、AES(ECB/CBC)、RSA等,根据不同需求选择加密方法,和后台交互过程的加密可以和后台自行商定。
加密类
-
.h文件
#import <Foundation/Foundation.h> /// 加密工具类 /// 提供RSA & AES加密方法 @interface CryptorTools : NSObject #pragma mark - AES 加密/解密 /// AES 加密 /// /// @param data 要加密的二进制数据 /// @param keyString 加密密钥 /// @param iv IV向量 /// /// @return 加密后的二进制数据 + (NSData *)AESEncryptData:(NSData *)data keyString:(NSString *)keyString iv:(NSData *)iv; /// AES 加密字符串 // /// @param string 要加密的字符串 /// @param keyString 加密密钥 /// @param iv IV向量 /// /// @return 加密后的 BASE64 编码字符串 + (NSString *)AESEncryptString:(NSString *)string keyString:(NSString *)keyString iv:(NSData *)iv; /// AES 解密 /// /// @param data 要解密的二进制数据 /// @param keyString 解密密钥 /// @param iv IV向量 /// /// @return 解密后的二进制数据 + (NSData *)AESDecryptData:(NSData *)data keyString:(NSString *)keyString iv:(NSData *)iv; /// AES 解密 / // /// @param string 要解密的 BASE64 编码字符串 / // @param keyString 解密密钥 /// @param iv IV向量 /// /// @return 解密后的二进制数据 + (NSString *)AESDecryptString:(NSString *)string keyString:(NSString *)keyString iv:(NSData *)iv; #pragma mark - RSA 加密/解密算法 /// 加载公钥 /// /// @param filePath DER 公钥文件路径 - (void)loadPublicKeyWithFilePath:(NSString *)filePath; /// 加载私钥 /// /// @param filePath P12 私钥文件路径 /// @param password P12 密码 - (void)loadPrivateKey:(NSString *)filePath password:(NSString *)password; /// RSA 加密数据 /// /// @param data 要加密的数据 /// /// @return 加密后的二进制数据 - (NSData *)RSAEncryptData:(NSData *)data; /// RSA 加密字符串 /// /// @param string 要加密的字符串 /// /// @return 加密后的 BASE64 编码字符串 - (NSString *)RSAEncryptString:(NSString *)string; /// RSA 解密数据 /// /// @param data 要解密的数据 /// /// @return 解密后的二进制数据 - (NSData *)RSADecryptData:(NSData *)data; /// RSA 解密字符串 /// /// @param string 要解密的 BASE64 编码字符串 /// /// @return 解密后的字符串 - (NSString *)RSADecryptString:(NSString *)string; @end
.m文件
// 填充模式 #define kTypeOfWrapPadding kSecPaddingPKCS1 @interface CryptorTools() { SecKeyRef _publicKeyRef; // 公钥引用 SecKeyRef _privateKeyRef; // 私钥引用 } @end @implementation CryptorTools #pragma mark - AES 加密/解密 #pragma mark 加密 + (NSData *)AESEncryptData:(NSData *)data keyString:(NSString *)keyString iv:(NSData *)iv { return [self AESData:data operation:kCCEncrypt keyString:keyString iv:iv]; } + (NSString *)AESEncryptString:(NSString *)string keyString:(NSString *)keyString iv:(NSData *)iv { NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding]; NSData *result = [self AESEncryptData:data keyString:keyString iv:iv]; // BASE 64 编码 return [result base64EncodedStringWithOptions:0]; } #pragma mark 解密 + (NSData *)AESDecryptData:(NSData *)data keyString:(NSString *)keyString iv:(NSData *)iv { return [self AESData:data operation:kCCDecrypt keyString:keyString iv:iv]; } + (NSString *)AESDecryptString:(NSString *)string keyString:(NSString *)keyString iv:(NSData *)iv { // BASE 64 解码 NSData *data = [[NSData alloc] initWithBase64EncodedString:string options:0]; NSData *result = [self AESDecryptData:data keyString:keyString iv:iv]; return [[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding]; } #pragma mark AES 加密&解密 + (NSData *)AESData:(NSData *)data operation:(CCOperation)operation keyString:(NSString *)keyString iv:(NSData *)iv { // 设置密钥 NSData *keyData = [keyString dataUsingEncoding:NSUTF8StringEncoding]; uint8_t cKey[kCCKeySizeAES128]; bzero(cKey, sizeof(cKey)); [keyData getBytes:cKey length:kCCKeySizeAES128]; // 设置 IV 向量 uint8_t cIv[kCCBlockSizeAES128]; bzero(cIv, kCCBlockSizeAES128); int option = kCCOptionPKCS7Padding | kCCOptionECBMode; if (iv) { [iv getBytes:cIv length:kCCBlockSizeAES128]; option = kCCOptionPKCS7Padding; } // 设置输出缓冲区 size_t bufferSize = [data length] + kCCBlockSizeAES128; void *buffer = malloc(bufferSize); // 加密或解密 size_t cryptorSize = 0; CCCryptorStatus cryptStatus = CCCrypt(operation, kCCAlgorithmAES, option, cKey, kCCKeySizeAES128, cIv, [data bytes], [data length], buffer, bufferSize, &cryptorSize); NSData *result = nil; if (cryptStatus == kCCSuccess) { result = [NSData dataWithBytesNoCopy:buffer length:cryptorSize]; } else { free(buffer); NSLog(@"[错误] 加密或解密失败 | 状态编码: %d", cryptStatus); } return result; } #pragma mark - RSA 加密/解密算法 - (void)loadPublicKeyWithFilePath:(NSString *)filePath; { NSAssert(filePath.length != 0, @"公钥路径为空"); // 删除当前公钥 if (_publicKeyRef) CFRelease(_publicKeyRef); // 从一个 DER 表示的证书创建一个证书对象 NSData *certificateData = [NSData dataWithContentsOfFile:filePath]; SecCertificateRef certificateRef = SecCertificateCreateWithData(kCFAllocatorDefault, (__bridge CFDataRef)certificateData); NSAssert(certificateRef != NULL, @"公钥文件错误"); // 返回一个默认 X509 策略的公钥对象,使用之后需要调用 CFRelease 释放 SecPolicyRef policyRef = SecPolicyCreateBasicX509(); // 包含信任管理信息的结构体 SecTrustRef trustRef; // 基于证书和策略创建一个信任管理对象 OSStatus status = SecTrustCreateWithCertificates(certificateRef, policyRef, &trustRef); NSAssert(status == errSecSuccess, @"创建信任管理对象失败"); // 信任结果 SecTrustResultType trustResult; // 评估指定证书和策略的信任管理是否有效 status = SecTrustEvaluate(trustRef, &trustResult); NSAssert(status == errSecSuccess, @"信任评估失败"); // 评估之后返回公钥子证书 _publicKeyRef = SecTrustCopyPublicKey(trustRef); NSAssert(_publicKeyRef != NULL, @"公钥创建失败"); if (certificateRef) CFRelease(certificateRef); if (policyRef) CFRelease(policyRef); if (trustRef) CFRelease(trustRef); } - (void)loadPrivateKey:(NSString *)filePath password: (NSString *)password { NSAssert(filePath.length != 0, @"私钥路径为空"); // 删除当前私钥 if (_privateKeyRef) CFRelease(_privateKeyRef); NSData *PKCS12Data = [NSData dataWithContentsOfFile:filePath]; CFDataRef inPKCS12Data = (__bridge CFDataRef)PKCS12Data; CFStringRef passwordRef = (__bridge CFStringRef)password; // 从 PKCS #12 证书中提取标示和证书 SecIdentityRef myIdentity; SecTrustRef myTrust; const void *keys[] = {kSecImportExportPassphrase}; const void *values[] = {passwordRef}; CFDictionaryRef optionsDictionary = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL); CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL); // 返回 PKCS #12 格式数据中的标示和证书 OSStatus status = SecPKCS12Import(inPKCS12Data, optionsDictionary, &items); if (status == noErr) { CFDictionaryRef myIdentityAndTrust = CFArrayGetValueAtIndex(items, 0); myIdentity = (SecIdentityRef)CFDictionaryGetValue(myIdentityAndTrust, kSecImportItemIdentity); myTrust = (SecTrustRef)CFDictionaryGetValue(myIdentityAndTrust, kSecImportItemTrust); } if (optionsDictionary) CFRelease(optionsDictionary); NSAssert(status == noErr, @"提取身份和信任失败"); SecTrustResultType trustResult; // 评估指定证书和策略的信任管理是否有效 status = SecTrustEvaluate(myTrust, &trustResult); NSAssert(status == errSecSuccess, @"信任评估失败"); // 提取私钥 status = SecIdentityCopyPrivateKey(myIdentity, &_privateKeyRef); NSAssert(status == errSecSuccess, @"私钥创建失败"); CFRelease(items); } - (NSString *)RSAEncryptString:(NSString *)string { NSData *cipher = [self RSAEncryptData:[string dataUsingEncoding:NSUTF8StringEncoding]]; return [cipher base64EncodedStringWithOptions:0]; } - (NSData *)RSAEncryptData:(NSData *)data { OSStatus sanityCheck = noErr; size_t cipherBufferSize = 0; size_t keyBufferSize = 0; NSAssert(data, @"明文数据为空"); NSAssert(_publicKeyRef, @"公钥为空"); NSData *cipher = nil; uint8_t *cipherBuffer = NULL; // 计算缓冲区大小 cipherBufferSize = SecKeyGetBlockSize(_publicKeyRef); keyBufferSize = data.length; if (kTypeOfWrapPadding == kSecPaddingNone) { NSAssert(keyBufferSize <= cipherBufferSize, @"加密内容 太大"); } else { NSAssert(keyBufferSize <= (cipherBufferSize - 11), @"加密内容太大"); } // 分配缓冲区 cipherBuffer = malloc(cipherBufferSize * sizeof(uint8_t)); memset((void *)cipherBuffer, 0x0, cipherBufferSize); // 使用公钥加密 sanityCheck = SecKeyEncrypt(_publicKeyRef, kTypeOfWrapPadding, (const uint8_t *)data.bytes, keyBufferSize, cipherBuffer, &cipherBufferSize ); NSAssert(sanityCheck == noErr, @"加密错误,OSStatus == %d", sanityCheck); // 生成密文数据 cipher = [NSData dataWithBytes:(const void *)cipherBuffer length:(NSUInteger)cipherBufferSize]; if (cipherBuffer) free(cipherBuffer); return cipher; } - (NSString *)RSADecryptString:(NSString *)string { NSData *keyData = [self RSADecryptData:[[NSData alloc] initWithBase64EncodedString:string options:0]]; return [[NSString alloc] initWithData:keyData encoding:NSUTF8StringEncoding]; } - (NSData *)RSADecryptData:(NSData *)data { OSStatus sanityCheck = noErr; size_t cipherBufferSize = 0; size_t keyBufferSize = 0; NSData *key = nil; uint8_t *keyBuffer = NULL; SecKeyRef privateKey = _privateKeyRef; NSAssert(privateKey != NULL, @"私钥不存在"); // 计算缓冲区大小 cipherBufferSize = SecKeyGetBlockSize(privateKey); keyBufferSize = data.length; NSAssert(keyBufferSize <= cipherBufferSize, @"解密内容太大"); // 分配缓冲区 keyBuffer = malloc(keyBufferSize * sizeof(uint8_t)); memset((void *)keyBuffer, 0x0, keyBufferSize); // 使用私钥解密 sanityCheck = SecKeyDecrypt(privateKey, kTypeOfWrapPadding, (const uint8_t *)data.bytes, cipherBufferSize, keyBuffer, &keyBufferSize ); NSAssert1(sanityCheck == noErr, @"解密错误,OSStatus == %d", sanityCheck); // 生成明文数据 key = [NSData dataWithBytes:(const void *)keyBuffer length:(NSUInteger)keyBufferSize]; if (keyBuffer) free(keyBuffer); return key; } @end
调用示例
//
// WKCryptorToolDemoVC.m
// -对称加密加密演练-
//
// Created by egs on 2017/9/26.
// Copyright © 2017年 恋guang年. All rights reserved.
//
#import "WKCryptorToolDemoVC.h"
#import "CryptorTools.h"
@interface UIViewController ()
@end
@implementation WKCryptorToolDemoVC
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:
(UIEvent *)event
{
//// 非对称加密
//创建加密工具对象
CryptorTools *tool = [[CryptorTools alloc]init];
//加密内容
NSString *content = @"i love you";
//加载公钥
[tool loadPublicKeyWithFilePath:[[NSBundle
mainBundle]pathForResource:@"rsacert.der" ofType:nil]];
//加密
NSString *encodeStr = [tool RSAEncryptString:content];
//加载私钥 password是导出P12文件的密码
[tool loadPrivateKey:[[NSBundle mainBundle]pathForResource:@"p.p12" ofType:nil] password:@"815476562"];
//解密
NSString *decondeStr = [tool RSADecryptString:encodeStr];
NSLog(@"RSA 加密结果 = %@ , 解密结果 =%@",encodeStr,decondeStr);
}
/**
* 对称加密 加密和解密用的同一把密钥
*/
- (void)test
{
//声明一个密钥
NSString *key = @"itcast";
//要加密的内容
NSString *content = @"i love you";
//ECB 加密和界面 电子密码本,就是每个块都是独立加密的 AES高级加密标准
NSString *encodeStr = [CryptorTools AESEncryptString:content keyString:key iv:nil];
NSString *decondeStr = [CryptorTools AESDecryptString:encodeStr keyString:key iv:nil];
NSLog(@" ----ECB加密结果 = %@ , 解密结果 = %@-----",encodeStr,decondeStr);
// CBC:密码块链,每个明文块的加密结果都会参与下一个块的加密,使用一个密钥和一个初始化向量对数据进行加密转换.开发中推荐使用CBC,ECB少用.
//声明iv
uint8_t iv[9] = {1,2,3,4,5,6,7,8,11};
//将iv转换成NSData
NSData *ivData = [NSData dataWithBytes:iv length:sizeof(iv)];
NSString *encryStr = [CryptorTools AESEncryptString:content keyString:key iv:ivData];
NSString *decryStr = [CryptorTools AESDecryptString:encryStr keyString:key iv:ivData];
NSLog(@" ----CBC加密结果 = %@ , 解密结果 = %@-----",encryStr,decryStr);
}
@end