Crypto 加密函数库
Perfect-Crypto 是一个基于OpenSSL的通用加密算法函数库。该函数库为以下加密工作提供高级编程接口:
- 消息摘要码(Digest)和哈希码(Hash)生成
- 加密(Cipher)编码和解码
- 根据指定字节长度生成随机数
同时本函数库还提供相关通用编码函数接口,特别是将二进制数据转化为可打印文本。
为了使用本函数库,请确定在源代码开始段导入import PerfectCrypto
.
编译
请在您的Package.swift文件中增加下列依存关系:
.Package(url: "https://github.com/PerfectlySoft/Perfect-Crypto.git", majorVersion: 3)
Linux 编译说明
请确保您的系统上已经安装了 libssl-dev 函数库
sudo apt-get install libssl-dev
概述
本函数库将OpenSSL的部分功能进行了封装并在Swift基本类型上进行了扩展,主要内容包括:
- 对于字符串、[UInt8] 和 UnsafeRawBufferPointer 指针增加了基本的编解码、摘要码和加密操作。
- 针对于非零结尾指针创建UTF-8字符串的方法
- 对OpenSSL BIO函数类的封装,提供可过滤的链式操作。
使用范例
16进制编解码
let testStr = "Hello, world!" guard let hexBytes = testStr.encode(.hex) else { return } String(validatingUTF8: hexBytes) == "48656c6c6f2c20776f726c6421" guard let unHex = hexBytes.decode(.hex) else { return } String(validatingUTF8: unHex) == testStr
Base 64 编解码
let testStr = "Hello, world!" guard let baseBytes = testStr.encode(.base64) else { return } String(validatingUTF8: baseBytes) == "SGVsbG8sIHdvcmxkIQ==" guard let unBase = baseBytes.decode(.base64) else { return } String(validatingUTF8: unBase) == testStr
摘要码
let testStr = "Hello, world!" let testAnswer = "315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3" guard let enc = testStr.digest(.sha256)?.encode(.hex) else { return } String(validatingUTF8: enc) == testAnswer
HMAC 签名和校验
下列代码用于 HMAC-SHA1 内容签名和 base64 编码,然后解码并校验。请根据需要自行调整.sha1 或者 .base64 算法:
let password = "用于测试的密码" let data = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" if let signed = data.sign(.sha1, key: HMACKey(password))?.encode(.base64), let base64Str = String(validatingUTF8: signed), let reRawData = base64Str.decode(.base64) { let verifyResult = data.verify(.sha1, signature: reRawData, key: HMACKey(password)) XCTAssert(verifyResult) } else { XCTAssert(false, "签名失败") }
API参考
public extension String { /// 从UTF8数组创建字符串,数组长度决定了转换内容长度;如果数据无效则字符串为空 init?(validatingUTF8 a: [UInt8]) /// 从指针构造字符串。指针可以不是零值结尾,而是由缓冲区长度决定转换内容长度 /// 输入内容无效则返回为空 init?(validatingUTF8 ptr: UnsafeRawBufferPointer?) /// 从字符串内获得缓冲区指针。 func withBufferPointer<Result>(_ body: (UnsafeRawBufferPointer) throws -> Result) rethrows -> Result } public extension String { /// 将字符串转换为指定编码类型的线性表。 func encode(_ encoding: Encoding) -> [UInt8]? /// 将字符串解码为制定编码类型的线性表。 func decode(_ encoding: Encoding) -> [UInt8]? /// 摘要计算 func digest(_ digest: Digest) -> [UInt8]? /// 根据算法和钥匙签署字符串并生成字节数组 func sign(_ digest: Digest, key: Key) -> [UInt8]? /// 根据字符串验证签名 /// 验证成功返回真,否则返回伪 func verify(_ digest: Digest, signature: [UInt8], key: Key) -> Bool /// 根据缓冲区密文、密码和盐对数据进行加密 /// 字符串的 UTF8 字符将被编码 /// 返回数据为CMS格式的PEM编码。 func encrypt(_ cipher: Cipher, password: String, salt: String, keyIterations: Int = 2048, keyDigest: Digest = .html5) -> String? /// 根据密码和盐进行CMS格式PEM编码数据解码 /// 解码结果必须为UTF8编码否则操作失败 func decrypt(_ cipher: Cipher, password: String, salt: String, keyIterations: Int = 2048, keyDigest: Digest = .html5) -> String? } public protocol Octal {} extension UInt8: Octal {} public extension Array where Element: Octal { /// 将数组转换为指定编码类型的线性表。 func encode(_ encoding: Encoding) -> [UInt8]? /// 将数组解码为制定编码类型的线性表。 func decode(_ encoding: Encoding) -> [UInt8]? /// 摘要计算 func digest(_ digest: Digest) -> [UInt8]? /// 根据算法和钥匙签署字符串并生成字节数组 func sign(_ digest: Digest, key: Key) -> [UInt8]? /// 根据字符串验证签名 /// 验证成功返回真,否则返回伪 func verify(_ digest: Digest, signature: [UInt8], key: Key) -> Bool /// 根据缓冲区密文、密码和盐对数据进行加密 /// 字符串的 UTF8 字符将被编码 /// 返回数据为CMS格式的PEM编码。 func encrypt(_ cipher: Cipher, password: String, salt: String, keyIterations: Int = 2048, keyDigest: Digest = .html5) -> String? /// 根据密码和盐进行CMS格式PEM编码数据解码 /// 解码结果必须为UTF8编码否则操作失败 func decrypt(_ cipher: Cipher, password: String, salt: String, keyIterations: Int = 2048, keyDigest: Digest = .html5) -> String? } public extension UnsafeRawBufferPointer { /// 使用缓冲区生成编码内容,返回结果使用完后必须自行释放 func encode(_ encoding: Encoding) -> UnsafeMutableRawBufferPointer? /// 使用缓冲区生成解码内容,返回结果使用完后必须自行释放 func decode(_ encoding: Encoding) -> UnsafeMutableRawBufferPointer? /// 生成摘要内容,生成结果必须手工释放 func digest(_ digest: Digest) -> UnsafeMutableRawBufferPointer? /// 根据算法和钥匙签署字符串并生成字节数组 /// 返回结果必须由用户自行释放内存 func sign(_ digest: Digest, key: Key) -> UnsafeMutableRawBufferPointer? /// 根据数据验证签名 /// 验证成功返回真,否则返回伪 func verify(_ digest: Digest, signature: UnsafeRawBufferPointer, key: Key) -> Bool /// 根据密文、密码和iv(初始化向量)对数据进行加密 /// 生成结果必须手工释放 func encrypt(_ cipher: Cipher, key: UnsafeRawBufferPointer, iv: UnsafeRawBufferPointer) -> UnsafeMutableRawBufferPointer? /// 根据密文、密码和iv(初始化向量)对数据进行解密 /// 生成结果必须手工释放 func decrypt(_ cipher: Cipher, key: UnsafeRawBufferPointer, iv: UnsafeRawBufferPointer) -> UnsafeMutableRawBufferPointer? /// 根据密文、密码和盐对数据进行进行CMS格式PEM加密 /// 生成结果必须手工释放 func encrypt(_ cipher: Cipher, password: UnsafeRawBufferPointer, salt: UnsafeRawBufferPointer, keyIterations: Int = 2048, keyDigest: Digest = .html5) -> UnsafeMutableRawBufferPointer? /// 根据密码和盐对数据进行进行CMS格式PEM解密 /// 生成结果必须手工释放 func decrypt(_ cipher: Cipher, password: UnsafeRawBufferPointer, salt: UnsafeRawBufferPointer, keyIterations: Int = 2048, keyDigest: Digest = .html5) -> UnsafeMutableRawBufferPointer? } public extension UnsafeRawBufferPointer { /// 根据长度要求填充随机数 /// /// - 结果:内存被分配并被自动初始化为随机数 static func allocateRandom(count size: Int) -> UnsafeRawBufferPointer? }
JSON 网络通行证 (JWT)
本组件为JWT创建和验证函数库。
JSON Web Token (以下简称网络通行证JWT) 为开放互联网标准协议 (RFC 7519) 用于定义在通信双方会话过程中以JSON为载体安全传输加密信息的方法。该信息可以用于互信和校验因为采用数字签名。JWTs 可用于HMAC算法加密签名,或者采用RSA公开/私有钥匙对签名方法,详见 JWT.
首先,新的JWT令牌可以可用 JWTCreator
对象创建。
/// 创建并签署新的 JWT 令牌。 public struct JWTCreator { /// 根据荷载内容创建新的通行券。 /// 荷载内容可以用于创建JWT字符串 public init?(payload: [String:Any]) /// 使用HMAC钥匙创建并返回一个新的JWT令牌。 /// 可根据需要自行追加其他头数据 /// 如果生成令牌中出现问题,会抛出签名错误 JWT.Error.signingError public func sign(alg: JWT.Alg, key: String, headers: [String:Any] = [:]) throws -> String /// 根据指定钥匙签署并生成新的 JWT 令牌。 /// 可根据需要自行追加其他头数据 /// 钥匙类型必须与算法 `algo` 兼容 /// 如果生成令牌中出现问题,会抛出签名错误 JWT.Error.signingError public func sign(alg: JWT.Alg, key: Key, headers: [String:Any] = [:]) throws -> String }
现有 JWT 通行证可以通过 JWTVerifier
对象进行验证
/// 接受一个 JWT 通行证并验证签名 public struct JWTVerifier { /// 从通行证内取出的头数据 public var header: [String:Any] /// 通行证内的荷载数据 public var payload: [String:Any] /// 从通行证字符串中创建 JWTVerifier 签名对象。通行证格式应该为 "aaaa.bbbb.cccc" /// 如果通行证无效则返回为 nil /// *注意这一步不做验证* 必须手工调用 `verify` 验证钥匙 /// 如果验证成功,则头数据`.headers`和荷载数据 `.payload`才能安全生效 public init?(_ jwt: String) /// 使用指定算法和HMAC钥匙验证通行证 /// 如果生成令牌中出现问题,会抛出验证错误 JWT.Error.verificationError /// 如果验证无误则正常执行 /// 参数 `algo` 必须与通行证中的 "alg" 头数据字段吻合 public func verify(algo: JWT.Alg, key: String) throws /// 使用指定算法和HMAC钥匙验证通行证 /// 如果生成令牌中出现问题,会抛出验证错误 JWT.Error.verificationError /// 如果验证无误则正常执行 /// 参数 `algo` 必须与通行证中的 "alg" 头数据字段吻合 public func verify(algo: JWT.Alg, key: Key) throws }
以下示范说明了如何创建并使用“HS256”算法验证一个网络通行证。
let name = "John Doe" let tstPayload = ["sub": "1234567890", "name": name, "admin": true] as [String : Any] let secret = "secret" guard let jwt1 = JWTCreator(payload: tstPayload) else { return // fatal error } let token = try jwt1.sign(alg: .hs256, key: secret) guard let jwt = JWTVerifier(token) else { return // fatal error } try jwt.verify(algo: .hs256, key: HMACKey(secret)) let fndName = jwt.payload["name"] as? String // name == fndName!
⚠️注意⚠️ JWTVerifier 能够验证通行证加密,但是 ⚠️不会⚠️ 验证数据内容,比如签发者(iss)和有效期(exp)。用户需要自行从荷载数据字典中提取上述信息并进行进一步用户身份验证。
算法清单
/// 编码方法 public enum Encoding { case base64 case hex } /// 摘要码算法 public enum Digest { case md4 case md5 case sha case sha1 case dss case dss1 case ecdsa case sha224 case sha256 case sha384 case sha512 case ripemd160 case whirlpool case custom(String) } /// 可用密文 public enum Cipher { case des_ecb case des_ede case des_ede3 case des_ede_ecb case des_ede3_ecb case des_cfb64 case des_cfb1 case des_cfb8 case des_ede_cfb64 case des_ede3_cfb1 case des_ede3_cfb8 case des_ofb case des_ede_ofb case des_ede3_ofb case des_cbc case des_ede_cbc case des_ede3_cbc case desx_cbc case des_ede3_wrap case rc4 case rc4_40 case rc4_hmac_md5 case rc2_ecb case rc2_cbc case rc2_40_cbc case rc2_64_cbc case rc2_cfb64 case rc2_ofb case bf_ecb case bf_cbc case bf_cfb64 case bf_ofb case cast5_ecb case cast5_cbc case cast5_cfb64 case cast5_ofb case aes_128_ecb case aes_128_cbc case aes_128_cfb1 case aes_128_cfb8 case aes_128_cfb128 case aes_128_ofb case aes_128_ctr case aes_128_ccm case aes_128_gcm case aes_128_xts case aes_128_wrap case aes_192_ecb case aes_192_cbc case aes_192_cfb1 case aes_192_cfb8 case aes_192_cfb128 case aes_192_ofb case aes_192_ctr case aes_192_ccm case aes_192_gcm case aes_192_wrap case aes_256_ecb case aes_256_cbc case aes_256_cfb1 case aes_256_cfb8 case aes_256_cfb128 case aes_256_ofb case aes_256_ctr case aes_256_ccm case aes_256_gcm case aes_256_xts case aes_256_wrap case aes_128_cbc_hmac_sha1 case aes_256_cbc_hmac_sha1 case aes_128_cbc_hmac_sha256 case aes_256_cbc_hmac_sha256 case camellia_128_ecb case camellia_128_cbc case camellia_128_cfb1 case camellia_128_cfb8 case camellia_128_cfb128 case camellia_128_ofb case camellia_192_ecb case camellia_192_cbc case camellia_192_cfb1 case camellia_192_cfb8 case camellia_192_cfb128 case camellia_192_ofb case camellia_256_ecb case camellia_256_cbc case camellia_256_cfb1 case camellia_256_cfb8 case camellia_256_cfb128 case camellia_256_ofb case seed_ecb case seed_cbc case seed_cfb128 case seed_ofb case custom(String) }