Perfect-Crypto 简体中文

Get Involed with Perfect!

Swift 5.2 Platforms OS X | Linux License Apache

为 Perfect 提供摘要、密码和编码支持。

构建

将此项目作为依赖项添加到您的 Package.swift 文件中。

.package(url: "https://github.com/PerfectlySoft/Perfect-Crypto.git", from: "4.0.0")

Linux 构建注意事项

请确保已安装 libssl-dev。此软件包需要 OpenSSL 1.0.2+。在 Ubuntu 14 或某些 Debian 发行版上,您需要更新 OpenSSL 才能构建此软件包。

sudo apt-get install openssl libssl-dev

概述

此软件包封装了 OpenSSL 提供的一些功能,并在其之上添加了一个 Swift 层。主要特点是

使用示例

编码/解码十六进制

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 = "this is a good pw"
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, "Failed signing")
}

公共 API

public extension String {
	/// Construct a string from a UTF8 character pointer.
	/// Character data does not need to be null terminated.
	/// The buffer's count indicates how many characters are to be converted.
	/// Returns nil if the data is invalid.
	init?(validatingUTF8 ptr: UnsafeRawBufferPointer?)
	/// Construct a string from a UTF8 character array.
	/// The array's count indicates how many characters are to be converted.
	/// Returns nil if the data is invalid.
	init?(validatingUTF8 a: [UInt8])
}

public extension String {
	/// Decode the String into an array of bytes using the indicated encoding.
	/// The string's UTF8 characters are decoded.
	func decode(_ encoding: Encoding) -> [UInt8]?
	/// Encode the String into an array of bytes using the indicated encoding.
	/// The string's UTF8 characters are decoded.
	func encode(_ encoding: Encoding) -> [UInt8]?
	/// Perform the digest algorithm on the String's UTF8 bytes
	func digest(_ digest: Digest) -> [UInt8]?
	/// Sign the String data into an array of bytes using the indicated algorithm and key.
	func sign(_ digest: Digest, key: Key) -> [UInt8]?
	/// Verify the signature against the String data.
	/// Returns true if the signature is verified. Returns false otherwise.
	func verify(_ digest: Digest, signature: [UInt8], key: Key) -> Bool
	/// Encrypt this buffer using the indicated cipher, password, and salt.
	/// The string's UTF8 characters are encoded.
	/// Resulting data is in PEM encoded CMS format.
	func encrypt(_ cipher: Cipher,
	             password: String,
	             salt: String,
	             keyIterations: Int = 2048,
	             keyDigest: Digest = .md5) -> String?
	/// Decrypt this PEM encoded CMS buffer using the indicated password and salt.
	/// Resulting decrypted data must be valid UTF-8 characters or the operation will fail.
	func decrypt(_ cipher: Cipher,
	             password: String,
	             salt: String,
	             keyIterations: Int = 2048,
	             keyDigest: Digest = .md5) -> String?
}

public protocol Octal {}
extension UInt8: Octal {}

public extension Array where Element: Octal {
	/// Encode the Array into An array of bytes using the indicated encoding.
	func encode(_ encoding: Encoding) -> [UInt8]?
	/// Decode the Array into an array of bytes using the indicated encoding.
	func decode(_ encoding: Encoding) -> [UInt8]?
	/// Digest the Array data into an array of bytes using the indicated algorithm.
	func digest(_ digest: Digest) -> [UInt8]?
	/// Sign the Array data into an array of bytes using the indicated algorithm and key.
	func sign(_ digest: Digest, key: Key) -> [UInt8]?
	/// Verify the array against the signature.
	/// Returns true if the signature is verified. Returns false otherwise.
	func verify(_ digest: Digest, signature: [UInt8], key: Key) -> Bool
	/// Decrypt this buffer using the indicated cipher, key an iv (initialization vector).
	func encrypt(_ cipher: Cipher, key: [UInt8], iv: [UInt8]) -> [UInt8]?
	/// Decrypt this buffer using the indicated cipher, key an iv (initialization vector).
	func decrypt(_ cipher: Cipher, key: [UInt8], iv: [UInt8]) -> [UInt8]?
	/// Encrypt this buffer using the indicated cipher, password, and salt.
	/// Resulting data is PEM encoded CMS format.
	func encrypt(_ cipher: Cipher,
	             password: [UInt8],
	             salt: [UInt8],
	             keyIterations: Int = 2048,
	             keyDigest: Digest = .md5) -> [UInt8]?
	/// Decrypt this PEM encoded CMS buffer using the indicated password and salt.
	func decrypt(_ cipher: Cipher,
	             password: [UInt8],
	             salt: [UInt8],
	             keyIterations: Int = 2048,
	             keyDigest: Digest = .md5) -> [UInt8]?
}

public extension UnsafeRawBufferPointer {
	/// Encode the buffer using the indicated encoding.
	/// The return value must be deallocated by the caller.
	func encode(_ encoding: Encoding) -> UnsafeMutableRawBufferPointer?
	/// Decode the buffer using the indicated encoding.
	/// The return value must be deallocated by the caller.
	func decode(_ encoding: Encoding) -> UnsafeMutableRawBufferPointer?
	/// Digest the buffer using the indicated algorithm.
	/// The return value must be deallocated by the caller.
	func digest(_ digest: Digest) -> UnsafeMutableRawBufferPointer?
	/// Sign the buffer using the indicated algorithm and key.
	/// The return value must be deallocated by the caller.
	func sign(_ digest: Digest, key: Key) -> UnsafeMutableRawBufferPointer?
	/// Verify the signature against the buffer.
	/// Returns true if the signature is verified. Returns false otherwise.
	func verify(_ digest: Digest, signature: UnsafeRawBufferPointer, key: Key) -> Bool
	/// Encrypt this buffer using the indicated cipher, key and iv (initialization vector).
	/// Returns a newly allocated buffer which must be freed by the caller.
	func encrypt(_ cipher: Cipher, key: UnsafeRawBufferPointer, iv: UnsafeRawBufferPointer) -> UnsafeMutableRawBufferPointer?
	/// Decrypt this buffer using the indicated cipher, key and iv (initialization vector).
	/// Returns a newly allocated buffer which must be freed by the caller.
	func decrypt(_ cipher: Cipher, key: UnsafeRawBufferPointer, iv: UnsafeRawBufferPointer) -> UnsafeMutableRawBufferPointer?
	/// Encrypt this buffer to PEM encoded CMS format using the indicated cipher, password, and salt.
	/// Returns a newly allocated buffer which must be freed by the caller.
	func encrypt(_ cipher: Cipher,
	             password: UnsafeRawBufferPointer,
	             salt: UnsafeRawBufferPointer,
	             keyIterations: Int = 2048,
	             keyDigest: Digest = .md5) -> UnsafeMutableRawBufferPointer?
   	/// Decrypt this PEM encoded CMS buffer using the indicated password and salt.
	/// Returns a newly allocated buffer which must be freed by the caller.
	func decrypt(_ cipher: Cipher,
	             password: UnsafeRawBufferPointer,
	             salt: UnsafeRawBufferPointer,
	             keyIterations: Int = 2048,
	             keyDigest: Digest = .md5) -> UnsafeMutableRawBufferPointer?
}

public extension UnsafeRawBufferPointer {
	/// Allocate memory for `size` bytes with word alignment from the encryption library's
	///	random number generator.
	///
	/// - Postcondition: The memory is allocated and initialized to random bits.
	static func allocateRandom(count size: Int) -> UnsafeRawBufferPointer? 
}

public extension FixedWidthInteger {
  /// get a random integer, i.e., signed or unsigned int8/16/32/64
  public static var random: Self
}
public extension Float {
  /// get a random float
  public static var random: Float
}
public extension Double {
  /// get a random double
  public static var random: Double 
}

JSON Web Tokens (JWT)

此加密包提供了一种创建新 JWT 令牌和验证现有令牌的方法。

JSON Web Token (JWT) 是一种开放标准 (RFC 7519),它定义了一种紧凑且自包含的方式,用于作为 JSON 对象在各方之间安全地传输信息。此信息可以被验证并信任,因为它已进行数字签名。 JWT 可以使用密钥(使用 HMAC 算法)或使用 RSA 的公钥/私钥对进行签名。来源:JWT

新的 JWT 令牌通过 JWTCreator 对象创建。

/// Creates and signs new JWT tokens.
public struct JWTCreator {
	/// Creates a new JWT token given a payload.
	/// The payload can then be signed to generate a JWT token string.
	public init?(payload: [String:Any])
	/// Sign and return a new JWT token string using an HMAC key.
	/// Additional headers can be optionally provided.
	/// Throws a JWT.Error.signingError if there is a problem generating the token string.
	public func sign(alg: JWT.Alg, key: String, headers: [String:Any] = [:]) throws -> String
	/// Sign and return a new JWT token string using the given key.
	/// Additional headers can be optionally provided.
	/// The key type must be compatible with the indicated `algo`.
	/// Throws a JWT.Error.signingError if there is a problem generating the token string.
	public func sign(alg: JWT.Alg, key: Key, headers: [String:Any] = [:]) throws -> String
}

现有的 JWT 令牌可以通过 JWTVerifier 对象进行验证。

/// Accepts a JWT token string and verifies its structural validity and signature.
public struct JWTVerifier {
	/// The headers obtained from the token.
	public var header: [String:Any]
	/// The payload carried by the token.
	public var payload: [String:Any]
	/// Create a JWTVerifier given a source string in the "aaaa.bbbb.cccc" format.
	/// Returns nil if the given string is not a valid JWT.
	/// *Does not perform verification in this step.* Call `verify` with your key to validate.
	/// If verification succeeds then the `.headers` and `.payload` properties can be safely accessed.
	public init?(_ jwt: String)
	/// Verify the token based on the indicated algorithm and HMAC key.
	/// Throws a JWT.Error.verificationError if any aspect of the token is incongruent.
	/// Returns without any error if the token was able to be verified.
	/// The parameter `algo` must match the token's "alg" header.
	public func verify(algo: JWT.Alg, key: String) throws
	/// Verify the token based on the indicated algorithm and key.
	/// Throws a JWT.Error.verificationError if any aspect of the token is incongruent.
	/// Returns without any error if the token was able to be verified.
	/// The parameter `algo` must match the token's "alg" header.
	/// The key type must be compatible with the indicated `algo`.
	public func verify(algo: JWT.Alg, key: Key) throws
}

以下示例将使用 "HS256" alg 方案创建然后验证令牌。

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(uer) 或 exp(iration)。您可以从有效负载字典中获取这些信息,并根据您的应用程序的需求进行验证。

支持的编码、摘要和密码

/// Available encoding methods.
public enum Encoding {
	case base64
	case hex
}

/// Available digest methods.
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)
}

/// Available ciphers.
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)
}

更多信息

有关更多文档,请访问 perfect.org