]> Repositorios git - scryer-prolog.git/commitdiff
ADDED: crypto_data_hash/3, computing cryptographically secure digests
authorMarkus Triska <[email protected]>
Wed, 13 May 2020 17:13:20 +0000 (19:13 +0200)
committerMarkus Triska <[email protected]>
Wed, 13 May 2020 17:23:09 +0000 (19:23 +0200)
README.md
src/prolog/clause_types.rs
src/prolog/lib/crypto.pl
src/prolog/machine/system_calls.rs

index 1eb989826d44611ff5a9272ff3e8d37d5741013e..26aec64e80ef167e4c91cd735859434decc2977c 100644 (file)
--- a/README.md
+++ b/README.md
@@ -377,6 +377,8 @@ The modules that ship with Scryer&nbsp;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&nbsp;DCG to
index 0850735c969fe0819abbe77ebd0f4137d9664ec2..d6c4e13dde5713d9073942f3a70cb9eb9cc65203 100644 (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,
         }
     }
index 36f68009598fa71cb5c34f9ed673a48707ad939f..67b5ac80f8f0fb27cc558cbdc6350d8a6b5ed060 100644 (file)
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
 
 :- 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).
index 3b8409a3c5d462a7ab952f96eb0d9defdd2dc066..47d7f91104bb3a00db7c28cee0ecca0504691085 100644 (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)