From 346d1ea47bb2e62479fab7a02fea5f8164abd169 Mon Sep 17 00:00:00 2001 From: Didactic Drunk <1479616+didactic-drunk@users.noreply.github.com> Date: Thu, 27 Jun 2019 13:52:09 -0700 Subject: [PATCH] Add Chalsa stream ciphers. --- README.md | 11 ++-- spec/cox/cipher/chalsa_spec.cr | 23 ++++++++ src/cox/cipher/chalsa.cr | 100 +++++++++++++++++++++++++++++++++ src/cox/lib_sodium.cr | 15 +++++ 4 files changed, 144 insertions(+), 5 deletions(-) create mode 100644 spec/cox/cipher/chalsa_spec.cr create mode 100644 src/cox/cipher/chalsa.cr diff --git a/README.md b/README.md index 3fb25e0..23a1e0b 100644 --- a/README.md +++ b/README.md @@ -29,10 +29,11 @@ dependencies: - [x] Detached Signatures - Secret-Key Cryptography - [x] Secret Box - - [ ] Salsa20 - - [ ] XSalsa20 - - [ ] ChaCha20 - - [ ] XChaCha20 + - [x] XSalsa20 + - [x] Salsa20 + - [x] XChaCha20 + - [x] ChaCha20 Ietf + - [x] ChaCha20 - Hashing - [x] Blake2b - [ ] SipHash @@ -44,9 +45,9 @@ dependencies: - [ ] One time auth Several libsodium API's are already provided by Crystal: +* Random (Use [Random::Secure](https://crystal-lang.org/api/latest/Random/Secure.html)) * SHA-2 (Use [OpenSSL::Digest](https://crystal-lang.org/api/latest/OpenSSL/Digest.html)) * HMAC SHA-2 (Use [OpenSSL::HMAC](https://crystal-lang.org/api/latest/OpenSSL/HMAC.html)) -* Random (Use [Random::Secure](https://crystal-lang.org/api/latest/Random/Secure.html)) ## Usage diff --git a/spec/cox/cipher/chalsa_spec.cr b/spec/cox/cipher/chalsa_spec.cr new file mode 100644 index 0000000..39925cd --- /dev/null +++ b/spec/cox/cipher/chalsa_spec.cr @@ -0,0 +1,23 @@ +require "../../spec_helper" +require "../../../src/cox/cipher/chalsa" + +{% for name in %w(XSalsa20 Salsa20 XChaCha20 ChaCha20Ietf ChaCha20) %} +# TODO: verify against test vectors. + describe Cox::Cipher::{{ name.id }} do + it "xors" do + data = Bytes.new(100) + cipher1 = Cox::Cipher::{{ name.id }}.new + cipher2 = Cox::Cipher::{{ name.id }}.new + key = cipher1.random_key + nonce = cipher1.random_nonce + output = cipher1.update data + cipher1.update(data).should_not eq output # Verify offset is incremented. + cipher1.final.should eq Bytes.new(0) + + cipher2.key = key + cipher2.nonce = nonce + cipher2.update(output).should eq data + end + end +{% end %} + diff --git a/src/cox/cipher/chalsa.cr b/src/cox/cipher/chalsa.cr new file mode 100644 index 0000000..45e160c --- /dev/null +++ b/src/cox/cipher/chalsa.cr @@ -0,0 +1,100 @@ +require "../lib_sodium" + +module Cox::Cipher + # The great beat you can eat! + # + # What? They're both dance? + abstract class Chalsa + @key : Bytes? + @nonce : Bytes? + + # Advanced usage. Don't touch. + property offset = 0 + + def initialize + end + + def initialize(key, nonce) + self.key = key if key + self.nonce = nonce if nonce + end + + def key=(key : Bytes) + raise ArgumentError.new("key must be #{key_size} bytes, got #{key.bytesize}") if key.bytesize != key_size + @key = key + key + end + + def nonce=(nonce : Bytes) + raise ArgumentError.new("nonce must be #{nonce_size} bytes, got #{nonce.bytesize}") if nonce.bytesize != nonce_size + @nonce = nonce + nonce + end + + def random_key + self.key = Random::Secure.random_bytes key_size + end + + def random_nonce + self.nonce = Random::Secure.random_bytes nonce_size + end + + # Xor's src with the cipher output and returns a new Slice + def update(src : Bytes) : Bytes + update src, Bytes.new(src.bytesize) + end + + + # Provided for compatibility with block ciphers. + # Stream ciphers don't have additional data. + def final + Bytes.new(0) + end + + # Sadness... + def edible? + false + end + + abstract def update(src : Bytes, dst : Bytes) + abstract def key_size() + abstract def nonce_size() + end + + {% for key, val in { "XSalsa20" => "xsalsa20", "Salsa20" => "salsa20", "XChaCha20" => "xchacha20", "ChaCha20Ietf" => "chacha20_ietf", "ChaCha20" => "chacha20",} %} + # These classes can be used to generate pseudo-random data from a key, + # or as building blocks for implementing custom constructions, but they + # are not alternatives to secretbox. + # + # See [https://libsodium.gitbook.io/doc/advanced/stream_ciphers](https://libsodium.gitbook.io/doc/advanced/stream_ciphers) for further information. + # + # This class mimicks the OpenSSL::Cipher interface with minor differences. + # + # See `spec/cox/cipher/chalsa_spec.cr` for examples on how to use this class. + class {{ key.id }} < Chalsa + # Xor's src with the cipher output and places in dst. + # + # src and dst may be the same object but should not overlap. + def update(src : Bytes, dst : Bytes) : Bytes + if (key = @key) && (nonce = @nonce) + raise ArgumentError.new("src and dst bytesize must be identical") if src.bytesize != dst.bytesize + if LibSodium.crypto_stream_{{ val.id }}_xor_ic(dst, src, src.bytesize, nonce, @offset, key) != 0 + raise Cox::Error.new("crypto_stream_{{ val.id }}_xor_ic") + end + @offset += src.bytesize + dst + else + raise Cox::Error.new("key and nonce must be set before calling update #{@key.nil?} #{@nonce.nil?}") + end + end + + def key_size + LibSodium.crypto_stream_chacha20_ietf_keybytes + end + + def nonce_size + LibSodium.crypto_stream_chacha20_ietf_noncebytes + end + end + {% end %} +end diff --git a/src/cox/lib_sodium.cr b/src/cox/lib_sodium.cr index bd5d51c..4c0218a 100644 --- a/src/cox/lib_sodium.cr +++ b/src/cox/lib_sodium.cr @@ -63,6 +63,21 @@ module Cox key : Pointer(LibC::UChar), ) : LibC::Int +# TODO: Add reduced round variants. + {% for name in ["_chacha20", "_chacha20_ietf", "_xchacha20", "_salsa20", "_xsalsa20"] %} + fun crypto_stream{{ name.id}}_keybytes() : LibC::SizeT + fun crypto_stream{{ name.id}}_noncebytes() : LibC::SizeT + + fun crypto_stream{{ name.id }}_xor_ic( + c : Pointer(LibC::UChar), + m : Pointer(LibC::UChar), + len : LibC::ULongLong, + nonce : Pointer(LibC::UChar), + offset : LibC::UInt64T, + key : Pointer(LibC::UChar) + ) : LibC::Int + {% end %} + fun crypto_box_keypair( public_key_output : Pointer(LibC::UChar), secret_key_output : Pointer(LibC::UChar)