Merge pull request #496 from triska/master
ADDED: crypto_data_hash/3, computing cryptographically secure digests
This commit is contained in:
@@ -377,6 +377,8 @@ The modules that ship with Scryer Prolog are also called
|
|||||||
Probabilistic predicates and random number generators.
|
Probabilistic predicates and random number generators.
|
||||||
* [`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)
|
||||||
|
Cryptographically secure random numbers and hashes.
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
@@ -286,7 +286,8 @@ pub enum SystemClauseType {
|
|||||||
WriteTerm,
|
WriteTerm,
|
||||||
WriteTermToChars,
|
WriteTermToChars,
|
||||||
ScryerPrologVersion,
|
ScryerPrologVersion,
|
||||||
CryptoRandomByte
|
CryptoRandomByte,
|
||||||
|
CryptoDataHash
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SystemClauseType {
|
impl SystemClauseType {
|
||||||
@@ -470,6 +471,7 @@ impl SystemClauseType {
|
|||||||
&SystemClauseType::WriteTermToChars => clause_name!("$write_term_to_chars"),
|
&SystemClauseType::WriteTermToChars => clause_name!("$write_term_to_chars"),
|
||||||
&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"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -633,6 +635,7 @@ impl SystemClauseType {
|
|||||||
("$write_term_to_chars", 7) => Some(SystemClauseType::WriteTermToChars),
|
("$write_term_to_chars", 7) => Some(SystemClauseType::WriteTermToChars),
|
||||||
("$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),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,27 +13,30 @@
|
|||||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
:- module(crypto, [hex_bytes/2,
|
:- 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(error)).
|
||||||
:- use_module(library(lists)).
|
:- use_module(library(lists)).
|
||||||
:- use_module(library(between)).
|
:- use_module(library(between)).
|
||||||
:- use_module(library(dcgs)).
|
:- use_module(library(dcgs)).
|
||||||
|
|
||||||
% hex_bytes(?Hex, ?Bytes) is det.
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
%
|
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*
|
Relation between a hexadecimal sequence and a list of bytes. Hex
|
||||||
% between 0 and 255 that represent the sequence as a list of bytes.
|
is a string of hexadecimal numbers. Bytes is a list of *integers*
|
||||||
% At least one of the arguments must be instantiated.
|
between 0 and 255 that represent the sequence as a list of bytes.
|
||||||
%
|
At least one of the arguments must be instantiated.
|
||||||
% Example:
|
|
||||||
%
|
Example:
|
||||||
% ==
|
|
||||||
% ?- hex_bytes("501ACE", Bs).
|
?- hex_bytes("501ACE", Bs).
|
||||||
% Bs = [80,26,206]
|
Bs = [80,26,206]
|
||||||
% ; false.
|
; false.
|
||||||
% ==
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
|
||||||
hex_bytes(Hs, Bytes) :-
|
hex_bytes(Hs, Bytes) :-
|
||||||
( ground(Hs) ->
|
( ground(Hs) ->
|
||||||
@@ -45,10 +48,7 @@ hex_bytes(Hs, Bytes) :-
|
|||||||
)
|
)
|
||||||
; must_be(list, Bytes),
|
; must_be(list, Bytes),
|
||||||
maplist(must_be(integer), Bytes),
|
maplist(must_be(integer), Bytes),
|
||||||
( member(B, Bytes), \+ between(0, 255, B) ->
|
must_be_bytes(Bytes, hex_bytes/2),
|
||||||
type_error(byte, B, hex_bytes/2)
|
|
||||||
; true
|
|
||||||
),
|
|
||||||
phrase(bytes_hex(Bytes), Hs)
|
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), !.
|
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) :-
|
crypto_n_random_bytes(N, Bs) :-
|
||||||
must_be(integer, N),
|
must_be(integer, N),
|
||||||
length(Bs, N),
|
length(Bs, N),
|
||||||
maplist(crypto_random_byte, Bs).
|
maplist(crypto_random_byte, Bs).
|
||||||
|
|
||||||
crypto_random_byte(B) :- '$crypto_random_byte'(B).
|
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).
|
||||||
|
|||||||
@@ -39,6 +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;
|
||||||
|
|
||||||
pub fn get_key() -> KeyEvent {
|
pub fn get_key() -> KeyEvent {
|
||||||
let key;
|
let key;
|
||||||
@@ -5203,6 +5204,72 @@ impl MachineState {
|
|||||||
|
|
||||||
self.unify(arg, byte);
|
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)
|
return_from_clause!(self.last_call, self)
|
||||||
|
|||||||
Reference in New Issue
Block a user