【译】如何在 Swift 中使用 CommonCrypto 类进行加密

原文连接:Cryptography in Swift with CommonCrypto 原文日期:2015/08/10
译者:CMB 校对:numbbbbb 定稿:shanks

现在,许多开发者已经不需要在 App 中进行加密处理。即使你在远程服务器上使用了 REST API,通常情况下使用 HTTPS 就可以解决大多数的安全通信问题,剩下的问题可以使用苹果提供的“保护模式”和硬件/软件加密组合方式来解决。然而在很多情况下,你还是需要对通信或文件进行加密。也许你正在把一个现有的涉及到文件/信息加密的方案移植到 iOS 上,也许你在制作一个保密性要求极高的App,或者你只是想提高数据的安全级别(这是一件好事)。

无论是哪种情况,(在iOS和OS X系统中)Cocoa 都选择 CommonCrypto 来完成任务。然而 CommonCrypto 的 API 使用的仍然是老旧的C风格(C-Style)。这种 API 已经过时了,在 Swift 中用它们非常别扭。此外,在 Swift 中用强类型属性处理 CCCrypt 中不同类型的数据(对称式加密框架的主要加密/解密功能)很不优雅。我们先来看一下 CCCrypt 的定义:

CCCrypt(op: CCOperation, alg: CCAlgorithm, 
            options: CCOptions, 
            key: UnsafePointer<Void>, 
            keyLength: Int, 
            iv: UnsafePointer<Void>, 
            dataIn: UnsafePointer<Void>, 
            dataInLength: Int, 
            dataOut: UnsafeMutablePointer<Void>, 
            dataOutAvailable: Int, 
            dataOutMoved: UnsafeMutablePointer<Int>)

再来看看 Objective-C(更准确来说是 C 版本的)函数声明:

CCCryptorStatus CCCrypt(
 CCOperation op,          // operation: kCCEncrypt or kCCDecrypt
 CCAlgorithm alg,         // algorithm: kCCAlgorithmAES128... 
 CCOptions options,       // operation: kCCOptionPKCS7Padding...
 const void *key,         // key
 size_t keyLength,        // key length
 const void *iv,          // initialization vector (optional)
 const void *dataIn,      // input data
 size_t dataInLength,     // input data length
 void *dataOut,           // output data buffer
 size_t dataOutAvailable, // output data length available
 size_t *dataOutMoved)    // real output data length generated

在 Objective-C 中,可以简单地使用预定义常量(比如“kCCAlgorithm3DES”)来定义这些参数,然后传入不同的数组和大小,完全不必担心它们的确切类型(给 size_t 参数传入 int 变量,或者给 void* 参数传入 char* 变量)。这不是最好的做法,但确实可以完成任务(只需要进行一些类型转换)。

但是 Swift 剔除了 Objective-C 中属于 C 的部分,因此我们需要做一些准备工作才能在 Swift 和 Cocoa 中使用 CommonCrypto。

操作(Operation)、算法(Algorithm)和选项(Options)

在 App 中对称编码是最简单的一种发送和接收加密数据的方法。这种方法只有一个密钥,它用于加密和解密操作(非对称加密则不同,它通常使用一对公-私密钥)。对称密码有许多不同的算法,所有的算法都可以有不同的设置。三个主要概念是:操作(加密/解密)、算法(DES,AES,RC4……)和设置,对应 CommonCrypto 的 CCOperation、CCAgorithm 和 CCOptions。

CCOperation、CCAgorithm 和 CCOptions 本质上就是 uint32_t(一个占32位存储的 unsigned int),所以我们可以通过 CommonCrypto 常量来构造它们:

let operation = CCOperation(kCCEncrypt)
let algorithm = CCAlgorithm(kCCAlgorithmAES)
let options = CCOptions(kCCOptionPKCS7Padding | kCCOptionECBMode)

Unsafe 指针

Swift 抽象出 Unsafe 指针来对应 C 语言的指针(C-Pointers)。Swift 试图把所有的指针和 C 风格的内存管理器都抽象出来。通常来说你不需要使用它们,除非你需要使用旧式(old-style)API(比如 CommonCrypto)。如果你真的如此不幸,那就需要学习如何处理它们:

在 Swift 中有两种类型的指针:UnsafePointers 和 UnsafeMutablePointers 类型。第一个用于常量寄存器,内存空间上的指针是恒定不变的;第二个用于可变的内存空间。对应到 C 语言,UnsafePointer 类型是"const type *"缓冲类型,UnsafeMutablePointer 是"type *"缓冲类型(这里的"缓冲"一词只是过去习惯的叫法)。指针的具体类型写在声明之后的<>中,所以如果你想去声明一个"void *"类型的指针,需要写成:UnsafeMutablePointer<Void>。如果要声明"const unsigned char *"缓冲类型的指针,你需要使用:UnsafePointer<UInt8>。虽然苹果确实提供了纯 C 类型到 Swift 类型的转换,但是一定要注意,CChar、CInt、CUnsignedLongLong…这样的类型不能直接用在 UnsafePointers 中,需要使用原生的 Swift 类型。这就出现一个问题,到底什么时候能用这些类型呢?我们需要深入一下 Swift 的类型定义:

typealias CShort = Int16
typealias CSignedChar = Int8
typealias CUnsignedChar = UInt8
typealias CUnsignedInt = UInt32
typealias CUnsignedLong = UInt
typealias CUnsignedLongLong = UInt64
typealias CUnsignedShort = UInt16

值得庆幸的是我们不需要实现 UnsafePointers 和 UnsafeMutablePointers 类型的内存管理(只要你使用的是类似 NSData 这样的 Cocoa 对象)。Swift 会自动管理(和桥接)它们。如果你需要加密/解密数据并把密钥存到 NSData 中,那就可以调用data.bytes或者data.mutableBytes来获取对应的 UnsafePointer 和 UnsafeMutablePointer 指针。

另一种得到 UnsafePointer 变量的方式是&。处理输出变量时(需要内存的地址)就是通过&符号得到 Int 类型的 Unsafe(Mutable)Pointer<Int>。我们可以在 CCCrypt 中使用这种方法把"Int"变量地址传给最后一个参数 :"dataOutMoved" 。注意:let 定义的变量对应 UnsafePointer<Type> 类型,var 变量对应 UnsafeMutablePointer<Type> 类型。

现在,我们已经拥有了调用 CCCrypt 所需的所有元素。

桥接

CommonCrypto 还没有兼容 Swift,所以为了使用它,我们需要通过头文件导入 Objective-C 形式的 CommonCrypto。

#import <CommonCrypto/CommonCrypto.h>

SymmetricCryptor类

最近我需要做对称加密的项目,为了更容易的加密和解密数据,我建了一个SymmetricCryptor类(不要在意这个可怕的名字)。它可以把数据转换成恰当的 CommonCrypto 类型中。你可以使用它来方便的加密或解密数据。

let sc = SymmetricCryptor(algorithm: .AES128, options: CCOptions(kCCOptionPKCS7Padding))
cypher.setRandomIV()
do { let cypherText = try sc.crypt(string: clearText, key: key) } catch { print("Error while encrypting: \(error)") }

CommonCrypto 提供了多种算法和设置,不过我只想解决最常见的加密问题,因此简化了配置。比如说,使用 RC4 的时候,你可以使用 40 或者 128 位的密钥(对应的常量是 RC4_40 和 RC4_128)。同理,AES 也有一些常用的常量(128b、256b……)。因此我定义了一个名为SymmetricCryptorAlgorithm的枚举变量,里面定了许多常见的配置(比如 AES 256),不仅包含算法,还包含很多其他信息,比如密钥长度和块大小。

在 SymmetricCryptor 的 GitHub 页面中,你可以看到一个对称加密/解密示例,它展示了如何简单地实现对称加密/解密。

我会继续介绍非对称加密技术和公私密钥对,如果感兴趣请继续关注我。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容