[译]使用CommonCrypto对文字进行AES的加密

原文链接:点我跳转

译者注:由于需要写一个基于 SOCKS 5 协议的代理软件,所以免不了对 SOCKS 5 协议的加密方式有一定的了解,这篇文章就是译者在翻阅读物中找到的不错的文章,希望能和大家分享。
CommonCrypto 是一个 Mac 中自带的加密库,专门用来对相关的内容进行加密。

更新1:你现在可以从我的 github 下载文章中所有的源代码。

更新2:以下的内容,除非你想尝试去用你自己方式实现,否则你可以通过一个个很简单的方法,接入RNCryptor来自动的实现。

我翻阅了很多使用CCCrypt()的小例子,但是不幸的是,其中大多数都是错误的,在我完成了我将要出版的书的前10章的时候,我认为我需要需要写一篇短文来帮助人们来搞清楚这些东西。这就像小旋风一样,但是这只是一些简单的小例子。如果你想要更加清楚的其中的内容。你可以在今年晚些的时候阅读这本书来获得相关的内容.(微笑脸)

在这当中最主要也必须写在前面的,就这秘钥。因为这在互联网上搜索相关的内容的过程中,所找到的内容总是错的。因为人们输入的不是一个AES的秘钥,因为秘钥中的熵差总是实在是太小了。直接把它作为 AES 的秘钥会导致你受到各种各样的攻击。特别是按照下面的代码来设置秘钥,这都是错误的行为。

1
2
3
4
5
// DO NOT DO THIS
NSString *password = @"P4ssW0rd!";
char keyPtr[kCCKeySizeAES128];
[password getCString:keyPtr maxLength:sizeof( keyPtr ) encoding:NSUTF8StringEncoding];
// DO NOT DO THIS

这样设置秘钥容易收到很多攻击。同时他也不能应对一些频繁的变化。如果密码比秘钥还要长,那么加密后的密码将会被缩短。这就是为什么你需要设置一个正确的秘钥的重要性。

首先你得**你的秘钥,这意味着你需要将一个随机数据加入到秘钥中,因此如果你将相同的数据使用相同的密码进行加密,最终产生的结果也是不一样的。再加入随机数组后,你需要对秘钥进行哈希化,这样最终获得结果肯定能够获得对应的长度。而做这个最正确的方法就是使用PKCS #5(PBKDF2)不幸的是,在 Mac OS X 10.7 之前,没有一个简单的方法来加密。所以在下面的代码中,都会建立在 10.7 以后。但是如果你想要一个简单的解决方法。只要增加8个随机字符到字符串中,同时调用 hash 函数。在那之后再调用一次 hash 加密,这样重复 100,000 次。取秘钥中较低 “X” 位。虽然这不是很完美,但是这是一个简单的方法来编码实现它。在 MacBook Pro 的 Mac OS X 上面将花费 100ms,在相同时间的情况下 iPhone 4 上面大概只能实现 10,000 次。这个这目的只是为了让攻击者能够花费更多的时间来破解。

(如果你有更好的或者更加快速且简单的加密方法,请在下面留言)

但是就像我所说的,你不需要这么做,因为 PBKDF2 已经加入到 CommonCrypto(在iOS 10.7之后)。在进行混淆之后,你的密码和你秘钥的长度都会产生,先别着急写代码。在接下去的内容,我讲告诉你如何去进行编码。

我没提到过 CommonCrypto 是一个开源的么?所以如果你希望在其他平台上使用 PBKDF2,你可以通过源码来了解他的实现方式。

好了,现在你已经混淆了他,接下来我们要做什么?把密文保存下来,因为之后你可能需要对它进行解密。已经被混淆了的数据会被认为是公共信息,所以你不需要保护他。

大家可能会对神秘的初始化向量(IV)(前缀的字符串)感到迷惑。在 CBC模式 下每16个字节进行加密后会影响后面16个字节的加密。这很棒,这使得我们的加密更加安全有效。而且这种情况是默认的。但是其中的问题就是。对于开始的那几个字符,我们怎么处理?解决方案就是使用随机块-1,那就是添加前缀字符串。

这个初始化向量在 CCCrypt() 函数中被列为可选的。这又会让人感到费解,因为真正的 CBC模式中的这个值是不可选的。如果你没有提供这个选项。他将会使用全为0的向量给你。这将会对第一个数据块进行保护,但是没有这个必要这么做。因为前缀字符串只是个简单的16个随机字节。“将这16个字节保存下来,因为接下来的解密中,你将会需要这段内容。这些内容也考虑到了公共信息,所以你不需要对他采取保护措施。”

好了现在我们将会继续我们的加密,让我们来看下下面的小练习。首先,你需要知道如何使用它。这个方法将会返回已经加密的数据(如果失败,那么返回nil)同时返回对应的前缀字符串的数据,混淆之后的内容和加密错误的错误信息的引用。将所有数据,IV,混淆的内容用一种你觉得简单的方法写到文件中。其中 IV 已经被 AES 加密,混淆的内容的长度可能会不一样,但是我的代码中,我都会将设置为8个字节长,因为这是 PKCS#5 需要的最小长度。

1
2
3
4
5
6
7
8
NSData *iv;
NSData *salt;
NSError *error;
NSData *encryptedData = [RNCryptManager encryptedDataForData:plaintextData
password:password
iv:&iv
salt:&salt
error:&error];

然后接下来的代码。我将会把解密方法作为读者可以使用的一个练习,这些内容在一定意义上是共通的,你最好理解其中的代码,而不仅仅是简单的复制。不要忘记加入 Security.framework 这个库

接下来就是代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#import <CommonCrypto/CommonCryptor.h>
#import <CommonCrypto/CommonKeyDerivation.h>
NSString * const
kRNCryptManagerErrorDomain = @"net.robnapier.RNCryptManager";
const CCAlgorithm kAlgorithm = kCCAlgorithmAES128;
const NSUInteger kAlgorithmKeySize = kCCKeySizeAES128;
const NSUInteger kAlgorithmBlockSize = kCCBlockSizeAES128;
const NSUInteger kAlgorithmIVSize = kCCBlockSizeAES128;
const NSUInteger kPBKDFSaltSize = 8;
const NSUInteger kPBKDFRounds = 10000; // ~80ms on an iPhone 4
// ===================
+ (NSData *)encryptedDataForData:(NSData *)data
password:(NSString *)password
iv:(NSData **)iv
salt:(NSData **)salt
error:(NSError **)error {
NSAssert(iv, @"IV must not be NULL");
NSAssert(salt, @"salt must not be NULL");
*iv = [self randomDataOfLength:kAlgorithmIVSize];
*salt = [self randomDataOfLength:kPBKDFSaltSize];
NSData *key = [self AESKeyForPassword:password salt:*salt];
size_t outLength;
NSMutableData *
cipherData = [NSMutableData dataWithLength:data.length +
kAlgorithmBlockSize];
CCCryptorStatus
result = CCCrypt(kCCEncrypt, // operation
kAlgorithm, // Algorithm
kCCOptionPKCS7Padding, // options
key.bytes, // key
key.length, // keylength
(*iv).bytes,// iv
data.bytes, // dataIn
data.length, // dataInLength,
cipherData.mutableBytes, // dataOut
cipherData.length, // dataOutAvailable
&outLength); // dataOutMoved
if (result == kCCSuccess) {
cipherData.length = outLength;
}
else {
if (error) {
*error = [NSError errorWithDomain:kRNCryptManagerErrorDomain
code:result
userInfo:nil];
}
return nil;
}
return cipherData;
}
// ===================
+ (NSData *)randomDataOfLength:(size_t)length {
NSMutableData *data = [NSMutableData dataWithLength:length];
int result = SecRandomCopyBytes(kSecRandomDefault,
length,
data.mutableBytes);
NSAssert(result == 0, @"Unable to generate random bytes: %d",
errno);
return data;
}
// ===================
// Replace this with a 10,000 hash calls if you don't have CCKeyDerivationPBKDF
+ (NSData *)AESKeyForPassword:(NSString *)password
salt:(NSData *)salt {
NSMutableData *
derivedKey = [NSMutableData dataWithLength:kAlgorithmKeySize];
int
result = CCKeyDerivationPBKDF(kCCPBKDF2, // algorithm
password.UTF8String, // password
[password lengthOfBytesUsingEncoding:NSUTF8StringEncoding], // passwordLength
salt.bytes, // salt
salt.length, // saltLen
kCCPRFHmacAlgSHA1, // PRF
kPBKDFRounds, // rounds
derivedKey.mutableBytes, // derivedKey
derivedKey.length); // derivedKeyLen
// Do not log password here
NSAssert(result == kCCSuccess,
@"Unable to create AES key for password: %d", result);
return derivedKey;
}