Add Chalsa stream ciphers.
This commit is contained in:
parent
2c3ddf6f0d
commit
346d1ea47b
11
README.md
11
README.md
@ -29,10 +29,11 @@ dependencies:
|
|||||||
- [x] Detached Signatures
|
- [x] Detached Signatures
|
||||||
- Secret-Key Cryptography
|
- Secret-Key Cryptography
|
||||||
- [x] Secret Box
|
- [x] Secret Box
|
||||||
- [ ] Salsa20
|
- [x] XSalsa20
|
||||||
- [ ] XSalsa20
|
- [x] Salsa20
|
||||||
- [ ] ChaCha20
|
- [x] XChaCha20
|
||||||
- [ ] XChaCha20
|
- [x] ChaCha20 Ietf
|
||||||
|
- [x] ChaCha20
|
||||||
- Hashing
|
- Hashing
|
||||||
- [x] Blake2b
|
- [x] Blake2b
|
||||||
- [ ] SipHash
|
- [ ] SipHash
|
||||||
@ -44,9 +45,9 @@ dependencies:
|
|||||||
- [ ] One time auth
|
- [ ] One time auth
|
||||||
|
|
||||||
Several libsodium API's are already provided by Crystal:
|
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))
|
* 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))
|
* 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
|
## Usage
|
||||||
|
|
||||||
|
23
spec/cox/cipher/chalsa_spec.cr
Normal file
23
spec/cox/cipher/chalsa_spec.cr
Normal 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
100
src/cox/cipher/chalsa.cr
Normal 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
|
@ -63,6 +63,21 @@ module Cox
|
|||||||
key : Pointer(LibC::UChar),
|
key : Pointer(LibC::UChar),
|
||||||
) : LibC::Int
|
) : 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(
|
fun crypto_box_keypair(
|
||||||
public_key_output : Pointer(LibC::UChar),
|
public_key_output : Pointer(LibC::UChar),
|
||||||
secret_key_output : Pointer(LibC::UChar)
|
secret_key_output : Pointer(LibC::UChar)
|
||||||
|
Loading…
Reference in New Issue
Block a user