Add Chalsa stream ciphers.
parent
2c3ddf6f0d
commit
346d1ea47b
11
README.md
11
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
|
||||
|
||||
|
|
|
@ -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 %}
|
||||
|
|
@ -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
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue