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
- [x] ☑ Crypto Box Easy
- [x] Sealed Box
- [ ] Combined Signatures
- [x] ☑ Detached Signatures
- [ ] Pre-hashed Signatures
- Secret-Key Cryptography
- Secret Box
- [x] ☑ Combined mode
- [ ] Detached mode
- [x] Secret Stream
- AEAD
- [ ] AES256-GCM (Requires hardware acceleration)
- [x] ☑ XChaCha20-Poly1305-IETF
- [ ] ChaCha20-Poly1305-IETF
- [ ] ChaCha20-Poly1305
- [x] Combined and detached mode
- Hashing
- [x] ☑ Blake2b
- [x] Complete libsodium implementation including
key
,salt
,personal
and fully selectable output sizes. - [ ] SipHash
- Password Hashing
- Other
- [x] Key Derivation
- [ ] Key Exchange
- Advanced
- Stream Ciphers
- [x] XSalsa20
- [x] Salsa20
- [x] XChaCha20
- [x] ChaCha20 Ietf
- [x] ChaCha20
- [x] Easy to use methods available for use as a CSPRNG that are faster and safer than Crystal's. See
benchmarks/rand.out
. - [ ] One time auth
- [ ] Padding
- Library features
- [x] Faster builds by requiring what you need (
require "sodium/secret_box"
) - [x] Nonce reuse detection.
- [x] All SecretKey's held in libsodium guarded memory.
- [x] No heap allocations after #initialize when possible.
- [x] Fast. Benchmarks available in
benchmarks
. - [x] Most classes are safe to share between threads.
- [x] Tested with real crystal threads and will continue to work when crystal officially supports threading.
- [x] Most classes are safe to share between threads. Even
- [ ] Controlled memory wiping (by calling
.close
)
☑ 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)
- Hex conversion (Use String#hexbytes)
What should I use for my application?
| Class | |
| --- | --- |
| Only use CryptoBox::SecretKey
Sign::SecretKey
Aead::XChaCha20Poly1305Ietf
SecretBox
| I don't know much about crypto. |
| Sodium::CryptoBox::SecretKey
.box | I want to encrypt + authenticate data using public key encryption. |
| Sodium::CryptoBox::SecretKey
.encrypt | I want anonymously send encrypted data. (No signatures) |
| Sodium::Sign::SecretKey
| I want to sign or verify messages. (No encryption) |
| Sodium::Cipher::Aead::XChaCha20Poly1305Ietf
(new applications) Sodium::SecretBox
(compatibility with older applications) | I have a shared key and want to encrypt + authenticate data. |
| Sodium::Cipher::Aead::XChaCha20Poly1305Ietf
| I have a shared key and want to encrypt + authenticate data and authenticate additional plaintext data. |
| Sodium::Cipher::SecretStream
| I have a shared key and want encrypt + authenticate streamed data. |
| 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::Password::Hash
| I want to hash a password and store it. |
| Sodium::Password::Key
| 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:
github: didactic-drunk/sodium.cr
Usage
See examples
for help on using these classes in a complete application.
The specs
provide the best examples of how to use or misuse individual classes.
### CryptoBox authenticated 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
encrypted, nonce = 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 = box.decrypt encrypted, nonce: nonce
String.new(decrypted) # => "Hello World!" end
### Unauthenticated public key encryption
data = "Hello World!"
Bob is the recipient
bob = Sodium::CryptoBox::SecretKey.new
Encrypt a message for Bob using his public key
encrypted = bob.public_key.encrypt data
Decrypt the message using Bob's secret key
decrypted = bob.decrypt encrypted String.new(decrypted) # => "Hello World!"
### 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
On 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
box = Sodium::SecretBox.new
message = "foobar" encrypted, nonce = box.encrypt message
On the other side.
box = Sodium::SecretKey.new key message = box.decrypt encrypted, nonce: 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.hexfinal
digest.reset # Reuse existing object to hash again. digest.update data output = d.hexfinal
### 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 based keys
pwcreate = Sodium::Password::Key::Create.new
Take approximately 1 second to derive a key.
pwcreate.tcost = 1.0
pass = "1234" key, params = pwcreate.create_key pass
Store params
or params.to_h
for later.
Derive the same key from the stored params.
pwkey = Sodium::Password::Key.from_params params.to_h key = pekey.derive_key pass
### Password Hashing
pwhash = Sodium::Password::Hash.new
pwhash.mem = Sodium::Password::MEMLIMIT_MIN pwhash.ops = Sodium::Password::OPSLIMIT_MIN
pass = "1234" hash = pwhash.create 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
1. Fork it ( https://github.com/didactic-drunk/sodium.cr/fork )
2. **Install a formatting check git hook (ln -sf ../../scripts/git/pre-commit .git/hooks)**
3. Create your feature branch (git checkout -b my-new-feature)
4. Commit your changes (git commit -am 'Add some feature')
5. Push to the branch (git push origin my-new-feature)
6. Create a new Pull Request
## Project History
* Originally created by [Andrew Hamon](https://github.com/andrewhamons/cox)
* Forked by [Didactic Drunk](https://github.com/didactic-drunk/cox) 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](https://github.com/andrewhamon) Andrew Hamon - creator, former maintainer
- [dorkrawk](https://github.com/dorkrawk) Dave Schwantes - contributor
- [didactic-drunk](https://github.com/didactic-drunk) - current maintainer