.github/workflows | ||
spec | ||
src | ||
.editorconfig | ||
.gitignore | ||
LICENSE | ||
README.md | ||
shard.yml |
crypto-secret.cr
Secrets hold sensitive information
The Secret interface manages limited time access to a secret and securely erases the secret when no longer needed.
Multiple Secret
classes exist. Most of the time you shouldn't need to change the Secret
type - the cryptographic library should have sane defaults.
If you have a high security or high performance application see which secret type should I use?
Secret providers may implement additional protections via:
#noaccess
,#readonly
or#readwrite
- 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
Installation
-
Add the dependency to your
shard.yml
:dependencies: crypto-secret: github: didactic-drunk/crypto-secret
-
Run
shards install
Usage
Rules:
- Secrets should be erased (wiped) ASAP
- Secrets are only available within a
readonly
orreadwrite
block - Secrets are not thread safe except for the provided
Slice
(only when reading) within a singlereadonly
orreadwrite
block
require "crypto-secret/bidet"
# Bidet is a minimal but fast secret implementation
secret = Crypto::Secret::Bidet.new 32
# Don't forget to wipe!
secret.wipe do
secret.readonly do |slice|
# May only read slice
end
secret.readwrite do |slice|
# May read or write to slice
end
end # secret is erased
Breaking the rules:
If you need thread safety :
- Switch to a Stateless Secret
- 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
- Or wrap all access in a Mutex (compatible with all Secret classes)
If you need more better performance:
- Consider 1. or 2.
If you need compatibility with any Secret
:
- Always use a
Mutex
- Never rely on 1. or 2.
Converting Bytes
to a Secret
slice = method_that_returns_bytes()
secret = Crypto::Secret::Bidet.move_from slice # erases slice
# or
secret = Crypto::Secret::Bidet.copy_from slice
# or
secret = Crypto::Secret::Bidet size_in_bytes
secret.move_from slice
What is a Secret?
Secrets are Keys
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
- 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
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. Or for most applications the master key may use a best effort approach using a combination of [guard pages, mlock, mprotect]. Other keys in the same application may handle a high volume of messages where [guard pages, mlock, mprotect] overhead is too high. A key verifying a public key signature may not be Secret (but is a Secret::Not).
How do I use a Secret returned by a shard?
Accessing as a Slice(UInt8) | Bytes
secret = method_that_returns_a_secret()
secret.wipe do
secret.readonly do |slice|
...
end
secret.readwrite do |slice|
...
end
end
Using a Secret to hold decrypted file contents:
key = ...another Secret...
encrypted_str = File.read("filename")
decrypted_size = encrypted_str.bytesize - mac_size
file_secret = Crypto::Secret::Large.new decrypted_size
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
Reusing a Secret
# May be used to generate new keys
secret.random
# Copy to secret and wipe `slice`
secret.move_from slice
When should I use a Secret?
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.
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?
- 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
Each implementation may add additional protections
Crypto::Secret::Key
- May use mlock, mprotect and canaries in future versionsCrypto::Secret::Large
- May use mprotect in future versionsCrypto::Secret::Not
- It's not secret. Doesn't wipe and no additional protection.
Other languages/libraries
- rust: secrets
- c: libsodium
- go: memguard
- haskell: securemem
- c#: SecureString
Implementing a new Secret holding class
Only intended for use by crypto library authors
class MySecret
# Choose one
include Crypto::Secret::Stateless
include Crypto::Secret::Stateful
def initialize(size)
# allocate or reference storage
# optionally mlock
end
protected def to_slice(& : Bytes -> Nil)
# The yielded Slice only needs to be valid within the block
# yield Slice.new(pointer, size)
ensure
# optionally reencrypt or signal HSM
end
def bytesize : Int32
# return the size
end
# optionally override [noaccess, readonly, readwrite]
# optionally override (almost) any other method with an implementation specific version
end
Contributing
Open a discussion or issue before creating PR's
- Fork it (https://github.com/your-github-user/crypto-secret/fork)
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create a new Pull Request
Contributors
- didactic-drunk - current maintainer