Merge pull request #496 from triska/master

ADDED: crypto_data_hash/3, computing cryptographically secure digests
This commit is contained in:
Mark Thom
2020-05-13 17:32:46 -03:00
committed by GitHub
4 changed files with 185 additions and 20 deletions

View File

@@ -377,6 +377,8 @@ The modules that ship with Scryer Prolog are also called
Probabilistic predicates and random number generators.
* [`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.
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

@@ -286,7 +286,8 @@ pub enum SystemClauseType {
WriteTerm,
WriteTermToChars,
ScryerPrologVersion,
CryptoRandomByte
CryptoRandomByte,
CryptoDataHash
}
impl SystemClauseType {
@@ -470,6 +471,7 @@ impl SystemClauseType {
&SystemClauseType::WriteTermToChars => clause_name!("$write_term_to_chars"),
&SystemClauseType::ScryerPrologVersion => clause_name!("$scryer_prolog_version"),
&SystemClauseType::CryptoRandomByte => clause_name!("$crypto_random_byte"),
&SystemClauseType::CryptoDataHash => clause_name!("$crypto_data_hash"),
}
}
@@ -633,6 +635,7 @@ impl SystemClauseType {
("$write_term_to_chars", 7) => Some(SystemClauseType::WriteTermToChars),
("$scryer_prolog_version", 1) => Some(SystemClauseType::ScryerPrologVersion),
("$crypto_random_byte", 1) => Some(SystemClauseType::CryptoRandomByte),
("$crypto_data_hash", 3) => Some(SystemClauseType::CryptoDataHash),
_ => None,
}
}

View File

@@ -13,27 +13,30 @@
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
:- module(crypto, [hex_bytes/2,
crypto_n_random_bytes/2]).
crypto_n_random_bytes/2,
crypto_data_hash/3
]).
:- use_module(library(error)).
:- use_module(library(lists)).
:- use_module(library(between)).
:- use_module(library(dcgs)).
% hex_bytes(?Hex, ?Bytes) is det.
%
% Relation between a hexadecimal sequence and a list of bytes. Hex
% is a string of hexadecimal numbers. Bytes is a list of *integers*
% between 0 and 255 that represent the sequence as a list of bytes.
% At least one of the arguments must be instantiated.
%
% Example:
%
% ==
% ?- hex_bytes("501ACE", Bs).
% Bs = [80,26,206]
% ; false.
% ==
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
hex_bytes(?Hex, ?Bytes) is det.
Relation between a hexadecimal sequence and a list of bytes. Hex
is a string of hexadecimal numbers. Bytes is a list of *integers*
between 0 and 255 that represent the sequence as a list of bytes.
At least one of the arguments must be instantiated.
Example:
?- hex_bytes("501ACE", Bs).
Bs = [80,26,206]
; false.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
hex_bytes(Hs, Bytes) :-
( ground(Hs) ->
@@ -45,10 +48,7 @@ hex_bytes(Hs, Bytes) :-
)
; must_be(list, Bytes),
maplist(must_be(integer), Bytes),
( member(B, Bytes), \+ between(0, 255, B) ->
type_error(byte, B, hex_bytes/2)
; true
),
must_be_bytes(Bytes, hex_bytes/2),
phrase(bytes_hex(Bytes), Hs)
).
@@ -72,9 +72,102 @@ char_hexval(C, H) :- nth0(H, "0123456789abcdef", C), !.
char_hexval(C, H) :- nth0(H, "0123456789ABCDEF", C), !.
must_be_bytes(Bytes, Context) :-
( member(B, Bytes), \+ between(0, 255, B) ->
type_error(byte, B, Context)
; true
).
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
crypto_n_random_bytes(+N, -Bytes) is det
Bytes is unified with a list of N cryptographically secure
pseudo-random bytes. Each byte is an integer between 0 and 255. If
the internal pseudo-random number generator (PRNG) has not been
seeded with enough entropy to ensure an unpredictable byte
sequence, an exception is thrown.
One way to relate such a list of bytes to an _integer_ is to use
CLP() constraints as follows:
:- use_module(library(clpz)).
:- use_module(library(lists)).
bytes_integer(Bs, N) :-
foldl(pow, Bs, 0-0, N-_).
pow(B, N0-I0, N-I) :-
B in 0..255,
N #= N0 + B*256^I0,
I #= I0 + 1.
With this definition, we can generate a random 256-bit integer
_from_ a list of 32 random _bytes_:
?- crypto_n_random_bytes(32, Bs),
bytes_integer(Bs, I).
Bs = [146,166,162,210,242,7,25,132,64,94|...],
I = 337420085690608915485...(56 digits omitted)
The above relation also works in the other direction, letting you
translate an integer _to_ a list of bytes. In addition, you can
use hex_bytes/2 to convert bytes to _tokens_ that can be easily
exchanged in your applications.
?- crypto_n_random_bytes(12, Bs),
hex_bytes(Hex, Bs).
Bs = [34,25,50,72,58,63,50,172,32,46|...], Hex = "221932483a3f32ac202 ..."
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
crypto_n_random_bytes(N, Bs) :-
must_be(integer, N),
length(Bs, N),
maplist(crypto_random_byte, Bs).
crypto_random_byte(B) :- '$crypto_random_byte'(B).
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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.
The single supported option is:
algorithm(A)
where A is one of sha256, sha384, sha512, sha512_256, or a variable.
If A is a variable, then it is unified with the default algorithm,
which is an algorithm that is considered cryptographically secure
at the time of this writing.
Example:
?- crypto_data_hash([0'a,0'b,0'c], 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
),
( hash_algorithm(A) -> true
; domain_error(hash_algorithm, A, crypto_data_hash/3)
),
'$crypto_data_hash'(Data, HashBytes, A),
hex_bytes(Hash, HashBytes).
hash_algorithm(sha256).
hash_algorithm(sha512).
hash_algorithm(sha384).
hash_algorithm(sha512_256).

View File

@@ -39,6 +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;
pub fn get_key() -> KeyEvent {
let key;
@@ -5203,6 +5204,72 @@ 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 algorithm = self[temp_v!(3)];
let algorithm_str = match self.store(self.deref(algorithm)) {
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 hash = digest::digest(
match algorithm_str {
"sha256" => { &digest::SHA256 }
"sha384" => { &digest::SHA384 }
"sha512" => { &digest::SHA512 }
"sha512_256" => { &digest::SHA512_256 }
_ => { unreachable!() }
},
&bytes);
let ints = hash.as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b))));
let ints_list = Addr::HeapCell(self.heap.to_list(ints));
self.unify(self[temp_v!(2)], ints_list);
}
};
return_from_clause!(self.last_call, self)