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?
- 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
Crypto::Secret::Guarded
- Guard pages, mprotect, doesn't appear in core dumps (os dependent)Crypto::Secret::Bidet
- Wipe only. Low overhead.Crypto::Secret::Not
- It's not secret. Doesn't wipe and no additional protection.Crypto::Secret::Todo
- Uses mlock, mprotect and canaries in future versions
Secret providers may implement additional protections via:
#noaccess
,#readonly
or#readwrite
viamprotect
- 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
-
Add the dependency to your
shard.yml
:dependencies: crypto-secret: github: didactic-drunk/crypto-secret.cr
-
Run
shards install
- 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"
secret = Crypto::Secret.for(32, :secret_key)
# Don't forget to wipe!
secret.wipe do
secret.readonly do |slice|
# May only read from slice
end
secret.readwrite do |slice|
# May read or write to slice
end
end # secret is erased
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.
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.new size_in_bytes
secret.move_from slice
The default should be sufficient for most applications. Do not change unless you have special needs.
Password managers or cryptocurrency wallets may prefer :strong or :paranoid.
Blockchain verifiers or apps that only handle high volume public info may prefer :lax.
# Choose one
Crypto::Secret::Config.setup :paranoid
Crypto::Secret::Config.setup :strong
#Crypto::Secret::Config.setup :default # automatic
Crypto::Secret::Config.setup :lax
See #setup for further information.
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 stateIO::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
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).
secret = method_that_returns_a_secret()
secret.wipe do
secret.readonly do |slice|
...
end
secret.readwrite do |slice|
...
end
end
key = ...another Secret...
encrypted_str = File.read("filename")
decrypted_size = encrypted_str.bytesize - mac_size
file_secret = Crypto::Secret.for(decrypted_size, :data)
file_secret.wipe do
file_secret.readwrite do |slice|
decrypt(key: key, src: encrypted_str, dst: slice)
# Do something with file contents in slice
end
end # Decrypted data is erased
# May be used to generate new keys
secret.random
# Copy to secret and wipe `slice`
secret.move_from slice
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.for(key_size, :secret_key)
...
{key, encrypted_slice}
end
end
- rust: secrets
- c: libsodium
- go: memguard
- haskell: securemem
- c#: SecureString
Only intended for use by crypto library authors
class MySecret < Crypto::Secret
# Choose one
include Crypto::Secret::Stateless
include Crypto::Secret::Stateful
def initialize(size : Int32)
# 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 buffer_bytesize : Int32
# return the size
end
# if Stateful provide [noaccess_impl, readonly_impl, readwrite_impl]
# optionally override (almost) any other method with an implementation specific version
end
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
- didactic-drunk - current maintainer