Merge pull request #502 from triska/hkdf

ADDED: HMAC-based key derivation (HKDF) via crypto_data_hkdf/4
This commit is contained in:
Mark Thom
2020-05-14 15:13:32 -03:00
committed by GitHub
5 changed files with 209 additions and 56 deletions

View File

@@ -378,8 +378,8 @@ The modules that ship with Scryer Prolog are also called
* [`sockets`](src/prolog/lib/sockets.pl) * [`sockets`](src/prolog/lib/sockets.pl)
Predicates for opening and accepting TCP connections as streams. Predicates for opening and accepting TCP connections as streams.
* [`crypto`](src/prolog/lib/crypto.pl) * [`crypto`](src/prolog/lib/crypto.pl)
Cryptographically secure random numbers and hashes, and Cryptographically secure random numbers and hashes, HMAC-based
reasoning about elliptic curves. key derivation (HKDF), and reasoning about elliptic curves.
To read contents of external files, use `phrase_from_file/2` from To read contents of external files, use `phrase_from_file/2` from
[`library(pio)`](src/prolog/lib/pio.pl) to apply a DCG to [`library(pio)`](src/prolog/lib/pio.pl) to apply a DCG to

View File

@@ -287,7 +287,8 @@ pub enum SystemClauseType {
WriteTermToChars, WriteTermToChars,
ScryerPrologVersion, ScryerPrologVersion,
CryptoRandomByte, CryptoRandomByte,
CryptoDataHash CryptoDataHash,
CryptoDataHKDF
} }
impl SystemClauseType { impl SystemClauseType {
@@ -472,6 +473,7 @@ impl SystemClauseType {
&SystemClauseType::ScryerPrologVersion => clause_name!("$scryer_prolog_version"), &SystemClauseType::ScryerPrologVersion => clause_name!("$scryer_prolog_version"),
&SystemClauseType::CryptoRandomByte => clause_name!("$crypto_random_byte"), &SystemClauseType::CryptoRandomByte => clause_name!("$crypto_random_byte"),
&SystemClauseType::CryptoDataHash => clause_name!("$crypto_data_hash"), &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), ("$scryer_prolog_version", 1) => Some(SystemClauseType::ScryerPrologVersion),
("$crypto_random_byte", 1) => Some(SystemClauseType::CryptoRandomByte), ("$crypto_random_byte", 1) => Some(SystemClauseType::CryptoRandomByte),
("$crypto_data_hash", 3) => Some(SystemClauseType::CryptoDataHash), ("$crypto_data_hash", 3) => Some(SystemClauseType::CryptoDataHash),
("$crypto_data_hkdf", 6) => Some(SystemClauseType::CryptoDataHKDF),
_ => None, _ => None,
} }
} }

View File

@@ -14,12 +14,13 @@
:- module(crypto, [hex_bytes/2, :- module(crypto, [hex_bytes/2,
crypto_n_random_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_name_curve/2, % +Name, -Curve
crypto_curve_order/2, % +Curve, -Order crypto_curve_order/2, % +Curve, -Order
crypto_curve_generator/2, % +Curve, -Generator crypto_curve_generator/2, % +Curve, -Generator
crypto_curve_scalar_mult/4 % +Curve, +Scalar, +Point, -Result crypto_curve_scalar_mult/4 % +Curve, +Scalar, +Point, -Result
]). ]).
:- use_module(library(error)). :- use_module(library(error)).
:- use_module(library(lists)). :- use_module(library(lists)).
@@ -52,9 +53,7 @@ hex_bytes(Hs, Bytes) :-
true true
; domain_error(hex_encoding, Hs, hex_bytes/2) ; domain_error(hex_encoding, Hs, hex_bytes/2)
) )
; must_be(list, Bytes), ; must_be_bytes(Bytes, hex_bytes/2),
maplist(must_be(integer), Bytes),
must_be_bytes(Bytes, hex_bytes/2),
phrase(bytes_hex(Bytes), Hs) phrase(bytes_hex(Bytes), Hs)
). ).
@@ -79,6 +78,8 @@ char_hexval(C, H) :- nth0(H, "0123456789ABCDEF", C), !.
must_be_bytes(Bytes, Context) :- must_be_bytes(Bytes, Context) :-
must_be(list, Bytes),
maplist(must_be(integer), Bytes),
( member(B, Bytes), \+ between(0, 255, B) -> ( member(B, Bytes), \+ between(0, 255, B) ->
type_error(byte, B, Context) type_error(byte, B, Context)
; true ; true
@@ -86,6 +87,9 @@ must_be_bytes(Bytes, Context) :-
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Cryptographically secure random numbers
=======================================
crypto_n_random_bytes(+N, -Bytes) is det crypto_n_random_bytes(+N, -Bytes) is det
Bytes is unified with a list of N cryptographically secure 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). crypto_random_byte(B) :- '$crypto_random_byte'(B).
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Hashing
=======
crypto_data_hash(+Data, -Hash, +Options) crypto_data_hash(+Data, -Hash, +Options)
Where Data is a list of bytes (integers between 0 and 255), Where Data is a list of bytes (integers between 0 and 255) or
and Hash is the computed hash as a list of hexadecimal characters. characters, and Hash is the computed hash as a list of hexadecimal
characters.
The single supported option is: The single supported option is:
@@ -153,27 +161,43 @@ crypto_random_byte(B) :- '$crypto_random_byte'(B).
Example: Example:
?- crypto_data_hash([0'a,0'b,0'c], Hs, [algorithm(sha256)]). ?- crypto_data_hash("abc", Hs, [algorithm(sha256)]).
Hs = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" Hs = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
; false. ; false.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
crypto_data_hash(Data, Hash, Options) :- /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
must_be(list, Data), SHA256 is the current default for several hash-related predicates.
must_be_bytes(Data, crypto_data_hash/3), It is deemed sufficiently secure for the foreseeable future. Yet,
must_be(list, Options), application programmers must be aware that the default may change in
( Options = [algorithm(A)] -> true future versions. The hash predicates all yield the algorithm they
; true used if a Prolog variable is used for the pertaining option.
), - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
( var(A) -> A = sha256
; true 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 ( hash_algorithm(A) -> true
; domain_error(hash_algorithm, A, crypto_data_hash/3) ; domain_error(hash_algorithm, A, crypto_data_hash/3)
), ),
'$crypto_data_hash'(Data, HashBytes, A), '$crypto_data_hash'(Data, HashBytes, A),
hex_bytes(Hash, HashBytes). 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(ripemd160).
hash_algorithm(sha256). hash_algorithm(sha256).
hash_algorithm(sha512). hash_algorithm(sha512).
@@ -181,6 +205,60 @@ hash_algorithm(sha384).
hash_algorithm(sha512_256). 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. Modular multiplicative inverse.

View File

@@ -2746,6 +2746,51 @@ impl MachineState {
*list = result; *list = result;
} }
pub(super)
fn integers_to_bytevec(
&self,
r: RegType,
caller: MachineStub,
) -> Vec<u8> {
let mut bytes: Vec<u8> = 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) pub(super)
fn try_from_list( fn try_from_list(
&self, &self,

View File

@@ -39,7 +39,7 @@ use crate::crossterm::event::{read, Event, KeyCode, KeyEvent, KeyModifiers};
use crate::crossterm::terminal::{enable_raw_mode, disable_raw_mode}; use crate::crossterm::terminal::{enable_raw_mode, disable_raw_mode};
use ring::rand::{SecureRandom, SystemRandom}; use ring::rand::{SecureRandom, SystemRandom};
use ring::digest; use ring::{digest,hkdf};
use ripemd160::{Ripemd160, Digest}; use ripemd160::{Ripemd160, Digest};
pub fn get_key() -> KeyEvent { pub fn get_key() -> KeyEvent {
@@ -5206,41 +5206,8 @@ impl MachineState {
self.unify(arg, byte); self.unify(arg, byte);
} }
&SystemClauseType::CryptoDataHash => { &SystemClauseType::CryptoDataHash => {
let mut bytes: Vec<u8> = Vec::new();
let stub = MachineError::functor_stub(clause_name!("crypto_data_hash"), 3); let stub = MachineError::functor_stub(clause_name!("crypto_data_hash"), 3);
let bytes = self.integers_to_bytevec(temp_v!(1), stub);
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 algorithm = self[temp_v!(3)]; let algorithm = self[temp_v!(3)];
let algorithm_str = match self.store(self.deref(algorithm)) { let algorithm_str = match self.store(self.deref(algorithm)) {
@@ -5276,6 +5243,58 @@ impl MachineState {
self.unify(self[temp_v!(2)], ints_list); 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<u8> = 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) return_from_clause!(self.last_call, self)
@@ -5292,3 +5311,11 @@ fn rng() -> &'static dyn SecureRandom {
RANDOM.deref() RANDOM.deref()
} }
struct MyKey<T: core::fmt::Debug + PartialEq>(T);
impl hkdf::KeyType for MyKey<usize> {
fn len(&self) -> usize {
self.0
}
}