
(********************************************************************)
(*                                                                  *)
(*  msgdigest.s7i  Message digest and secure hash algorithms.       *)
(*  Copyright (C) 2013, 2014, 2017  Thomas Mertes                   *)
(*                                                                  *)
(*  This file is part of the Seed7 Runtime Library.                 *)
(*                                                                  *)
(*  The Seed7 Runtime Library is free software; you can             *)
(*  redistribute it and/or modify it under the terms of the GNU     *)
(*  Lesser General Public License as published by the Free Software *)
(*  Foundation; either version 2.1 of the License, or (at your      *)
(*  option) any later version.                                      *)
(*                                                                  *)
(*  The Seed7 Runtime Library 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 Lesser General Public License for more    *)
(*  details.                                                        *)
(*                                                                  *)
(*  You should have received a copy of the GNU Lesser General       *)
(*  Public License along with this program; if not, write to the    *)
(*  Free Software Foundation, Inc., 51 Franklin Street,             *)
(*  Fifth Floor, Boston, MA  02110-1301, USA.                       *)
(*                                                                  *)
(********************************************************************)


include "bytedata.s7i";
include "bin32.s7i";
include "bin64.s7i";
include "float.s7i";
include "math.s7i";


(**
 *  Compute a message digest with the MD4 message digest algorithm.
 *  The security of MD4 has been severely compromised. This function
 *  is provided for backward compatibility.
 *  @return the MD4 message digest (a string of 16 bytes).
 *  @exception RANGE_ERROR When ''message'' contains a character beyond '\255;'.
 *)
const func string: md4 (in var string: message) is func
  result
    var string: digest is "";
  local
    # Specify the per-round shift amounts
    const array integer: shiftAmount is [] (
        3,  7, 11, 19,  3,  7, 11, 19,  3,  7, 11, 19,  3,  7, 11, 19,
        3,  5,  9, 13,  3,  5,  9, 13,  3,  5,  9, 13,  3,  5,  9, 13,
        3,  9, 11, 15,  3,  9, 11, 15,  3,  9, 11, 15,  3,  9, 11, 15);
    const array integer: idx is [] (
        1,  9,  5, 13,  3, 11,  7, 15,  2, 10,  6, 14,  4, 12,  8, 16);
    var integer: length is 0;
    var integer: wordIndex is 1;
    var integer: index is 0;
    var array bin32: m is 16 times bin32.value;
    var integer: a0 is 16#67452301;   # a
    var integer: b0 is 16#efcdab89;   # b
    var integer: c0 is 16#98badcfe;   # c
    var integer: d0 is 16#10325476;   # d
    var bin32: a is bin32(0);
    var bin32: b is bin32(0);
    var bin32: c is bin32(0);
    var bin32: d is bin32(0);
    var bin32: f is bin32(0);
    var integer: g is 0;
    var bin32: temp is bin32(0);
  begin
    length := length(message);
    # Append the bit '1' to the message.
    message &:= '\16#80;';
    # Append '0' bits, so that the resulting bit length is congruent to 448 (mod 512).
    message &:= "\0;" mult 63 - (length + 8) mod 64;
    # Append length of message (before pre-processing), in bits, as 64-bit little-endian integer.
    message &:= int64AsEightBytesLe(8 * length);

    # Process the message in successive 512-bit chunks:
    while wordIndex <= length(message) do
      # Break chunk into sixteen 32-bit little-endian words.
      for index range 1 to 16 do
        m[index] := bin32(bytes2Int(message[wordIndex len 4], UNSIGNED, LE));
        wordIndex +:= 4;
      end for;

      a := bin32(a0 mod 16#100000000);
      b := bin32(b0 mod 16#100000000);
      c := bin32(c0 mod 16#100000000);
      d := bin32(d0 mod 16#100000000);

      for index range 1 to 48 do
        if index <= 16 then
          f := d >< (b & (c >< d));
          g := index;
        elsif index <= 32 then
          f := bin32(ord(b & (c | d) | (c & d)) + 16#5a827999);
          g := (4 * index + 7) mod 15 + (index div 32) * 15 + 1;
        else
          f := bin32(ord(b >< c >< d) + 16#6ed9eba1);
          g := idx[index - 32];
        end if;
        temp := d;
        d := c;
        c := b;
        b := rotLeft(bin32((ord(a) + ord(f) + ord(m[g])) mod 16#100000000),
                     shiftAmount[index]);
        a := temp;
      end for;

      # Add this chunk's hash to result so far:
      a0 +:= ord(a);
      b0 +:= ord(b);
      c0 +:= ord(c);
      d0 +:= ord(d);
    end while;

    # Produce the final hash value:
    digest := int32AsFourBytesLe(a0) &
              int32AsFourBytesLe(b0) &
              int32AsFourBytesLe(c0) &
              int32AsFourBytesLe(d0);
  end func;


# Use binary integer part of the sines of integers (Radians) as constants:
const func array integer: createMd5Table is func
  result
    var array integer: k is 64 times 0;
  local
    var integer: index is 0;
  begin
    for index range 1 to 64 do
      k[index] := trunc(abs(sin(flt(index))) * 2.0 ** 32);
    end for;
  end func;


(**
 *  Compute a message digest with the MD5 message digest algorithm.
 *  MD5 is considered to be cryptographically broken. This function
 *  is provided for backward compatibility.
 *  @return the MD5 message digest (a string of 16 bytes).
 *  @exception RANGE_ERROR When ''message'' contains a character beyond '\255;'.
 *)
const func string: md5 (in var string: message) is func
  result
    var string: digest is "";
  local
    # Specify the per-round shift amounts
    const array integer: shiftAmount is [] (
        7, 12, 17, 22,  7, 12, 17, 22,  7, 12, 17, 22,  7, 12, 17, 22,
        5,  9, 14, 20,  5,  9, 14, 20,  5,  9, 14, 20,  5,  9, 14, 20,
        4, 11, 16, 23,  4, 11, 16, 23,  4, 11, 16, 23,  4, 11, 16, 23,
        6, 10, 15, 21,  6, 10, 15, 21,  6, 10, 15, 21,  6, 10, 15, 21);
    const array integer: k is createMd5Table;
    var integer: length is 0;
    var integer: wordIndex is 1;
    var integer: index is 0;
    var array bin32: m is 16 times bin32.value;
    var integer: a0 is 16#67452301;   # a
    var integer: b0 is 16#efcdab89;   # b
    var integer: c0 is 16#98badcfe;   # c
    var integer: d0 is 16#10325476;   # d
    var bin32: a is bin32(0);
    var bin32: b is bin32(0);
    var bin32: c is bin32(0);
    var bin32: d is bin32(0);
    var bin32: f is bin32(0);
    var integer: g is 0;
    var bin32: temp is bin32(0);
  begin
    length := length(message);
    # Append the bit '1' to the message.
    message &:= '\16#80;';
    # Append '0' bits, so that the resulting bit length is congruent to 448 (mod 512).
    message &:= "\0;" mult 63 - (length + 8) mod 64;
    # Append length of message (before pre-processing), in bits, as 64-bit little-endian integer.
    message &:= int64AsEightBytesLe(8 * length);

    # Process the message in successive 512-bit chunks:
    while wordIndex <= length(message) do
      # Break chunk into sixteen 32-bit little-endian words.
      for index range 1 to 16 do
        m[index] := bin32(bytes2Int(message[wordIndex len 4], UNSIGNED, LE));
        wordIndex +:= 4;
      end for;

      a := bin32(a0 mod 16#100000000);
      b := bin32(b0 mod 16#100000000);
      c := bin32(c0 mod 16#100000000);
      d := bin32(d0 mod 16#100000000);

      for index range 1 to 64 do
        if index <= 16 then
          f := d >< (b & (c >< d));
          g := index;
        elsif index <= 32 then
          f := c >< (d & (b >< c));
          g := (5 * index - 4) mod 16 + 1;
        elsif index <= 48 then
          f := b >< c >< d;
          g := (3 * index + 2) mod 16 + 1;
        else
          f := c >< (b | (bin32(16#ffffffff) >< d));
          g := (7 * pred(index)) mod 16 + 1;
        end if;

        temp := d;
        d := c;
        c := b;
        b := bin32((ord(b) +
             ord(rotLeft(bin32((ord(a) + ord(f) + k[index] + ord(m[g])) mod 16#100000000),
                         shiftAmount[index]))) mod 16#100000000);
        a := temp;
      end for;

      # Add this chunk's hash to result so far:
      a0 +:= ord(a);
      b0 +:= ord(b);
      c0 +:= ord(c);
      d0 +:= ord(d);
    end while;

    # Produce the final hash value:
    digest := int32AsFourBytesLe(a0) &
              int32AsFourBytesLe(b0) &
              int32AsFourBytesLe(c0) &
              int32AsFourBytesLe(d0);
  end func;


(**
 *  Compute a message digest with the SHA-1 secure hash algorithm.
 *  @return the SHA-1 message digest (a string of 20 bytes).
 *  @exception RANGE_ERROR When ''message'' contains a character beyond '\255;'.
 *)
const func string: sha1 (in var string: message) is func
  result
    var string: digest is "";
  local
    var integer: length is 0;
    var integer: wordIndex is 1;
    var integer: index is 0;
    var array bin32: w is 80 times bin32.value;
    var integer: h0 is 16#67452301;
    var integer: h1 is 16#efcdab89;
    var integer: h2 is 16#98badcfe;
    var integer: h3 is 16#10325476;
    var integer: h4 is 16#c3d2e1f0;
    var bin32: a is bin32(0);
    var bin32: b is bin32(0);
    var bin32: c is bin32(0);
    var bin32: d is bin32(0);
    var bin32: e is bin32(0);
    var bin32: f is bin32(0);
    var bin32: g is bin32(0);
    var integer: temp is 0;
    var integer: k is 0;
  begin
    length := length(message);
    # Append the bit '1' to the message.
    message &:= '\16#80;';
    # Append '0' bits, so that the resulting bit length is congruent to 448 (mod 512).
    message &:= "\0;" mult 63 - (length + 8) mod 64;
    # Append length of message (before pre-processing), in bits, as 64-bit big-endian integer.
    message &:= int64AsEightBytesBe(8 * length);

    # Process the message in successive 512-bit chunks:
    while wordIndex <= length(message) do
      # Break chunk into sixteen 32-bit big-endian words.
      for index range 1 to 16 do
        w[index] := bin32(bytes2Int(message[wordIndex len 4], UNSIGNED, BE));
        wordIndex +:= 4;
      end for;

      # Extend the sixteen 32-bit words into eighty 32-bit words.
      for index range 17 to 80 do
        g := w[index-3] >< w[index-8] >< w[index-14] >< w[index-16];
        w[index] := rotLeft(g, 1);
      end for;

      a := bin32(h0 mod 16#100000000);
      b := bin32(h1 mod 16#100000000);
      c := bin32(h2 mod 16#100000000);
      d := bin32(h3 mod 16#100000000);
      e := bin32(h4 mod 16#100000000);

      for index range 1 to 80 do
        if index <= 20 then
          f := d >< (b & (c >< d));
          k := 16#5a827999;
        elsif index <= 40 then
          f := b >< c >< d;
          k := 16#6ed9eba1;
        elsif index <= 60 then
          f := (b & c) | (d & (b | c));
          k := 16#8f1bbcdc;
        else
          f := b >< c >< d;
          k := 16#ca62c1d6;
        end if;

        temp := ord(rotLeft(a, 5));
        temp +:= ord(f) + ord(e) + k + ord(w[index]);
        e := d;
        d := c;
        c := rotLeft(b, 30);
        b := a;
        a := bin32(temp mod 16#100000000);
      end for;

      # Add this chunk's hash to result so far:
      h0 +:= ord(a);
      h1 +:= ord(b);
      h2 +:= ord(c);
      h3 +:= ord(d);
      h4 +:= ord(e);
    end while;

    # Produce the final hash value:
    digest := int32AsFourBytesBe(h0) &
              int32AsFourBytesBe(h1) &
              int32AsFourBytesBe(h2) &
              int32AsFourBytesBe(h3) &
              int32AsFourBytesBe(h4);
  end func;


(**
 *  Compute a message digest with the SHA-224 secure hash algorithm.
 *  @return the SHA-224 message digest (a string of 28 bytes).
 *  @exception RANGE_ERROR When ''message'' contains a character beyond '\255;'.
 *)
const func string: sha224 (in var string: message) is func
  result
    var string: digest is "";
  local
    # Initialize array of round constants with the first 32 bits of
    # the fractional parts of the cube roots of the first 64 primes 2..311.
    const array integer: k is [] (
        16#428a2f98, 16#71374491, 16#b5c0fbcf, 16#e9b5dba5, 16#3956c25b, 16#59f111f1, 16#923f82a4, 16#ab1c5ed5,
        16#d807aa98, 16#12835b01, 16#243185be, 16#550c7dc3, 16#72be5d74, 16#80deb1fe, 16#9bdc06a7, 16#c19bf174,
        16#e49b69c1, 16#efbe4786, 16#0fc19dc6, 16#240ca1cc, 16#2de92c6f, 16#4a7484aa, 16#5cb0a9dc, 16#76f988da,
        16#983e5152, 16#a831c66d, 16#b00327c8, 16#bf597fc7, 16#c6e00bf3, 16#d5a79147, 16#06ca6351, 16#14292967,
        16#27b70a85, 16#2e1b2138, 16#4d2c6dfc, 16#53380d13, 16#650a7354, 16#766a0abb, 16#81c2c92e, 16#92722c85,
        16#a2bfe8a1, 16#a81a664b, 16#c24b8b70, 16#c76c51a3, 16#d192e819, 16#d6990624, 16#f40e3585, 16#106aa070,
        16#19a4c116, 16#1e376c08, 16#2748774c, 16#34b0bcb5, 16#391c0cb3, 16#4ed8aa4a, 16#5b9cca4f, 16#682e6ff3,
        16#748f82ee, 16#78a5636f, 16#84c87814, 16#8cc70208, 16#90befffa, 16#a4506ceb, 16#bef9a3f7, 16#c67178f2);
    var integer: length is 0;
    var integer: wordIndex is 1;
    var integer: index is 0;
    var array bin32: w is 64 times bin32.value;
    # Initialize hash values with the second 32 bits of
    # the fractional parts of the square roots of the 9th through 16th primes 23..53.
    var integer: h0 is 16#c1059ed8;
    var integer: h1 is 16#367cd507;
    var integer: h2 is 16#3070dd17;
    var integer: h3 is 16#f70e5939;
    var integer: h4 is 16#ffc00b31;
    var integer: h5 is 16#68581511;
    var integer: h6 is 16#64f98fa7;
    var integer: h7 is 16#befa4fa4;
    var bin32: a is bin32(0);
    var bin32: b is bin32(0);
    var bin32: c is bin32(0);
    var bin32: d is bin32(0);
    var bin32: e is bin32(0);
    var bin32: f is bin32(0);
    var bin32: g is bin32(0);
    var bin32: h is bin32(0);
    var bin32: s0 is bin32(0);
    var bin32: s1 is bin32(0);
    var integer: temp1 is 0;
    var integer: temp2 is 0;
    var bin32: ch is bin32(0);
    var bin32: maj is bin32(0);
  begin
    length := length(message);
    # Append the bit '1' to the message.
    message &:= '\16#80;';
    # Append '0' bits, so that the resulting bit length is congruent to 448 (mod 512).
    message &:= "\0;" mult 63 - (length + 8) mod 64;
    # Append length of message (before pre-processing), in bits, as 64-bit big-endian integer.
    message &:= int64AsEightBytesBe(8 * length);

    # Process the message in successive 512-bit chunks:
    while wordIndex <= length(message) do
      # Break chunk into sixteen 32-bit big-endian words.
      for index range 1 to 16 do
        w[index] := bin32(bytes2Int(message[wordIndex len 4], UNSIGNED, BE));
        wordIndex +:= 4;
      end for;

      # Extend the first 16 words into the remaining 48 words of message schedule array:
      for index range 17 to 64 do
        w[index] := bin32(ord(w[index-16]) +
                          ord(rotRight(w[index-15], 7) >< rotRight(w[index-15], 18) >< (w[index-15] >> 3)) +
                          ord(w[index-7]) +
                          ord(rotRight(w[index-2], 17) >< rotRight(w[index-2], 19) >< (w[index-2] >> 10))) &
                    bin32(16#ffffffff);
      end for;

      # Initialize working variables to current hash value:
      a := bin32(h0 mod 16#100000000);
      b := bin32(h1 mod 16#100000000);
      c := bin32(h2 mod 16#100000000);
      d := bin32(h3 mod 16#100000000);
      e := bin32(h4 mod 16#100000000);
      f := bin32(h5 mod 16#100000000);
      g := bin32(h6 mod 16#100000000);
      h := bin32(h7 mod 16#100000000);

      # Compression function main loop:
      for index range 1 to 64 do
        s1 := rotRight(e, 6) >< rotRight(e, 11) >< rotRight(e, 25);
        ch := (e & f) >< ((bin32(16#ffffffff) >< e) & g);
        temp1 := ord(h) + ord(s1) + ord(ch) + k[index] + ord(w[index]);
        s0 := rotRight(a, 2) >< rotRight(a, 13) >< rotRight(a, 22);
        maj := (a & b) >< (a & c) >< (b & c);
        temp2 := ord(s0) + ord(maj);

        h := g;
        g := f;
        f := e;
        e := bin32((ord(d) + temp1) mod 16#100000000);
        d := c;
        c := b;
        b := a;
        a := bin32((temp1 + temp2) mod 16#100000000);
      end for;

      # Add the compressed chunk to the current hash value:
      h0 +:= ord(a);
      h1 +:= ord(b);
      h2 +:= ord(c);
      h3 +:= ord(d);
      h4 +:= ord(e);
      h5 +:= ord(f);
      h6 +:= ord(g);
      h7 +:= ord(h);
    end while;

    # Produce the final hash value:
    digest := int32AsFourBytesBe(h0) &
              int32AsFourBytesBe(h1) &
              int32AsFourBytesBe(h2) &
              int32AsFourBytesBe(h3) &
              int32AsFourBytesBe(h4) &
              int32AsFourBytesBe(h5) &
              int32AsFourBytesBe(h6);
  end func;


(**
 *  Compute a message digest with the SHA-256 secure hash algorithm.
 *  @return the SHA-256 message digest (a string of 32 bytes).
 *  @exception RANGE_ERROR When ''message'' contains a character beyond '\255;'.
 *)
const func string: sha256 (in var string: message) is func
  result
    var string: digest is "";
  local
    # Initialize array of round constants with the first 32 bits of
    # the fractional parts of the cube roots of the first 64 primes 2..311.
    const array integer: k is [] (
        16#428a2f98, 16#71374491, 16#b5c0fbcf, 16#e9b5dba5, 16#3956c25b, 16#59f111f1, 16#923f82a4, 16#ab1c5ed5,
        16#d807aa98, 16#12835b01, 16#243185be, 16#550c7dc3, 16#72be5d74, 16#80deb1fe, 16#9bdc06a7, 16#c19bf174,
        16#e49b69c1, 16#efbe4786, 16#0fc19dc6, 16#240ca1cc, 16#2de92c6f, 16#4a7484aa, 16#5cb0a9dc, 16#76f988da,
        16#983e5152, 16#a831c66d, 16#b00327c8, 16#bf597fc7, 16#c6e00bf3, 16#d5a79147, 16#06ca6351, 16#14292967,
        16#27b70a85, 16#2e1b2138, 16#4d2c6dfc, 16#53380d13, 16#650a7354, 16#766a0abb, 16#81c2c92e, 16#92722c85,
        16#a2bfe8a1, 16#a81a664b, 16#c24b8b70, 16#c76c51a3, 16#d192e819, 16#d6990624, 16#f40e3585, 16#106aa070,
        16#19a4c116, 16#1e376c08, 16#2748774c, 16#34b0bcb5, 16#391c0cb3, 16#4ed8aa4a, 16#5b9cca4f, 16#682e6ff3,
        16#748f82ee, 16#78a5636f, 16#84c87814, 16#8cc70208, 16#90befffa, 16#a4506ceb, 16#bef9a3f7, 16#c67178f2);
    var integer: length is 0;
    var integer: wordIndex is 1;
    var integer: index is 0;
    var array bin32: w is 64 times bin32.value;
    # Initialize hash values with the first 32 bits of
    # the fractional parts of the square roots of the first 8 primes 2..19.
    var integer: h0 is 16#6a09e667;
    var integer: h1 is 16#bb67ae85;
    var integer: h2 is 16#3c6ef372;
    var integer: h3 is 16#a54ff53a;
    var integer: h4 is 16#510e527f;
    var integer: h5 is 16#9b05688c;
    var integer: h6 is 16#1f83d9ab;
    var integer: h7 is 16#5be0cd19;
    var bin32: a is bin32(0);
    var bin32: b is bin32(0);
    var bin32: c is bin32(0);
    var bin32: d is bin32(0);
    var bin32: e is bin32(0);
    var bin32: f is bin32(0);
    var bin32: g is bin32(0);
    var bin32: h is bin32(0);
    var bin32: s0 is bin32(0);
    var bin32: s1 is bin32(0);
    var integer: temp1 is 0;
    var integer: temp2 is 0;
    var bin32: ch is bin32(0);
    var bin32: maj is bin32(0);
  begin
    length := length(message);
    # Append the bit '1' to the message.
    message &:= '\16#80;';
    # Append '0' bits, so that the resulting bit length is congruent to 448 (mod 512).
    message &:= "\0;" mult 63 - (length + 8) mod 64;
    # Append length of message (before pre-processing), in bits, as 64-bit big-endian integer.
    message &:= int64AsEightBytesBe(8 * length);

    # Process the message in successive 512-bit chunks:
    while wordIndex <= length(message) do
      # Break chunk into sixteen 32-bit big-endian words.
      for index range 1 to 16 do
        w[index] := bin32(bytes2Int(message[wordIndex len 4], UNSIGNED, BE));
        wordIndex +:= 4;
      end for;

      # Extend the first 16 words into the remaining 48 words of message schedule array:
      for index range 17 to 64 do
        w[index] := bin32(ord(w[index-16]) +
                          ord(rotRight(w[index-15], 7) >< rotRight(w[index-15], 18) >< (w[index-15] >> 3)) +
                          ord(w[index-7]) +
                          ord(rotRight(w[index-2], 17) >< rotRight(w[index-2], 19) >< (w[index-2] >> 10))) &
                    bin32(16#ffffffff);
      end for;

      # Initialize working variables to current hash value:
      a := bin32(h0 mod 16#100000000);
      b := bin32(h1 mod 16#100000000);
      c := bin32(h2 mod 16#100000000);
      d := bin32(h3 mod 16#100000000);
      e := bin32(h4 mod 16#100000000);
      f := bin32(h5 mod 16#100000000);
      g := bin32(h6 mod 16#100000000);
      h := bin32(h7 mod 16#100000000);

      # Compression function main loop:
      for index range 1 to 64 do
        s1 := rotRight(e, 6) >< rotRight(e, 11) >< rotRight(e, 25);
        ch := (e & f) >< ((bin32(16#ffffffff) >< e) & g);
        temp1 := ord(h) + ord(s1) + ord(ch) + k[index] + ord(w[index]);
        s0 := rotRight(a, 2) >< rotRight(a, 13) >< rotRight(a, 22);
        maj := (a & b) >< (a & c) >< (b & c);
        temp2 := ord(s0) + ord(maj);

        h := g;
        g := f;
        f := e;
        e := bin32((ord(d) + temp1) mod 16#100000000);
        d := c;
        c := b;
        b := a;
        a := bin32((temp1 + temp2) mod 16#100000000);
      end for;

      # Add the compressed chunk to the current hash value:
      h0 +:= ord(a);
      h1 +:= ord(b);
      h2 +:= ord(c);
      h3 +:= ord(d);
      h4 +:= ord(e);
      h5 +:= ord(f);
      h6 +:= ord(g);
      h7 +:= ord(h);
    end while;

    # Produce the final hash value:
    digest := int32AsFourBytesBe(h0) &
              int32AsFourBytesBe(h1) &
              int32AsFourBytesBe(h2) &
              int32AsFourBytesBe(h3) &
              int32AsFourBytesBe(h4) &
              int32AsFourBytesBe(h5) &
              int32AsFourBytesBe(h6) &
              int32AsFourBytesBe(h7);
  end func;


(**
 *  Compute a message digest with the SHA-384 secure hash algorithm.
 *  @return the SHA-384 message digest (a string of 48 bytes).
 *  @exception RANGE_ERROR When ''message'' contains a character beyond '\255;'.
 *)
const func string: sha384 (in var string: message) is func
  result
    var string: digest is "";
  local
    const bigInteger: powTwo64 is 2_**64;
    # Initialize array of round constants with the first 64 bits of
    # the fractional parts of the cube roots of the first 80 primes 2..409.
    const array bigInteger: k is [] (
        16#428a2f98d728ae22_, 16#7137449123ef65cd_, 16#b5c0fbcfec4d3b2f_, 16#e9b5dba58189dbbc_,
        16#3956c25bf348b538_, 16#59f111f1b605d019_, 16#923f82a4af194f9b_, 16#ab1c5ed5da6d8118_,
        16#d807aa98a3030242_, 16#12835b0145706fbe_, 16#243185be4ee4b28c_, 16#550c7dc3d5ffb4e2_,
        16#72be5d74f27b896f_, 16#80deb1fe3b1696b1_, 16#9bdc06a725c71235_, 16#c19bf174cf692694_,
        16#e49b69c19ef14ad2_, 16#efbe4786384f25e3_, 16#0fc19dc68b8cd5b5_, 16#240ca1cc77ac9c65_,
        16#2de92c6f592b0275_, 16#4a7484aa6ea6e483_, 16#5cb0a9dcbd41fbd4_, 16#76f988da831153b5_,
        16#983e5152ee66dfab_, 16#a831c66d2db43210_, 16#b00327c898fb213f_, 16#bf597fc7beef0ee4_,
        16#c6e00bf33da88fc2_, 16#d5a79147930aa725_, 16#06ca6351e003826f_, 16#142929670a0e6e70_,
        16#27b70a8546d22ffc_, 16#2e1b21385c26c926_, 16#4d2c6dfc5ac42aed_, 16#53380d139d95b3df_,
        16#650a73548baf63de_, 16#766a0abb3c77b2a8_, 16#81c2c92e47edaee6_, 16#92722c851482353b_,
        16#a2bfe8a14cf10364_, 16#a81a664bbc423001_, 16#c24b8b70d0f89791_, 16#c76c51a30654be30_,
        16#d192e819d6ef5218_, 16#d69906245565a910_, 16#f40e35855771202a_, 16#106aa07032bbd1b8_,
        16#19a4c116b8d2d0c8_, 16#1e376c085141ab53_, 16#2748774cdf8eeb99_, 16#34b0bcb5e19b48a8_,
        16#391c0cb3c5c95a63_, 16#4ed8aa4ae3418acb_, 16#5b9cca4f7763e373_, 16#682e6ff3d6b2b8a3_,
        16#748f82ee5defb2fc_, 16#78a5636f43172f60_, 16#84c87814a1f0ab72_, 16#8cc702081a6439ec_,
        16#90befffa23631e28_, 16#a4506cebde82bde9_, 16#bef9a3f7b2c67915_, 16#c67178f2e372532b_,
        16#ca273eceea26619c_, 16#d186b8c721c0c207_, 16#eada7dd6cde0eb1e_, 16#f57d4f7fee6ed178_,
        16#06f067aa72176fba_, 16#0a637dc5a2c898a6_, 16#113f9804bef90dae_, 16#1b710b35131c471b_,
        16#28db77f523047d84_, 16#32caab7b40c72493_, 16#3c9ebe0a15c9bebc_, 16#431d67c49c100d4c_,
        16#4cc5d4becb3e42b6_, 16#597f299cfc657e2a_, 16#5fcb6fab3ad6faec_, 16#6c44198c4a475817_);
    var integer: length is 0;
    var integer: chunkIndex is 0;
    var integer: index is 0;
    var array bin64: w is 80 times bin64.value;
    # Initialize hash values with the first 64 bits of
    # the fractional parts of the square roots of the first 8 primes 2..19.
    var bigInteger: h0 is 16#cbbb9d5dc1059ed8_;
    var bigInteger: h1 is 16#629a292a367cd507_;
    var bigInteger: h2 is 16#9159015a3070dd17_;
    var bigInteger: h3 is 16#152fecd8f70e5939_;
    var bigInteger: h4 is 16#67332667ffc00b31_;
    var bigInteger: h5 is 16#8eb44a8768581511_;
    var bigInteger: h6 is 16#db0c2e0d64f98fa7_;
    var bigInteger: h7 is 16#47b5481dbefa4fa4_;
    var bin64: a is bin64(0);
    var bin64: b is bin64(0);
    var bin64: c is bin64(0);
    var bin64: d is bin64(0);
    var bin64: e is bin64(0);
    var bin64: f is bin64(0);
    var bin64: g is bin64(0);
    var bin64: h is bin64(0);
    var bin64: s0 is bin64(0);
    var bin64: s1 is bin64(0);
    var bin64: temp1 is bin64(0);
    var bin64: temp2 is bin64(0);
    var bin64: ch is bin64(0);
    var bin64: maj is bin64(0);
  begin
    length := length(message);
    # Append the bit '1' to the message.
    message &:= '\16#80;';
    # Append '0' bits, so that the resulting bit length is congruent to 448 (mod 1024).
    message &:= "\0;" mult 127 - (length + 8) mod 128;
    # Append length of message (before pre-processing), in bits, as 64-bit big-endian integer.
    message &:= int64AsEightBytesBe(8 * length);

    # Process the message in successive 1024-bit chunks:
    for chunkIndex range 1 to length(message) step 128 do
      # Check that the input contains no character beyond '\255;'.
      for index range 0 to 127 do
        if ord(message[chunkIndex + index]) > 255 then
          raise RANGE_ERROR;
        end if;
      end for;

      # Break chunk into sixteen 64-bit big-endian words.
      for index range 1 to 16 do
        w[index] := bin64(message[chunkIndex + 8 * pred(index) len 8], BE);
      end for;

      # Extend the first 16 words into the remaining 64 words of message schedule array:
      for index range 17 to 80 do
        w[index] := bin64((big(w[index-16]) +
                           big(rotRight(w[index-15], 1) ><
                               rotRight(w[index-15], 8) ><
                               (w[index-15] >> 7)) +
                           big(w[index-7]) +
                           big(rotRight(w[index-2], 19) ><
                               rotRight(w[index-2], 61) ><
                               (w[index-2] >> 6))) mod powTwo64);
      end for;

      # Initialize working variables to current hash value:
      a := bin64(h0 mod powTwo64);
      b := bin64(h1 mod powTwo64);
      c := bin64(h2 mod powTwo64);
      d := bin64(h3 mod powTwo64);
      e := bin64(h4 mod powTwo64);
      f := bin64(h5 mod powTwo64);
      g := bin64(h6 mod powTwo64);
      h := bin64(h7 mod powTwo64);

      # Compression function main loop:
      for index range 1 to 80 do
        s1 := rotRight(e, 14) >< rotRight(e, 18) >< rotRight(e, 41);
        ch := (e & f) >< (~e & g);
        temp1 := bin64((big(h) + big(s1) + big(ch) + k[index] + big(w[index])) mod powTwo64);
        s0 := rotRight(a, 28) >< rotRight(a, 34) >< rotRight(a, 39);
        maj := (a & b) >< (a & c) >< (b & c);
        temp2 := bin64((big(s0) + big(maj)) mod powTwo64);

        h := g;
        g := f;
        f := e;
        e := bin64((big(d) + big(temp1)) mod powTwo64);
        d := c;
        c := b;
        b := a;
        a := bin64((big(temp1) + big(temp2)) mod powTwo64);
      end for;

      # Add the compressed chunk to the current hash value:
      h0 +:= big(a);
      h1 +:= big(b);
      h2 +:= big(c);
      h3 +:= big(d);
      h4 +:= big(e);
      h5 +:= big(f);
      h6 +:= big(g);
      h7 +:= big(h);
    end for;

    # Produce the final hash value:
    digest := bigAsEightBytesBe(h0) &
              bigAsEightBytesBe(h1) &
              bigAsEightBytesBe(h2) &
              bigAsEightBytesBe(h3) &
              bigAsEightBytesBe(h4) &
              bigAsEightBytesBe(h5);
  end func;


(**
 *  Compute a message digest with the SHA-512 secure hash algorithm.
 *  @return the SHA-512 message digest (a string of 64 bytes).
 *  @exception RANGE_ERROR When ''message'' contains a character beyond '\255;'.
 *)
const func string: sha512 (in var string: message) is func
  result
    var string: digest is "";
  local
    const bigInteger: powTwo64 is 2_**64;
    # Initialize array of round constants with the first 64 bits of
    # the fractional parts of the cube roots of the first 80 primes 2..409.
    const array bigInteger: k is [] (
        16#428a2f98d728ae22_, 16#7137449123ef65cd_, 16#b5c0fbcfec4d3b2f_, 16#e9b5dba58189dbbc_,
        16#3956c25bf348b538_, 16#59f111f1b605d019_, 16#923f82a4af194f9b_, 16#ab1c5ed5da6d8118_,
        16#d807aa98a3030242_, 16#12835b0145706fbe_, 16#243185be4ee4b28c_, 16#550c7dc3d5ffb4e2_,
        16#72be5d74f27b896f_, 16#80deb1fe3b1696b1_, 16#9bdc06a725c71235_, 16#c19bf174cf692694_,
        16#e49b69c19ef14ad2_, 16#efbe4786384f25e3_, 16#0fc19dc68b8cd5b5_, 16#240ca1cc77ac9c65_,
        16#2de92c6f592b0275_, 16#4a7484aa6ea6e483_, 16#5cb0a9dcbd41fbd4_, 16#76f988da831153b5_,
        16#983e5152ee66dfab_, 16#a831c66d2db43210_, 16#b00327c898fb213f_, 16#bf597fc7beef0ee4_,
        16#c6e00bf33da88fc2_, 16#d5a79147930aa725_, 16#06ca6351e003826f_, 16#142929670a0e6e70_,
        16#27b70a8546d22ffc_, 16#2e1b21385c26c926_, 16#4d2c6dfc5ac42aed_, 16#53380d139d95b3df_,
        16#650a73548baf63de_, 16#766a0abb3c77b2a8_, 16#81c2c92e47edaee6_, 16#92722c851482353b_,
        16#a2bfe8a14cf10364_, 16#a81a664bbc423001_, 16#c24b8b70d0f89791_, 16#c76c51a30654be30_,
        16#d192e819d6ef5218_, 16#d69906245565a910_, 16#f40e35855771202a_, 16#106aa07032bbd1b8_,
        16#19a4c116b8d2d0c8_, 16#1e376c085141ab53_, 16#2748774cdf8eeb99_, 16#34b0bcb5e19b48a8_,
        16#391c0cb3c5c95a63_, 16#4ed8aa4ae3418acb_, 16#5b9cca4f7763e373_, 16#682e6ff3d6b2b8a3_,
        16#748f82ee5defb2fc_, 16#78a5636f43172f60_, 16#84c87814a1f0ab72_, 16#8cc702081a6439ec_,
        16#90befffa23631e28_, 16#a4506cebde82bde9_, 16#bef9a3f7b2c67915_, 16#c67178f2e372532b_,
        16#ca273eceea26619c_, 16#d186b8c721c0c207_, 16#eada7dd6cde0eb1e_, 16#f57d4f7fee6ed178_,
        16#06f067aa72176fba_, 16#0a637dc5a2c898a6_, 16#113f9804bef90dae_, 16#1b710b35131c471b_,
        16#28db77f523047d84_, 16#32caab7b40c72493_, 16#3c9ebe0a15c9bebc_, 16#431d67c49c100d4c_,
        16#4cc5d4becb3e42b6_, 16#597f299cfc657e2a_, 16#5fcb6fab3ad6faec_, 16#6c44198c4a475817_);
    var integer: length is 0;
    var integer: chunkIndex is 0;
    var integer: index is 0;
    var array bin64: w is 80 times bin64.value;
    # Initialize hash values with the first 64 bits of
    # the fractional parts of the square roots of the first 8 primes 2..19.
    var bigInteger: h0 is 16#6a09e667f3bcc908_;
    var bigInteger: h1 is 16#bb67ae8584caa73b_;
    var bigInteger: h2 is 16#3c6ef372fe94f82b_;
    var bigInteger: h3 is 16#a54ff53a5f1d36f1_;
    var bigInteger: h4 is 16#510e527fade682d1_;
    var bigInteger: h5 is 16#9b05688c2b3e6c1f_;
    var bigInteger: h6 is 16#1f83d9abfb41bd6b_;
    var bigInteger: h7 is 16#5be0cd19137e2179_;
    var bin64: a is bin64(0);
    var bin64: b is bin64(0);
    var bin64: c is bin64(0);
    var bin64: d is bin64(0);
    var bin64: e is bin64(0);
    var bin64: f is bin64(0);
    var bin64: g is bin64(0);
    var bin64: h is bin64(0);
    var bin64: s0 is bin64(0);
    var bin64: s1 is bin64(0);
    var bin64: temp1 is bin64(0);
    var bin64: temp2 is bin64(0);
    var bin64: ch is bin64(0);
    var bin64: maj is bin64(0);
  begin
    length := length(message);
    # Append the bit '1' to the message.
    message &:= '\16#80;';
    # Append '0' bits, so that the resulting bit length is congruent to 448 (mod 1024).
    message &:= "\0;" mult 127 - (length + 8) mod 128;
    # Append length of message (before pre-processing), in bits, as 64-bit big-endian integer.
    message &:= int64AsEightBytesBe(8 * length);

    # Process the message in successive 1024-bit chunks:
    for chunkIndex range 1 to length(message) step 128 do
      # Check that the input contains no character beyond '\255;'.
      for index range 0 to 127 do
        if ord(message[chunkIndex + index]) > 255 then
          raise RANGE_ERROR;
        end if;
      end for;

      # Break chunk into sixteen 64-bit big-endian words.
      for index range 1 to 16 do
        w[index] := bin64(message[chunkIndex + 8 * pred(index) len 8], BE);
      end for;

      # Extend the first 16 words into the remaining 64 words of message schedule array:
      for index range 17 to 80 do
        w[index] := bin64((big(w[index-16]) +
                           big(rotRight(w[index-15], 1) ><
                               rotRight(w[index-15], 8) ><
                               (w[index-15] >> 7)) +
                           big(w[index-7]) +
                           big(rotRight(w[index-2], 19) ><
                               rotRight(w[index-2], 61) ><
                               (w[index-2] >> 6))) mod powTwo64);
      end for;

      # Initialize working variables to current hash value:
      a := bin64(h0 mod powTwo64);
      b := bin64(h1 mod powTwo64);
      c := bin64(h2 mod powTwo64);
      d := bin64(h3 mod powTwo64);
      e := bin64(h4 mod powTwo64);
      f := bin64(h5 mod powTwo64);
      g := bin64(h6 mod powTwo64);
      h := bin64(h7 mod powTwo64);

      # Compression function main loop:
      for index range 1 to 80 do
        s1 := rotRight(e, 14) >< rotRight(e, 18) >< rotRight(e, 41);
        ch := (e & f) >< (~e & g);
        temp1 := bin64((big(h) + big(s1) + big(ch) + k[index] + big(w[index])) mod powTwo64);
        s0 := rotRight(a, 28) >< rotRight(a, 34) >< rotRight(a, 39);
        maj := (a & b) >< (a & c) >< (b & c);
        temp2 := bin64((big(s0) + big(maj)) mod powTwo64);

        h := g;
        g := f;
        f := e;
        e := bin64((big(d) + big(temp1)) mod powTwo64);
        d := c;
        c := b;
        b := a;
        a := bin64((big(temp1) + big(temp2)) mod powTwo64);
      end for;

      # Add the compressed chunk to the current hash value:
      h0 +:= big(a);
      h1 +:= big(b);
      h2 +:= big(c);
      h3 +:= big(d);
      h4 +:= big(e);
      h5 +:= big(f);
      h6 +:= big(g);
      h7 +:= big(h);
    end for;

    # Produce the final hash value:
    digest := bigAsEightBytesBe(h0) &
              bigAsEightBytesBe(h1) &
              bigAsEightBytesBe(h2) &
              bigAsEightBytesBe(h3) &
              bigAsEightBytesBe(h4) &
              bigAsEightBytesBe(h5) &
              bigAsEightBytesBe(h6) &
              bigAsEightBytesBe(h7);
  end func;


(**
 *  Enumeration of message digest algorithms.
 *  Defines: NO_DIGEST, MD4, MD5, SHA1, SHA224, SHA256, SHA384 and SHA512.
 *)
const type: digestAlgorithm is new enum
    NO_DIGEST, MD4, MD5, SHA1, SHA224, SHA256, SHA384, SHA512
  end enum;


(**
 *  Compute a message digest with the given [[msgdigest#digestAlgorithm|digestAlgorithm]].
 *  @param digestAlg The [[msgdigest#digestAlgorithm|digestAlgorithm]] to be used.
 *  @return the message digest of the ''message''.
 *  @exception RANGE_ERROR When ''message'' contains a character beyond '\255;'.
 *)
const func string: msgDigest (in digestAlgorithm: digestAlg, in string: message) is DYNAMIC;

const func string: msgDigest (NO_DIGEST, in string: message) is return "";
const func string: msgDigest (MD4, in string: message)       is return md4(message);
const func string: msgDigest (MD5, in string: message)       is return md5(message);
const func string: msgDigest (SHA1, in string: message)      is return sha1(message);
const func string: msgDigest (SHA224, in string: message)    is return sha224(message);
const func string: msgDigest (SHA256, in string: message)    is return sha256(message);
const func string: msgDigest (SHA384, in string: message)    is return sha384(message);
const func string: msgDigest (SHA512, in string: message)    is return sha512(message);


(**
 *  Block size used by the given [[msgdigest#digestAlgorithm|digestAlgorithm]].
 *  @return the block size in bytes used by the message digest algorithm.
 *)
const func integer: blockSize (in digestAlgorithm: digestAlg) is DYNAMIC;

const func integer: blockSize (NO_DIGEST) is   0;
const func integer: blockSize (MD4)       is  64;
const func integer: blockSize (MD5)       is  64;
const func integer: blockSize (SHA1)      is  64;
const func integer: blockSize (SHA224)    is  64;
const func integer: blockSize (SHA256)    is  64;
const func integer: blockSize (SHA384)    is 128;
const func integer: blockSize (SHA512)    is 128;


(**
 *  Size of a message digest computed with the given [[msgdigest#digestAlgorithm|digestAlgorithm]].
 *  @return the size of a message digest in bytes.
 *)
const func integer: digestSize (in digestAlgorithm: digestAlg) is DYNAMIC;

const func integer: digestSize (NO_DIGEST) is  0;
const func integer: digestSize (MD4)       is 16;
const func integer: digestSize (MD5)       is 16;
const func integer: digestSize (SHA1)      is 20;
const func integer: digestSize (SHA224)    is 28;
const func integer: digestSize (SHA256)    is 32;
const func integer: digestSize (SHA384)    is 48;
const func integer: digestSize (SHA512)    is 64;
