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.
This commit is contained in:
Markus Triska
2020-05-14 20:07:59 +02:00
parent c07274a6fa
commit 50776748a7
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)
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

View File

@@ -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,
}
}

View File

@@ -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.

View File

@@ -2746,6 +2746,51 @@ impl MachineState {
*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)
fn try_from_list(
&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 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<u8> = 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<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)
@@ -5292,3 +5311,11 @@ fn rng() -> &'static dyn SecureRandom {
RANDOM.deref()
}
struct MyKey<T: core::fmt::Debug + PartialEq>(T);
impl hkdf::KeyType for MyKey<usize> {
fn len(&self) -> usize {
self.0
}
}