前段时间由于工作需要,接触到了App安全相关的冰山一角,在此整理一下以备后续需要。
通常为了提升用户的体验,我们会在本地缓存一些数据如:缓存访问令牌、用户信息来实现一段时间内免登陆功能。接下来我们来讨论如何提高这些本地缓存数据的安全性。
加密算法
首先了解一下加密算法,常见的加密算法可以分成三类,对称密钥加密、公开密钥加密、散列函数。
对称密钥加密
对称密钥加密又称为对称加密、私钥加密、共享密钥加密,是密码学中的一类加密算法。这类算法在加密和解密时使用相同的密钥,或是使用两个可以简单地相互推算的密钥。与公开密钥加密相比,要求双方获取相同的密钥是对称密钥加密的主要缺点之一。
常见的对称加密算法有:DES、3DES、AES、Blowfish、IDEA、RC5、RC6。
对称加密的速度比公钥加密快很多,在很多场合都需要对称加密。
公开密钥加密
公开密钥加密,也称为非对称加密,是密码学的一种算法,它需要两个密钥,一个是公开密钥,另一个是私有密钥;一个用作加密的时候,另一个则用作解密。
使用最广泛的是RSA算法。
公开密钥加密的速度比对称密钥速度慢。
散列函数
散列函数(Hash function)又称散列算法、哈希函数,是一种从任何一种数据中创建小的数字“指纹”的方法。散列函数把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来。该函数将数据打乱混合,重新创建一个叫做散列值(hash values,hash codes,hash sums,或hashes)的指纹。散列值通常用一个短的随机字母和数字组成的字符串来代表。
目前流行的 Hash 算法包括 MD5、SHA-1 和 SHA-2
哈希算法生成的密文不可逆,因此可以用来判断数据的完整性。
存储加密
为了不明文存储数据,我们需要选择一种加密算法,将明文加密后存储,在需要的时候再读取密文解密。由于哈希算法不可逆,排除,由于不需要将缓存的数据共享到服务器,并且非对称加密算法加密解密速度慢,因此在项目中我选用了对称加密AES来加密缓存的数据。
使用AES加密缓存的数据,重要的是保存好密钥,目前我使用的办法是将密钥分成4部分隐藏在项目中。
// 获取分散存储的密钥
+ (NSString *)getAESKey {
NSString *key = [NSString stringWithFormat:@"%@%@%@%@", TDKeyA, TDKeyB, TDKeyC, TDKeyD];
return key;
}
// AES加密
+ (NSData *)aes128EncryptData:(NSData *)data {
if (data == nil) {
return nil;
}
NSString *key = [self getAESKey];
char keyPtr[kCCKeySizeAES128+1];
memset(keyPtr, 0, sizeof(keyPtr));
[key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
NSUInteger dataLength = [data length];
size_t bufferSize = dataLength + kCCBlockSizeAES128;
void *buffer = malloc(bufferSize);
size_t numBytesEncrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,
kCCAlgorithmAES128,
kCCOptionPKCS7Padding|kCCOptionECBMode,
keyPtr,
kCCBlockSizeAES128,
NULL,
[data bytes],
dataLength,
buffer,
bufferSize,
&numBytesEncrypted);
if (cryptStatus == kCCSuccess) {
NSData *resultData = [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
return resultData;
}
free(buffer);
return nil;
}
// AES解密
+ (NSData *)aes128DecryptData:(NSData *)data {
NSString *key = [self getAESKey];
char keyPtr[kCCKeySizeAES128 + 1];
memset(keyPtr, 0, sizeof(keyPtr));
[key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
NSUInteger dataLength = [data length];
size_t bufferSize = dataLength + kCCBlockSizeAES128;
void *buffer = malloc(bufferSize);
size_t numBytesCrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt,
kCCAlgorithmAES128,
kCCOptionPKCS7Padding|kCCOptionECBMode,
keyPtr,
kCCBlockSizeAES128,
NULL,
[data bytes],
dataLength,
buffer,
bufferSize,
&numBytesCrypted);
if (cryptStatus == kCCSuccess) {
NSData *resultData = [NSData dataWithBytesNoCopy:buffer length:numBytesCrypted];
return resultData;
}
free(buffer);
return nil;
}
别忘记在文件开头导入#import <CommonCrypto/CommonDigest.h>
数据完整性验证
想要检查你的文件数据是否被篡改,这时候哈希算法就可以登场啦,由于哈希算法生成文件的唯一标志信息是与文件的每一个字节都相关,并且不可逆。因此只要你的文件被篡改,那么生成的这个唯一标志信息也会跟随改变。这里我使用了MD5算法:
+ (NSString *)md5FromString:(NSString *)string {
const char *cStr = [string UTF8String];
unsigned char result[CC_MD5_DIGEST_LENGTH];
CC_MD5(cStr, strlen(cStr), result);
return [[NSString stringWithFormat:@"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
result[0], result[1], result[2], result[3],
result[4], result[5], result[6], result[7],
result[8], result[9], result[10], result[11],
result[12], result[13], result[14], result[15]
] lowercaseString];
}
别忘记在文件开头导入#import <CommonCrypto/CommonDigest.h>
总结
为了保证项目中缓存数据的完整性以及密文存储,我做了以下处理:
- 存储文件
- 通过MD5算法获取文件数据的散列值并存储(以便后续检查存储的文件数据的完整性)。
- 将该文件数据通过AES加密并存储加密后的密文。
- 获取文件数据
- 读取存储的密文数据并进行AES解密得到明文数据。
- 验证文件数据完整性
- 通过MD5算法获取解密后的数据的散列值hashA。
- 读取存储的散列值hashB。
- 将存储的散列值hashB与hashA比较,如果相同,则文件数据未被篡改。