Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 )
Expand Down
26 changes: 26 additions & 0 deletions spec/cox/kdf_spec.cr
Original file line number Diff line number Diff line change
@@ -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
47 changes: 47 additions & 0 deletions src/cox/kdf.cr
Original file line number Diff line number Diff line change
@@ -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
13 changes: 13 additions & 0 deletions src/cox/lib_sodium.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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),
Expand Down Expand Up @@ -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