SHA-1 Hash in Pure Prolog
I’m continuing to work on the cryptopals challenges and it remains very fun.
The challenge I just finished asks you to find an implementation of the SHA-1 algorithm in the language you’re using. I wasn’t able to find an implemention of the algorithm in pure Prolog (the SWI-Prolog implementation uses a foreign library to provide the function), so I went ahead & did it myself.
I wanted to include it here, just in case someone else may find it useful:
(note that, of course, SHA-1 isn’t really safe to use – this is just for fun).
:- module(s4c28, [sha1/2]).
:- use_module(library(apply), [foldl/4]).
:- use_module(library(clpfd)).
:- use_module(library(list_util), [take/3, drop/3, replicate/3]).
blocks([], _, []) :- !.
blocks(Bytes, BSize, [Block|NextBlocks]) :-
take(BSize, 0, Bytes, Block),
drop(BSize, Bytes, NextBytes),
blocks(NextBytes, BSize, NextBlocks).
int_bytes_be(I, Bs) :-
foldl([E, I0, I]>>(
E in 0..255,
I #= I0*256 + E),
Bs, 0, I).
int_bytes_be_64(I, B) :-
int_bytes_be(I, B),
length(B, 8), !.
int_bytes_be_32(I, B) :-
int_bytes_be(I, B),
length(B, 4), !.
sha1(In, Digest) :-
In ins 0..255,
sha1_preprocess(In, Message),
% process the message in successive 512-bit (64-byte) chunks
blocks(Message, 64, MsgBlocks),
% process chunks
H0 = 0x67452301,
H1 = 0xefcdab89,
H2 = 0x98badcfe,
H3 = 0x10325476,
H4 = 0xc3d2e1f0,
foldl([Block, H0-H1-H2-H3-H4, G0-G1-G2-G3-G4]>>(
sha1_process_chunk(H0-H1-H2-H3-H4, Block, A-B-C-D-E),
G0 #= (H0 + A) /\ 0xffff_ffff,
G1 #= (H1 + B) /\ 0xffff_ffff,
G2 #= (H2 + C) /\ 0xffff_ffff,
G3 #= (H3 + D) /\ 0xffff_ffff,
G4 #= (H4 + E) /\ 0xffff_ffff
), MsgBlocks, H0-H1-H2-H3-H4, Hh0-Hh1-Hh2-Hh3-Hh4),
Out #= ((Hh0 << 128) \/ (Hh1 << 96) \/ (Hh2 << 64) \/ (Hh3 << 32) \/ Hh4),
int_bytes_be(Out, Digest), length(Digest, 20), !.
sha1_preprocess(Input, Message) :-
length(Input, InL),
Ml #= InL * 8, % original message length in bits
% append the bit '1' to the message
append(Input, [0x80], M1),
% append zeros to the message st resultant length in bits is
% congruent to −64 ≡ 448 (mod 512)
% 64 bits = 8 bytes, 512 bits = 64 bytes
% -8 ≡ 56 (mod 64)
K #= (56 - (InL + 1)) mod 64,
replicate(K, 0, Zeroes),
append(M1, Zeroes, M2),
% append the original length as a big-endian 64-bit integer
int_bytes_be_64(Ml, MlBytes),
append(M2, MlBytes, Message),
length(Message, MessageLen),
0 #= MessageLen mod 64.
leftrotate(I, N, RotI) :-
WordLen #= 32,
WordMask #= (1 << WordLen) - 1,
RotI #= ((I << N) /\ WordMask) \/ (I >> (WordLen - N)).
sha1_process_chunk(H0-H1-H2-H3-H4, Chunk, ChunkHash) :-
% break each chunk into 16 32-bit (4-byte) big-endian words
blocks(Chunk, 4, Words_),
length(Words_, 16),
maplist(int_bytes_be_32, Words, Words_),
% extend the 16 32-bit words into 80 32-bit words
Extra #= 80 - 16,
length(ExtraWords, Extra),
append(Words, ExtraWords, ExtendedWords),
numlist(16, 79, Indices),
maplist({ExtendedWords}/[I,W]>>(
I3 is I - 3, I8 is I - 8,
I14 is I - 14, I16 is I - 16,
nth0(I3, ExtendedWords, W3),
nth0(I8, ExtendedWords, W8),
nth0(I14, ExtendedWords, W14),
nth0(I16, ExtendedWords, W16),
W_ is W3 xor W8 xor W14 xor W16,
leftrotate(W_, 1, W)
), Indices, ExtraWords),
% main loop
numlist(0, 79, Is),
foldl({ExtendedWords}/[I, A0-B0-C0-D0-E0, A-B-C-D-E]>>(
main_loop_step(I, B0-C0-D0, F, K),
E = D0,
D = C0,
leftrotate(B0, 30, C),
B = A0,
leftrotate(A0, 5, X),
nth0(I, ExtendedWords, Wi),
A #= (X + F + E0 + K + Wi) /\ 0xffff_ffff
), Is, H0-H1-H2-H3-H4, ChunkHash).
main_loop_step(I, B-C-D, F, K) :-
between(0, 19, I),
F #= (B /\ C) \/ ((\B) /\ D),
K = 0x5a827999.
main_loop_step(I, B-C-D, F, K) :-
between(20, 39, I),
F #= B xor C xor D,
K = 0x6ed9eba1.
main_loop_step(I, B-C-D, F, K) :-
between(40, 59, I),
F #= (B /\ C) \/ (B /\ D) \/ (C /\ D),
K = 0x8f1bbcdc.
main_loop_step(I, B-C-D, F, K) :-
between(60, 79, I),
F #= B xor C xor D,
K = 0xca62c1d6.
% tests
:- use_module(library(crypto), [hex_bytes/2]).
:- use_module(library(plunit)).
:- begin_tests(s4c28).
test(sha1_empty) :-
sha1(``, O),
hex_bytes(Hex, O), !,
Hex = 'da39a3ee5e6b4b0d3255bfef95601890afd80709'.
test(sha1_dog) :-
sha1(`The quick brown fox jumps over the lazy dog`, O),
hex_bytes(Hex, O),
Hex = '2fd4e1c67a2d28fced849ee1bb76e7391b93eb12'.
test(sha1_cog) :-
sha1(`The quick brown fox jumps over the lazy cog`, O),
hex_bytes(Hex, O),
Hex = 'de9f2c7fd25e1b3afad3e85a0bd17d9b100db4b3'.
test(sha1_do) :-
sha1(`The quick brown fox jumps over the lazy do`, O),
hex_bytes(Hex, O),
Hex = 'f2f38c31109f8a9421f301e9d554193bc4274a32'.
:- end_tests(s4c28).
Hope this may prove useful, or at least interesting, to you!