├── spec ├── spec_helper.cr └── cox_spec.cr ├── src ├── cox │ ├── version.cr │ ├── key.cr │ ├── public_key.cr │ ├── secret_key.cr │ ├── sign_public_key.cr │ ├── sign_secret_key.cr │ ├── key_pair.cr │ ├── nonce.cr │ ├── sign_key_pair.cr │ └── lib_sodium.cr └── cox.cr ├── shard.yml ├── .editorconfig ├── .gitignore ├── .travis.yml ├── travis-install-lib-sodium.sh ├── LICENSE └── README.md /spec/spec_helper.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "../src/cox" 3 | -------------------------------------------------------------------------------- /src/cox/version.cr: -------------------------------------------------------------------------------- 1 | module Cox 2 | VERSION = "0.1.0" 3 | end 4 | -------------------------------------------------------------------------------- /shard.yml: -------------------------------------------------------------------------------- 1 | name: cox 2 | version: 0.1.0 3 | 4 | authors: 5 | - Andrew Hamon 6 | 7 | crystal: 0.24.1 8 | 9 | license: MIT 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cr] 2 | charset = utf-8 3 | end_of_line = lf 4 | insert_final_newline = true 5 | indent_style = space 6 | indent_size = 2 7 | trim_trailing_whitespace = true 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /doc/ 2 | /lib/ 3 | /bin/ 4 | /.shards/ 5 | 6 | # Libraries don't need dependency lock 7 | # Dependencies will be locked in application that uses them 8 | /shard.lock 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: true 2 | language: crystal 3 | install: 4 | - ./travis-install-lib-sodium.sh 5 | before_script: 6 | - export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib 7 | -------------------------------------------------------------------------------- /travis-install-lib-sodium.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # The purpose of this file is to install libsodium in 3 | # the Travis CI environment. We recommend using a 4 | # package manager. 5 | 6 | set -e 7 | 8 | wget https://download.libsodium.org/libsodium/releases/LATEST.tar.gz 9 | tar xvfz LATEST.tar.gz 10 | cd libsodium-stable 11 | sudo ./configure 12 | sudo make 13 | sudo make install 14 | -------------------------------------------------------------------------------- /src/cox/key.cr: -------------------------------------------------------------------------------- 1 | module Cox 2 | abstract class Key 3 | abstract def bytes 4 | 5 | def pointer 6 | bytes.to_unsafe 7 | end 8 | 9 | def pointer(size) 10 | bytes.pointer(size) 11 | end 12 | 13 | def to_base64 14 | Base64.encode(bytes) 15 | end 16 | 17 | def self.from_base64(encoded_key) 18 | new(Base64.decode(encoded_key)) 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /src/cox/public_key.cr: -------------------------------------------------------------------------------- 1 | require "./lib_sodium" 2 | 3 | module Cox 4 | class PublicKey < Key 5 | property bytes : Bytes 6 | 7 | KEY_LENGTH = LibSodium::PUBLIC_KEY_BYTES 8 | 9 | def initialize(@bytes : Bytes) 10 | if bytes.bytesize != KEY_LENGTH 11 | raise ArgumentError.new("Public key must be #{KEY_LENGTH} bytes, got #{bytes.bytesize}") 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /src/cox/secret_key.cr: -------------------------------------------------------------------------------- 1 | require "./lib_sodium" 2 | 3 | module Cox 4 | class SecretKey < Key 5 | property bytes : Bytes 6 | 7 | KEY_LENGTH = LibSodium::SECRET_KEY_BYTES 8 | 9 | def initialize(@bytes : Bytes) 10 | if bytes.bytesize != KEY_LENGTH 11 | raise ArgumentError.new("Secret key must be #{KEY_LENGTH} bytes, got #{bytes.bytesize}") 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /src/cox/sign_public_key.cr: -------------------------------------------------------------------------------- 1 | require "./lib_sodium" 2 | 3 | module Cox 4 | class SignPublicKey < Key 5 | property bytes : Bytes 6 | 7 | KEY_LENGTH = LibSodium::PUBLIC_SIGN_BYTES 8 | 9 | def initialize(@bytes : Bytes) 10 | if bytes.bytesize != KEY_LENGTH 11 | raise ArgumentError.new("Public key must be #{KEY_LENGTH} bytes, got #{bytes.bytesize}") 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /src/cox/sign_secret_key.cr: -------------------------------------------------------------------------------- 1 | require "./lib_sodium" 2 | 3 | module Cox 4 | class SignSecretKey < Key 5 | property bytes : Bytes 6 | 7 | KEY_LENGTH = LibSodium::SECRET_SIGN_BYTES 8 | 9 | def initialize(@bytes : Bytes) 10 | if bytes.bytesize != KEY_LENGTH 11 | raise ArgumentError.new("Secret key must be #{KEY_LENGTH} bytes, got #{bytes.bytesize}") 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /src/cox/key_pair.cr: -------------------------------------------------------------------------------- 1 | require "./lib_sodium" 2 | 3 | module Cox 4 | class KeyPair 5 | property public : PublicKey 6 | property secret : SecretKey 7 | 8 | def initialize(@public, @secret) 9 | end 10 | 11 | def self.new(pub : Bytes, sec : Bytes) 12 | new(PublicKey.new(pub), SecretKey.new(sec)) 13 | end 14 | 15 | def self.new 16 | public_key = Bytes.new(PublicKey::KEY_LENGTH) 17 | secret_key = Bytes.new(SecretKey::KEY_LENGTH) 18 | 19 | LibSodium.crypto_box_keypair(public_key.to_unsafe, secret_key.to_unsafe) 20 | 21 | new(public_key, secret_key) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /src/cox/nonce.cr: -------------------------------------------------------------------------------- 1 | require "./lib_sodium" 2 | require "random/secure" 3 | 4 | module Cox 5 | class Nonce 6 | property bytes : Bytes 7 | 8 | NONCE_LENGTH = LibSodium::NONCE_BYTES 9 | 10 | def initialize(@bytes : Bytes) 11 | if bytes.bytesize != NONCE_LENGTH 12 | raise ArgumentError.new("Nonce must be #{NONCE_LENGTH} bytes, got #{bytes.bytesize}") 13 | end 14 | end 15 | 16 | def self.new 17 | new(Random::Secure.random_bytes(NONCE_LENGTH)) 18 | end 19 | 20 | def pointer 21 | bytes.to_unsafe 22 | end 23 | 24 | def pointer(size) 25 | bytes.pointer(size) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /src/cox/sign_key_pair.cr: -------------------------------------------------------------------------------- 1 | require "./lib_sodium" 2 | 3 | module Cox 4 | class SignKeyPair 5 | property public : SignPublicKey 6 | property secret : SignSecretKey 7 | 8 | def initialize(@public, @secret) 9 | end 10 | 11 | def self.new(pub : Bytes, sec : Bytes) 12 | new(SignPublicKey.new(pub), SignSecretKey.new(sec)) 13 | end 14 | 15 | def self.new 16 | public_key = Bytes.new(SignPublicKey::KEY_LENGTH) 17 | secret_key = Bytes.new(SignSecretKey::KEY_LENGTH) 18 | 19 | LibSodium.crypto_sign_keypair(public_key.to_unsafe, secret_key.to_unsafe) 20 | 21 | new(public_key, secret_key) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Andrew Hamon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /spec/cox_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | describe Cox do 4 | # TODO: Write tests 5 | 6 | it "works for encrypting" do 7 | data = "Hello World!" 8 | 9 | # Alice is the sender 10 | alice = Cox::KeyPair.new 11 | 12 | # Bob is the recipient 13 | bob = Cox::KeyPair.new 14 | 15 | # Encrypt a message for Bob using his public key, signing it with Alice's 16 | # secret key 17 | nonce, encrypted = Cox.encrypt(data, bob.public, alice.secret) 18 | 19 | # Decrypt the message using Bob's secret key, and verify its signature against 20 | # Alice's public key 21 | decrypted = Cox.decrypt(encrypted, nonce, alice.public, bob.secret) 22 | 23 | String.new(decrypted).should eq(data) 24 | end 25 | 26 | it "works for signing" do 27 | message = "test" 28 | 29 | signing_pair = Cox::SignKeyPair.new 30 | 31 | # Create signature using the secret key 32 | signature = Cox.sign_detached(message, signing_pair.secret) 33 | 34 | # Verify the signature on the message 35 | verified = Cox.verify_detached(signature, message, signing_pair.public) 36 | 37 | verified.should eq(true) 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cox 2 | [![Build Status](https://travis-ci.org/andrewhamon/cox.svg?branch=master)](https://travis-ci.org/andrewhamon/cox) 3 | 4 | Crystal bindings for the [libsodium box API](https://download.libsodium.org/doc/public-key_cryptography/authenticated_encryption.html) 5 | 6 | Given a recipients public key, you can encrypt and sign a message for them. Upon 7 | receipt, they can decrypt and authenticate the message as having come from you. 8 | 9 | ## Installation 10 | 11 | **[Install libsodium](https://download.libsodium.org/doc/installation/)**, then: 12 | 13 | Add this to your application's `shard.yml`: 14 | 15 | ```yaml 16 | dependencies: 17 | cox: 18 | github: andrewhamon/cox 19 | ``` 20 | 21 | ## Usage 22 | 23 | ```crystal 24 | require "cox" 25 | 26 | data = "Hello World!" 27 | 28 | # Alice is the sender 29 | alice = Cox::KeyPair.new 30 | 31 | # Bob is the recipient 32 | bob = Cox::KeyPair.new 33 | 34 | # Encrypt a message for Bob using his public key, signing it with Alice's 35 | # secret key 36 | nonce, encrypted = Cox.encrypt(data, bob.public, alice.secret) 37 | 38 | # Decrypt the message using Bob's secret key, and verify its signature against 39 | # Alice's public key 40 | decrypted = Cox.decrypt(encrypted, nonce, alice.public, bob.secret) 41 | 42 | String.new(decrypted) # => "Hello World!" 43 | 44 | 45 | # Public key signing 46 | 47 | message = "Hello World!" 48 | 49 | signing_pair = Cox::SignKeyPair.new 50 | 51 | # Sign the message 52 | signature = Cox.sign_detached(message, signing_pair.secret) 53 | 54 | # And verify 55 | Cox.verify_detached(signature, message, signing_pair.public) # => true 56 | ``` 57 | 58 | ## Contributing 59 | 60 | 1. Fork it ( https://github.com/andrewhamon/cox/fork ) 61 | 2. Create your feature branch (git checkout -b my-new-feature) 62 | 3. Commit your changes (git commit -am 'Add some feature') 63 | 4. Push to the branch (git push origin my-new-feature) 64 | 5. Create a new Pull Request 65 | 66 | ## Contributors 67 | 68 | - [andrewhamon](https://github.com/andrewhamon) Andrew Hamon - creator, maintainer 69 | - [dorkrawk](https://github.com/dorkrawk) Dave Schwantes - contributor 70 | -------------------------------------------------------------------------------- /src/cox.cr: -------------------------------------------------------------------------------- 1 | require "./cox/*" 2 | 3 | module Cox 4 | def self.encrypt(data, nonce : Nonce, recipient_public_key : PublicKey, sender_secret_key : SecretKey) 5 | data_buffer = data.to_slice 6 | data_size = data_buffer.bytesize 7 | output_buffer = Bytes.new(data_buffer.bytesize + LibSodium::MAC_BYTES) 8 | LibSodium.crypto_box_easy(output_buffer.to_unsafe, data_buffer, data_size, nonce.pointer, recipient_public_key.pointer, sender_secret_key.pointer) 9 | output_buffer 10 | end 11 | 12 | def self.encrypt(data, recipient_public_key : PublicKey, sender_secret_key : SecretKey) 13 | nonce = Nonce.new 14 | {nonce, encrypt(data, nonce, recipient_public_key, sender_secret_key)} 15 | end 16 | 17 | def self.decrypt(data, nonce : Nonce, sender_public_key : PublicKey, recipient_secret_key : SecretKey) 18 | data_buffer = data.to_slice 19 | data_size = data_buffer.bytesize 20 | output_buffer = Bytes.new(data_buffer.bytesize - LibSodium::MAC_BYTES) 21 | LibSodium.crypto_box_open_easy(output_buffer.to_unsafe, data_buffer.to_unsafe, data_size, nonce.pointer, sender_public_key.pointer, recipient_secret_key.pointer) 22 | output_buffer 23 | end 24 | 25 | def self.sign_detached(message, secret_key : SignSecretKey) 26 | message_buffer = message.to_slice 27 | message_buffer_size = message_buffer.bytesize 28 | signature_output_buffer = Bytes.new(LibSodium::SIGNATURE_BYTES) 29 | 30 | LibSodium.crypto_sign_detached(signature_output_buffer.to_unsafe, 0, message_buffer.to_unsafe, message_buffer_size, secret_key.pointer) 31 | signature_output_buffer 32 | end 33 | 34 | def self.verify_detached(signature, message, public_key : SignPublicKey) 35 | signature_buffer = signature.to_slice 36 | message_buffer = message.to_slice 37 | message_buffer_size = message_buffer.bytesize 38 | 39 | verified = LibSodium.crypto_sign_verify_detached(signature_buffer.to_unsafe, message_buffer.to_unsafe, message_buffer_size, public_key.pointer) 40 | verified.zero? 41 | end 42 | end 43 | 44 | if Cox::LibSodium.sodium_init() == -1 45 | STDERR.puts("Failed to init libsodium") 46 | exit(1) 47 | end 48 | -------------------------------------------------------------------------------- /src/cox/lib_sodium.cr: -------------------------------------------------------------------------------- 1 | module Cox 2 | @[Link("sodium")] 3 | lib LibSodium 4 | fun sodium_init() : LibC::Int 5 | 6 | fun crypto_box_publickeybytes() : LibC::SizeT 7 | fun crypto_box_secretkeybytes() : LibC::SizeT 8 | fun crypto_box_noncebytes() : LibC::SizeT 9 | fun crypto_box_macbytes() : LibC::SizeT 10 | fun crypto_sign_publickeybytes() : LibC::SizeT 11 | fun crypto_sign_secretkeybytes() : LibC::SizeT 12 | fun crypto_sign_bytes() : LibC::SizeT 13 | 14 | PUBLIC_KEY_BYTES = crypto_box_publickeybytes() 15 | SECRET_KEY_BYTES = crypto_box_secretkeybytes() 16 | NONCE_BYTES = crypto_box_noncebytes() 17 | MAC_BYTES = crypto_box_macbytes() 18 | PUBLIC_SIGN_BYTES = crypto_sign_publickeybytes() 19 | SECRET_SIGN_BYTES = crypto_sign_secretkeybytes() 20 | SIGNATURE_BYTES = crypto_sign_bytes() 21 | 22 | fun crypto_box_keypair( 23 | public_key_output : Pointer(LibC::UChar), 24 | secret_key_output : Pointer(LibC::UChar) 25 | ) 26 | 27 | fun crypto_box_easy( 28 | output : Pointer(LibC::UChar), 29 | data : Pointer(LibC::UChar), 30 | data_size : LibC::ULongLong, 31 | nonce : Pointer(LibC::UChar), 32 | recipient_public_key : Pointer(LibC::UChar), 33 | sender_secret_key : Pointer(LibC::UChar) 34 | ) : LibC::Int 35 | 36 | fun crypto_box_open_easy( 37 | output : Pointer(LibC::UChar), 38 | data : Pointer(LibC::UChar), 39 | data_size : LibC::ULongLong, 40 | nonce : Pointer(LibC::UChar), 41 | sender_public_key : Pointer(LibC::UChar), 42 | recipient_secret_key : Pointer(LibC::UChar) 43 | ) : LibC::Int 44 | 45 | fun crypto_sign_keypair( 46 | public_key_output : Pointer(LibC::UChar), 47 | secret_key_output : Pointer(LibC::UChar) 48 | ) : LibC::Int 49 | 50 | fun crypto_sign_detached( 51 | signature_output : Pointer(LibC::UChar), 52 | signature_output_size : LibC::ULongLong, 53 | message : Pointer(LibC::UChar), 54 | message_size : LibC::ULongLong, 55 | secret_key : Pointer(LibC::UChar) 56 | ) : LibC::Int 57 | 58 | fun crypto_sign_verify_detached( 59 | signature : Pointer(LibC::UChar), 60 | message : Pointer(LibC::UChar), 61 | message_size : LibC::ULongLong, 62 | public_key : Pointer(LibC::UChar) 63 | ) : LibC::Int 64 | end 65 | end 66 | --------------------------------------------------------------------------------