Crypto

The Perfect-Crypto package is a general purpose cryptography library built on OpenSSL. It provides high level objects for dealing with the following cryptographic tasks:

Message digests and hashes, sign/verify

Cipher based encryption/decryption

Random data generation of arbitrary byte lengths

HMAC key generation

PEM format public/private key reading

JWT (JSON Web Token) creation and validation

It also provides some encoding related functions which are generally useful but are commonly used along with cryptography, particularly when converting binary data to and from a character printable state.

To use the functionality of this package ensure you import PerfectCrypto.

Initialization

The underlying cryptography library requires a one-time initialization to be performed before any related functions are used. This is generally done once when your program starts before performing any actual tasks. Calling PerfectCrypto.isInitialized will initialize the library and return true. Calling this more than once is fine, however one would generally only call this once when boot-strapping your application.

Extensions

Much of the functionality provided by this package is through extensions on several of the Swift builtin types, namely: String, Array<UInt8>, UnsafeRawBufferPointer, and UnsafeMutableRawBufferPointer. The extensions mainly consist of adding encode/decode, encrypt/decrypt, sign/verify and digest functions for these types. Others provide convenience functions for dealing with raw binary data, generating random data, etc.

The extensions are listed below in order of "convenience". The higher level functions are listed first and the more efficient "unsafes" are listed at the end.

String

The String extensions are divided into two groups. The first group provides the encode, decode and digest functions for Strings.

The encode and decode functions are given an Encoding type and will return the result as a [UInt8] or nil if the input data was invalid for that particular encoding. The encoding type must be one of the valid Encoding enums. For example .hex, or .base64url.

This example shows how one would encode a String as base64 and decode it back into the original.

The digest function allows one to perform a message digest operation on the String's characters. The digest data is returned as a [UInt8]. nil is returned if the indicated encoding is not supported by the underlying system.

This example shows a sha256 digest calculated for a String. The resulting value is then converted into a printable hexadecimal String.

The sign and verify functions permit a block of data to be cryptographically signed using a specified key and then later verified to ensure that the data has not been modified in any way. Signing can be done with either HMAC, RSA or EC type keys.

The second group of String extensions add convenience functions for creating a String from non null terminated UTF-8 characters. These characters can be given through either a [UInt8] or UnsafeRawBufferPointer.

public extension String {
/// 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])
/// 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?)
/// Obtain a buffer pointer for the String's UTF8 characters.
func withBufferPointer<Result>(_ body: (UnsafeRawBufferPointer) throws -> Result) rethrows -> Result
}

The final function, withBufferPointer, is useful for obtaining an UnsafeRawBufferPointer containing the String's UTF8 characters. The buffer is valid only within the provided closure/function's body.

Array<UInt8>

The extensions on Array are only provided for those containing UInt8 values. These functions provide encode/decode, encrypt/decrypt and digest operations as well as an initializer which allows creating an array of randomly generated data.

The encode, decode and digest functions work identically to the String versions, except that the input data is a [UInt8]. The encode and decode functions are given an Encoding type and will return the result as a [UInt8] or nil if the input data was invalid for that particular encoding. The encoding type must be one of the valid Encoding enums. For example .hex, or .base64url.

The digest function allows one to perform a message digest operation on the array values. The digest data is returned as a [UInt8]. nil is returned if the indicated digest is not supported by the underlying system.

The first set of encrypt and decrypt functions will encrypt/decrypt the array data based on the indicated Cipher enum value. These operations also require input for the key and initialization vector (iv) parameters.

The second set of encrypt and decrypt functions accept a Cipher, password and salt value and encrypt/decrypt the data. These encryption functions produce and consume PEM encoded Cryptographic Message Syntax (CMS) data.

The sizes of the key and iv arrays will differ based the cipher in use. Cipher enum values provide properties for the individual cipher's blockSize, keyLength and ivLength. All of these values are indicated in bytes.

The snippet below will use the .aes_256_cbc cipher to encrypt an array of random bytes. The key and the iv for this example are also generated at random based on the sizes required for the cipher.

UnsafeXX Extensions

The extensions provided on UnsafeMutableRawBufferPointer and UnsafeRawBufferPointer give lower level access to the buffers used for the crypto operations. They provide more efficient behaviour but all the same operations as the Array extension counterparts.

UnsafeMutableRawBufferPointer

The extensions on UnsafeMutableRawBufferPointer permit one to generate or fill a buffer of randomly generated data.

It's very important to adhere to the proper memory ownership guidelines when using these functions. Any UnsafeMutableRawBufferPointer returned by one of these functions must at some point be deallocated by the caller. All such return buffers will properly have their .count properties set to indicate their size. No UnsafeRawBufferPointer passed into a function will be deallocated or otherwise modified.

JSON Web Tokens (JWT)

This crypto package provides an means for creating new JWT tokens and validating existing tokens.

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA. Source: JWT.

New JWT tokens are created through the JWTCreator object.

/// 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
}

Existing JWT tokens can be validated through the JWTVerifier object.

/// 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
}

The following example will create and then verify a token using the "HS256" alg scheme.

It's important to note that the JWTVerifier will verify that the token is cryptographically sound, but it does not validate payload claims such as iss(uer) or exp(iration). You can obtain these from the payload dictionary and validate according to the needs of your application.

Algorithms

The available encoding, digest and cipher algorithms are enumerated in the Encoding, Digest and Cipher enums, respectively.

The Digest and Cipher enums provide a custom case which can be used to indicate the digest or encoding by name. This can be useful if your system includes additional algorithms which are not made explicitly available by this package. All of the digest and cipher algorithms are implemented by the underlaying crypto library (OpenSSL). Some of the encodings are implemented by the underlying library but others may be implemented directly by this package (hex, for example).