diff --git a/README.md b/README.md index 4bd0022..4c449e1 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,15 @@ signature = Cox.sign_detached(message, signing_pair.secret) Cox.verify_detached(signature, message, signing_pair.public) # => true ``` +# Key derivation +kdf = Cox::Kdf.new + +# kdf.derive(8_byte_context, subkey_size, subkey_id) +subkey1 = kdf.derive "context1", 16, 0 +subkey2 = kdf.derive "context1", 16, 1 +subkey3 = kdf.derive "context2", 32, 0 +subkey4 = kdf.derive "context2", 64, 1 + ## Contributing 1. Fork it ( https://github.com/andrewhamon/cox/fork ) diff --git a/spec/cox/kdf_spec.cr b/spec/cox/kdf_spec.cr new file mode 100644 index 0000000..35bbdfe --- /dev/null +++ b/spec/cox/kdf_spec.cr @@ -0,0 +1,26 @@ +require "../spec_helper" + +CONTEXT = "8_bytess" + +describe Cox::Kdf do + it "generates master key" do + kdf1 = Cox::Kdf.new + + # verify loading saved key + kdf2 = Cox::Kdf.from_base64 kdf1.to_base64 + + # verify generated subkey's are the same after loading + key1_s1 = kdf1.derive CONTEXT, 16, 0 + key2_s1 = kdf2.derive CONTEXT, 16, 0 + key1_s1.should eq key2_s1 + end + + it "generates different keys" do + kdf1 = Cox::Kdf.new + subkey1 = kdf1.derive CONTEXT, 16, 0 + subkey2 = kdf1.derive CONTEXT, 16, 1 + subkey1.should_not eq subkey2 + end + +# TODO: test exceptions +end diff --git a/src/cox/kdf.cr b/src/cox/kdf.cr new file mode 100644 index 0000000..60fd277 --- /dev/null +++ b/src/cox/kdf.cr @@ -0,0 +1,47 @@ +module Cox + class Kdf + property bytes : Bytes + + def initialize(bytes : Bytes) + if bytes.bytesize != LibSodium::KDF_KEY_BYTES + raise ArgumentError.new("bytes must be #{LibSodium::KDF_KEY_BYTES}, got #{bytes.bytesize}") + end + + @bytes = bytes + end + + def initialize + @bytes = Random::Secure.random_bytes(LibSodium::KDF_KEY_BYTES) + end + + # context must be 8 bytes + # subkey_size must be 16..64 bytes as of libsodium 1.0.17 + def derive(context, subkey_size, subkey_id = 0) + if context.bytesize != LibSodium::KDF_CONTEXT_BYTES + raise ArgumentError.new("context must be #{LibSodium::KDF_CONTEXT_BYTES}, got #{context.bytesize}") + end + + subkey = Bytes.new subkey_size + if (ret = LibSodium.crypto_kdf_derive_from_key(subkey, subkey.bytesize, subkey_id, context, @bytes)) != 0 + raise Cox::Error.new("crypto_kdf_derive_from_key returned #{ret} (subkey size is probably out of range)") + end + subkey + end + + def pointer + bytes.to_unsafe + end + + def pointer(size) + bytes.pointer(size) + end + + def to_base64 + Base64.encode(bytes) + end + + def self.from_base64(encoded_key) + new(Base64.decode(encoded_key)) + end + end +end diff --git a/src/cox/lib_sodium.cr b/src/cox/lib_sodium.cr index 06ab66f..4d25991 100644 --- a/src/cox/lib_sodium.cr +++ b/src/cox/lib_sodium.cr @@ -10,6 +10,8 @@ module Cox fun crypto_sign_publickeybytes() : LibC::SizeT fun crypto_sign_secretkeybytes() : LibC::SizeT fun crypto_sign_bytes() : LibC::SizeT + fun crypto_kdf_keybytes() : LibC::SizeT + fun crypto_kdf_contextbytes() : LibC::SizeT PUBLIC_KEY_BYTES = crypto_box_publickeybytes() SECRET_KEY_BYTES = crypto_box_secretkeybytes() @@ -18,6 +20,8 @@ module Cox PUBLIC_SIGN_BYTES = crypto_sign_publickeybytes() SECRET_SIGN_BYTES = crypto_sign_secretkeybytes() SIGNATURE_BYTES = crypto_sign_bytes() + KDF_KEY_BYTES = crypto_kdf_keybytes() + KDF_CONTEXT_BYTES = crypto_kdf_contextbytes() fun crypto_box_keypair( public_key_output : Pointer(LibC::UChar), @@ -61,5 +65,14 @@ module Cox message_size : LibC::ULongLong, public_key : Pointer(LibC::UChar) ) : LibC::Int + + + fun crypto_kdf_derive_from_key( + subkey : Pointer(LibC::UChar), + subkey_len : LibC::SizeT, + subkey_id : UInt64, + ctx : Pointer(LibC::UChar), + key : Pointer(LibC::UChar) + ) : LibC::Int end end