-- SHA1 - Provide an idealized interface to the US Secure Hash Algorithm 1 function. -- Copyright (C) 2019 Prince Trippy programmer@verisimilitudes.net . -- This program is free software: you can redistribute it and/or modify it under the terms of the -- GNU Affero General Public License version 3 as published by the Free Software Foundation. -- This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without -- even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -- See the GNU Affero General Public License for more details. -- You should have received a copy of the GNU Affero General Public License along with this program. -- If not, see <http://www.gnu.org/licenses/>. with Interfaces; -- I can always trivially define this rotation function myself were I ever needing. package body SHA1 is function Rotate (Value : Word; Amount : Natural) return Word is begin return Word(Interfaces.Rotate_Left(Interfaces.Unsigned_32(Value), Amount)); end Rotate; function K (T : Cycle) return Word is begin -- Perhaps I'll just make this an array; would it be as size efficient though, I do wonder. case T is when 0 .. 19 => return 16#5A827999#; when 20 .. 39 => return 16#6ED9EBA1#; when 40 .. 59 => return 16#8F1BBCDC#; when 60 .. 79 => return 16#CA62C1D6#; end case; end K; function F (T : Cycle; B, C, D : Word) return Word is begin case T is when 0 .. 19 => return (B and C) or (D and not B); when 40 .. 59 => return (B and C) or (B and D) or (C and D); when 20 .. 39 | 60 .. 79 => return B xor C xor D; end case; end F; procedure Pad (Primary : in out Word_Block; Auxiliary : out Word_Block; Bit_Length : in Natural; Overflow : out Boolean) is procedure Place (Data : in out Word_Block) is begin -- I'm paying care here to ensure bit lengths greater than that 2**64 limit are ignored. Data(15) := Word((Bit_Length / 2**16) / 2**16) and 2**32-1; Data(16) := Word(Bit_Length) and 2**32-1; end Place; procedure Term (Data : in out Word_Block) is L : Positive := ((Bit_Length / 2**5) mod 16) + 1; M : Natural := Bit_Length mod 2**5; begin -- This unnecessarily writes zeroes to the final two words of Primary in some instances. Data(L) := (Data(L) and ((2**(32-M)-1) xor (2**32-1))) or 2**(31-M); Data(Positive'Succ(L) .. Data'Last) := (others => 0); end Term; begin -- I'd figure this implementation should be improved, but I can do so later, unlike design. if Bit_Length = 0 then -- This is a rather unfortunate edge case I noticed under a new review. Overflow := False; Term(Primary); Place(Primary); return; -- Perhaps I could parameterize. end if; -- There must be sixty-five bits available for that ending bit and the message length. case Bit_Length mod 512 is -- Why use others when I can quite easily cover all possible cases? when 0 => Auxiliary := (1 => 2**31, 2 .. 14 => 0, 15 .. 16 => <>); when 448 .. 511 => Auxiliary := (1 .. 14 => 0, 15 .. 16 => <>); Term(Primary); when 1 .. 447 => Overflow := False; Term(Primary); Place(Primary); return; -- Usual one. when others => null; -- This should be entirely unnecessary, so why must I list it now? end case; Overflow := True; Place(Auxiliary); end Pad; procedure Hash (State : in out Status; Data : in Word_Block) is A, B, C, D, E, Temporary : Word; W : array (Cycle) of Word; -- W : array (Cycle) of Word := (0 .. 15 => Data(1 .. 16), others => <>); begin for I in Cycle range Cycle'First .. 15 loop W(I) := Data(Positive(I+1)); end loop; for I in Cycle range 16 .. Cycle'Last loop W(I) := Rotate(Amount => 1, Value => (W(I-3) xor W(I-8) xor W(I-14) xor W(I-16))); end loop; A := State.H0; B := State.H1; C := State.H2; D := State.H3; E := State.H4; for I in Cycle'Range loop Temporary := Rotate(A, 5) + F(I, B, C, D) + E + W(I) + K(I); E := D; D := C; C := Rotate(B, 30); B := A; A := Temporary; end loop; State.H0 := State.H0 + A; State.H1 := State.H1 + B; State.H2 := State.H2 + C; State.H3 := State.H3 + D; State.H4 := State.H4 + E; end Hash; function Hash (State : Status; Data : Word_Block) return Status is S : Status := State; begin Hash(S, Data); return S; end Hash; function Hash (Data : Word_Array) return Digest is -- ; Bit_Length:Natural:= Word_Array'Length*32 S : Status := Initial_Status; B, Two : Word_Block; Needed : Boolean; begin declare I : Positive := 1; begin loop exit when Data'Length < I + 16; -- Exit when all but the last block have been processed. B := Data(I .. I + 15); I := I + 16; Hash(S, B); end loop; if Data'Length mod 16 = 0 then -- Surely I can write that much better once I sleep on this. B(1 .. 16) := Data(I .. Data'Last); else B(1 .. Data'Last mod 16) := Data(I .. Data'Last); end if; end; Pad(Primary => B, Auxiliary => Two, Bit_Length => Data'Length * 32, Overflow => Needed); Hash(S, B); -- The bit-length for this procedure will always be a multiple of thirty-two. if Needed then -- Using this two-block approach for padding results in the simplest program. Hash(S, Two); -- Isn't this a nice little function? end if; return To_Digest(S); end Hash; function To_Digest (State : Status) return Digest is begin return Digest(State); end To_Digest; function To_String (Datum : Digest) return String is S : String (1 .. 40); O : constant Octet_Array := To_Octets(Datum); H : constant array (Octet range 0 .. 15) of Character := "0123456789ABCDEF"; begin for I in O'Range loop S(I*2-1) := H(O(I) / 16); S(I*2) := H(O(I) mod 16); end loop; return S; end To_String; function To_Bits (Datum : Digest) return Bit_Array is B : Bit_Array (1 .. 160); W : constant Word_Array := To_Words(Datum); begin for I in W'Range loop for J in 0 .. 31 loop B(J+1+(32*(I-1))) := Bit((W(I) / 2**(31 - J)) mod 2); end loop; end loop; return B; end To_Bits; function To_Octets (Datum : Digest) return Octet_Array is O : Octet_Array (1 .. 20); W : constant Word_Array := To_Words(Datum); begin for I in W'Range loop O(I*4-3) := Octet(W(I) / 2**24); O(I*4-2) := Octet(W(I) / 2**16 mod 256); O(I*4-1) := Octet(W(I) / 2**8 mod 256); O(I*4) := Octet(W(I) mod 256); end loop; return O; end To_Octets; function To_Words (Datum : Digest) return Word_Array is begin return (Datum.H0, Datum.H1, Datum.H2, Datum.H3, Datum.H4); end To_Words; end SHA1;