From 8cd7d0857347849c92c840fdddcac0f4cd60aef9 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Wed, 13 May 2020 19:13:20 +0200 Subject: [PATCH 1/5] ADDED: crypto_data_hash/3, computing cryptographically secure digests --- README.md | 2 + src/prolog/clause_types.rs | 5 +- src/prolog/lib/crypto.pl | 131 ++++++++++++++++++++++++----- src/prolog/machine/system_calls.rs | 67 +++++++++++++++ 4 files changed, 185 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 1eb98982..26aec64e 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/prolog/clause_types.rs b/src/prolog/clause_types.rs index 0850735c..d6c4e13d 100644 --- a/src/prolog/clause_types.rs +++ b/src/prolog/clause_types.rs @@ -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, } } diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index 36f68009..67b5ac80 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -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). diff --git a/src/prolog/machine/system_calls.rs b/src/prolog/machine/system_calls.rs index 3b8409a3..47d7f911 100644 --- a/src/prolog/machine/system_calls.rs +++ b/src/prolog/machine/system_calls.rs @@ -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 = 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) From a86019d53ba8c5534cc883e0c1cec89499abb1fd Mon Sep 17 00:00:00 2001 From: notoria Date: Wed, 13 May 2020 21:19:47 +0200 Subject: [PATCH 2/5] Enhanced between/3 --- src/prolog/lib/between.pl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/prolog/lib/between.pl b/src/prolog/lib/between.pl index 16c73145..4ce92db7 100644 --- a/src/prolog/lib/between.pl +++ b/src/prolog/lib/between.pl @@ -9,7 +9,11 @@ between(Lower, Upper, X) :- must_be(integer, Lower), must_be(integer, Upper), can_be(integer, X), - between_(Lower, Upper, X). + ( nonvar(X) -> + Lower =< X, + X =< Upper + ; between_(Lower, Upper, X) + ). between_(Lower, Upper, Lower) :- Lower =< Upper. From fd761735d40bc57a374eb1fb8a9396dd8f3abf3f Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Wed, 13 May 2020 20:36:25 +0200 Subject: [PATCH 3/5] ADDED: ripemd160 digest algorithm This is used for example for Bitcoin address generation. --- Cargo.toml | 1 + src/prolog/lib/crypto.pl | 4 +++- src/prolog/machine/system_calls.rs | 30 ++++++++++++++++++------------ 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8e63ec9a..9f9dc993 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,3 +35,4 @@ rug = { version = "1.4.0", optional = true } rustyline = "6.0.0" unicode_reader = "1.0.0" ring = "0.16.13" +ripemd160 = "0.8.0" diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index 67b5ac80..44e8547e 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -138,7 +138,8 @@ crypto_random_byte(B) :- '$crypto_random_byte'(B). algorithm(A) - where A is one of sha256, sha384, sha512, sha512_256, or a variable. + where A is one of ripemd160, 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 @@ -167,6 +168,7 @@ crypto_data_hash(Data, Hash, Options) :- '$crypto_data_hash'(Data, HashBytes, A), hex_bytes(Hash, HashBytes). +hash_algorithm(ripemd160). hash_algorithm(sha256). hash_algorithm(sha512). hash_algorithm(sha384). diff --git a/src/prolog/machine/system_calls.rs b/src/prolog/machine/system_calls.rs index 47d7f911..b572af41 100644 --- a/src/prolog/machine/system_calls.rs +++ b/src/prolog/machine/system_calls.rs @@ -40,6 +40,7 @@ use crate::crossterm::terminal::{enable_raw_mode, disable_raw_mode}; use ring::rand::{SecureRandom, SystemRandom}; use ring::digest; +use ripemd160::{Ripemd160, Digest}; pub fn get_key() -> KeyEvent { let key; @@ -5255,18 +5256,23 @@ impl MachineState { } }; - 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)); + let ints_list = + if algorithm_str == "ripemd160" { + let mut context = Ripemd160::new(); + context.input(&bytes); + Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) + } else { + let ints = digest::digest( + match algorithm_str { + "sha256" => { &digest::SHA256 } + "sha384" => { &digest::SHA384 } + "sha512" => { &digest::SHA512 } + "sha512_256" => { &digest::SHA512_256 } + _ => { unreachable!() } + }, + &bytes); + Addr::HeapCell(self.heap.to_list(ints.as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) + }; self.unify(self[temp_v!(2)], ints_list); } From 4084005ee7c1b6d30dd9e02478849eb16d773894 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Wed, 13 May 2020 20:50:54 +0200 Subject: [PATCH 4/5] ADDED: Reasoning about elliptic curves in library(crypto). MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is useful to establish shared secrets, using ECDH key exchange. Note that CLP(ℤ) goal expansion is currently disabled due to #445, and this slows down the computations considerably for the time being. --- README.md | 3 +- src/prolog/lib/crypto.pl | 200 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 201 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 26aec64e..61a4cc9e 100644 --- a/README.md +++ b/README.md @@ -378,7 +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. + Cryptographically secure random numbers and hashes, 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 diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index 44e8547e..23a3273f 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -14,13 +14,19 @@ :- module(crypto, [hex_bytes/2, crypto_n_random_bytes/2, - crypto_data_hash/3 + crypto_data_hash/3, + 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)). :- use_module(library(between)). :- use_module(library(dcgs)). +:- use_module(library(clpz)). +:- use_module(library(arithmetic)). /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - hex_bytes(?Hex, ?Bytes) is det. @@ -173,3 +179,195 @@ hash_algorithm(sha256). hash_algorithm(sha512). hash_algorithm(sha384). hash_algorithm(sha512_256). + + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Modular multiplicative inverse. + + Compute Y = X^(-1) mod p, using the extended Euclidean algorithm. +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +multiplicative_inverse_modulo_p(X, P, Y) :- + eea(X, P, _, _, Y), + R #= X*Y mod P, + zcompare(C, 1, R), + must_be_one(C, X, P, Y). + +must_be_one(=, _, _, _). +must_be_one(>, X, P, Y) :- throw(multiplicative_inverse_modulo_p(X,P,Y)). +must_be_one(<, X, P, Y) :- throw(multiplicative_inverse_modulo_p(X,P,Y)). + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Extended Euclidean algorithm. + + Computes the GCD and the Bézout coefficients S and T. +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +eea(I, J, G, S, T) :- + State0 = state(1,0,0,1), + eea_loop(I, J, State0, G, S, T). + +eea_loop(I, J, State0, G, S, T) :- + zcompare(C, 0, J), + eea_(C, I, J, State0, G, S, T). + +eea_(=, I, _, state(_,_,U,V), I, U, V). +eea_(<, I0, J0, state(S0,T0,U0,V0), I, U, V) :- + Q #= I0 // J0, + R #= I0 mod J0, + S1 #= U0 - (Q*S0), + T1 #= V0 - (Q*T0), + eea_loop(J0, R, state(S1,T1,S0,T0), I, U, V). + + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Operations on Elliptic Curves + ============================= + + Sample use: Establishing a shared secret S, using ECDH key exchange. + + ?- crypto_name_curve(Name, C), + crypto_curve_generator(C, Generator), + PrivateKey = 10, + crypto_curve_scalar_mult(C, PrivateKey, Generator, PublicKey), + Random = 12, + crypto_curve_scalar_mult(C, Random, Generator, R), + crypto_curve_scalar_mult(C, Random, PublicKey, S), + crypto_curve_scalar_mult(C, PrivateKey, R, S). + +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + An elliptic curve over a prime field F_p is represented as: + + curve(P,A,B,point(X,Y),Order,Cofactor). + + First, we define suitable accessors. +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +curve_p(curve(P,_,_,_,_,_), P). +curve_a(curve(_,A,_,_,_,_), A). +curve_b(curve(_,_,B,_,_,_), B). + +crypto_curve_order(curve(_,_,_,_,Order,_), Order). +crypto_curve_generator(curve(_,_,_,G,_,_), G). + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Scalar point multiplication. + + R = k*Q. + + The Montgomery ladder method is used to mitigate side-channel + attacks such as timing attacks, since the number of multiplications + and additions is independent of the private key K. This method does + not even reveal the key's Hamming weight (number of 1s). +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +crypto_curve_scalar_mult(Curve, K, Q, R) :- + msb(K, Upper), + scalar_multiplication(Curve, K, Upper, ml(null,Q)-R), + must_be_on_curve(Curve, R). + +scalar_multiplication(Curve, K, I, R0-R) :- + zcompare(C, -1, I), + scalar_mult_(C, Curve, K, I, R0-R). + +scalar_mult_(=, _, _, _, ml(R,_)-R). +scalar_mult_(<, Curve, K, I0, ML0-R) :- + BitSet #= K /\ (1 << I0), + zcompare(C, 0, BitSet), + montgomery_step(C, Curve, ML0, ML1), + I1 #= I0 - 1, + scalar_multiplication(Curve, K, I1, ML1-R). + +montgomery_step(=, Curve, ml(R0,S0), ml(R,S)) :- + curve_points_addition(Curve, R0, S0, S), + curve_point_double(Curve, R0, R). +montgomery_step(<, Curve, ml(R0,S0), ml(R,S)) :- + curve_points_addition(Curve, R0, S0, R), + curve_point_double(Curve, S0, S). + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Doubling a point: R = A + A. +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +curve_point_double(_, null, null). +curve_point_double(Curve, point(AX,AY), R) :- + curve_p(Curve, P), + curve_a(Curve, A), + Numerator #= (3*AX^2 + A) mod P, + Denom0 #= 2*AY mod P, + multiplicative_inverse_modulo_p(Denom0, P, Denom), + S #= (Numerator*Denom) mod P, + R = point(RX,RY), + RX #= (S^2 - 2*AX) mod P, + RY #= (S*(AX - RX) - AY) mod P, + must_be_on_curve(Curve, R). + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Adding two points. +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +curve_points_addition(Curve, P, Q, R) :- + curve_points_addition_(P, Curve, Q, R). + +curve_points_addition_(null, _, P, P). +curve_points_addition_(P, _, null, P). +curve_points_addition_(point(AX,AY), Curve, point(BX,BY), R) :- + curve_p(Curve, P), + Numerator #= (AY - BY) mod P, + Denom0 #= (AX - BX) mod P, + multiplicative_inverse_modulo_p(Denom0, P, Denom), + S #= (Numerator * Denom) mod P, + R = point(RX,RY), + RX #= (S^2 - AX - BX) mod P, + RY #= (S*(AX - RX) - AY) mod P, + must_be_on_curve(Curve, R). + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Validation. +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +curve_contains_point(Curve, point(QX,QY)) :- + curve_a(Curve, A), + curve_b(Curve, B), + curve_p(Curve, P), + QY^2 mod P #= (QX^3 + A*QX + B) mod P. + +must_be_on_curve(Curve, P) :- + \+ curve_contains_point(Curve, P), + throw(not_on_curve(P)). +must_be_on_curve(Curve, P) :- curve_contains_point(Curve, P). + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Predefined curves + ================= + + List available curves: + + $ openssl ecparam -list_curves + + Show curve parameters for secp256k1: + + $ openssl ecparam -param_enc explicit -conv_form uncompressed \ + -text -no_seed -name secp256k1 + + You must remove the leading "04:" from the generator. +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +crypto_name_curve(secp112r1, + curve(0x00db7c2abf62e35e668076bead208b, + 0x00db7c2abf62e35e668076bead2088, + 0x659ef8ba043916eede8911702b22, + point(0x09487239995a5ee76b55f9c2f098, + 0xa89ce5af8724c0a23e0e0ff77500), + 0x00db7c2abf62e35e7628dfac6561c5, + 1)). +crypto_name_curve(secp256k1, + curve(0x00fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f, + 0x0, + 0x7, + point(0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798, + 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8), + 0x00fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141, + 1)). From 50776748a7b864f48548073da85432586a1d0cfe Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Thu, 14 May 2020 20:07:59 +0200 Subject: [PATCH 5/5] 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. --- README.md | 4 +- src/prolog/clause_types.rs | 5 +- src/prolog/lib/crypto.pl | 114 +++++++++++++++++++---- src/prolog/machine/machine_state_impl.rs | 45 +++++++++ src/prolog/machine/system_calls.rs | 97 ++++++++++++------- 5 files changed, 209 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 61a4cc9e..1e39f219 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/prolog/clause_types.rs b/src/prolog/clause_types.rs index d6c4e13d..30576ee8 100644 --- a/src/prolog/clause_types.rs +++ b/src/prolog/clause_types.rs @@ -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, } } diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index 23a3273f..2968d4a1 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -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. diff --git a/src/prolog/machine/machine_state_impl.rs b/src/prolog/machine/machine_state_impl.rs index ea1d722a..ab0a796c 100644 --- a/src/prolog/machine/machine_state_impl.rs +++ b/src/prolog/machine/machine_state_impl.rs @@ -2746,6 +2746,51 @@ impl MachineState { *list = result; } + + pub(super) + fn integers_to_bytevec( + &self, + r: RegType, + caller: MachineStub, + ) -> Vec { + + let mut bytes: Vec = 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, diff --git a/src/prolog/machine/system_calls.rs b/src/prolog/machine/system_calls.rs index b572af41..0de1f423 100644 --- a/src/prolog/machine/system_calls.rs +++ b/src/prolog/machine/system_calls.rs @@ -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 = 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 = 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); + +impl hkdf::KeyType for MyKey { + fn len(&self) -> usize { + self.0 + } +}