2021-06-13 02:42:07 +02:00
# crypto-secret.cr
[![Crystal CI ](https://github.com/didactic-drunk/crypto-secret.cr/actions/workflows/crystal.yml/badge.svg )](https://github.com/didactic-drunk/crypto-secret.cr/actions/workflows/crystal.yml)
[![GitHub release ](https://img.shields.io/github/release/didactic-drunk/crypto-secret.cr.svg )](https://github.com/didactic-drunk/crypto-secret.cr/releases)
![GitHub commits since latest release (by date) for a branch ](https://img.shields.io/github/commits-since/didactic-drunk/crypto-secret.cr/latest )
2021-06-13 02:43:35 +02:00
[![Docs ](https://img.shields.io/badge/docs-available-brightgreen.svg )](https://didactic-drunk.github.io/crypto-secret.cr/main)
2021-06-13 01:03:59 +02:00
2021-06-14 04:00:53 +02:00
Secrets hold sensitive information
2021-06-14 09:29:23 +02:00
The Secret interface manages limited time access to a secret and securely erases the secret when no longer needed.
2021-06-14 04:00:53 +02:00
2021-06-17 12:16:34 +02:00
Multiple `Secret` classes exist. Most of the time you shouldn't need to change the `Secret` type - the cryptographic library should have sane defaults.
2021-06-17 18:41:52 +02:00
If you have a high security or high performance application see [which secret type should I use? ](https://didactic-drunk.github.io/crypto-secret.cr/main/Crypto/Secret.html )
2021-06-17 12:16:34 +02:00
2021-06-14 04:00:53 +02:00
Secret providers may implement additional protections via:
2021-06-14 20:15:48 +02:00
* `#noaccess` , `#readonly` or `#readwrite`
2021-06-14 04:00:53 +02:00
* Using [mprotect]() to control access
* Encrypting the data when not in use
* Deriving keys on demand from a HSM
* Preventing the Secret from entering swap ([mlock]())
* Preventing the Secret from entering core dumps
* Other
2021-06-13 01:03:59 +02:00
## Installation
1. Add the dependency to your `shard.yml` :
```yaml
dependencies:
2021-06-13 02:42:07 +02:00
crypto-secret:
github: didactic-drunk/crypto-secret
2021-06-13 01:03:59 +02:00
```
2. Run `shards install`
## Usage
2021-06-14 21:58:16 +02:00
#### Rules:
2021-06-15 21:06:33 +02:00
1. Secrets should be erased (wiped) ASAP
2. Secrets are only available within a `readonly` or `readwrite` block
3. Secrets are not thread safe except for the provided `Slice` (only when reading) within a single `readonly` or `readwrite` block
2021-06-14 21:58:16 +02:00
2021-06-13 01:03:59 +02:00
```crystal
2021-06-16 00:55:03 +02:00
require "crypto-secret/bidet"
2021-06-13 01:03:59 +02:00
2021-06-16 00:55:03 +02:00
# Bidet is a minimal but fast secret implementation
secret = Crypto::Secret::Bidet.new 32
# Don't forget to wipe!
2021-06-14 09:29:23 +02:00
secret.wipe do
secret.readonly do |slice|
2021-06-14 04:00:53 +02:00
# May only read slice
end
2021-06-14 09:29:23 +02:00
secret.readwrite do |slice|
2021-06-14 04:00:53 +02:00
# May read or write to slice
end
2021-06-14 09:29:23 +02:00
end # secret is erased
2021-06-13 01:03:59 +02:00
```
2021-06-14 21:58:16 +02:00
#### Breaking the rules:
If you need thread safety :
1. Switch to a Stateless Secret
2. Or switch the Secret's state to readonly or readwrite after construction and never switch it again. [sodium.cr]() makes use of this technique to provide thread safe encryption/decryption
2021-06-15 21:06:33 +02:00
3. Or wrap all access in a Mutex (compatible with all Secret classes)
2021-06-14 21:58:16 +02:00
If you need more better performance:
* Consider 1. or 2.
2021-06-15 21:06:33 +02:00
If you need compatibility with any `Secret` :
* Always use a `Mutex`
* Never rely on 1. or 2.
#### Converting `Bytes` to a `Secret`
```crystal
2021-06-22 00:41:33 +02:00
slice = method_that_returns_bytes()
2021-06-15 21:06:33 +02:00
secret = Crypto::Secret::Bidet.move_from slice # erases slice
# or
secret = Crypto::Secret::Bidet.copy_from slice
2021-06-21 11:36:10 +02:00
# or
secret = Crypto::Secret::Bidet size_in_bytes
secret.move_from slice
2021-06-15 21:06:33 +02:00
```
2021-06-14 04:00:53 +02:00
## What is a Secret?
< strike > Secrets are Keys< / strike >
That's complicated and specific to the application. Some examples:
* Passwords
* A crypto key is always a Secret. Except when used for verification (sometimes)
* A decrypted password vault (but it's not a Key)
Not secrets:
* Digest output. Except when used for key derivation, then it's a Secret, including the Digest state
2021-06-16 05:17:38 +02:00
* IO::Memory or writing a file. Except when the file is a password vault, cryptocurrency wallet, encrypted mail/messages, goat porn, maybe "normal" porn, sometimes scat porn, occassionally furry, not people porn
2021-06-14 04:00:53 +02:00
## Why?
The Secret interface is designed to handle varied levels of confidentiality with a unified API for cryptography libraries.
There is no one size fits all solution. Different applications have different security requirements. Sometimes for the same algorithm.
A master key (kgk) may reside on a HSM and generate subkeys on demand.
2021-06-14 09:29:23 +02:00
Or for most applications the master key may use a best effort approach using a combination of [guard pages, mlock, mprotect].
2021-06-14 04:00:53 +02:00
Other keys in the same application may handle a high volume of messages where [guard pages, mlock, mprotect] overhead is too high.
2021-06-14 09:29:23 +02:00
A key verifying a public key signature may not be Secret (but is a Secret::Not).
2021-06-14 04:00:53 +02:00
2021-06-14 09:29:23 +02:00
## How do I use a Secret returned by a shard?
2021-06-22 00:41:33 +02:00
#### Accessing as a `Slice(UInt8) | Bytes`
2021-06-14 09:29:23 +02:00
2021-06-22 00:41:33 +02:00
```crystal
secret = method_that_returns_a_secret()
secret.wipe do
secret.readonly do |slice|
...
end
secret.readwrite do |slice|
...
end
end
```
2021-06-14 09:29:23 +02:00
#### Using a Secret to hold decrypted file contents:
2021-06-22 00:41:33 +02:00
```crystal
2021-06-14 09:29:23 +02:00
key = ...another Secret...
encrypted_str = File.read("filename")
decrypted_size = encrypted_str.bytesize - mac_size
2021-06-22 00:41:33 +02:00
file_secret = Crypto::Secret::Large.new decrypted_size
2021-06-14 09:29:23 +02:00
file_secret.wipe do
file_secrets.readwrite do |slice|
decrypt(key: key, src: encrypted_str, dst: slice)
# Do something with file contents in slice
end
end # Decrypted data is erased
```
2021-06-22 00:41:33 +02:00
#### Reusing a `Secret`
```crystal
# May be used to generate new keys
secret.random
# Copy to secret and wipe `slice`
secret.move_from slice
```
2021-06-14 09:29:23 +02:00
## When should I use a Secret?
2021-06-22 00:41:33 +02:00
When implementing an encryption class return `Secret` keys with a sane default implementation that suits the average use for your class. Several default implementations are provided.
2021-06-14 09:29:23 +02:00
Allow overriding the default returned key and/or allow users of your class to provide their own `Secret` for cases where they need more or less protection.
Example:
```
class SimplifiedEncryption
# Allow users of your library to provide their own Secret key. Also provide a sane default.
def encrypt(data : Bytes | String, key : Secret? = nil) : {Secret, Bytes}
key ||= Crypto::Secret::Default.random
...
{key, encrypted_slice}
end
end
```
## What attacks does a Secret protect against?
2021-06-14 20:15:48 +02:00
* Timing attacks when comparing secrets by overriding `==`
* Leaking data in to logs by overriding `inspect`
* Wiping memory when the secret is no longer in use
2021-06-22 00:41:33 +02:00
Each implementation may add additional protections
* `Crypto::Secret::Key` - May use mlock, mprotect and canaries in future versions
* `Crypto::Secret::Large` - May use mprotect in future versions
* `Crypto::Secret::Not` - It's not secret. Doesn't wipe and no additional protection.
2021-06-14 09:29:23 +02:00
2021-06-14 20:15:48 +02:00
2021-06-14 09:29:23 +02:00
## Other languages/libraries
* rust: [secrets ](https://github.com/stouset/secrets/ )
* c: [libsodium ](https://github.com/jedisct1/libsodium-doc/blob/master/helpers/memory_management.md#guarded_heap_allocations )
* go: [memguard ](https://github.com/awnumar/memguard )
* haskell: [securemem ](https://hackage.haskell.org/package/securemem )
* c#: [SecureString ](https://docs.microsoft.com/en-us/dotnet/api/system.security.securestring )
2021-06-14 04:00:53 +02:00
2021-06-13 01:03:59 +02:00
## Implementing a new Secret holding class
2021-06-14 04:00:53 +02:00
**Only intended for use by crypto library authors**
2021-06-13 01:03:59 +02:00
```
2022-05-12 07:30:05 +02:00
class MySecret < Crypto::Secret
2021-06-15 21:06:33 +02:00
# Choose one
include Crypto::Secret::Stateless
include Crypto::Secret::Stateful
2021-06-13 01:03:59 +02:00
2022-05-12 07:30:05 +02:00
def initialize(size : Int32)
2021-06-15 21:06:33 +02:00
# allocate or reference storage
2021-06-13 01:03:59 +02:00
# optionally mlock
end
2021-06-16 23:20:11 +02:00
protected def to_slice(& : Bytes -> Nil)
2021-06-14 09:29:23 +02:00
# The yielded Slice only needs to be valid within the block
2021-06-14 04:00:53 +02:00
# yield Slice.new(pointer, size)
ensure
# optionally reencrypt or signal HSM
end
2022-05-12 07:30:05 +02:00
def buffer_bytesize : Int32
2021-06-14 04:00:53 +02:00
# return the size
2021-06-13 01:03:59 +02:00
end
# optionally override [noaccess, readonly, readwrite]
2021-06-14 09:29:23 +02:00
# optionally override (almost) any other method with an implementation specific version
2021-06-13 01:03:59 +02:00
end
```
## Contributing
2021-06-14 09:29:23 +02:00
**Open a discussion or issue before creating PR's**
2021-06-14 04:00:53 +02:00
2021-06-13 02:42:07 +02:00
1. Fork it (< https: // github . com / your-github-user / crypto-secret / fork > )
2021-06-13 01:03:59 +02:00
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create a new Pull Request
## Contributors
- [didactic-drunk ](https://github.com/didactic-drunk ) - current maintainer