From: Markus Triska Date: Thu, 14 May 2020 18:07:59 +0000 (+0200) Subject: ADDED: HMAC-based key derivation (HKDF) via crypto_data_hkdf/4 X-Git-Tag: v0.8.123~33^2^2 X-Git-Url: https://git.sagredo.dev/?a=commitdiff_plain;h=50776748a7b864f48548073da85432586a1d0cfe;p=scryer-prolog.git ADDED: HMAC-based key derivation (HKDF) via crypto_data_hkdf/4 This is useful to generate keys and initialization vectors from suitable input keying material, so that future predicates for symmetric encryption can be used with appropriate parameters. --- diff --git a/README.md b/README.md index 61a4cc9e..1e39f219 100644 --- a/README.md +++ b/README.md @@ -378,8 +378,8 @@ The modules that ship with Scryer Prolog are also called * [`sockets`](src/prolog/lib/sockets.pl) Predicates for opening and accepting TCP connections as streams. * [`crypto`](src/prolog/lib/crypto.pl) - Cryptographically secure random numbers and hashes, and - reasoning about elliptic curves. + Cryptographically secure random numbers and hashes, HMAC-based + key derivation (HKDF), and reasoning about elliptic curves. To read contents of external files, use `phrase_from_file/2` from [`library(pio)`](src/prolog/lib/pio.pl) to apply a DCG to diff --git a/src/prolog/clause_types.rs b/src/prolog/clause_types.rs index d6c4e13d..30576ee8 100644 --- a/src/prolog/clause_types.rs +++ b/src/prolog/clause_types.rs @@ -287,7 +287,8 @@ pub enum SystemClauseType { WriteTermToChars, ScryerPrologVersion, CryptoRandomByte, - CryptoDataHash + CryptoDataHash, + CryptoDataHKDF } impl SystemClauseType { @@ -472,6 +473,7 @@ impl SystemClauseType { &SystemClauseType::ScryerPrologVersion => clause_name!("$scryer_prolog_version"), &SystemClauseType::CryptoRandomByte => clause_name!("$crypto_random_byte"), &SystemClauseType::CryptoDataHash => clause_name!("$crypto_data_hash"), + &SystemClauseType::CryptoDataHKDF => clause_name!("$crypto_data_hkdf"), } } @@ -636,6 +638,7 @@ impl SystemClauseType { ("$scryer_prolog_version", 1) => Some(SystemClauseType::ScryerPrologVersion), ("$crypto_random_byte", 1) => Some(SystemClauseType::CryptoRandomByte), ("$crypto_data_hash", 3) => Some(SystemClauseType::CryptoDataHash), + ("$crypto_data_hkdf", 6) => Some(SystemClauseType::CryptoDataHKDF), _ => None, } } diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index 23a3273f..2968d4a1 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -14,12 +14,13 @@ :- module(crypto, [hex_bytes/2, crypto_n_random_bytes/2, - crypto_data_hash/3, + crypto_data_hash/3, % +Data, -Hash, +Options + crypto_data_hkdf/4, % +Data, +Length, -Bytes, +Options crypto_name_curve/2, % +Name, -Curve crypto_curve_order/2, % +Curve, -Order crypto_curve_generator/2, % +Curve, -Generator crypto_curve_scalar_mult/4 % +Curve, +Scalar, +Point, -Result - ]). + ]). :- use_module(library(error)). :- use_module(library(lists)). @@ -52,9 +53,7 @@ hex_bytes(Hs, Bytes) :- true ; domain_error(hex_encoding, Hs, hex_bytes/2) ) - ; must_be(list, Bytes), - maplist(must_be(integer), Bytes), - must_be_bytes(Bytes, hex_bytes/2), + ; must_be_bytes(Bytes, hex_bytes/2), phrase(bytes_hex(Bytes), Hs) ). @@ -79,6 +78,8 @@ char_hexval(C, H) :- nth0(H, "0123456789ABCDEF", C), !. must_be_bytes(Bytes, Context) :- + must_be(list, Bytes), + maplist(must_be(integer), Bytes), ( member(B, Bytes), \+ between(0, 255, B) -> type_error(byte, B, Context) ; true @@ -86,6 +87,9 @@ must_be_bytes(Bytes, Context) :- /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Cryptographically secure random numbers + ======================================= + crypto_n_random_bytes(+N, -Bytes) is det Bytes is unified with a list of N cryptographically secure @@ -135,10 +139,14 @@ crypto_n_random_bytes(N, Bs) :- crypto_random_byte(B) :- '$crypto_random_byte'(B). /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Hashing + ======= + crypto_data_hash(+Data, -Hash, +Options) - Where Data is a list of bytes (integers between 0 and 255), - and Hash is the computed hash as a list of hexadecimal characters. + Where Data is a list of bytes (integers between 0 and 255) or + characters, and Hash is the computed hash as a list of hexadecimal + characters. The single supported option is: @@ -153,27 +161,43 @@ crypto_random_byte(B) :- '$crypto_random_byte'(B). Example: - ?- crypto_data_hash([0'a,0'b,0'c], Hs, [algorithm(sha256)]). + ?- crypto_data_hash("abc", Hs, [algorithm(sha256)]). Hs = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" ; false. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -crypto_data_hash(Data, Hash, Options) :- - must_be(list, Data), - must_be_bytes(Data, crypto_data_hash/3), - must_be(list, Options), - ( Options = [algorithm(A)] -> true - ; true - ), - ( var(A) -> A = sha256 - ; true - ), +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + SHA256 is the current default for several hash-related predicates. + It is deemed sufficiently secure for the foreseeable future. Yet, + application programmers must be aware that the default may change in + future versions. The hash predicates all yield the algorithm they + used if a Prolog variable is used for the pertaining option. +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +crypto_data_hash(Data0, Hash, Options0) :- + chars_bytes_(Data0, Data, crypto_data_hash/3), + must_be(list, Options0), + functor_hash_options(algorithm, A, Options0, _), ( hash_algorithm(A) -> true ; domain_error(hash_algorithm, A, crypto_data_hash/3) ), '$crypto_data_hash'(Data, HashBytes, A), hex_bytes(Hash, HashBytes). + +default_hash(sha256). + +functor_hash_options(F, Hash, Options0, [Option|Options]) :- + Option =.. [F,Hash], + ( select(Option, Options0, Options) -> + ( var(Hash) -> + default_hash(Hash) + ; must_be(atom, Hash) + ) + ; Options = Options0, + default_hash(Hash) + ). + hash_algorithm(ripemd160). hash_algorithm(sha256). hash_algorithm(sha512). @@ -181,6 +205,60 @@ hash_algorithm(sha384). hash_algorithm(sha512_256). +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + crypto_data_hkdf(+Data, +Length, -Bytes, +Options) is det. + + Concentrate possibly dispersed entropy of Data and then expand it + to the desired length. Data is a list of bytes or characters. + + Bytes is unified with a list of bytes of length Length, and is + suitable as input keying material and initialization vectors to + symmetric encryption algorithms. + + Admissible options are: + + - algorithm(+Algorithm) + A hashing algorithm as specified to crypto_data_hash/3. The + default is a cryptographically secure algorithm. If you + specify a variable, then it is unified with the algorithm + that was used, which is a cryptographically secure algorithm. + - info(+Info) + Optional context and application specific information, + specified as a list of bytes or characters. The default is []. + - salt(+List) + Optionally, a list of bytes that are used as salt. The + default is all zeroes. + + The `info/1` option can be used to generate multiple keys from a + single master key, using for example values such as "key" and + "iv", or the name of a file that is to be encrypted. + + See crypto_n_random_bytes/2 to obtain a suitable salt. +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +crypto_data_hkdf(Data0, L, Bytes, Options0) :- + functor_hash_options(algorithm, Algorithm, Options0, Options), + chars_bytes_(Data0, Data, crypto_data_hkdf/4), + option(salt(SaltBytes), Options, []), + must_be_bytes(SaltBytes, crypto_data_hkdf/4), + option(info(Info0), Options, []), + chars_bytes_(Info0, Info, crypto_data_hkdf/4), + '$crypto_data_hkdf'(Data, SaltBytes, Info, Algorithm, L, Bytes). + +option(What, Options, Default) :- + ( member(What, Options) -> true + ; What =.. [_,Default] + ). + +chars_bytes_(Cs, Bytes, Context) :- + must_be(list, Cs), + ( maplist(integer, Cs) -> Bytes = Cs + ; % use chars_utf8bytes/2 here once it becomes available! + maplist(atom_codes, Cs, Css), + append(Css, Bytes) + ), + must_be_bytes(Bytes, Context). + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Modular multiplicative inverse. diff --git a/src/prolog/machine/machine_state_impl.rs b/src/prolog/machine/machine_state_impl.rs index ea1d722a..ab0a796c 100644 --- a/src/prolog/machine/machine_state_impl.rs +++ b/src/prolog/machine/machine_state_impl.rs @@ -2746,6 +2746,51 @@ impl MachineState { *list = result; } + + pub(super) + fn integers_to_bytevec( + &self, + r: RegType, + caller: MachineStub, + ) -> Vec { + + let mut bytes: Vec = Vec::new(); + + match self.try_from_list(r, caller) { + Err(_) => { unreachable!() } + Ok(addrs) => { + + for addr in addrs { + let addr = self.store(self.deref(addr)); + + match Number::try_from((addr, &self.heap)) { + Ok(Number::Fixnum(n)) => { + match u8::try_from(n) { + Ok(b) => { + bytes.push(b); + } + Err(_) => { } + } + + continue; + } + Ok(Number::Integer(n)) => { + if let Some(b) = n.to_u8() { + bytes.push(b); + } + + continue; + } + _ => { + } + } + } + } + } + bytes + } + + pub(super) fn try_from_list( &self, diff --git a/src/prolog/machine/system_calls.rs b/src/prolog/machine/system_calls.rs index b572af41..0de1f423 100644 --- a/src/prolog/machine/system_calls.rs +++ b/src/prolog/machine/system_calls.rs @@ -39,7 +39,7 @@ use crate::crossterm::event::{read, Event, KeyCode, KeyEvent, KeyModifiers}; use crate::crossterm::terminal::{enable_raw_mode, disable_raw_mode}; use ring::rand::{SecureRandom, SystemRandom}; -use ring::digest; +use ring::{digest,hkdf}; use ripemd160::{Ripemd160, Digest}; pub fn get_key() -> KeyEvent { @@ -5206,41 +5206,8 @@ impl MachineState { self.unify(arg, byte); } &SystemClauseType::CryptoDataHash => { - let mut bytes: Vec = Vec::new(); - let stub = MachineError::functor_stub(clause_name!("crypto_data_hash"), 3); - - match self.try_from_list(temp_v!(1), stub) { - Err(e) => return Err(e), - Ok(addrs) => { - - for addr in addrs { - let addr = self.store(self.deref(addr)); - - match Number::try_from((addr, &self.heap)) { - Ok(Number::Fixnum(n)) => { - match u8::try_from(n) { - Ok(b) => { - bytes.push(b); - } - Err(_) => { } - } - - continue; - } - Ok(Number::Integer(n)) => { - if let Some(b) = n.to_u8() { - bytes.push(b); - } - - continue; - } - _ => { - } - } - } - } - } + let bytes = self.integers_to_bytevec(temp_v!(1), stub); let algorithm = self[temp_v!(3)]; let algorithm_str = match self.store(self.deref(algorithm)) { @@ -5276,6 +5243,58 @@ impl MachineState { self.unify(self[temp_v!(2)], ints_list); } + &SystemClauseType::CryptoDataHKDF => { + let stub1 = MachineError::functor_stub(clause_name!("crypto_data_hkdf"), 6); + let data = self.integers_to_bytevec(temp_v!(1), stub1); + let stub2 = MachineError::functor_stub(clause_name!("crypto_data_hkdf"), 6); + let salt = self.integers_to_bytevec(temp_v!(2), stub2); + let stub3 = MachineError::functor_stub(clause_name!("crypto_data_hkdf"), 6); + let info = self.integers_to_bytevec(temp_v!(3), stub3); + + let algorithm = match self.store(self.deref(self[temp_v!(4)])) { + Addr::Con(h) if self.heap.atom_at(h) => { + if let HeapCellValue::Atom(ref atom, _) = &self.heap[h] { + atom.as_str() + } else { + unreachable!() + } + } + _ => { + unreachable!() + } + }; + + let length = + match Number::try_from((self[temp_v!(5)], &self.heap)) { + Ok(Number::Fixnum(n)) => { + usize::try_from(n).unwrap() + } + Ok(Number::Integer(n)) => { + n.to_usize().unwrap() + } + _ => { + unreachable!() + } + }; + + let ints_list = + { let digest_alg = + match algorithm { + "sha256" => { hkdf::HKDF_SHA256 } + "sha384" => { hkdf::HKDF_SHA384 } + "sha512" => { hkdf::HKDF_SHA512 } + _ => { unreachable!() } + }; + let salt = hkdf::Salt::new(digest_alg, &salt); + let mut bytes : Vec = Vec::new(); + bytes.resize(length, 0); + salt.extract(&data).expand(&[&info[..]], MyKey(length)).unwrap().fill(&mut bytes).unwrap(); + + Addr::HeapCell(self.heap.to_list(bytes.iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) + }; + + self.unify(self[temp_v!(6)], ints_list); + } }; return_from_clause!(self.last_call, self) @@ -5292,3 +5311,11 @@ fn rng() -> &'static dyn SecureRandom { RANDOM.deref() } + +struct MyKey(T); + +impl hkdf::KeyType for MyKey { + fn len(&self) -> usize { + self.0 + } +}