iPhone上NSString的AES加密


124

谁能指出我正确的方向,以便能够加密一个字符串,并返回另一个带有加密数据的字符串?(我一直在尝试使用AES256加密。)我想编写一个方法,该方法需要两个NSString实例,一个实例是要加密的消息,另一个是要使用其加密的“密码”-我怀疑我必须生成如果密码随加密数据一起提供,则可以用相反的方式将密码和密码一起加密。然后,该方法应返回根据加密数据创建的NSString。

我已经尝试了这篇文章的第一条评论中详细介绍的技术,但是到目前为止我还没有运气。苹果的CryptoExercise肯定有一些东西,但是我无法理解……我已经看到很多CCCrypt的引用,但是在我使用过的每种情况下都失败了。

我还必须能够解密加密的字符串,但我希望它像kCCEncrypt / kCCDecrypt一样简单。


1
请注意,我已经赏识了Rob Napier提供的安全版本的答案。
Maarten Bodewes,2012年

Answers:


126

由于您尚未发布任何代码,因此很难确切地知道您遇到了哪些问题。但是,您链接到的博客文章的确运行得相当不错……除了每次调用CCCrypt()中引起逗号错误的逗号之外。

后来对该帖子的评论包括此改编的代码,该代码对我有用,并且看起来更简单。如果将他们的代码包括在NSData类别中,则可以编写如下代码:(注:printf()调用仅用于说明数据在各个点上的状态-在实际应用程序中,打印此类值没有任何意义)

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    NSString *key = @"my password";
    NSString *secret = @"text to encrypt";

    NSData *plain = [secret dataUsingEncoding:NSUTF8StringEncoding];
    NSData *cipher = [plain AES256EncryptWithKey:key];
    printf("%s\n", [[cipher description] UTF8String]);

    plain = [cipher AES256DecryptWithKey:key];
    printf("%s\n", [[plain description] UTF8String]);
    printf("%s\n", [[[NSString alloc] initWithData:plain encoding:NSUTF8StringEncoding] UTF8String]);

    [pool drain];
    return 0;
}

有了这段代码,并且加密的数据不能总是很好地转换为NSString的事实,编写两种包装所需功能的方法可能更方便,即正向和反向...

- (NSData*) encryptString:(NSString*)plaintext withKey:(NSString*)key {
    return [[plaintext dataUsingEncoding:NSUTF8StringEncoding] AES256EncryptWithKey:key];
}

- (NSString*) decryptData:(NSData*)ciphertext withKey:(NSString*)key {
    return [[[NSString alloc] initWithData:[ciphertext AES256DecryptWithKey:key]
                                  encoding:NSUTF8StringEncoding] autorelease];
}

这绝对适用于Snow Leopard,@ Boz报告CommonCrypto是iPhone核心操作系统的一部分。10.4和10.5都有/usr/include/CommonCrypto,尽管10.5的手册页CCCryptor.3cc没有10.4 的手册页,所以YMMV。


编辑:有关使用Base64编码使用安全无损转换将加密的数据字节表示为字符串(如果需要)的信息,请参见以下后续问题


1
谢谢。CommonCrypto是iPhone上核心操作系统的一部分,我也正在运行10.6。
博兹2009年

1
我做了-1,因为引用的代码很危险地不安全。请看Rob Napier的答案。他的博客文章” robnapier.net/aes-commoncrypto细节究竟为什么这是不安全的。
埃里克Engheim

1
在我的情况下,此解决方案不起作用。我有一个要解码的字符串:U2FsdGVkX1 + MEhsbofUNj58m + 8tu9ifAKRiY / Zf8YIw =,并且我有密钥:3841b8485cd155d932a2d601b8cee2ec。我无法在您的解决方案中使用密钥解密字符串。谢谢
乔治

此解决方案在El Capitan上使用XCode7的Cocoa应用程序中不起作用。ARC禁止使用autorelease
Volomike '16

@QuinnTaylor我可以编辑此答案,但想让您有机会根据自己的喜好进行更改。我在这里修复了您的代码。另外,您可能要指出的是,没有经过修改的代码,它将无法编译。因此,我使用XCode7在El Capitan上的Cocoa应用程序上工作。现在,我想做的是弄清楚如何对这些数据进行Base64Encode / Base64Decode,以使其可以传输而不被传输干扰,而不是返回原始数据。
Volomike '16

46

我整理了NSData和NSString的类别集合,这些类别使用​​了Jeff LaMarche博客中找到的解决方案以及Quinn Taylor在Stack Overflow上的一些提示

它使用类别扩展NSData以提供AES256加密,还提供NSString的扩展以将BASE64编码的加密数据安全地编码为字符串。

这是显示加密字符串的用法的示例:

NSString *plainString = @"This string will be encrypted";
NSString *key = @"YourEncryptionKey"; // should be provided by a user

NSLog( @"Original String: %@", plainString );

NSString *encryptedString = [plainString AES256EncryptWithKey:key];
NSLog( @"Encrypted String: %@", encryptedString );

NSLog( @"Decrypted String: %@", [encryptedString AES256DecryptWithKey:key] );

在此处获取完整的源代码:

https://gist.github.com/838614

感谢所有有用的提示!

-迈克尔


NSString * key = @“ YourEncryptionKey”; //应该由用户提供。我们可以生成一个随机的安全256位密钥,而不是由用户提供吗。
Pranav Jaiswal '04年

杰夫·
拉玛什

35

@owlstead,关于您对“给定答案之一的加密安全变体”的请求,请参阅RNCryptor。它旨在完全满足您的要求(并针对此处列出的代码问题而构建)。

RNCryptor将PBKDF2与盐结合使用,提供随机IV,并将HMAC(也由PBKDF2生成的具有自己的盐)附加。它支持同步和异步操作。


有趣的代码,也许值得。PBKDF2的迭代计数是多少,您如何计算HMAC?我想只是加密的数据吗?我无法在提供的文档中轻松找到它。
Maarten Bodewes 2012年

有关详细信息,请参见“最佳实践安全性”。我建议在iOS上进行1万次迭代(在iPhone 4上约为80毫秒)。是的,比HMAC加密。我今晚可能会查看“数据格式”页面,以确保它在v2.0上是最新的(主要文档是最新的,但是我不记得是否修改了数据格式页面)。
罗布·纳皮尔

嗯,是的,找到了文档中的回合数并查看了代码。我看到清理功能,并在那里分离了HMAC和加密密钥。如果时间允许,我明天将尝试做更深入的了解。然后,我将分配分数。
Maarten Bodewes 2012年

5
加密为NSData,并使用许多Base64编码器之一将其转换为字符串。没有数据到字符串编码器,就无法从字符串加密到字符串。
罗布·纳皮尔

1
@Jack在我的律师的建议下(他以极其丰富的措辞描述了我在出口合规法律方面的专业知识不足……),我不再提供有关出口合规法律的建议。您需要与您的律师讨论。
罗布·纳皮尔

12

我在@QuinnTaylor上稍等了一下,以更新他的答案,但是由于他没有,所以这里的答案更加清楚了,并且将在XCode7(甚至更大)上加载。我在Cocoa应用程序中使用了此功能,但它也可以在iOS应用程序中正常工作。没有ARC错误。

粘贴到AppDelegate.m或AppDelegate.mm文件中的任何@implementation节之前。

#import <CommonCrypto/CommonCryptor.h>

@implementation NSData (AES256)

- (NSData *)AES256EncryptWithKey:(NSString *)key {
    // 'key' should be 32 bytes for AES256, will be null-padded otherwise
    char keyPtr[kCCKeySizeAES256+1]; // room for terminator (unused)
    bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding)

    // fetch key data
    [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];

    NSUInteger dataLength = [self length];

    //See the doc: For block ciphers, the output size will always be less than or 
    //equal to the input size plus the size of one block.
    //That's why we need to add the size of one block here
    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) */,
                                     [self bytes], dataLength, /* input */
                                     buffer, bufferSize, /* output */
                                     &numBytesEncrypted);
    if (cryptStatus == kCCSuccess) {
        //the returned NSData takes ownership of the buffer and will free it on deallocation
        return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
    }

    free(buffer); //free the buffer;
    return nil;
}

- (NSData *)AES256DecryptWithKey:(NSString *)key {
    // 'key' should be 32 bytes for AES256, will be null-padded otherwise
    char keyPtr[kCCKeySizeAES256+1]; // room for terminator (unused)
    bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding)

    // fetch key data
    [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];

    NSUInteger dataLength = [self length];

    //See the doc: For block ciphers, the output size will always be less than or 
    //equal to the input size plus the size of one block.
    //That's why we need to add the size of one block here
    size_t bufferSize = dataLength + kCCBlockSizeAES128;
    void *buffer = malloc(bufferSize);

    size_t numBytesDecrypted = 0;
    CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
                                     keyPtr, kCCKeySizeAES256,
                                     NULL /* initialization vector (optional) */,
                                     [self bytes], dataLength, /* input */
                                     buffer, bufferSize, /* output */
                                     &numBytesDecrypted);

    if (cryptStatus == kCCSuccess) {
        //the returned NSData takes ownership of the buffer and will free it on deallocation
        return [NSData dataWithBytesNoCopy:buffer length:numBytesDecrypted];
    }

    free(buffer); //free the buffer;
    return nil;
}

@end

将这两个函数粘贴到所需的@implementation类中。就我而言,我在AppDelegate.mm或AppDelegate.m文件中选择了@implementation AppDelegate。

- (NSString *) encryptString:(NSString*)plaintext withKey:(NSString*)key {
    NSData *data = [[plaintext dataUsingEncoding:NSUTF8StringEncoding] AES256EncryptWithKey:key];
    return [data base64EncodedStringWithOptions:kNilOptions];
}

- (NSString *) decryptString:(NSString *)ciphertext withKey:(NSString*)key {
    NSData *data = [[NSData alloc] initWithBase64EncodedString:ciphertext options:kNilOptions];
    return [[NSString alloc] initWithData:[data AES256DecryptWithKey:key] encoding:NSUTF8StringEncoding];
}

注意:1.进行填充时(PKCS#7),解密时输出大小将小于输入大小。没有理由增加bufferSize,仅使用加密的数据大小即可。2.而不是malloc dataWithBytesNoCopy分配缓冲区,而是分配一个NSMutableDatawith dataWithLength并将其mutableBytes用于字节指针,然后通过设置其length属性来调整大小。3.直接使用字符串进行加密非常不安全,应该使用派生密钥,例如PBKDF2创建的密钥。
zaph '16

@zaph,您可以在某个地方做一个pastebin / pastie,以便我可以看到更改吗?顺便说一句,在上面的代码中,我只是改编了我从奎因·泰勒(Quinn Taylor)看到的代码以使其工作。我仍在继续学习这项业务,您的意见对我非常有用。
Volomike '16

看到这样的答案,它甚至具有最小的错误处理,并且可以处理加密和解密。无需在解密时扩展缓冲区,如果只有很少的收获,则不需要专门的附加代码就更少了。如果需要使用null扩展键(不应该这样做),只需创建一个可变版本的键并设置长度:keyData.length = kCCKeySizeAES256;
zaph

有关使用PBKDF2从字符串创建密钥的信息,请参见此SO答案
zaph

@Volomike如果使用此功能,则应 在iTunes-Connect上选择“ 导出合规信息”(YES)吗?
杰克

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.