RNCryptor
跨语言 AES 加密器/解密器 数据格式。
主要目标是 Swift 和 Objective-C,但还有 C、C++、C#、Erlang、Go、Haskell、Java、PHP、Python、JavaScript 和 Ruby 的实现。
数据格式包括所有用于安全实现 AES 加密的所需元数据,如“使用 CommonCrypto 正确加密 AES”和iOS 6 编程:突破极限第 15 章所述。具体来说,它包括:
- AES-256 加密
- CBC 模式
- 使用 PBKDF2 进行密码拉伸
- 密码加盐
- 随机 IV
- 先加密后哈希 HMAC
内容
格式与实现
RNCryptor 数据格式是跨平台的,并且有许多不同的实现。名为 "RNCryptor" 的框架是 Swift 和 Objective-C 的一个特定实现。它们都有版本号。当前的数据格式是 v3。当前框架实现(读取 v3 格式)的版本是 v4。
基本密码使用方法
// Encryption
let data: NSData = ...
let password = "Secret password"
let ciphertext = RNCryptor.encrypt(data: data, withPassword: password)
// Decryption
do {
let originalData = try RNCryptor.decrypt(data: ciphertext, withPassword: password)
// ...
} catch {
print(error)
}
增量使用
RNCryptor 支持增量使用,例如与 NSURLSession
一起使用。这对加密或解密数据无法轻松放入内存的情况也很有用。
要在增量模式下运行,您需要创建一个 Encryptor
或 Decryptor
,多次调用 updateWithData()
聚集其结果,然后调用 finalData()
并聚集其结果。
//
// Encryption
//
let password = "Secret password"
let encryptor = RNCryptor.Encryptor(password: password)
let ciphertext = NSMutableData()
// ... Each time data comes in, update the encryptor and accumulate some ciphertext ...
ciphertext.appendData(encryptor.updateWithData(data))
// ... When data is done, finish up ...
ciphertext.appendData(encryptor.finalData())
//
// Decryption
//
let password = "Secret password"
let decryptor = RNCryptor.Decryptor(password: password)
let plaintext = NSMutableData()
// ... Each time data comes in, update the decryptor and accumulate some plaintext ...
try plaintext.appendData(decryptor.updateWithData(data))
// ... When data is done, finish up ...
try plaintext.appendData(decryptor.finalData())
导入 Swift
大多数 RNCryptor 符号都位于 RNCryptor
命名空间内。
安装
要求
RNCryptor 5 使用 Swift 3 编写,并不桥接到 Objective-C(它包含了Objective-C中没有的功能)。如果您需要Objective-C的实现,请查看 RNCryptor-objc。该版本可以从 Swift 访问,或者两个版本可以存在于同一个项目中。
桥接头
CommonCrypto 不是模块化头文件(而且苹果建议它可能永远也不会是)。这使得将其导入 Swift 变得非常具有挑战性。为了解决这个问题,必要的头文件已经被复制到 RNCryptor.h
中,这需要将其桥接进 Swift。你可以这样做,既可以用 RNCryptor 作为框架,将 #import "RNCryptor/RNCryptor.h"
添加到你的现有桥接头文件中,或者将 RNCryptor/RNCryptor.h
作为你在构建设置中的桥接头文件,“Objective-C 桥接头”。
手动安装
使用 RNCryptor 的一种最简单的方式是将其作为项目的一部分进行安装,而不是作为框架。RNCryptor 只是一个 Swift 文件和一个桥接头文件,这样你就可以跳过管理框架的所有复杂性。如果你使用子模块或将这些特定版本的 RNCryptor 检入到你的仓库中,这也使得版本控制非常简单。
此过程适用于大多数目标:iOS 和 OS X GUI 应用程序、Swift 框架和 OS X 命令行应用程序。它不适用于 ObjC 框架或可能被导入到 ObjC 中的框架,因为如果其他框架包含 RNCryptor,则会导致符号重复。
- 将
RNCryptor/RNCryptor.swift
和RNCryptor.h
拖放到你的项目 - 如果你已经有了桥接头文件,添加
#import "RNCryptor.h"
(或你复制的RNCryptor.h
的路径)。 - 如果你没有桥接头文件
- Swift 项目:在你的目标的构建设置中,将“Objective-C 桥接头”设置为
RNCryptor.h
的路径。(或者创建一个桥接头文件并遵循上面的说明。) - ObjC 项目:Xcode 会询问你是否想创建一个桥接头。允许它创建,并将
添加到头文件(或你复制的
RNCryptor.h
的路径)
- Swift 项目:在你的目标的构建设置中,将“Objective-C 桥接头”设置为
- 要从 Swift 访问 RNCryptor,你不需要导入任何内容。它只是你模块的一部分。
- 要从 ObjC 访问 RNCryptor,导入你的 Swift 头文件(modulename-Swift.h)。例如:
#import "MyApp-Swift.h"
。
以这种方式构建时,你不需要(也不能)在代码中导入 import RNCryptor
。RNCryptor 将成为你模块的一部分。
Carthage
github "RNCryptor/RNCryptor" ~> 5.0
这种方法不适用于 OS X 命令行应用程序。别忘了嵌入 RNCryptor.framework
。
以这种方式构建时,你应该在 Objective-C 或 Swift 代码中添加 ①import RNCryptor;
。
这种方法不适用于 OS X 命令行应用程序。
CocoaPods
pod 'RNCryptor', '~> 5.0'
这种方法不适用于 OS X 命令行应用程序。
以这种方式构建时,你应该在你的 Swift 代码中添加 import RNCryptor
。
高级用法
版本特定的密码器
默认的 RNCryptor.Encryptor
是数据格式的“当前版本”(目前为v3)。如果您与其他实现进行交互,您可能需要选择特定的格式以实现兼容性。
要创建版本固定的密码器,请使用 RNCryptor.EncryptorV3
和 RNCryptor.DecryptorV3
。
请注意:此处指定的版本是 格式版本,而不是实现版本。v4 RNCryptor 框架读取和写入v3 RNCryptor数据格式。
基于密钥的加密
正确使用基于密钥的加密需要一定的专业知识,并且很容易创建看似安全但不安全的系统。最重要的规则是密钥在其所有字节上必须随机。如果您不熟悉基本密码学概念,如AES-CBC、IV和HMAC,您可能最好避免使用基于密钥的加密。
密码学使用密钥,密钥是特定长度的随机字节序列。RNCryptor v3格式使用两个256位(32字节)的密钥来进行加密和验证。
密码不是一个“特定长度的随机字节序列”。它们根本不是随机的,并且可以有各种各样的长度,很少是正好32个字节。RNCryptor定义了一种特定且安全的方法将密码转换为密钥,这是它的主要功能之一。
偶尔有直接使用随机密钥的理由。将密码转换为密钥是故意慢的(几十毫秒)。密码加密消息也比密钥加密消息长16字节。如果您系统加密和解密许多短消息,这可能会造成显著的性能影响,尤其是在服务器上。
RNCryptor支持直接基于密钥的加密和解密。密钥的大小和数量在不同格式版本之间可能不同,因此基于密钥的密码器是 特定于版本的。
为了安全,密钥必须是一个随机的字节序列。有关如何使用密码仅创建随机字节序列的说明,请参阅 将密码转换为密钥。
let encryptor = RNCryptor.EncryptorV3(encryptionKey: encryptKey, hmacKey: hmacKey)
let decryptor = RNCryptor.DecryptorV3(encryptionKey: encryptKey, hmacKey: hmacKey)
常见问题解答
我如何检测错误的密码?
如果您用错误的密码进行解密,您将收到一个 HMACMismatch
错误。这与您销毁密文时收到的错误相同。
v3数据格式没有直接检测错误密码的方法。它只会解密乱码,然后使用HMAC(一种加密散列)来确定结果已损坏。您只能在调用finalData()
时完成整个消息的解密后才能发现这一点。
如果用户用错误的密码解密一个非常大的文件,这可能会给用户带来不便。如果您有这种情况,建议使用相同的密码加密一些已知的小块数据。在解密较大密文之前,在小块密文中测试密码。
v4数据格式将为验证密码或密钥提供更快速和更实用的机制。
什么是“HMAC错误”?(错误代码1)
参见上一问题。要么您的数据被破坏了,要么您使用了错误的密码。
如果您(在密码正确的情况下)犯的最常见错误就是您错误理解了在将数据传输到或从服务器过程中 Base64编码 的工作方式。如果您有一个类似于 "YXR0YWNrIGF0IGRhd24=" 的字符串,这并不是 "数据"。这是一个字符串。它很可能是Base64编码的,这是一种将数据转换为字符串的机制。某些语言(JavaScript、PHP)有在数据与Base64字符串之间隐式转换的习惯,这既令人困惑又容易出错(也是许多此类问题的根源)。简单规则:如果在你的终端上可以打印出来而不会崩溃,那么它就不是加密数据。
如果您使用 dataUsingEncoding()
将Base64编码的字符串转换为数据,对于RNCryptor来说,您将得到乱码。您需要使用 init?(base64EncodedData:options:)
。根据iOS侧或服务器端的选项,空格和新行可能很重要。您需要验证从加密器出来的字节与进入解密器的字节完全相同。
我能否使用RNCryptor读取和写入非RNCryptor数据格式?
编号 RNCryptor 实现了一种特定的数据格式。它不是一个通用加密库。如果您创建了自己的数据格式,您将需要编写特定的代码来处理您所创建的内容。请确保您所发明的数据格式是安全的。(这比听起来要难得多。)
如果您正在使用 OpenSSL 加密格式,请参阅 RNOpenSSLCryptor。
我能否修改所使用的参数(算法、迭代次数等)?
不行。见前一个问题。v4格式将允许对PBKDF2迭代次数进行一些控制,但v3格式唯一可配置的是是否使用密码或密钥。这使RNCryptor实现更加简单且具有互操作性。
我如何手动设置初始化向量(IV)?
您不需要。见最后两个问题。
请注意,如果您不时重用密钥+IV组合,则攻击者可能会解密您的消息的开头。静态IV使密钥+IV的重用可能性大大增加(如果您还有静态密钥,则保证)。维基百科对这个问题的概述非常快:初始化向量概述。
我如何加密/解密字符串?
AES 加密字节。它不加密字符、字母、单词、图片、视频、猫或无聊。它只加密字节。您需要以一致的方式将其他内容(如字符串)转换为字节并将字节转换回其他内容。有几种方法可以做到这一点。最受欢迎的包括UTF-8编码、Base-64编码和十六进制编码。还有许多其他选择。RNCryptor没有良好的方法来猜测您想要的编码,所以它不尝试这样做。它以 NSData
形式接受和返回字节。
要将字符串转换为UTF-8数据,请使用 dataUsingEncoding()
和 init(data:encoding:)
。要将字符串转换为Base-64数据,请使用 init(base64EncodedString:options:)
和 base64EncodedStringWithOptions()
。
Does RNCryptor support random access decryption?
This use case is usually when encrypting media files such as videos. RNCryptor uses CBC encryption, which prevents easy random access. Although other modes are better for random access (CTR for instance), they are more difficult to implement correctly, and CommonCrypto does not support their random access usage anyway.
It would be relatively easy to create a wrapper around RNCryptor that allows random access to blocks of some fixed size (say 64k), and this might work well for videos with modest overhead (see inferno for a similar idea in C#). Such a format would be easy to port to other platforms that already support RNCryptor.
If there is interest, I may eventually build this as a separate framework.
See also Issue #161 for a more detailed discussion of this topic.
Design Considerations
RNCryptor
has several design goals, in order of importance
Easy to use correctly for most common use cases
The most critical concern is that it be easy for non-experts to use RNCryptor
correctly. A framework that is more secure but requires a steep learning curve for the developer will either not be used or used incorrectly. Whenever possible, a single line of code should "do the right thing" for the most common cases.
This also requires that it fail correctly and provide good errors.
Reliance on CommonCryptor functionality
RNCryptor
has very little "security" code. It relies as much as possible on the OS-provided CommonCryptor. If a feature does not exist in CommonCryptor, then it generally will not be provided in RNCryptor
.
最佳安全实践
在上述限制范围内,尽可能地应用最佳算法。这意味着AES-256、HMAC+SHA256和PBKDF2。(注意,其中一些决定对于v3版本来说是合理的,但可能对于v4版本会有所改变。)
-
AES-256。虽然Bruce Schneier对由于AES-256的某些攻击建议迁移到AES-128,但我的当前想法与Colin Percival的观点一致。PBKDF2输出是近乎随机的,这应该可以消除针对我们感兴趣的使用案例的相关密钥攻击。
-
AES-CBC模式。这是一个有些复杂的决策,但CBC的普遍性使得其他考虑因素相形见绌。CBC模式没有出现重大问题,以nonce为基础的模式如CTR也有其他权衡。请参阅“RNCryptor模式变更”获取有关此决策的更多详细信息。
-
加密后再进行MAC。如果iOS上有好的认证AES模式(例如GCM),我可能会Because its simplicity。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
的输出格式。
许可协议
除非在源代码中有其他说明,否则本代码的许可协议为 MIT 许可证。
在此特此授予任何获得本软件及其相关文档文件(以下简称“软件”)副本的任何人免费使用该软件的权利,不受限制地处理该软件,包括但不限于使用、复制、修改、合并、发布、分发、再许可和/或出售软件的副本,并允许将软件提供给其他人进行上述操作,但应遵守以下条件:
上述版权声明和本许可声明应包含在软件的所有副本或主要部分中。
本软件按现状提供,不提供任何类型的保证,无论是明示的还是暗示的,包括但不限于适销性、适用于特定目的和非侵权性保证。在任何情况下,作者或版权所有者不对因合同、侵权或其他行为而产生的任何索赔、损害或其他责任负责,前提是这种索赔、损害或其他责任源于、产生于或与该软件或使用或操作该软件有关。