Go to file
2021-06-21 17:19:42 -07:00
.github/workflows Github actions 2021-06-12 19:57:37 -07:00
spec Add Crypto::Secret::Key & Crypto::Secret::Large 2021-06-17 03:16:34 -07:00
src Secret: #readonly, #readwrite, #noaccess add abstract & type restrictions 2021-06-21 17:19:42 -07:00
.editorconfig Init 2021-06-12 16:03:59 -07:00
.gitignore Init 2021-06-12 16:03:59 -07:00
LICENSE Init 2021-06-12 16:03:59 -07:00
README.md README.md: Finish TODO sections 2021-06-21 15:45:07 -07:00
shard.yml Version 0.2.0 2021-06-21 02:53:45 -07:00

crypto-secret.cr

Crystal CI GitHub release GitHub commits since latest release (by date) for a branch Docs

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

  1. Add the dependency to your shard.yml:

    dependencies:
      crypto-secret:
        github: didactic-drunk/crypto-secret
    
  2. Run shards install

Usage

Rules:

  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
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 :

  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
  3. 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 versions
  • Crypto::Secret::Large - May use mprotect in future versions
  • Crypto::Secret::Not - It's not secret. Doesn't wipe and no additional protection.

Other languages/libraries

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

  1. Fork it (https://github.com/your-github-user/crypto-secret/fork)
  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