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:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user