11 KiB
sodium.cr
Crystal bindings for the libsodium API
Goals
- Provide the most commonly used libsodium API's.
- Provide an easy to use API based on reviewing most other libsodium bindings.
- Test for compatibility against other libsodium bindings to ensure interoperability.
- Always provide a stream interface to handle arbitrarily sized data when one is available.
- Drop in replacement classes compatible with OpenSSL::{Digest,Cipher} when possible.
- Use the newest system packaged libsodium or download the most recent stable version without manual configuration.
Features
- Public-Key Cryptography
- Secret-Key Cryptography
- Secret Box
- AEAD
- AES256-GCM (Requires hardware acceleration)
- XChaCha20-Poly1305-IETF
- ChaCha20-Poly1305-IETF
- ChaCha20-Poly1305
- Hashing
- Password Hashing
- Other
- [Advanced](https://libsodium.gitbook.io/doc/advanced0
- Stream Ciphers
- XSalsa20
- Salsa20
- XChaCha20
- ChaCha20 Ietf
- ChaCha20
- One time auth
- Padding
- (Partial) Semi-automatic memory wiping.
- Stream Ciphers
☑ Indicate specs are compared against test vectors from another source.
Several features in libsodium are already provided by Crystal:
- Random (Use Random::Secure)
- SHA-2 (Use OpenSSL::Digest)
- HMAC SHA-2 (Use OpenSSL::HMAC)
What should I use for my application?
Class | |
---|---|
CryptoBox Sign SecretBox |
I don't know much about crypto. |
Sodium::CryptoBox::SecretKey |
I want to encrypt + authenticate data using public key encryption. |
Sodium::Sign::SecretKey |
I want to sign or verify messages without encryption. |
Sodium::SecretBox |
I have a shared key and want to encrypt + authenticate data. |
AEAD | I have a shared key and want encrypt + authenticate streamed data. (Not implemented yet) |
Sodium::Digest::Blake2b |
I want to hash data fast and securely. |
Sodium::Digest::SipHash |
I want to hash data really fast and less securely. (Not implemented yet) |
Sodium::Pwhash |
I want to hash a password and store it. |
Sodium::Pwhash |
I want to derive a key from a password. |
Sodium::Kdf |
I have a high quality master key and want to make subkeys. |
Sodium::Cipher::Chalsa |
What goes with guacamole? |
Everything else | I want to design my own crypto protocol and probably do it wrong. |
Installation
Optionally Install libsodium. A recent version of libsodium is automatically downloaded and compiled if you don't install your own version.
Add this to your application's shard.yml
:
dependencies:
sodium.cr:
github: didactic-drunk/sodium.cr
Usage
The specs
provide the best examples of how to use or misuse this shard.
Warning
This library uses automatic memory wiping. Make sure you write your keys to disk or send them over a network
before losing references to objects that contain keys. You may also call .dup
.
# Returns Bytes representation and loses reference to SecretKey
def new_key
secret_key = Sodium::CryptoBox::SecretKey.new
secret_key.bytes
end
saved_key = new_key
p saved_key[0, 8]
GC.collect
p saved_key[0, 8]
# Before GC
Bytes[175, 134, 134, 159, 149, 208, 171, 251]
# After GC
Bytes[0, 0, 0, 0, 0, 0, 0, 0]
You may call .close
on any object that retains keying material to wipe it's key(s) earlier.
Objects with a .close
method also respond to Class.open
and wipe when the block returns.
Sodium::CryptoBox::SecretKey.open(sec_key, pub_key) do |secret_key|
... Do crypto operations ...
end
# sec_key is wiped
# public keys aren't wiped.
CryptoBox easy encryption
require "sodium"
data = "Hello World!"
# Alice is the sender
alice = Sodium::CryptoBox::SecretKey.new
# Bob is the recipient
bob = Sodium::CryptoBox::SecretKey.new
# Precompute a shared secret between alice and bob.
box = alice.box bob.public_key
# Encrypt a message for Bob using his public key, signing it with Alice's
# secret key
nonce, encrypted = box.encrypt data
# Precompute within a block. The shared secret is wiped when the block exits.
bob.box alice.public_key do |box|
# Decrypt the message using Bob's secret key, and verify its signature against
# Alice's public key
decrypted = Sodium.decrypt(encrypted, nonce, alice.public, bob.secret)
String.new(decrypted) # => "Hello World!"
end
Public key signing
message = "Hello World!"
secret_key = Sodium::Sign::SecretKey.new
# Sign the message
signature = secret_key.sign_detached message
# Send secret_key.public_key to the recipient
public_key = Sodium::Sign::PublicKey.new key_bytes
# raises Sodium::Error::VerificationFailed on failure.
public_key.verify_detached message, signature
Secret Key Encryption
key = Sodium::SecretKey.new
message = "foobar"
encrypted, nonce = key.encrypt_easy message
# On the other side.
key = Sodium::SecretKey.new key
message = key.decrypt_easy encrypted, nonce
Blake2b
key = Bytes.new Sodium::Digest::Blake2B::KEY_SIZE
salt = Bytes.new Sodium::Digest::Blake2B::SALT_SIZE
personal = Bytes.new Sodium::Digest::Blake2B::PERSONAL_SIZE
out_size = 64 # bytes between Sodium::Digest::Blake2B::OUT_SIZE_MIN and Sodium::Digest::Blake2B::OUT_SIZE_MAX
data = "data".to_slice
# output_size, key, salt, and personal are optional.
digest = Sodium::Digest::Blake2b.new out_size, key: key, salt: salt, personal: personal
digest.update data
output = d.hexdigest
digest.reset # Reuse existing object to hash again.
digest.update data
output = d.hexdigest
Key derivation
kdf = Sodium::Kdf.new
# kdf.derive(8_byte_context, subkey_id, subkey_size)
subkey1 = kdf.derive "context1", 0, 16
subkey2 = kdf.derive "context1", 1, 16
subkey3 = kdf.derive "context2", 0, 32
subkey4 = kdf.derive "context2", 1, 64
Password Hashing
pwhash = Sodium::Pwhash.new
pwhash.memlimit = Sodium::Pwhash::MEMLIMIT_MIN
pwhash.opslimit = Sodium::Pwhash::OPSLIMIT_MIN
pass = "1234"
hash = pwhash.hash_str pass
pwhash.verify hash, pass
Use examples/pwhash_selector.cr
to help choose ops/mem limits.
Example output: Ops limit →
1 | 4 | 16 | 64 | 256 | 1024 | 4096 | 16384 | 65536 | 262144 | 1048576 | |
---|---|---|---|---|---|---|---|---|---|---|---|
8K | 0.542s | 2.114s | |||||||||
32K | 0.513s | 2.069s | |||||||||
128K | 0.530s | 2.121s | |||||||||
512K | 0.566s | 2.237s | |||||||||
2048K | 0.567s | 2.290s | |||||||||
8192K | 0.670s | 2.542s | |||||||||
32768K | 0.684s | 2.777s | |||||||||
131072K | 0.805s | 3.106s | |||||||||
524288K | 0.504s | 1.135s | 3.661s | ||||||||
2097152K | 2.119s | ||||||||||
Memory |
Contributing
- Fork it ( https://github.com/didactic-drunk/sodium.cr/fork )
- Install a formatting check git hook (ln -sf ../../scripts/git/pre-commit .git/hooks)
- 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
Project History
- Originally created by Andrew Hamon
- Forked by Didactic Drunk for lack of updates in the original project.
- Complaints about the name being too controversial. Project name changed from "cox" to a more libsodium related name of "salty seaman".
- ~50% complete libsodium API.
- More complaints about the name. Dead hooker jokes added.
- None of the original API is left.
- More complaints threatening a boycott. Told them "Go ahead, I own Coca Cola and Water".
- Account unsuspended.
- Unrelated to the boycott the project name changed to "libsodium" because sodium happens to be a tasty byproduct of the two earlier names.
- Account unsuspended.
- Dead hooker jokes (mostly) removed.
Contributors
- andrewhamon Andrew Hamon - creator, former maintainer
- dorkrawk Dave Schwantes - contributor
- didactic-drunk - current maintainer