Merge pull request #502 from triska/hkdf
ADDED: HMAC-based key derivation (HKDF) via crypto_data_hkdf/4
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)
|
* [`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
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user