sodium.cr

Build Status GitHub release Docs

Crystal bindings for the libsodium API

Goals

Features

☑ Indicate specs are compared against test vectors from another source.

Several features in libsodium are already provided by Crystal:

What should I use for my application?

| Class | | | --- | --- | | Only use CryptoBox::SecretKey Sign::SecretKey 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::SecretBox | 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 authentication 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