Merge pull request #2353 from triska/hmac_verification

ENHANCED: Safe HMAC verification, using constant time string comparison.
This commit is contained in:
Mark Thom
2024-03-01 12:09:16 -07:00
committed by GitHub

View File

@@ -91,9 +91,36 @@ bytes_hex([B|Bs]) --> [C0,C1],
}, },
bytes_hex(Bs). bytes_hex(Bs).
char_hexval(C, H) :-
integer(H),
!,
hexval_char(H, C).
char_hexval(C, H) :- nth0(H, "0123456789abcdef", C), !. char_hexval(C, H) :- nth0(H, "0123456789abcdef", C), !.
char_hexval(C, H) :- nth0(H, "0123456789ABCDEF", C), !. char_hexval(C, H) :- nth0(H, "0123456789ABCDEF", C), !.
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
We specialize char_hexval/2 for use if the value is given,
so that it works in constant time in this case.
The security of HMAC verification depends on this property.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
hexval_char(0, '0').
hexval_char(1, '1').
hexval_char(2, '2').
hexval_char(3, '3').
hexval_char(4, '4').
hexval_char(5, '5').
hexval_char(6, '6').
hexval_char(7, '7').
hexval_char(8, '8').
hexval_char(9, '9').
hexval_char(0xa, a).
hexval_char(0xb, b).
hexval_char(0xc, c).
hexval_char(0xd, d).
hexval_char(0xe, e).
hexval_char(0xf, f).
must_be_bytes(Bytes, Context) :- must_be_bytes(Bytes, Context) :-
must_be(list, Bytes), must_be(list, Bytes),
@@ -195,7 +222,13 @@ crypto_random_byte(B) :- '$crypto_random_byte'(B).
% - `hmac(+Key)` % - `hmac(+Key)`
% Compute a hash-based message authentication code (HMAC) using % Compute a hash-based message authentication code (HMAC) using
% Key, a list of bytes. This option is currently supported for % Key, a list of bytes. This option is currently supported for
% algorithms `sha256`, `sha384` and `sha512`. % algorithms `sha256`, `sha384` and `sha512`. If `Hash` is
% instantiated, then it is compared with the computed HMAC
% in such a way that no information about the expected HMAC
% is revealed, using a comparison of strings that always takes
% the same time independent of whether and where the strings
% differ. This option can therefore also be used to safely
% _verify_ a given HMAC.
% %
% Example: % Example:
% %
@@ -222,10 +255,26 @@ crypto_data_hash(Data0, Hash, Options0) :-
( member(HMAC, Options0), nonvar(HMAC), HMAC = hmac(Ks) -> ( member(HMAC, Options0), nonvar(HMAC), HMAC = hmac(Ks) ->
must_be_bytes(Ks, crypto_data_hash/3), must_be_bytes(Ks, crypto_data_hash/3),
hmac_algorithm(A), hmac_algorithm(A),
'$crypto_hmac'(Data, Encoding, Ks, HashBytes, A) '$crypto_hmac'(Data, Encoding, Ks, HashBytes, A),
; '$crypto_data_hash'(Data, Encoding, HashBytes, A) ( var(Hash) ->
), hex_bytes(Hash, HashBytes)
hex_bytes(Hash, HashBytes). ; must_be(chars, Hash),
hex_bytes(HashMAC, HashBytes),
chars_equal_constant_time(Hash, HashMAC)
)
; '$crypto_data_hash'(Data, Encoding, HashBytes, A),
hex_bytes(Hash, HashBytes)
).
chars_equal_constant_time(As, Bs) :-
maplist(chars_xor, As, Bs, Xs),
sum_list(Xs, Sum),
Sum =:= 0.
chars_xor(A, B, Xor) :-
char_code(A, CA),
char_code(B, CB),
Xor is xor(CA,CB).
hmac_algorithm(sha256). hmac_algorithm(sha256).
hmac_algorithm(sha384). hmac_algorithm(sha384).