Add Chalsa stream ciphers.

master
Didactic Drunk 2019-06-27 13:52:09 -07:00
parent 2c3ddf6f0d
commit 346d1ea47b
4 changed files with 144 additions and 5 deletions

View File

@ -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

View File

@ -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 %}

100
src/cox/cipher/chalsa.cr Normal file
View File

@ -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

View File

@ -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)