crypto updates

This commit is contained in:
tanner0101 2018-04-02 21:10:19 -04:00
parent 7bc65a47c7
commit c463dfa0f7
16 changed files with 185 additions and 263 deletions

View File

@ -1,17 +0,0 @@
# Getting Started with Async
Vapor is powered by Apple's [SwiftNIO](https://github.com/apple/swift-nio), a powerful non-blocking networking framework. If you are using the fully-powered Vapor framework to create a website, then you will likely not need to use this directly. However, if you are using a lower-level library (like a Database framework) then you will need to understand a little about how it works.
## Workers
Vapor's async abstraction is built on a few pieces, including the async [`Worker`](https://github.com/vapor-community/async/blob/1.0.0-rc.1.1/Sources/Async/EventLoop/Worker.swift). This protocol has an `eventLoop`, which fits nicely with SwiftNIO's constructs.
### Example
You need to create a SwiftNIO [`EventGroup`](https://github.com/apple/swift-nio/blob/master/README.md#eventloops-and-eventloopgroups) that can power the connection:
```
let database = MySQLDatabase(config: config)
let worker = MultiThreadedEventLoopGroup(numThreads: System.coreCount)
let futureConnection = database.makeConnection(on: worker)
```

View File

@ -0,0 +1,38 @@
# Asymmetric Cryptography
Asymmetric cryptography (also called public-key cryptography) is a cryptographic system that uses multiple keys—usually a "public" and "private" key.
Read more about [public-key cryptography](https://en.wikipedia.org/wiki/Public-key_cryptography) on Wikipedia.
## RSA
A popular asymmetric cryptography algorithm is RSA. RSA has two key types: public and private.
RSA can create signatures from any data using a private key.
```swift
let privateKey: String = ...
let signature = try RSA.SHA512.sign("vapor", key: .private(pem: privateKey))
```
!!! info
Only private keys can _create_ signatures.
These signatures can be verified against the same data later using either the public or private key.
```swift
let publicKey: String = ...
try RSA.SHA512.verify(signature, signs: "vapor", key: .public(pem: publicKey)) // true
```
If RSA verifies that a signature matches input data for a public key, you can be sure that whoever generated that signature had access to that key's private key.
### Algorithms
RSA supports any of the Crypto module's [`DigestAlgorithm`](https://api.vapor.codes/crypto/latest/Crypto/Classes/DigestAlgorithm.html).
```swift
let privateKey: String = ...
let signature512 = try RSA.SHA512.sign("vapor", key: .private(pem: privateKey))
let signature256 = try RSA.SHA256.sign("vapor", key: .private(pem: privateKey))
```

View File

@ -1,3 +0,0 @@
# Base64
Coming soon

View File

@ -0,0 +1,40 @@
# Cipher Algorithms
Ciphers allow you to encrypt plaintext data with a key yielding ciphertext. This ciphertext can be later decrypted by the same cipher using the same key.
Read more about [ciphers](https://en.wikipedia.org/wiki/Cipher) on Wikipedia.
## Encrypt
Use the global convenience variables for encrypting data with common algorithms.
```swift
let ciphertext = try AES128.encrypt("vapor", key: "secret")
print(ciphertext) /// Data
```
## Decrypt
Decryption works very similarly to [encryption](#encrypt). The following snippet shows how to decrypt the ciphertext from our previous example.
```swift
let plaintext = try AES128.decrypt(ciphertext, key: "secret")
print(plaintext) /// "vapor"
```
See the Crypto module's [global variables](https://api.vapor.codes/crypto/latest/Crypto/Global%20Variables.html#/Ciphers) for a list of all available cipher algorithms.
## Streaming
Both encryption and decryption can work in a streaming mode that allows data to be chunked. This is useful for controlling memory usage while encrypting large amounts of data.
```swift
let key: Data // 16-bytes
let aes128 = Cipher(algorithm: .aes128ecb)
try aes128.reset(key: key, mode: .encrypt)
var buffer = Data()
try aes128.update(data: "hello", into: &buffer)
try aes128.update(data: "world", into: &buffer)
try aes128.finish(into: &buffer)
print(buffer) // Completed ciphertext
```

View File

@ -0,0 +1,71 @@
# Message Digests
Cryptographic hash functions (also known as message digest algorithms) convert data of arbitrary size to a fixed-size digest. These are most often used for generating checksums or identifiers for large data blobs.
Read more about [Cryptographic hash functions](https://en.wikipedia.org/wiki/Cryptographic_hash_function) on Wikipedia.
## Hash
Use the global convenience variables to create hashes using common algorithms.
```swift
import Crypto
let digest = try SHA1.hash("hello")
print(digest.hexEncodedString()) // aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d
```
See the Crypto module's [global variables](https://api.vapor.codes/crypto/latest/Crypto/Global%20Variables.html#/Digests) for a list of all available hash algorithms.
### Streaming
You can create a [`Digest`](https://api.vapor.codes/crypto/latest/Crypto/Classes/Digest.html) manually and use its instance methods to create a hash for one or more data chunks.
```swift
var sha256 = try Digest(algorithm: .sha256)
try sha256.reset()
try sha256.update(data: "hello")
try sha256.update(data: "world")
let digest = try sha256.finish()
print(digest) /// Data
```
## BCrypt
BCrypt is a popular hashing algorithm that has configurable complexity and handles salting automatically.
### Hash
Use the `hash(_:cost:salt:)` method to create BCrypt hashes.
```swift
let digest = try BCrypt.hash("vapor", cost: 4)
print(digest) /// data
```
Increasing the `cost` value will make hashing and verification take longer.
### Verify
Use the `verify(_:created:)` method to verify that a BCrypt hash was created by a given plaintext input.
```swift
let hash = try BCrypt.hash("vapor", cost: 4)
try BCrypt.verify("vapor", created: hash) // true
try BCrypt.verify("foo", created: hash) // false
```
## HMAC
HMAC is an algorithm for creating _keyed_ hashes. HMAC will generate different hashes for the same input if different keys are used.
```swift
let digest = try HMAC.SHA1.authenticate("vapor", key: "secret")
print(digest.hexEncodedString()) // digest
```
See the [`HMAC`](https://api.vapor.codes/crypto/latest/Crypto/Classes/HMAC.html) class for a list of all available hash algorithms.
### Streaming
HMAC hashes can also be streamed. The API is identical to [hash streaming](#streaming).

View File

@ -1,6 +1,9 @@
# Using Crypto
Crypto is a library containing all common APIs related to cryptography and security.
Crypto is a library containing common APIs related to cryptography and data generation. The [vapor/crypto](https://github.com/vapor/crypto) package contains two modules:
- `Crypto`
- `Random`
## With Vapor
@ -8,6 +11,7 @@ This package is included with Vapor by default, just add:
```swift
import Crypto
import Random
```
## Without Vapor
@ -25,9 +29,9 @@ let package = Package(
.package(url: "https://github.com/vapor/crypto.git", .upToNextMajor(from: "x.0.0")),
],
targets: [
.target(name: "Project", dependencies: ["Crypto", ... ])
.target(name: "Project", dependencies: ["Crypto", "Random", ... ])
]
)
```
Use `import Crypto` to access Crypto's APIs.
Use `import Crypto` to access Crypto's APIs and `import Random` to access Random's APIs.

View File

@ -1,102 +0,0 @@
# Hash
Hashes are a one-directional encryption that is commonly used for validating files or one-way securing data such as passwords.
### Available hashes
Crypto currently supports a few hashes.
- MD5
- SHA1
- SHA2 (all variants)
MD5 and SHA1 are generally used for file validation or legacy (weak) passwords. They're performant and lightweight.
Every Hash type has a set of helpers that you can use.
## Hashing blobs of data
Every `Hash` has a static method called `hash` that can be used for hashing the entire contents of `Foundation.Data`, `ByteBuffer` or `String`.
The result is `Data` containing the resulting hash. The hash's length is according to spec and defined in the static variable `digestSize`.
```swift
// MD5 with `Data`
let fileData = Data()
let fileMD5 = MD5.hash(fileData)
// SHA1 with `ByteBuffer`
let fileBuffer: ByteBuffer = ...
let fileSHA1 = SHA1.hash(fileBuffer)
// SHA2 variants with String
let staticUnsafeToken: String = "rsadd14ndmasidfm12i4j"
let tokenHashSHA224 = SHA224.hash(staticUnsafeToken)
let tokenHashSHA256 = SHA256.hash(staticUnsafeToken)
let tokenHashSHA384 = SHA384.hash(staticUnsafeToken)
let tokenHashSHA512 = SHA512.hash(staticUnsafeToken)
```
## Incremental hashes (manual)
To incrementally process hashes you can create an instance of the Hash. This will set up a context.
All hash context initializers are empty:
```swift
// Create an MD5 context
let md5Context = MD5()
```
To process a single chunk of data, you can call the `update` function on a context using any `Sequence` of `UInt8`. That means `Array`, `Data` and `ByteBuffer` work alongside any other sequence of bytes.
```swift
md5Context.update(data)
```
The data data need not be a specific length. Any length works.
When you need the result, you can call `md5Context.finalize()`. This will finish calculating the hash by appending the standard `1` bit, padding and message bitlength.
You can optionally provide a last set of data to `finalize()`.
After calling `finalize()`, do not update the hash if you want correct results.
### Fetching the results
The context can then be accessed to extract the resulting Hash.
```swift
let hash: Data = md5Context.hash
```
## Streaming hashes (Async)
Sometimes you need to hash the contents of a Stream, for example, when processing a file transfer. In this case you can use `ByteStreamHasher`.
First, create a new generic `ByteStreamHasher<Hash>` where `Hash` is the hash you want to use. In this case, SHA512.
```swift
let streamHasher = ByteStreamHasher<SHA512>()
```
This stream works like any `inputStream` by consuming the incoming data and passing the buffers to the hash context.
For example, draining a TCP socket.
```swift
let socket: TCP.Socket = ...
socket.drain(into: streamHasher)
```
This will incrementally update the hash using the provided TCP socket's data.
When the hash has been completely accumulated, you can `complete` the hash.
```swift
let hash = streamHasher.complete() // Foundation `Data`
```
This will reset the hash's context to the default configuration, ready to start over.

View File

@ -1,15 +0,0 @@
# Message authentication
Message authentication is used for verifying message authenticity and validity.
Common use cases are JSON Web Tokens.
For message authentication, Vapor only supports HMAC.
## Using HMAC
To use HMAC you first need to select the used hashing algorithm for authentication. This works using generics.
```swift
let hash = HMAC<SHA224>.authenticate(message, withKey: authenticationKey)
```

View File

@ -1,74 +0,0 @@
# Password hashing
Password management is critical for good user security and doesn't need to cost a lot of effort. No software is perfect. Even if your software is perfect, other software on the same server likely isn't. Good password encryption security prevents users' passwords from leaking out in case of a hypothetical future data breach.
For password hashing Vapor supports PBKDF2 and BCrypt.
We recommend using BCrypt over PBKDF2 for almost all scenarios. Whilst PBKDF2 is a proven standard, it's much more easily brute-forced than BCrypt and is less future-proof.
## BCrypt
BCrypt is an algorithm specifically designed for password hashing. It's easy to store and verify.
### Deriving a key
Unlike PBKDF2 you don't need to generate and store a salt, that's part of the BCrypt hashing and verification process.
The output is a combination of the BCrypt "cost" factor, salt and resulting hash. Meaning that the derived output contains all information necessary for verification, simplifying the database access.
```swift
let result: Data = try BCrypt.make(message: "MyPassword")
guard try BCrypt.verify(message: "MyPassword", matches: result) else {
fatalError("This never triggers, since the verification process will always be successful for the same password and conditions")
}
```
The default cost factor is `12`, based on the official recommendations.
### Storing the derived key as a String
BCrypt always outputs valid ASCII/UTF-8 for the resulting hash.
This means you can convert the output `Data` to a `String` as such:
```swift
guard let string = String(bytes: result, encoding: .utf8) else {
// This must never trigger
}
```
## PBKDF2
PBKDF2 is an algorithm that is almost always (and in Vapor, exclusively) used with HMAC for message authentication.
PBKDF2 can be paired up with any hashing algorithm and is simple to implement. PBKDF2 is used all over the world through the WPA2 standard, securing WiFi connections. But we still recommend PBKDF2 above any normal hashing function.
For PBKDF2 you also select the Hash using generics.
### Deriving a key
In the following example:
- `password` is either a `String` or `Data`
- The `salt` is `Data`
- Iterations is defaulted to `10_000` iterations
- The keySize is equivalent to 1 hash's length.
```swift
// Generate a random salt
let salt: Data = OSRandom().data(count: 32)
let hash = try PBKDF2<SHA256>.derive(fromPassword: password, salt: salt)
```
You can optionally configure PBKDF2 to use a different iteration count and output keysize.
```swift
// Iterates 20'000 times and outputs 100 bytes
let hash = try PBKDF2<SHA256>.derive(fromPassword: password, salt: salt, iterating: 20_000, derivedKeyLength: 100)
```
### Storing the results
When you're storing the PBKDF2 results, be sure to also store the Salt. Without the original salt, iteration count and other parameters you cannot reproduce the same hash for validation or authentication.

View File

@ -1,49 +1,32 @@
# Random
Crypto has two primary random number generators.
The `Random` module deals with random data generation including random number generation.
OSRandom generates random numbers by calling the operating system's random number generator.
## Data Generator
URandom generates random numbers by reading from `/dev/urandom`.
The [`DataGenerator`]() class powers all of the random data generators.
## Accessing random numbers
### Implementations
- [`OSRandom`](https://api.vapor.codes/crypto/latest/Random/Classes/OSRandom.html): Provides a random data generator using a platform-specific method.
- [`URandom`](https://api.vapor.codes/crypto/latest/Random/Classes/URandom.html) provides random data generation based on the `/dev/urandom` file.
- [`CryptoRandom`](https://api.vapor.codes/crypto/latest/Crypto/Classes/CryptoRandom.html) from the `Crypto` module provides cryptographically-secure random data using OpenSSL.
First, create an instance of the preferred random number generator:
```swift
let random = OSRandom()
let random: DataGenerator ...
let data = try random.generateData(bytes: 8)
```
or
### Generate
`DataGenerator`s are capable of generating random primitive types using the `generate(_:)` method.
```swift
let random = try URandom()
```
### Reading integers
For every Swift integer a random number function exists.
```swift
let int8: Int8 = try random.makeInt8()
let uint8: UInt8 = try random.makeUInt8()
let int16: Int16 = try random.makeInt16()
let uint16: UInt16 = try random.makeUInt16()
let int32: Int32 = try random.makeInt32()
let uint32: UInt32 = try random.makeUInt32()
let int64: Int64 = try random.makeInt64()
let uint64: UInt64 = try random.makeUInt64()
let int: Int = try random.makeInt()
let uint: UInt = try random.makeUInt()
```
### Reading random data
Random buffers of data are useful when, for example, generating tokens or other unique strings/blobs.
To generate a buffer of random data:
```swift
// generates 20 random bytes
let data: Data = random.data(count: 20)
let int = try OSRandom().generate(Int.self)
print(int) // Int
```

View File

@ -45,7 +45,7 @@ router.post("login") { req -> Future<HTTPStatus> in
}
```
We use `.map(to:)` here since `req.content.decode(_:)` returns a [future](futures.md).
We use `.map(to:)` here since `req.content.decode(_:)` returns a [future](async.md).
### Other Request Types

View File

@ -21,7 +21,7 @@ final class HelloController {
Controller methods should always accept a `Request` and return something `ResponseEncodable`.
!!! note
[Futures](futures.md) whose expectations are `ResponseEncodable` (i.e, `Future<String>`) are also `ResponseEncodable`.
[Futures](async.md) whose expectations are `ResponseEncodable` (i.e, `Future<String>`) are also `ResponseEncodable`.
To use this controller, we can simply initialize it, then pass the method to a router.

View File

@ -9,7 +9,7 @@ Most of your interaction with services will happen through a container. A contai
- [Services](#services): A collection of registered services.
- [Config](#config): Declared preferences for certain services over others.
- [Environment](#environment): The application's current environment type (testing, production, etc)
- [Worker](futures.md#event-loop): The event loop associated with this container.
- [Worker](async.md#event-loop): The event loop associated with this container.
The most common containers you will interact with in Vapor are:

View File

@ -94,9 +94,9 @@ Visiting this route should display your MySQL version.
A `MySQLConnection` is normally created using the `Request` container and can perform two different types of queries.
### Create (with Request)
### Create
There are two methods for creating a `MySQLConnection`.
There are a few methods for creating a `MySQLConnection` with a `Container` (typically a `Request`).
```swift
return req.withPooledConnection(to: .mysql) { conn in
@ -109,9 +109,7 @@ return req.withConnection(to: .mysql) { conn in
As the names imply, `withPooledConnection(to:)` utilizes a connection pool. `withConnection(to:)` does not. Connection pooling is a great way to ensure your application does not exceed the limits of your database, even under peak load.
### Create (manually)
If you are writing a simple tool and would like to use the `MySQL` wrapper directly, it is also quite simple. You should read up on Vapor's [async `Worker`](../../async/getting-started.md) to power the connection.
You can also create a connection manually using `MySQLDatabase.makeConnection(on:)` and passing a [`Worker`](../getting-started/async.md).
### Simply Query

View File

@ -16,7 +16,7 @@ pages:
- 'Controllers': 'getting-started/controllers.md'
- 'Routing': 'getting-started/routing.md'
- 'Content': 'getting-started/content.md'
- 'Futures': 'getting-started/futures.md'
- 'Async': 'getting-started/async.md'
- 'Services': 'getting-started/services.md'
- 'Deployment': 'getting-started/cloud.md'
- 'Routing':
@ -61,10 +61,9 @@ pages:
- 'Getting Started': 'websocket/websocket.md'
- 'Crypto':
- 'Getting Started': 'crypto/getting-started.md'
- 'Base64': 'crypto/base64.md'
- 'Hashes': 'crypto/hash.md'
- 'Message authentication': 'crypto/mac.md'
- 'Password hashing': 'crypto/passwords.md'
- 'Digests': 'crypto/digests.md'
- 'Ciphers': 'crypto/ciphers.md'
- 'Asymmetric': 'crypto/asymmetric.md'
- 'Random': 'crypto/random.md'
- 'Testing':
- 'Getting Started': 'testing/getting-started.md'