【问题标题】:AES Encryption for an NSString on the iPhoneiPhone 上 NSString 的 AES 加密
【发布时间】:2010-11-26 21:07:42
【问题描述】:

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

我已经尝试过the first comment on this post 中详述的技术,但到目前为止我还没有运气。 Apple 的 CryptoExercise 肯定有一些东西,但我无法理解它......我看到很多对 CCCrypt 的引用,但在我使用它的每一个案例中都失败了。

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

【问题讨论】:

  • 请注意,我奖励了 Rob Napier 的回答,他提供了安全版本的回答。

标签: iphone objective-c encryption nsstring aes


【解决方案1】:
Please use the below mentioned URL to encrypt string using AES excryption with 
key and IV values.

https://github.com/muneebahmad/AESiOSObjC

【讨论】:

    【解决方案2】:

    我在@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'ing一个缓冲区然后dataWithBytesNoCopy只需分配一个NSMutableDatadataWithLength并使用mutableBytes属性作为字节指针,然后通过设置它的length属性来调整大小。 3. 直接使用字符串加密是很不安全的,应该使用派生密钥,比如PBKDF2创建的。
    • @zaph,你能在某处做一个 pastebin/pastie 以便我看到变化吗?顺便说一句,在上面的代码中,我只是修改了我从 Quinn Taylor 那里看到的代码以使其工作。我仍在学习这项业务,您的意见对我非常有用。
    • 看到这个SO answer,它甚至有最小的错误处理并且处理加密和解密。解密时无需扩展缓冲区,如果没有什么可获取的,它只是更少的代码而不是额外的特殊代码。如果需要使用空值扩展密钥(不应该这样做),只需创建密钥的可变版本并设置长度:keyData.length = kCCKeySizeAES256;
    • 请参阅SO answer,了解如何使用 PBKDF2 从字符串创建密钥。
    • @Volomike 如果我使用它,那么我应该在 iTunes-Connect 上选择 Export Compliance Information (YES) 吗?
    【解决方案3】:

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

    RNCryptor 使用 PBKDF2 和 salt,提供随机 IV,并附加 HMAC(也是从 PBKDF2 生成的,带有自己的 salt。它支持同步和异步操作。

    【讨论】:

    • 有趣的代码,可能值得加分。 PBKDF2 的迭代次数是多少?您计算 HMAC 的依据是什么?我假设只是加密数据?我无法在提供的文档中轻松找到。
    • 查看“最佳实践安全”了解详情。我建议在 iOS 上进行 10k 次迭代(在 iPhone 4 上约为 80 毫秒)。是的,比 HMAC 加密。我今晚可能会查看“数据格式”页面,以确保它在 v2.0 上是最新的(主要文档是最新的,但我不记得我是否修改了数据格式页面)。跨度>
    • 啊,是的,在文档中找到了轮数并查看了代码。我在那里看到了清理功能和单独的 HMAC 和加密密钥。如果时间允许,我明天会尝试更深入地研究。那我给分吧。
    • 加密为 NSData,并使用众多 Base64 编码器之一将其转换为字符串。如果没有数据到字符串的编码器,就无法从字符串加密到字符串。
    • @Jack 根据我的律师的建议(他用非常生动的语言描述了我在出口合规法方面缺乏专业知识……),我不再提供有关出口合规法的建议。您需要与您的律师讨论。
    【解决方案4】:

    我已经为 NSData 和 NSString 收集了一组类别,这些类别使用​​了 Quinn Taylor 在 Stack Overflow 上的 Jeff LaMarche's blogsome hints 上找到的解决方案。

    它使用类别来扩展 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 位密钥,而不是由用户提供。
    • Jeff LaMarche 链接已损坏
    • @michael - 请您指导我stackoverflow.com/questions/63632975/… 谢谢
    【解决方案5】:

    由于您尚未发布任何代码,因此很难确切知道您遇到了哪些问题。但是,您链接到的博客文章似乎工作得相当不错......除了每次调用 CCCrypt() 时的额外逗号会导致编译错误。

    稍后对该帖子的评论包括this adapted code,这对我有用,而且看起来更简单一些。如果您将他们的代码包含在 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。


    编辑:请参阅this follow-up question,了解使用 Base64 编码将加密的数据字节表示为字符串(如果需要)使用安全、无损的转换。

    【讨论】:

    • 谢谢。 CommonCrypto 是 iPhone 上核心操作系统的一部分,我也在运行 10.6。
    • 我做了-1,因为引用的代码非常不安全。请查看 Rob Napier 的答案。他的博客文章“robnapier.net/aes-commoncrypto 详细说明了为什么这是不安全的。
    • 此解决方案不适用于我的情况。我有一个要解码的字符串: U2FsdGVkX1+MEhsbofUNj58m+8tu9ifAKRiY/Zf8YIw= 并且我有密钥: 3841b8485cd155d932a2d601b8cee2ec 。我无法使用您的解决方案中的密钥解密字符串。谢谢
    • 此解决方案不适用于 El Capitan 上使用 XCode7 的 Cocoa 应用程序。 ARC 禁止autorelease
    • @QuinnTaylor 我可以编辑这个答案,但想让你有机会在你认为合适的时候改变它。 I repaired your code here。此外,您可能想指出,如果没有that adapted code,它将无法编译。所以,我用 XCode7 让它在 El Capitan 上的 Cocoa 应用程序上工作。现在我要做的是弄清楚如何对这些数据进行 Base64Encode/Base64Decode 以便它可以传输而不会在传输过程中受到干扰,而不是返回原始数据。
    猜你喜欢
    • 2011-02-18
    • 2011-12-10
    • 2011-04-28
    • 2015-10-31
    • 1970-01-01
    • 2012-10-26
    • 1970-01-01
    • 2011-12-01
    相关资源
    最近更新 更多