RNCryptor-objc 3.0.6

RNCryptor-objc 3.0.6

测试已测试
语言语言 Objective-CObjective C
许可证 未知
发布最后发布2018年5月

Rob Napier维护。



  • 作者:
  • Rob Napier

RNCryptor

跨语言 AES 加密/解密 数据格式

主要目标是 Objective-C,但实现也适用于 C++C#JavaPHPPythonJavaScriptRuby

数据格式包含所有安全实现 AES 加密所需的元数据,如 "Properly encrypting with AES with CommonCrypto,"iOS 6 Programming Pushing the Limits 中的第 15 章所述。具体来说,它包括:

  • AES-256 加密
  • CBC 模式
  • 使用 PBKDF2 进行密码拉伸
  • 密码盐值
  • 随机 IV
  • 加密后哈希 HMAC

基本 Objective-C 使用

最常见的内存使用情况如下

NSData *data = [@"Data" dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSData *encryptedData = [RNEncryptor encryptData:data
                                   	withSettings:kRNCryptorAES256Settings
                                          password:aPassword
                                             error:&error];

这会生成一个包含标题、加密盐、HMAC 盐、IV、密文和 HMAC 的 NSData。要解密此数据包

NSData *decryptedData = [RNDecryptor decryptData:encryptedData
                                    withPassword:aPassword
                                           error:&error];

请注意,RNDecryptor 不需要设置。这些设置从标题中读取。

支持异步使用,特别设计用于配合 NSURLConnection 使用。这在加密或解密数据无法轻松适应内存的情况下也非常有用。如果数据可以轻松适应内存,异步操作最好使用 dispatch_async() 实现。

异步使用

RNCryptor 支持异步使用,特别是在与 NSURLConnection 协作时。这也适用于加密或解密的数据不易存入内存的情况。如果数据可以轻松存储在内存中,最好的异步操作方法是使用 dispatch_async()。

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
  [self.encryptedData addData:[self.cryptor addData:data]];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
  [self.cryptor finish];
}

// Other connection delegates

- (void)decryptionDidFinish {
  if (self.cryptor.error) {
    // An error occurred. You cannot trust encryptedData at this point
  }
  else {
    // self.encryptedData is complete. Use it as you like
  }
  self.encryptedData = nil;
  self.cryptor = nil;
  self.connection = nil;
}

- (void)decryptRequest:(NSURLRequest *)request {
  self.encryptedData = [NSMutableData data];
  self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
  self.cryptor = [[RNDecryptor alloc] initWithPassword:self.password
                                               handler:^(RNCryptor *cryptor, NSData *data) {
                                                   [self.decryptedData appendData:data];
                                                   if (cryptor.isFinished) {
                                                     [self decryptionDidFinish];
                                                   }
                                                 }];
}

异步与流

在流上执行异步操作时,数据可能会非常快地出现(尤其是从本地文件读取时)。如果你以愚笨的方式使用 RNCryptor,工作块可能会比引擎处理得更快,你的内存使用率会飙升。如果只有一个核心,例如 iPad 1,这种情况尤其严重。解决方案是仅在先前工作块完成后调度新的工作块。

// Make sure that this number is larger than the header + 1 block.
// 33+16 bytes = 49 bytes. So it shouldn't be a problem.
int blockSize = 32 * 1024;

NSInputStream *cryptedStream = [NSInputStream inputStreamWithFileAtPath:@"C++ Spec.pdf"];
NSOutputStream *decryptedStream = [NSOutputStream outputStreamToFileAtPath:@"/tmp/C++.crypt" append:NO];

[cryptedStream open];
[decryptedStream open];

// We don't need to keep making new NSData objects. We can just use one repeatedly.
__block NSMutableData *data = [NSMutableData dataWithLength:blockSize];
__block RNEncryptor *decryptor = nil;

dispatch_block_t readStreamBlock = ^{
  [data setLength:blockSize];
  NSInteger bytesRead = [cryptedStream read:[data mutableBytes] maxLength:blockSize];
  if (bytesRead < 0) {
    // Throw an error
  }
  else if (bytesRead == 0) {
    [decryptor finish];
  }
  else {
    [data setLength:bytesRead];
    [decryptor addData:data];
    NSLog(@"Sent %ld bytes to decryptor", (unsigned long)bytesRead);
  }
};

decryptor = [[RNEncryptor alloc] initWithSettings:kRNCryptorAES256Settings
                                         password:@"blah"
                                          handler:^(RNCryptor *cryptor, NSData *data) {
                                            NSLog(@"Decryptor recevied %ld bytes", (unsigned long)data.length);
                                            [decryptedStream write:data.bytes maxLength:data.length];
                                            if (cryptor.isFinished) {
                                              [decryptedStream close];
                                              // call my delegate that I'm finished with decrypting
                                            }
                                            else {
                                              // Might want to put this in a dispatch_async(), but I don't think you need it.
                                              readStreamBlock();
                                            }
                                          }];

// Read the first block to kick things off    
readStreamBlock();

我将最终将这些内容集成到 API 中以简化操作。更多讨论见 Cyrille 在 StackOverflow 上的问题。欢迎拉取请求。

构建

作为静态库提供,但源文件可以放入任何项目中。不需要 OpenSSL 文件。

需要 Security.framework

支持 10.6+ 和 iOS 4+。

当前文件格式为 3。要读取 v1 文件(见 Issue #44),需要设置编译时宏 QNCRYPTOR_ALLOW_V1_BAD_HMAC。不再可能写入 v1 文件。

设计考虑

RNCryptor 有几个设计目标,按重要性排序

易于使用并正确应用于大多数常见用例

最重要的关注点是它能简单易用地被非专业人士正确使用RNCryptor。一个安全性能更好,但需要开发者深入学习框架要么不会被使用,要么使用不正确。在可能的情况下,一行代码应该能在最常见情况下“做正确的事情”。

这也要求它能够正确失败并且提供良好的错误信息。

依赖CommonCryptor功能

RNCryptor的“安全”代码非常少。尽可能多地依赖于操作系统提供的CommonCryptor。如果一个特性在CommonCryptor中不存在,那么在RNCryptor中通常也不会提供。

最佳实践安全

在上述限制范围内,尽量使用最佳算法。这意味着AES-256、HMAC+SHA1和PBKDF2。

  • AES-256。虽然Bruce Schneier就转向AES-128提出了某些有趣的建议,因为对AES-256有某些攻击,但我的当前思路与Colin Percival的观点一致。PBKDF2输出实际上是随机的,这应该可以抵消相关密钥攻击对我们感兴趣的用例。

  • AES-CBC模式。这是一个相当复杂的决定,但CBC的普遍性超过了其他考虑因素。CBC模式没有重大问题,并且基于nonce的模式(如CTR)有其他的权衡。更多关于这个决定的细节,请参阅“RNCryptor模式变更”

  • 加密后再MAC。如果iOS上有好的认证AES模式(例如GCM),我可能会使用它,因为它的简单性。Colin Percival为手编加密后再MAC而不是使用认证AES模式提出了很好的论据,但在RNCryptor中管理HMAC实际上增加了相当多的复杂性。我宁愿将复杂性放在更广泛同行评审的层,如CommonCryptor,而不是在RNCryptor层。但是,这并不是一个选项,所以我回到自己的加密后再MAC。

  • HMAC+SHA256。这里没有惊喜。

  • PBKDF2。虽然bcrypt和scrypt可能比PBKDF2更安全,但CommonCryptor只支持PBKDF2。《NIST也继续推荐PBKDF2。我们使用10k轮PBKDF2,这在iPhone 4上大约是80ms。

代码简洁性

RNCryptor 的目标是以最简单的方式实现,避免复杂的代码。它旨在易于阅读和代码审查。

性能

性能是一个目标,但不是最重要的目标。代码必须安全且易于使用。在这个前提下,它尽可能快,并且内存效率尽可能高。

可移植性

在不牺牲其他目标的情况下,最好在其他平台上读取 RNCryptor 的输出格式。

版本历史

  • v2.2 切换到文件格式 v3 以处理问题 #77。
  • v2.1 切换到文件格式 v2 以处理问题 #44。
  • v2.0 添加了异步模式。
  • v2.1 将 RNCryptor 迁移到较旧的 Mac OS X 版本(以及可能 iOS)。

许可证

除非源代码中明确指出,此代码受 MIT 许可证许可

Permission is hereby granted, free of charge, to any person obtaining a 
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
 
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

部分代码,已在源中指明,受以下许可协议许可

/*-
 * Copyright (c) 2008 Damien Bergamini <[email protected]>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

部分代码,已在源中指明,受 APSL 许可证许可

/*
 * Copyright (c) 2006-2010 Apple Inc. All Rights Reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_LICENSE_HEADER_END@
 */