// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Mobius Forensic Toolkit
// Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022,2023 Eduardo Aguiar
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 2, or (at your option) any later
// version.
//
// 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 General
// Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
#include <mobius/crypt/cipher.h>
#include <mobius/crypt/hash.h>
#include <mobius/crypt/hmac.h>
#include <mobius/crypt/pkcs5.h>
#include <mobius/unittest.h>
#include <cstring>

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \see https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/TDES_Core.pdf
//! \see https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/TDES_CBC.pdf
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
static void
testcase_cipher_3des ()
{
  mobius::unittest test ("mobius::crypt::cipher_3des");

  // 3DES - mode ECB
  const std::string plaintext0 = "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51";

  mobius::crypt::cipher des0 ("3des", {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef});

  mobius::bytearray b0;
  b0.from_hexstring (plaintext0);

  mobius::bytearray b1 = des0.encrypt (b0);

  test.ASSERT_EQUAL (b1.to_hexstring (), "06ede3d82884090aff322c19f0518486730576972a666e58b6c88cf107340d3d");

  b1 = des0.decrypt (b1);

  test.ASSERT_EQUAL (b1.to_hexstring (), plaintext0);

  // 3DES - mode ECB
  mobius::crypt::cipher des1 ("3des", {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23});

  b1 = des1.encrypt (b0);

  test.ASSERT_EQUAL (b1.to_hexstring (), "714772f339841d34267fcc4bd2949cc3ee11c22a576a303876183f99c0b6de87");

  b1 = des1.decrypt (b1);

  test.ASSERT_EQUAL (b1.to_hexstring (), plaintext0);

  // 3DES - mode CBC
  mobius::crypt::cipher des2a ("3des", {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23}, "cbc", {0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17});

  mobius::bytearray b2 = des2a.encrypt (b0);
  
  test.ASSERT_EQUAL (b2.to_hexstring (), "2079c3d53aa763e193b79e2569ab5262516570481f25b50f73c0bda85c8e0da7");

  mobius::crypt::cipher des2b ("3des", {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23}, "cbc", {0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17});

  b2 = des2b.decrypt (b2);

  test.ASSERT_EQUAL (b2.to_hexstring (), plaintext0);

  // 3DES - mode CBC
  mobius::crypt::cipher des3a ("3des", {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, "cbc", {0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17});

  mobius::bytearray b3 = des3a.encrypt (b0);
  
  test.ASSERT_EQUAL (b3.to_hexstring (), "7401ce1eab6d003caff84bf47b36cc2154f0238f9ffecd8f6acf118392b45581");

  mobius::crypt::cipher des3b ("3des", {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, "cbc", {0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17});

  b3 = des3b.decrypt (b3);

  test.ASSERT_EQUAL (b3.to_hexstring (), plaintext0);

  test.end ();
}

static void
testcase_cipher_aes ()
{
  mobius::unittest test ("mobius::crypt::cipher_aes");

  //! \see https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/AES_ModesA_All.pdf

  const mobius::bytearray b_plaintext =
  {
    0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a,
    0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51,
    0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef,
    0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10
  };

  const mobius::bytearray iv =
  {
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
  };

  mobius::bytearray b (b_plaintext.size ());

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // AES128 - mode ECB
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  const mobius::bytearray b1_ciphertext =
  {
    0x3a, 0xd7, 0x7b, 0xb4, 0x0d, 0x7a, 0x36, 0x60, 0xa8, 0x9e, 0xca, 0xf3, 0x24, 0x66, 0xef, 0x97,
    0xf5, 0xd3, 0xd5, 0x85, 0x03, 0xb9, 0x69, 0x9d, 0xe7, 0x85, 0x89, 0x5a, 0x96, 0xfd, 0xba, 0xaf,
    0x43, 0xb1, 0xcd, 0x7f, 0x59, 0x8e, 0xce, 0x23, 0x88, 0x1b, 0x00, 0xe3, 0xed, 0x03, 0x06, 0x88,
    0x7b, 0x0c, 0x78, 0x5e, 0x27, 0xe8, 0xad, 0x3f, 0x82, 0x23, 0x20, 0x71, 0x04, 0x72, 0x5d, 0xd4
  };

  mobius::crypt::cipher aes1 ("aes", {0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c});

  b = aes1.encrypt (b_plaintext);
  test.ASSERT_EQUAL (b, b1_ciphertext);

  b = aes1.decrypt (b1_ciphertext);
  test.ASSERT_EQUAL (b, b_plaintext);

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // AES192 - mode ECB
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  const mobius::bytearray b2_ciphertext =
  {
    0xbd, 0x33, 0x4f, 0x1d, 0x6e, 0x45, 0xf2, 0x5f, 0xf7, 0x12, 0xa2, 0x14, 0x57, 0x1f, 0xa5, 0xcc,
    0x97, 0x41, 0x04, 0x84, 0x6d, 0x0a, 0xd3, 0xad, 0x77, 0x34, 0xec, 0xb3, 0xec, 0xee, 0x4e, 0xef,
    0xef, 0x7a, 0xfd, 0x22, 0x70, 0xe2, 0xe6, 0x0a, 0xdc, 0xe0, 0xba, 0x2f, 0xac, 0xe6, 0x44, 0x4e,
    0x9a, 0x4b, 0x41, 0xba, 0x73, 0x8d, 0x6c, 0x72, 0xfb, 0x16, 0x69, 0x16, 0x03, 0xc1, 0x8e, 0x0e
  };

  mobius::crypt::cipher aes2 ("aes", {0x8e, 0x73, 0xb0, 0xf7, 0xda, 0x0e, 0x64, 0x52, 0xc8, 0x10, 0xf3, 0x2b, 0x80, 0x90, 0x79, 0xe5, 0x62, 0xf8, 0xea, 0xd2, 0x52, 0x2c, 0x6b, 0x7b});

  b = aes2.encrypt (b_plaintext);
  test.ASSERT_EQUAL (b, b2_ciphertext);

  b = aes2.decrypt (b2_ciphertext);
  test.ASSERT_EQUAL (b, b_plaintext);

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // AES256 - mode ECB
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  const mobius::bytearray b3_ciphertext =
  {
    0xf3, 0xee, 0xd1, 0xbd, 0xb5, 0xd2, 0xa0, 0x3c, 0x06, 0x4b, 0x5a, 0x7e, 0x3d, 0xb1, 0x81, 0xf8,
    0x59, 0x1c, 0xcb, 0x10, 0xd4, 0x10, 0xed, 0x26, 0xdc, 0x5b, 0xa7, 0x4a, 0x31, 0x36, 0x28, 0x70,
    0xb6, 0xed, 0x21, 0xb9, 0x9c, 0xa6, 0xf4, 0xf9, 0xf1, 0x53, 0xe7, 0xb1, 0xbe, 0xaf, 0xed, 0x1d,
    0x23, 0x30, 0x4b, 0x7a, 0x39, 0xf9, 0xf3, 0xff, 0x06, 0x7d, 0x8d, 0x8f, 0x9e, 0x24, 0xec, 0xc7
  };

  mobius::crypt::cipher aes3 ("aes", {0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81, 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4});

  b = aes3.encrypt (b_plaintext);
  test.ASSERT_EQUAL (b, b3_ciphertext);

  b = aes3.decrypt (b3_ciphertext);
  test.ASSERT_EQUAL (b, b_plaintext);

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // AES128 - mode CBC
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  const mobius::bytearray b4_ciphertext =
  {
    0x76, 0x49, 0xab, 0xac, 0x81, 0x19, 0xb2, 0x46, 0xce, 0xe9, 0x8e, 0x9b, 0x12, 0xe9, 0x19, 0x7d,
    0x50, 0x86, 0xcb, 0x9b, 0x50, 0x72, 0x19, 0xee, 0x95, 0xdb, 0x11, 0x3a, 0x91, 0x76, 0x78, 0xb2,
    0x73, 0xbe, 0xd6, 0xb8, 0xe3, 0xc1, 0x74, 0x3b, 0x71, 0x16, 0xe6, 0x9e, 0x22, 0x22, 0x95, 0x16,
    0x3f, 0xf1, 0xca, 0xa1, 0x68, 0x1f, 0xac, 0x09, 0x12, 0x0e, 0xca, 0x30, 0x75, 0x86, 0xe1, 0xa7
  };

  mobius::crypt::cipher aes4a ("aes", {0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c}, "cbc", iv);

  b = aes4a.encrypt (b_plaintext);
  test.ASSERT_EQUAL (b, b4_ciphertext);

  mobius::crypt::cipher aes4b ("aes", {0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c}, "cbc", iv);

  b = aes4b.decrypt (b4_ciphertext);
  test.ASSERT_EQUAL (b, b_plaintext);

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // AES128 - mode IGE
  //! \see https://mgp25.com/AESIGE/
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  const mobius::bytearray b5a_plaintext (32);

  const mobius::bytearray b5a_ciphertext = {
     0x1a, 0x85, 0x19, 0xa6, 0x55, 0x7b, 0xe6, 0x52,
     0xe9, 0xda, 0x8e, 0x43, 0xda, 0x4e, 0xf4, 0x45,
     0x3c, 0xf4, 0x56, 0xb4, 0xca, 0x48, 0x8a, 0xa3,
     0x83, 0xc7, 0x9c, 0x98, 0xb3, 0x47, 0x97, 0xcb
  };
  
  const mobius::bytearray iv5a = {
     0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
     0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
     0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
     0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f
  };

  mobius::crypt::cipher aes5a ("aes",
    {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x7,
     0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}, "ige", iv5a
  );

  test.ASSERT_EQUAL (aes5a.encrypt (b5a_plaintext), b5a_ciphertext);
  aes5a.reset ();
  test.ASSERT_EQUAL (aes5a.decrypt (b5a_ciphertext), b5a_plaintext);

  const mobius::bytearray b5b_plaintext = {
     0x99, 0x70, 0x64, 0x87, 0xa1, 0xcd, 0xe6, 0x13,
     0xbc, 0x6d, 0xe0, 0xb6, 0xf2, 0x4b, 0x1c, 0x7a,
     0xa4, 0x48, 0xc8, 0xb9, 0xc3, 0x40, 0x3e, 0x34,
     0x67, 0xa8, 0xca, 0xd8, 0x93, 0x40, 0xf5, 0x3b
  };
  
  const mobius::bytearray b5b_ciphertext = {
     0x4c, 0x2e, 0x20, 0x4c, 0x65, 0x74, 0x27, 0x73,
     0x20, 0x68, 0x6f, 0x70, 0x65, 0x20, 0x42, 0x65,
     0x6e, 0x20, 0x67, 0x6f, 0x74, 0x20, 0x69, 0x74,
     0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x21, 0x0a     
  };
  
  const mobius::bytearray iv5b = {
     0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f,
     0x6e, 0x20, 0x6f, 0x66, 0x20, 0x49, 0x47, 0x45,
     0x20, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x66, 0x6f,
     0x72, 0x20, 0x4f, 0x70, 0x65, 0x6e, 0x53, 0x53
  };

  mobius::crypt::cipher aes5b ("aes",
    {0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20,
     0x61, 0x6e, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65}, "ige", iv5b
  );

  test.ASSERT_EQUAL (aes5b.encrypt (b5b_plaintext), b5b_ciphertext);
  aes5b.reset ();
  test.ASSERT_EQUAL (aes5b.decrypt (b5b_ciphertext), b5b_plaintext);

  test.end ();
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \see https://www.schneier.com/code/vectors.txt
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
static void
testcase_cipher_blowfish ()
{
  mobius::unittest test ("mobius::crypt::cipher_blowfish");

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // mode ECB - key length = 64 bits
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  const mobius::bytearray values1[34][3] =    // key, plaintext, ciphertext
  {
    {
      {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
      {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
      {0x4e, 0xf9, 0x97, 0x45, 0x61, 0x98, 0xdd, 0x78}
    },
    {
      {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
      {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
      {0x51, 0x86, 0x6f, 0xd5, 0xb8, 0x5e, 0xcb, 0x8a}
    },
    {
      {0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
      {0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
      {0x7d, 0x85, 0x6f, 0x9a, 0x61, 0x30, 0x63, 0xf2}
    },
    {
      {0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11},
      {0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11},
      {0x24, 0x66, 0xdd, 0x87, 0x8b, 0x96, 0x3c, 0x9d}
    },
    {
      {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef},
      {0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11},
      {0x61, 0xf9, 0xc3, 0x80, 0x22, 0x81, 0xb0, 0x96}
    },
    {
      {0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11},
      {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef},
      {0x7d, 0x0c, 0xc6, 0x30, 0xaf, 0xda, 0x1e, 0xc7}
    },
    {
      {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
      {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
      {0x4e, 0xf9, 0x97, 0x45, 0x61, 0x98, 0xdd, 0x78}
    },
    {
      {0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10},
      {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef},
      {0x0a, 0xce, 0xab, 0x0f, 0xc6, 0xa0, 0xa2, 0x8d}
    },
    {
      {0x7c, 0xa1, 0x10, 0x45, 0x4a, 0x1a, 0x6e, 0x57},
      {0x01, 0xa1, 0xd6, 0xd0, 0x39, 0x77, 0x67, 0x42},
      {0x59, 0xc6, 0x82, 0x45, 0xeb, 0x05, 0x28, 0x2b}
    },
    {
      {0x01, 0x31, 0xd9, 0x61, 0x9d, 0xc1, 0x37, 0x6e},
      {0x5c, 0xd5, 0x4c, 0xa8, 0x3d, 0xef, 0x57, 0xda},
      {0xb1, 0xb8, 0xcc, 0x0b, 0x25, 0x0f, 0x09, 0xa0}
    },
    {
      {0x07, 0xa1, 0x13, 0x3e, 0x4a, 0x0b, 0x26, 0x86},
      {0x02, 0x48, 0xd4, 0x38, 0x06, 0xf6, 0x71, 0x72},
      {0x17, 0x30, 0xe5, 0x77, 0x8b, 0xea, 0x1d, 0xa4}
    },
    {
      {0x38, 0x49, 0x67, 0x4c, 0x26, 0x02, 0x31, 0x9e},
      {0x51, 0x45, 0x4b, 0x58, 0x2d, 0xdf, 0x44, 0x0a},
      {0xa2, 0x5e, 0x78, 0x56, 0xcf, 0x26, 0x51, 0xeb}
    },
    {
      {0x04, 0xb9, 0x15, 0xba, 0x43, 0xfe, 0xb5, 0xb6},
      {0x42, 0xfd, 0x44, 0x30, 0x59, 0x57, 0x7f, 0xa2},
      {0x35, 0x38, 0x82, 0xb1, 0x09, 0xce, 0x8f, 0x1a}
    },
    {
      {0x01, 0x13, 0xb9, 0x70, 0xfd, 0x34, 0xf2, 0xce},
      {0x05, 0x9b, 0x5e, 0x08, 0x51, 0xcf, 0x14, 0x3a},
      {0x48, 0xf4, 0xd0, 0x88, 0x4c, 0x37, 0x99, 0x18}
    },
    {
      {0x01, 0x70, 0xf1, 0x75, 0x46, 0x8f, 0xb5, 0xe6},
      {0x07, 0x56, 0xd8, 0xe0, 0x77, 0x47, 0x61, 0xd2},
      {0x43, 0x21, 0x93, 0xb7, 0x89, 0x51, 0xfc, 0x98}
    },
    {
      {0x43, 0x29, 0x7f, 0xad, 0x38, 0xe3, 0x73, 0xfe},
      {0x76, 0x25, 0x14, 0xb8, 0x29, 0xbf, 0x48, 0x6a},
      {0x13, 0xf0, 0x41, 0x54, 0xd6, 0x9d, 0x1a, 0xe5}
    },
    {
      {0x07, 0xa7, 0x13, 0x70, 0x45, 0xda, 0x2a, 0x16},
      {0x3b, 0xdd, 0x11, 0x90, 0x49, 0x37, 0x28, 0x02},
      {0x2e, 0xed, 0xda, 0x93, 0xff, 0xd3, 0x9c, 0x79}
    },
    {
      {0x04, 0x68, 0x91, 0x04, 0xc2, 0xfd, 0x3b, 0x2f},
      {0x26, 0x95, 0x5f, 0x68, 0x35, 0xaf, 0x60, 0x9a},
      {0xd8, 0x87, 0xe0, 0x39, 0x3c, 0x2d, 0xa6, 0xe3}
    },
    {
      {0x37, 0xd0, 0x6b, 0xb5, 0x16, 0xcb, 0x75, 0x46},
      {0x16, 0x4d, 0x5e, 0x40, 0x4f, 0x27, 0x52, 0x32},
      {0x5f, 0x99, 0xd0, 0x4f, 0x5b, 0x16, 0x39, 0x69}
    },
    {
      {0x1f, 0x08, 0x26, 0x0d, 0x1a, 0xc2, 0x46, 0x5e},
      {0x6b, 0x05, 0x6e, 0x18, 0x75, 0x9f, 0x5c, 0xca},
      {0x4a, 0x05, 0x7a, 0x3b, 0x24, 0xd3, 0x97, 0x7b}
    },
    {
      {0x58, 0x40, 0x23, 0x64, 0x1a, 0xba, 0x61, 0x76},
      {0x00, 0x4b, 0xd6, 0xef, 0x09, 0x17, 0x60, 0x62},
      {0x45, 0x20, 0x31, 0xc1, 0xe4, 0xfa, 0xda, 0x8e}
    },
    {
      {0x02, 0x58, 0x16, 0x16, 0x46, 0x29, 0xb0, 0x07},
      {0x48, 0x0d, 0x39, 0x00, 0x6e, 0xe7, 0x62, 0xf2},
      {0x75, 0x55, 0xae, 0x39, 0xf5, 0x9b, 0x87, 0xbd}
    },
    {
      {0x49, 0x79, 0x3e, 0xbc, 0x79, 0xb3, 0x25, 0x8f},
      {0x43, 0x75, 0x40, 0xc8, 0x69, 0x8f, 0x3c, 0xfa},
      {0x53, 0xc5, 0x5f, 0x9c, 0xb4, 0x9f, 0xc0, 0x19}
    },
    {
      {0x4f, 0xb0, 0x5e, 0x15, 0x15, 0xab, 0x73, 0xa7},
      {0x07, 0x2d, 0x43, 0xa0, 0x77, 0x07, 0x52, 0x92},
      {0x7a, 0x8e, 0x7b, 0xfa, 0x93, 0x7e, 0x89, 0xa3}
    },
    {
      {0x49, 0xe9, 0x5d, 0x6d, 0x4c, 0xa2, 0x29, 0xbf},
      {0x02, 0xfe, 0x55, 0x77, 0x81, 0x17, 0xf1, 0x2a},
      {0xcf, 0x9c, 0x5d, 0x7a, 0x49, 0x86, 0xad, 0xb5}
    },
    {
      {0x01, 0x83, 0x10, 0xdc, 0x40, 0x9b, 0x26, 0xd6},
      {0x1d, 0x9d, 0x5c, 0x50, 0x18, 0xf7, 0x28, 0xc2},
      {0xd1, 0xab, 0xb2, 0x90, 0x65, 0x8b, 0xc7, 0x78}
    },
    {
      {0x1c, 0x58, 0x7f, 0x1c, 0x13, 0x92, 0x4f, 0xef},
      {0x30, 0x55, 0x32, 0x28, 0x6d, 0x6f, 0x29, 0x5a},
      {0x55, 0xcb, 0x37, 0x74, 0xd1, 0x3e, 0xf2, 0x01}
    },
    {
      {0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01},
      {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef},
      {0xfa, 0x34, 0xec, 0x48, 0x47, 0xb2, 0x68, 0xb2}
    },
    {
      {0x1f, 0x1f, 0x1f, 0x1f, 0x0e, 0x0e, 0x0e, 0x0e},
      {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef},
      {0xa7, 0x90, 0x79, 0x51, 0x08, 0xea, 0x3c, 0xae}
    },
    {
      {0xe0, 0xfe, 0xe0, 0xfe, 0xf1, 0xfe, 0xf1, 0xfe},
      {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef},
      {0xc3, 0x9e, 0x07, 0x2d, 0x9f, 0xac, 0x63, 0x1d}
    },
    {
      {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
      {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
      {0x01, 0x49, 0x33, 0xe0, 0xcd, 0xaf, 0xf6, 0xe4}
    },
    {
      {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
      {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
      {0xf2, 0x1e, 0x9a, 0x77, 0xb7, 0x1c, 0x49, 0xbc}
    },
    {
      {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef},
      {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
      {0x24, 0x59, 0x46, 0x88, 0x57, 0x54, 0x36, 0x9a}
    },
    {
      {0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10},
      {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
      {0x6b, 0x5c, 0x5a, 0x9c, 0x5d, 0x9e, 0x0a, 0x5a}
    },
  };

  for (int i = 0; i < 34; i++)
    {
      mobius::crypt::cipher cipher ("blowfish", values1[i][0]);
      test.ASSERT_EQUAL (cipher.encrypt (values1[i][1]), values1[i][2]);
      test.ASSERT_EQUAL (cipher.decrypt (values1[i][2]), values1[i][1]);
    }

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // mode ECB - variable key length
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  const mobius::bytearray plaintext = { 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10 };

  const mobius::bytearray values2[24][2] =    // key, ciphertext
  {
    {
      {0xf0},
      {0xf9, 0xad, 0x59, 0x7c, 0x49, 0xdb, 0x00, 0x5e}
    },
    {
      {0xf0, 0xe1},
      {0xe9, 0x1d, 0x21, 0xc1, 0xd9, 0x61, 0xa6, 0xd6}
    },
    {
      {0xf0, 0xe1, 0xd2},
      {0xe9, 0xc2, 0xb7, 0x0a, 0x1b, 0xc6, 0x5c, 0xf3}
    },
    {
      {0xf0, 0xe1, 0xd2, 0xc3},
      {0xbe, 0x1e, 0x63, 0x94, 0x08, 0x64, 0x0f, 0x05}
    },
    {
      {0xf0, 0xe1, 0xd2, 0xc3, 0xb4},
      {0xb3, 0x9e, 0x44, 0x48, 0x1b, 0xdb, 0x1e, 0x6e}
    },
    {
      {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5},
      {0x94, 0x57, 0xaa, 0x83, 0xb1, 0x92, 0x8c, 0x0d}
    },
    {
      {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96},
      {0x8b, 0xb7, 0x70, 0x32, 0xf9, 0x60, 0x62, 0x9d}
    },
    {
      {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87},
      {0xe8, 0x7a, 0x24, 0x4e, 0x2c, 0xc8, 0x5e, 0x82}
    },
    {
      {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78},
      {0x15, 0x75, 0x0e, 0x7a, 0x4f, 0x4e, 0xc5, 0x77}
    },
    {
      {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69},
      {0x12, 0x2b, 0xa7, 0x0b, 0x3a, 0xb6, 0x4a, 0xe0}
    },
    {
      {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a},
      {0x3a, 0x83, 0x3c, 0x9a, 0xff, 0xc5, 0x37, 0xf6}
    },
    {
      {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b},
      {0x94, 0x09, 0xda, 0x87, 0xa9, 0x0f, 0x6b, 0xf2}
    },
    {
      {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b, 0x3c},
      {0x88, 0x4f, 0x80, 0x62, 0x50, 0x60, 0xb8, 0xb4}
    },
    {
      {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b, 0x3c, 0x2d},
      {0x1f, 0x85, 0x03, 0x1c, 0x19, 0xe1, 0x19, 0x68}
    },
    {
      {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b, 0x3c, 0x2d, 0x1e},
      {0x79, 0xd9, 0x37, 0x3a, 0x71, 0x4c, 0xa3, 0x4f}
    },
    {
      {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b, 0x3c, 0x2d, 0x1e, 0x0f},
      {0x93, 0x14, 0x28, 0x87, 0xee, 0x3b, 0xe1, 0x5c}
    },
    {
      {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b, 0x3c, 0x2d, 0x1e, 0x0f, 0x00},
      {0x03, 0x42, 0x9e, 0x83, 0x8c, 0xe2, 0xd1, 0x4b}
    },
    {
      {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b, 0x3c, 0x2d, 0x1e, 0x0f, 0x00, 0x11},
      {0xa4, 0x29, 0x9e, 0x27, 0x46, 0x9f, 0xf6, 0x7b}
    },
    {
      {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b, 0x3c, 0x2d, 0x1e, 0x0f, 0x00, 0x11, 0x22},
      {0xaf, 0xd5, 0xae, 0xd1, 0xc1, 0xbc, 0x96, 0xa8}
    },
    {
      {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b, 0x3c, 0x2d, 0x1e, 0x0f, 0x00, 0x11, 0x22, 0x33},
      {0x10, 0x85, 0x1c, 0x0e, 0x38, 0x58, 0xda, 0x9f}
    },
    {
      {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b, 0x3c, 0x2d, 0x1e, 0x0f, 0x00, 0x11, 0x22, 0x33, 0x44},
      {0xe6, 0xf5, 0x1e, 0xd7, 0x9b, 0x9d, 0xb2, 0x1f}
    },
    {
      {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b, 0x3c, 0x2d, 0x1e, 0x0f, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55},
      {0x64, 0xa6, 0xe1, 0x4a, 0xfd, 0x36, 0xb4, 0x6f}
    },
    {
      {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b, 0x3c, 0x2d, 0x1e, 0x0f, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66},
      {0x80, 0xc7, 0xd7, 0xd4, 0x5a, 0x54, 0x79, 0xad}
    },
    {
      {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b, 0x3c, 0x2d, 0x1e, 0x0f, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77},
      {0x05, 0x04, 0x4b, 0x62, 0xfa, 0x52, 0xd0, 0x80}
    },
  };

  for (int i = 0; i < 24; i++)
    {
      mobius::crypt::cipher cipher ("blowfish", values2[i][0]);
      test.ASSERT_EQUAL (cipher.encrypt (plaintext), values2[i][1]);
      test.ASSERT_EQUAL (cipher.decrypt (values2[i][1]), plaintext);
    }

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // mode CBC
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  const mobius::bytearray key2 = {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87};
  const mobius::bytearray iv = {0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10};
  const mobius::bytearray plaintext2 = {0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x20, 0x4e, 0x6f, 0x77, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x00, 0x00, 0x00, 0x00};
  const mobius::bytearray ciphertext2 = {0x6b, 0x77, 0xb4, 0xd6, 0x30, 0x06, 0xde, 0xe6, 0x05, 0xb1, 0x56, 0xe2, 0x74, 0x03, 0x97, 0x93, 0x58, 0xde, 0xb9, 0xe7, 0x15, 0x46, 0x16, 0xd9, 0x59, 0xf1, 0x65, 0x2b, 0xd5, 0xff, 0x92, 0xcc};

  mobius::crypt::cipher cipher2 ("blowfish", key2, "cbc", iv);
  test.ASSERT_EQUAL (cipher2.encrypt (plaintext2), ciphertext2);

  mobius::crypt::cipher cipher3 ("blowfish", key2, "cbc", iv);
  test.ASSERT_EQUAL (cipher3.decrypt (ciphertext2), plaintext2);

  test.end ();
}

static void
testcase_cipher_des ()
{
  // DES - mode ECB
  mobius::crypt::cipher des0 ("des", {0x00, 0xe8, 0x03, 0x00, 0x00, 0xe8, 0x03});
  mobius::bytearray b0 = {0x33, 0xb8, 0x17, 0xb0, 0x9b, 0x82, 0xa4, 0xf1};

  mobius::unittest test ("mobius::crypt::cipher_des");
  test.ASSERT_EQUAL (b0.to_hexstring (), "33b817b09b82a4f1");

  b0 = des0.decrypt (b0);
  test.ASSERT_EQUAL (b0.to_hexstring (), "f4ed3a422ae3a1a6");

  // DES - mode ECB
  mobius::crypt::cipher des1 ("des", {0x13, 0x34, 0x57, 0x79, 0x9b, 0xbc, 0xdf, 0xf1});
  mobius::bytearray b1 = {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef};

  test.ASSERT_EQUAL (b1.to_hexstring (), "0123456789abcdef");

  b1 = des1.encrypt (b1);
  test.ASSERT_EQUAL (b1.to_hexstring (), "85e813540f0ab405");

  b1 = des1.decrypt (b1);
  test.ASSERT_EQUAL (b1.to_hexstring (), "0123456789abcdef");

  // DES - mode ECB
  mobius::crypt::cipher des2 ("des", {0x0e, 0x32, 0x92, 0x32, 0xea, 0x6d, 0x0d, 0x73});
  mobius::bytearray b2 (8);
  b2.fill (0x87);

  test.ASSERT_EQUAL (b2.to_hexstring (), "8787878787878787");

  b2 = des2.encrypt (b2);
  test.ASSERT_EQUAL (b2.to_hexstring (), "0000000000000000");

  b2 = des2.decrypt (b2);
  test.ASSERT_EQUAL (b2.to_hexstring (), "8787878787878787");

  // LM password "ABCD123"
  mobius::crypt::cipher des3 ("des", "ABCD123");
  mobius::bytearray b3 ("KGS!@#$%");

  test.ASSERT_EQUAL (b3.to_hexstring (), "4b47532140232425");

  b3 = des3.encrypt (b3);
  test.ASSERT_EQUAL (b3.to_hexstring (), "6f87cd328120cc55");

  b3 = des3.decrypt (b3);
  test.ASSERT_EQUAL (b3.to_hexstring (), "4b47532140232425");

  // LM password "ABCDE12"
  mobius::crypt::cipher des4 ("des", "ABCDE12");
  mobius::bytearray b4 ("KGS!@#$%");

  test.ASSERT_EQUAL (b4.to_hexstring (), "4b47532140232425");

  b4 = des4.encrypt (b4);
  test.ASSERT_EQUAL (b4.to_hexstring (), "722ac01404a75156");

  b4 = des4.decrypt (b4);
  test.ASSERT_EQUAL (b4.to_hexstring (), "4b47532140232425");

  // DES - mode CBC
  mobius::crypt::cipher des5 ("des", "ABCDE12", "cbc");
  mobius::bytearray b5 ("KGS!@#$%");

  test.ASSERT_EQUAL (b5.to_hexstring (), "4b47532140232425");

  b5 = des5.encrypt (b5);
  test.ASSERT_EQUAL (b5.to_hexstring (), "722ac01404a75156");

  des5.reset ();
  b5 = des5.decrypt (b5);
  test.ASSERT_EQUAL (b5.to_hexstring (), "4b47532140232425");

  // DES - mode ECB
  mobius::crypt::cipher des6 ("des", {0xe7, 0x37, 0x4e, 0x45, 0x8d, 0x54, 0xfd});
  mobius::bytearray b6 = {0x75, 0x3f, 0x61, 0x59, 0xc3, 0xa3, 0x11, 0x49};

  test.ASSERT_EQUAL (b6.to_hexstring (), "753f6159c3a31149");

  b6 = des6.decrypt (b6);
  test.ASSERT_EQUAL (b6.to_hexstring (), "0000000001000000");

  // DES - mode CBC
  mobius::crypt::cipher des7 ("des", {0x17,0xc0,0xfc,0x69,0x31,0x10,0xcb,0xd7}, "cbc");
  mobius::bytearray b7 ({0x62,0x75,0x32,0xeb,0xd7,0x97,0xe0,0xfe,0x7d,0xf4,0x7c,0x02,0xa5,0x12,0x61,0x3e,0x9f,0xdb,0xd3,0x7e,0x8b,0x89,0x2d,0xc1});

  test.ASSERT_EQUAL (b7.to_hexstring (), "627532ebd797e0fe7df47c02a512613e9fdbd37e8b892dc1");

  b7 = des7.decrypt (b7);
  test.ASSERT_EQUAL (b7.to_hexstring (), "e5a3ac4e83fa5862f59f5773981c70030808080808080808");

  test.end ();
}

static void
testcase_cipher_rc2 ()
{
  mobius::unittest test ("mobius::crypt::cipher_rc2");

  // RFC 2268, section 5
  mobius::crypt::cipher rc2_1 ("rc2", {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff});
  mobius::bytearray b_1 = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};

  test.ASSERT_EQUAL (b_1.to_hexstring (), "ffffffffffffffff");
  b_1 = rc2_1.encrypt (b_1);
  test.ASSERT_EQUAL (b_1.to_hexstring (), "278b27e42e2f0d49");
  b_1 = rc2_1.decrypt (b_1);
  test.ASSERT_EQUAL (b_1.to_hexstring (), "ffffffffffffffff");

  // RFC 2268, section 5
  mobius::crypt::cipher rc2_2 ("rc2", {0x30, 0, 0, 0, 0, 0, 0, 0});
  mobius::bytearray b_2 = {0x10, 0, 0, 0, 0, 0, 0, 0x01};

  test.ASSERT_EQUAL (b_2.to_hexstring (), "1000000000000001");
  b_2 = rc2_2.encrypt (b_2);
  test.ASSERT_EQUAL (b_2.to_hexstring (), "30649edf9be7d2c2");
  b_2 = rc2_2.decrypt (b_2);
  test.ASSERT_EQUAL (b_2.to_hexstring (), "1000000000000001");

  // RFC 2268, section 5
  mobius::crypt::cipher rc2_3 ("rc2", {0x88, 0xbc, 0xa9, 0x0e, 0x90, 0x87, 0x5a, 0x7f, 0x0f, 0x79, 0xc3, 0x84, 0x62, 0x7b, 0xaf, 0xb2});
  mobius::bytearray b_3 = {0, 0, 0, 0, 0, 0, 0, 0};

  test.ASSERT_EQUAL (b_3.to_hexstring (), "0000000000000000");
  b_3 = rc2_3.encrypt (b_3);
  test.ASSERT_EQUAL (b_3.to_hexstring (), "2269552ab0f85ca6");
  b_3 = rc2_3.decrypt (b_3);
  test.ASSERT_EQUAL (b_3.to_hexstring (), "0000000000000000");

  test.end ();
}

static void
testcase_cipher_rc4 ()
{
  mobius::unittest test ("mobius::crypt::cipher_rc4");

  // key="Key", Plaintext="Plaintext"
  mobius::crypt::cipher rc4_1 ("rc4", "Key");
  mobius::bytearray b_1 = "Plaintext";

  test.ASSERT_EQUAL (b_1.to_hexstring (), "506c61696e74657874");
  b_1 = rc4_1.encrypt (b_1);
  test.ASSERT_EQUAL (b_1.to_hexstring (), "bbf316e8d940af0ad3");
  rc4_1.reset ();
  b_1 = rc4_1.decrypt (b_1);
  test.ASSERT_EQUAL (b_1.to_hexstring (), "506c61696e74657874");

  // key="Secret", Plaintext="Attack at dawn"
  mobius::crypt::cipher rc4_2 ("rc4", "Secret");
  mobius::bytearray b_2 = "Attack at dawn";

  test.ASSERT_EQUAL (b_2.to_hexstring (), "41747461636b206174206461776e");
  b_2 = rc4_2.encrypt (b_2);
  test.ASSERT_EQUAL (b_2.to_hexstring (), "45a01f645fc35b383552544b9bf5");
  rc4_2.reset ();
  b_2 = rc4_2.decrypt (b_2);
  test.ASSERT_EQUAL (b_2.to_hexstring (), "41747461636b206174206461776e");
  test.end ();
}

static void
testcase_cipher_rot13 ()
{
  mobius::unittest test ("mobius::crypt::cipher_rot13");

  // nowhere <-> abjurer
  mobius::crypt::cipher c1 ("rot13");
  mobius::bytearray b_1 = "nowhere";

  b_1 = c1.encrypt (b_1);
  test.ASSERT_EQUAL (b_1, "abjurer");
  b_1 = c1.decrypt (b_1);
  test.ASSERT_EQUAL (b_1, "nowhere");

  // encrypt + encrypt = encrypt + decrypt
  b_1 = c1.encrypt (b_1);
  test.ASSERT_EQUAL (b_1, "abjurer");
  b_1 = c1.encrypt (b_1);
  test.ASSERT_EQUAL (b_1, "nowhere");

  // NERF <-> ARES
  mobius::crypt::cipher c2 ("rot13");
  mobius::bytearray b_2 = "NERF";

  b_2 = c2.encrypt (b_2);
  test.ASSERT_EQUAL (b_2, "ARES");
  b_2 = c2.decrypt (b_2);
  test.ASSERT_EQUAL (b_2, "NERF");

  // encrypt + encrypt = encrypt + decrypt
  b_2 = c2.encrypt (b_2);
  test.ASSERT_EQUAL (b_2, "ARES");
  b_2 = c2.encrypt (b_2);
  test.ASSERT_EQUAL (b_2, "NERF");

  test.end ();
}

static void
testcase_cipher_zip ()
{
  mobius::unittest test ("mobius::crypt::cipher_zip");

  // key="Key", Plaintext="Plaintext"
  mobius::crypt::cipher zip_1 ("zip", "Key");
  mobius::bytearray b_1 = "Plaintext";

  test.ASSERT_EQUAL (b_1.to_hexstring (), "506c61696e74657874");
  b_1 = zip_1.encrypt (b_1);
  test.ASSERT_EQUAL (b_1.to_hexstring (), "fe1995e4fe54a8c6f3");
  zip_1.reset ();
  b_1 = zip_1.decrypt (b_1);
  test.ASSERT_EQUAL (b_1.to_hexstring (), "506c61696e74657874");

  // key="Secret", Plaintext="Attack at dawn"
  mobius::crypt::cipher zip_2 ("zip", "Secret");
  mobius::bytearray b_2 = "Attack at dawn";

  test.ASSERT_EQUAL (b_2.to_hexstring (), "41747461636b206174206461776e");
  b_2 = zip_2.encrypt (b_2);
  test.ASSERT_EQUAL (b_2.to_hexstring (), "7595da02f5ec5c2c78755fd4069e");
  zip_2.reset ();
  b_2 = zip_2.decrypt (b_2);
  test.ASSERT_EQUAL (b_2.to_hexstring (), "41747461636b206174206461776e");
  test.end ();
}

static void
testcase_hash_adler32 ()
{
  mobius::unittest test ("mobius::crypt::hash_adler32");

  mobius::crypt::hash h1 ("adler32");
  mobius::crypt::hash h2 ("adler32");
  mobius::crypt::hash h3 ("adler32");

  test.ASSERT_EQUAL (h1.get_hex_digest (), "00000001");
  test.ASSERT_EQUAL (h2.get_hex_digest (), "00000001");
  test.ASSERT_EQUAL (h3.get_hex_digest (), "00000001");

  // update
  h2.update ("abc");
  test.ASSERT_EQUAL (h2.get_hex_digest (), "024d0127");

  h2.update ("d");
  test.ASSERT_EQUAL (h2.get_hex_digest (), "03d8018b");

  h3.update ("abcd");
  test.ASSERT_EQUAL (h3.get_hex_digest (), h2.get_hex_digest ());

  // update ("a"), update ("b") = update ("ab")
  mobius::crypt::hash h100 ("adler32");
  h100.update ("a");
  h100.get_digest ();
  h100.update ("b");
  auto d100 = h100.get_digest ();

  mobius::crypt::hash h101 ("adler32");
  h101.update ("ab");
  auto d101 = h101.get_digest ();

  test.ASSERT_EQUAL (h100.get_hex_digest (), h101.get_hex_digest ());

  test.end ();
}

static void
testcase_hash_crc32 ()
{
  mobius::unittest test ("mobius::crypt::hash_crc32");

  mobius::crypt::hash h1 ("crc32");
  mobius::crypt::hash h2 ("crc32");
  mobius::crypt::hash h3 ("crc32");

  test.ASSERT_EQUAL (h1.get_hex_digest (), "00000000");
  test.ASSERT_EQUAL (h2.get_hex_digest (), "00000000");
  test.ASSERT_EQUAL (h3.get_hex_digest (), "00000000");

  // update
  h2.update ("abc");
  test.ASSERT_EQUAL (h2.get_hex_digest (), "352441c2");

  h2.update ("d");
  test.ASSERT_EQUAL (h2.get_hex_digest (), "ed82cd11");

  h3.update ("abcd");
  test.ASSERT_EQUAL (h3.get_hex_digest (), h2.get_hex_digest ());

  // update ("a"), update ("b") = update ("ab")
  mobius::crypt::hash h100 ("crc32");
  h100.update ("a");
  h100.get_digest ();
  h100.update ("b");
  auto d100 = h100.get_digest ();

  mobius::crypt::hash h101 ("crc32");
  h101.update ("ab");
  auto d101 = h101.get_digest ();

  test.ASSERT_EQUAL (h100.get_hex_digest (), h101.get_hex_digest ());

  test.end ();
}

static void
testcase_hash_ed2k ()
{
  mobius::unittest test ("mobius::crypt::hash_ed2k");

  // Same as hash_md4 for data.size <= 9.728.000
  mobius::crypt::hash h1 ("ed2k");
  test.ASSERT_EQUAL (h1.get_hex_digest (), "31d6cfe0d16ae931b73c59d7e0c089c0");

  mobius::crypt::hash h2 ("ed2k");
  h2.update ("a");
  test.ASSERT_EQUAL (h2.get_hex_digest (), "bde52cb31de33e46245e05fbdbd6fb24");

  mobius::crypt::hash h3 ("ed2k");
  h3.update ("abc");
  test.ASSERT_EQUAL (h3.get_hex_digest (), "a448017aaf21d8525fc10ae87aa6729d");

  mobius::crypt::hash h4 ("ed2k");
  h4.update ("message digest");
  test.ASSERT_EQUAL (h4.get_hex_digest (), "d9130a8164549fe818874806e1c7014b");

  mobius::crypt::hash h5 ("ed2k");
  h5.update ("abcdefghijklmnopqrstuvwxyz");
  test.ASSERT_EQUAL (h5.get_hex_digest (), "d79e1c308aa5bbcdeea8ed63df412da9");

  mobius::crypt::hash h6 ("ed2k");
  h6.update ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
  test.ASSERT_EQUAL (h6.get_hex_digest (), "043f8582f241db351ce627e153e7f0e4");

  mobius::crypt::hash h7 ("ed2k");
  h7.update ("12345678901234567890123456789012345678901234567890123456789012345678901234567890");
  test.ASSERT_EQUAL (h7.get_hex_digest (), "e33b4ddc9c38f2199c3e7b164fcc0536");

  // data.size = 9.728.000
  mobius::crypt::hash h8 ("ed2k");
  mobius::bytearray d8 (9728000);
  d8.fill (0);
  h8.update (d8);
  test.ASSERT_EQUAL (h8.get_hex_digest (), "d7def262a127cd79096a108e7a9fc138");

  // data.size = 9.728.000 - 1
  mobius::crypt::hash h9 ("ed2k");
  mobius::bytearray d9 (9728000 - 1);
  d9.fill (0);
  h9.update (d9);
  test.ASSERT_EQUAL (h9.get_hex_digest (), "ac44b93fc9aff773ab0005c911f8396f");

  // data.size = 9.728.000 + 1
  mobius::crypt::hash h10 ("ed2k");
  mobius::bytearray d10 (9728000 + 1);
  d10.fill (0);
  h10.update (d10);
  test.ASSERT_EQUAL (h10.get_hex_digest (), "06329e9dba1373512c06386fe29e3c65");

  // update ("a"), update ("b") = update ("ab")
  mobius::crypt::hash h100 ("ed2k");
  h100.update ("a");
  h100.get_digest ();
  h100.update ("b");
  auto d100 = h100.get_digest ();

  mobius::crypt::hash h101 ("ed2k");
  h101.update ("ab");
  auto d101 = h101.get_digest ();

  test.ASSERT_EQUAL (h100.get_hex_digest (), h101.get_hex_digest ());

  test.end ();
}

static void
testcase_hash_md4 ()
{
  mobius::unittest test ("mobius::crypt::hash_md4");

  // RFC 1320 - section A.5
  mobius::crypt::hash h1 ("md4");
  test.ASSERT_EQUAL (h1.get_hex_digest (), "31d6cfe0d16ae931b73c59d7e0c089c0");

  mobius::crypt::hash h2 ("md4");
  h2.update ("a");
  test.ASSERT_EQUAL (h2.get_hex_digest (), "bde52cb31de33e46245e05fbdbd6fb24");

  mobius::crypt::hash h3 ("md4");
  h3.update ("abc");
  test.ASSERT_EQUAL (h3.get_hex_digest (), "a448017aaf21d8525fc10ae87aa6729d");

  mobius::crypt::hash h4 ("md4");
  h4.update ("message digest");
  test.ASSERT_EQUAL (h4.get_hex_digest (), "d9130a8164549fe818874806e1c7014b");

  mobius::crypt::hash h5 ("md4");
  h5.update ("abcdefghijklmnopqrstuvwxyz");
  test.ASSERT_EQUAL (h5.get_hex_digest (), "d79e1c308aa5bbcdeea8ed63df412da9");

  mobius::crypt::hash h6 ("md4");
  h6.update ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
  test.ASSERT_EQUAL (h6.get_hex_digest (), "043f8582f241db351ce627e153e7f0e4");

  mobius::crypt::hash h7 ("md4");
  h7.update ("12345678901234567890123456789012345678901234567890123456789012345678901234567890");
  test.ASSERT_EQUAL (h7.get_hex_digest (), "e33b4ddc9c38f2199c3e7b164fcc0536");

  // update ("a"), update ("b") = update ("ab")
  mobius::crypt::hash h100 ("md4");
  h100.update ("a");
  h100.get_digest ();
  h100.update ("b");
  auto d100 = h100.get_digest ();

  mobius::crypt::hash h101 ("md4");
  h101.update ("ab");
  auto d101 = h101.get_digest ();

  test.ASSERT_EQUAL (h100.get_hex_digest (), h101.get_hex_digest ());

  test.end ();
}

static void
testcase_hash_md5 ()
{
  mobius::unittest test ("mobius::crypt::hash_md5");

  // RFC 1321 - section A.5
  mobius::crypt::hash h1 ("md5");
  test.ASSERT_EQUAL (h1.get_hex_digest (), "d41d8cd98f00b204e9800998ecf8427e");

  mobius::crypt::hash h2 ("md5");
  h2.update ("a");
  test.ASSERT_EQUAL (h2.get_hex_digest (), "0cc175b9c0f1b6a831c399e269772661");

  mobius::crypt::hash h3 ("md5");
  h3.update ("abc");
  test.ASSERT_EQUAL (h3.get_hex_digest (), "900150983cd24fb0d6963f7d28e17f72");

  mobius::crypt::hash h4 ("md5");
  h4.update ("message digest");
  test.ASSERT_EQUAL (h4.get_hex_digest (), "f96b697d7cb7938d525a2f31aaf161d0");

  mobius::crypt::hash h5 ("md5");
  h5.update ("abcdefghijklmnopqrstuvwxyz");
  test.ASSERT_EQUAL (h5.get_hex_digest (), "c3fcd3d76192e4007dfb496cca67e13b");

  mobius::crypt::hash h6 ("md5");
  h6.update ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
  test.ASSERT_EQUAL (h6.get_hex_digest (), "d174ab98d277d9f5a5611c2c9f419d9f");

  mobius::crypt::hash h7 ("md5");
  h7.update ("12345678901234567890123456789012345678901234567890123456789012345678901234567890");
  test.ASSERT_EQUAL (h7.get_hex_digest (), "57edf4a22be3c955ac49da2e2107b67a");

  mobius::crypt::hash h8 ("md5");
  mobius::bytearray b1 (1000);
  b1.fill (0);
  h8.update (b1);
  test.ASSERT_EQUAL (h8.get_hex_digest (), "ede3d3b685b4e137ba4cb2521329a75e");

  // update ("a"), update ("b") = update ("ab")
  mobius::crypt::hash h100 ("md5");
  h100.update ("a");
  h100.get_digest ();
  h100.update ("b");
  auto d100 = h100.get_digest ();

  mobius::crypt::hash h101 ("md5");
  h101.update ("ab");
  auto d101 = h101.get_digest ();

  test.ASSERT_EQUAL (h100.get_hex_digest (), h101.get_hex_digest ());

  test.end ();
}

static void
testcase_hash_sha1 ()
{
  mobius::unittest test ("mobius::crypt::hash_sha1");

  // RFC 3174 - section 7.3
  mobius::crypt::hash h1 ("sha1");
  test.ASSERT_EQUAL (h1.get_hex_digest (), "da39a3ee5e6b4b0d3255bfef95601890afd80709");

  mobius::crypt::hash h2 ("sha1");
  h2.update ("abc");
  test.ASSERT_EQUAL (h2.get_hex_digest (), "a9993e364706816aba3e25717850c26c9cd0d89d");

  mobius::crypt::hash h3 ("sha1");
  h3.update ("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq");
  test.ASSERT_EQUAL (h3.get_hex_digest (), "84983e441c3bd26ebaae4aa1f95129e5e54670f1");

  mobius::crypt::hash h4 ("sha1");

  for (int i = 0; i < 1000000; i++)
    h4.update ("a");

  test.ASSERT_EQUAL (h4.get_hex_digest (), "34aa973cd4c4daa4f61eeb2bdbad27316534016f");

  mobius::crypt::hash h5 ("sha1");

  for (int i = 0; i < 20; i++)
    h5.update ("01234567012345670123456701234567");

  test.ASSERT_EQUAL (h5.get_hex_digest (), "dea356a2cddd90c7a7ecedc5ebb563934f460452");

  // update ("a"), update ("b") = update ("ab")
  mobius::crypt::hash h100 ("sha1");
  h100.update ("a");
  h100.get_digest ();
  h100.update ("b");
  auto d100 = h100.get_digest ();

  mobius::crypt::hash h101 ("sha1");
  h101.update ("ab");
  auto d101 = h101.get_digest ();

  test.ASSERT_EQUAL (h100.get_hex_digest (), h101.get_hex_digest ());

  test.end ();
}

static void
testcase_hash_sha2_224 ()
{
  mobius::unittest test ("mobius::crypt::hash_sha2_224");

  mobius::crypt::hash h1 ("sha2_224");
  test.ASSERT_EQUAL (h1.get_hex_digest (), "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f");

  //! \see https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/SHA224.pdf
  mobius::crypt::hash h2 ("sha2_224");
  h2.update ("abc");
  test.ASSERT_EQUAL (h2.get_hex_digest (), "23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7");

  mobius::crypt::hash h3 ("sha2_224");
  h3.update ("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq");
  test.ASSERT_EQUAL (h3.get_hex_digest (), "75388b16512776cc5dba5da1fd890150b0c6455cb4f58b1952522525");

  // update ("a"), update ("b") = update ("ab")
  mobius::crypt::hash h100 ("sha2_224");
  h100.update ("a");
  h100.get_digest ();
  h100.update ("b");
  auto d100 = h100.get_digest ();

  mobius::crypt::hash h101 ("sha2_224");
  h101.update ("ab");
  auto d101 = h101.get_digest ();

  test.ASSERT_EQUAL (h100.get_hex_digest (), h101.get_hex_digest ());

  test.end ();
}

static void
testcase_hash_sha2_256 ()
{
  mobius::unittest test ("mobius::crypt::hash_sha2_256");

  mobius::crypt::hash h1 ("sha2_256");
  test.ASSERT_EQUAL (h1.get_hex_digest (), "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");

  //! \see https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/SHA256.pdf
  mobius::crypt::hash h2 ("sha2_256");
  h2.update ("abc");
  test.ASSERT_EQUAL (h2.get_hex_digest (), "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");

  mobius::crypt::hash h3 ("sha2_256");
  h3.update ("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq");
  test.ASSERT_EQUAL (h3.get_hex_digest (), "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1");

  // update ("a"), update ("b") = update ("ab")
  mobius::crypt::hash h100 ("sha2_256");
  h100.update ("a");
  h100.get_digest ();
  h100.update ("b");
  auto d100 = h100.get_digest ();

  mobius::crypt::hash h101 ("sha2_256");
  h101.update ("ab");
  auto d101 = h101.get_digest ();

  test.ASSERT_EQUAL (h100.get_hex_digest (), h101.get_hex_digest ());

  test.end ();
}

static void
testcase_hash_sha2_384 ()
{
  mobius::unittest test ("mobius::crypt::hash_sha2_384");

  mobius::crypt::hash h1 ("sha2_384");
  test.ASSERT_EQUAL (h1.get_hex_digest (), "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b");

  //! \see https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/SHA384.pdf
  mobius::crypt::hash h2 ("sha2_384");
  h2.update ("abc");
  test.ASSERT_EQUAL (h2.get_hex_digest (), "cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7");

  mobius::crypt::hash h3 ("sha2_384");
  h3.update ( "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu");
  test.ASSERT_EQUAL (h3.get_hex_digest (), "09330c33f71147e83d192fc782cd1b4753111b173b3b05d22fa08086e3b0f712fcc7c71a557e2db966c3e9fa91746039");

  // update ("a"), update ("b") = update ("ab")
  mobius::crypt::hash h100 ("sha2_384");
  h100.update ("a");
  h100.get_digest ();
  h100.update ("b");
  auto d100 = h100.get_digest ();

  mobius::crypt::hash h101 ("sha2_384");
  h101.update ("ab");
  auto d101 = h101.get_digest ();

  test.ASSERT_EQUAL (h100.get_hex_digest (), h101.get_hex_digest ());

  test.end ();
}

static void
testcase_hash_sha2_512 ()
{
  mobius::unittest test ("mobius::crypt::hash_sha2_512");

  mobius::crypt::hash h1 ("sha2_512");
  test.ASSERT_EQUAL (h1.get_hex_digest (), "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e");

  //! \see https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/SHA512.pdf
  mobius::crypt::hash h2 ("sha2_512");
  h2.update ("abc");
  test.ASSERT_EQUAL (h2.get_hex_digest (), "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f");

  mobius::crypt::hash h3 ("sha2_512");
  h3.update ( "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu");
  test.ASSERT_EQUAL (h3.get_hex_digest (), "8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909");

  // update ("a"), update ("b") = update ("ab")
  mobius::crypt::hash h100 ("sha2_512");
  h100.update ("a");
  h100.get_digest ();
  h100.update ("b");
  auto d100 = h100.get_digest ();

  mobius::crypt::hash h101 ("sha2_512");
  h101.update ("ab");
  auto d101 = h101.get_digest ();

  test.ASSERT_EQUAL (h100.get_hex_digest (), h101.get_hex_digest ());

  test.end ();
}

static void
testcase_hash_sha2_512_224 ()
{
  mobius::unittest test ("mobius::crypt::hash_sha2_512_224");

  mobius::crypt::hash h1 ("sha2_512_224");
  test.ASSERT_EQUAL (h1.get_hex_digest (), "6ed0dd02806fa89e25de060c19d3ac86cabb87d6a0ddd05c333b84f4");

  //! \see https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/SHA512_224.pdf
  mobius::crypt::hash h2 ("sha2_512_224");
  h2.update ("abc");
  test.ASSERT_EQUAL (h2.get_hex_digest (), "4634270f707b6a54daae7530460842e20e37ed265ceee9a43e8924aa");

  mobius::crypt::hash h3 ("sha2_512_224");
  h3.update ( "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu");
  test.ASSERT_EQUAL (h3.get_hex_digest (), "23fec5bb94d60b23308192640b0c453335d664734fe40e7268674af9");

  // update ("a"), update ("b") = update ("ab")
  mobius::crypt::hash h100 ("sha2_512_224");
  h100.update ("a");
  h100.get_digest ();
  h100.update ("b");
  auto d100 = h100.get_digest ();

  mobius::crypt::hash h101 ("sha2_512_224");
  h101.update ("ab");
  auto d101 = h101.get_digest ();

  test.ASSERT_EQUAL (h100.get_hex_digest (), h101.get_hex_digest ());

  test.end ();
}

static void
testcase_hash_sha2_512_256 ()
{
  mobius::unittest test ("mobius::crypt::hash_sha2_512_256");

  mobius::crypt::hash h1 ("sha2_512_256");
  test.ASSERT_EQUAL (h1.get_hex_digest (), "c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a");

  //! \see https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/SHA512_256.pdf
  mobius::crypt::hash h2 ("sha2_512_256");
  h2.update ("abc");
  test.ASSERT_EQUAL (h2.get_hex_digest (), "53048e2681941ef99b2e29b76b4c7dabe4c2d0c634fc6d46e0e2f13107e7af23");

  mobius::crypt::hash h3 ("sha2_512_256");
  h3.update ( "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu");
  test.ASSERT_EQUAL (h3.get_hex_digest (), "3928e184fb8690f840da3988121d31be65cb9d3ef83ee6146feac861e19b563a");

  // update ("a"), update ("b") = update ("ab")
  mobius::crypt::hash h100 ("sha2_512_256");
  h100.update ("a");
  h100.get_digest ();
  h100.update ("b");
  auto d100 = h100.get_digest ();

  mobius::crypt::hash h101 ("sha2_512_256");
  h101.update ("ab");
  auto d101 = h101.get_digest ();

  test.ASSERT_EQUAL (h100.get_hex_digest (), h101.get_hex_digest ());

  test.end ();
}

static void
testcase_hash_zip ()
{
  mobius::unittest test ("mobius::crypt::hash_zip");

  mobius::crypt::hash h1 ("zip");
  mobius::crypt::hash h2 ("zip");
  mobius::crypt::hash h3 ("zip");

  test.ASSERT_EQUAL (h1.get_hex_digest (), "123456782345678934567890");
  test.ASSERT_EQUAL (h2.get_hex_digest (), "123456782345678934567890");
  test.ASSERT_EQUAL (h3.get_hex_digest (), "123456782345678934567890");

  // update
  h2.update ("abc");
  test.ASSERT_EQUAL (h2.get_hex_digest (), "5dd2af4d589d03b43cf5ffa4");

  h2.update ("d");
  test.ASSERT_EQUAL (h2.get_hex_digest (), "42ef4ac38d167254428e6d93");

  h3.update ("abcd");
  test.ASSERT_EQUAL (h3.get_hex_digest (), h2.get_hex_digest ());

  // update ("a"), update ("b") = update ("ab")
  mobius::crypt::hash h100 ("zip");
  h100.update ("a");
  h100.get_digest ();
  h100.update ("b");
  auto d100 = h100.get_digest ();

  mobius::crypt::hash h101 ("zip");
  h101.update ("ab");
  auto d101 = h101.get_digest ();

  test.ASSERT_EQUAL (h100.get_hex_digest (), h101.get_hex_digest ());

  test.end ();
}

static void
testcase_hmac ()
{
  mobius::unittest test ("mobius::crypt::hmac");
  mobius::bytearray data (50);

  // RFC 2202 - section 2
  mobius::crypt::hmac h1 ("\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b");
  h1.update ("Hi There");
  test.ASSERT_EQUAL (h1.get_hex_digest (), "9294727a3638bb1c13f48ef8158bfc9d");

  mobius::crypt::hmac h2 ("Jefe");
  h2.update ("what do ya want for nothing?");
  test.ASSERT_EQUAL (h2.get_hex_digest (), "750c783e6ab0b503eaa86e310a5db738");

  mobius::crypt::hmac h3 ("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa");
  data.fill (0xdd);
  h3.update (data);
  test.ASSERT_EQUAL (h3.get_hex_digest (), "56be34521d144c88dbb8c733f0e8b3f6");

  mobius::crypt::hmac h4 ("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19");
  data.fill (0xcd);
  h4.update (data);
  test.ASSERT_EQUAL (h4.get_hex_digest (), "697eaf0aca3a3aea3a75164746ffaa79");

  mobius::crypt::hmac h5 ("\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c");
  h5.update ("Test With Truncation");
  test.ASSERT_EQUAL (h5.get_hex_digest (), "56461ef2342edc00f9bab995690efd4c");

  mobius::bytearray k6 (80);
  k6.fill (0xaa);
  mobius::crypt::hmac h6 (k6);
  h6.update ("Test Using Larger Than Block-Size Key - Hash Key First");
  test.ASSERT_EQUAL (h6.get_hex_digest (), "6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd");

  mobius::crypt::hmac h7 (k6);
  h7.update ("Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data");
  test.ASSERT_EQUAL (h7.get_hex_digest (), "6f630fad67cda0ee1fb1f562db3aa53e");

  // RFC 2202 - section 3
  mobius::crypt::hmac h8 ("\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b", "sha1");
  h8.update ("Hi There");
  test.ASSERT_EQUAL (h8.get_hex_digest (), "b617318655057264e28bc0b6fb378c8ef146be00");

  mobius::crypt::hmac h9 ("Jefe", "sha1");
  h9.update ("what do ya want for nothing?");
  test.ASSERT_EQUAL (h9.get_hex_digest (), "effcdf6ae5eb2fa2d27416d5f184df9c259a7c79");

  mobius::crypt::hmac h10 ("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa", "sha1");
  data.fill (0xdd);
  h10.update (data);
  test.ASSERT_EQUAL (h10.get_hex_digest (), "125d7342b9ac11cd91a39af48aa17b4f63f175d3");

  mobius::crypt::hmac h11 ("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19", "sha1");
  data.fill (0xcd);
  h11.update (data);
  test.ASSERT_EQUAL (h11.get_hex_digest (), "4c9007f4026250c6bc8414f9bf50c86c2d7235da");

  mobius::crypt::hmac h12 ("\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c", "sha1");
  h12.update ("Test With Truncation");
  test.ASSERT_EQUAL (h12.get_hex_digest (), "4c1a03424b55e07fe7f27be1d58bb9324a9a5a04");

  mobius::crypt::hmac h13 (k6, "sha1");
  h13.update ("Test Using Larger Than Block-Size Key - Hash Key First");
  test.ASSERT_EQUAL (h13.get_hex_digest (), "aa4ae5e15272d00e95705637ce8a3b55ed402112");

  mobius::crypt::hmac h14 (k6, "sha1");
  h14.update ("Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data");
  test.ASSERT_EQUAL (h14.get_hex_digest (), "e8e99d0f45237d786d6bbaa7965c7808bbff1a91");

  // RFC 4231 - section 4.2
  mobius::bytearray k15 ("\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b");

  mobius::crypt::hmac h15a (k15, "sha2_224");
  h15a.update ("Hi There");
  test.ASSERT_EQUAL (h15a.get_hex_digest (), "896fb1128abbdf196832107cd49df33f47b4b1169912ba4f53684b22");

  mobius::crypt::hmac h15b (k15, "sha2_256");
  h15b.update ("Hi There");
  test.ASSERT_EQUAL (h15b.get_hex_digest (), "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7");

  mobius::crypt::hmac h15c (k15, "sha2_384");
  h15c.update ("Hi There");
  test.ASSERT_EQUAL (h15c.get_hex_digest (), "afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c7cebc59cfaea9ea9076ede7f4af152e8b2fa9cb6");

  mobius::crypt::hmac h15d (k15, "sha2_512");
  h15d.update ("Hi There");
  test.ASSERT_EQUAL (h15d.get_hex_digest (), "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854");

  // RFC 4231 - section 4.3
  mobius::crypt::hmac h16a ("Jefe", "sha2_224");
  h16a.update ("what do ya want for nothing?");
  test.ASSERT_EQUAL (h16a.get_hex_digest (), "a30e01098bc6dbbf45690f3a7e9e6d0f8bbea2a39e6148008fd05e44");

  mobius::crypt::hmac h16b ("Jefe", "sha2_256");
  h16b.update ("what do ya want for nothing?");
  test.ASSERT_EQUAL (h16b.get_hex_digest (), "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843");

  mobius::crypt::hmac h16c ("Jefe", "sha2_384");
  h16c.update ("what do ya want for nothing?");
  test.ASSERT_EQUAL (h16c.get_hex_digest (), "af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec3736322445e8e2240ca5e69e2c78b3239ecfab21649");

  mobius::crypt::hmac h16d ("Jefe", "sha2_512");
  h16d.update ("what do ya want for nothing?");
  test.ASSERT_EQUAL (h16d.get_hex_digest (), "164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737");

  // RFC 4231 - section 4.4
  mobius::bytearray k17 ("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa");
  data.fill (0xdd);

  mobius::crypt::hmac h17a (k17, "sha2_224");
  h17a.update (data);
  test.ASSERT_EQUAL (h17a.get_hex_digest (), "7fb3cb3588c6c1f6ffa9694d7d6ad2649365b0c1f65d69d1ec8333ea");

  mobius::crypt::hmac h17b (k17, "sha2_256");
  h17b.update (data);
  test.ASSERT_EQUAL (h17b.get_hex_digest (), "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe");

  mobius::crypt::hmac h17c (k17, "sha2_384");
  h17c.update (data);
  test.ASSERT_EQUAL (h17c.get_hex_digest (), "88062608d3e6ad8a0aa2ace014c8a86f0aa635d947ac9febe83ef4e55966144b2a5ab39dc13814b94e3ab6e101a34f27");

  mobius::crypt::hmac h17d (k17, "sha2_512");
  h17d.update (data);
  test.ASSERT_EQUAL (h17d.get_hex_digest (), "fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb");

  // RFC 4231 - section 4.5
  mobius::bytearray k18 ("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19");
  data.fill (0xcd);

  mobius::crypt::hmac h18a (k18, "sha2_224");
  h18a.update (data);
  test.ASSERT_EQUAL (h18a.get_hex_digest (), "6c11506874013cac6a2abc1bb382627cec6a90d86efc012de7afec5a");

  mobius::crypt::hmac h18b (k18, "sha2_256");
  h18b.update (data);
  test.ASSERT_EQUAL (h18b.get_hex_digest (), "82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b");

  mobius::crypt::hmac h18c (k18, "sha2_384");
  h18c.update (data);
  test.ASSERT_EQUAL (h18c.get_hex_digest (), "3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e1f573b4e6801dd23c4a7d679ccf8a386c674cffb");

  mobius::crypt::hmac h18d (k18, "sha2_512");
  h18d.update (data);
  test.ASSERT_EQUAL (h18d.get_hex_digest (), "b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd");

  // RFC 4231 - section 4.7
  mobius::bytearray k19 (131);
  mobius::bytearray data19 ("Test Using Larger Than Block-Size Key - Hash Key First");
  k19.fill (0xaa);

  mobius::crypt::hmac h19a (k19, "sha2_224");
  h19a.update (data19);
  test.ASSERT_EQUAL (h19a.get_hex_digest (), "95e9a0db962095adaebe9b2d6f0dbce2d499f112f2d2b7273fa6870e");

  mobius::crypt::hmac h19b (k19, "sha2_256");
  h19b.update (data19);
  test.ASSERT_EQUAL (h19b.get_hex_digest (), "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54");

  mobius::crypt::hmac h19c (k19, "sha2_384");
  h19c.update (data19);
  test.ASSERT_EQUAL (h19c.get_hex_digest (), "4ece084485813e9088d2c63a041bc5b44f9ef1012a2b588f3cd11f05033ac4c60c2ef6ab4030fe8296248df163f44952");

  mobius::crypt::hmac h19d (k19, "sha2_512");
  h19d.update (data19);
  test.ASSERT_EQUAL (h19d.get_hex_digest (), "80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598");

  // RFC 4231 - section 4.8
  mobius::bytearray k20 = k19;
  mobius::bytearray data20 ("This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.");

  mobius::crypt::hmac h20a (k20, "sha2_224");
  h20a.update (data20);
  test.ASSERT_EQUAL (h20a.get_hex_digest (), "3a854166ac5d9f023f54d517d0b39dbd946770db9c2b95c9f6f565d1");

  mobius::crypt::hmac h20b (k20, "sha2_256");
  h20b.update (data20);
  test.ASSERT_EQUAL (h20b.get_hex_digest (), "9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2");

  mobius::crypt::hmac h20c (k20, "sha2_384");
  h20c.update (data20);
  test.ASSERT_EQUAL (h20c.get_hex_digest (), "6617178e941f020d351e2f254e8fd32c602420feb0b8fb9adccebb82461e99c5a678cc31e799176d3860e6110c46523e");

  mobius::crypt::hmac h20d (k20, "sha2_512");
  h20d.update (data20);
  test.ASSERT_EQUAL (h20d.get_hex_digest (), "e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58");

  test.end ();
}

static void
testcase_pbkdf1 ()
{
  mobius::unittest test ("mobius::crypt::pbkdf1");

  //! \see http://www.di-mgt.com.au/cryptoKDFs.html
  ASSERT_EXCEPTION (test, mobius::crypt::pbkdf1 ("md5", "password", "salt", 1000, 0), std::out_of_range);

  test.ASSERT_EQUAL (mobius::crypt::pbkdf1 ("sha1", "password", "\x78\x57\x8E\x5A\x5D\x63\xCB\x06", 1000, 16).to_hexstring (), "dc19847e05c64d2faf10ebfb4a3d2a20");

  test.ASSERT_EQUAL (mobius::crypt::pbkdf1 ("md5", "password", "salt", 1000, 1).to_hexstring (), "84");
  test.ASSERT_EQUAL (mobius::crypt::pbkdf1 ("md5", "password", "salt", 1000, 8).to_hexstring (), "8475c6a8531a5d27");
  test.ASSERT_EQUAL (mobius::crypt::pbkdf1 ("md5", "password", "salt", 1000, 16).to_hexstring (), "8475c6a8531a5d27e386cd496457812c");
  test.ASSERT_EQUAL (mobius::crypt::pbkdf1 ("sha1", "password", "salt", 1000, 20).to_hexstring (), "4a8fd48e426ed081b535be5769892fa396293efb");

  test.end ();
}

static void
testcase_pbkdf2_hmac ()
{
  mobius::unittest test ("mobius::crypt::pbkdf2_hmac");

  // RFC 6070 - section 2
  ASSERT_EXCEPTION (test, mobius::crypt::pbkdf2_hmac ("md5", "password", "salt", 1000, 0), std::out_of_range);

  test.ASSERT_EQUAL (mobius::crypt::pbkdf2_hmac ("sha1", "password", "salt", 1, 20).to_hexstring (), "0c60c80f961f0e71f3a9b524af6012062fe037a6");
  test.ASSERT_EQUAL (mobius::crypt::pbkdf2_hmac ("sha1", "password", "salt", 2, 20).to_hexstring (), "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957");
  test.ASSERT_EQUAL (mobius::crypt::pbkdf2_hmac ("sha1", "password", "salt", 4096, 20).to_hexstring (),"4b007901b765489abead49d926f721d065a429c1");
  //test.ASSERT_EQUAL (pbkdf2_hmac ("password", "salt", 16777216, 20, "sha1").to_hexstring (),"eefe3d61cd4da4e4e9945b3d6ba2158c2634e984"); .. very slow
  test.ASSERT_EQUAL (mobius::crypt::pbkdf2_hmac ("sha1", "passwordPASSWORDpassword", "saltSALTsaltSALTsaltSALTsaltSALTsalt", 4096, 25).to_hexstring (), "3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038");
  test.ASSERT_EQUAL (mobius::crypt::pbkdf2_hmac ("sha1", {'p','a','s','s',0,'w','o','r','d'}, {'s','a',0,'l','t'}, 4096, 16).to_hexstring (), "56fa6aa75548099dcc37d7f03425e0c3");

  // RFC 7914 - section 11
  test.ASSERT_EQUAL (mobius::crypt::pbkdf2_hmac ("sha2_256", "passwd", "salt", 1, 64).to_hexstring (), "55ac046e56e3089fec1691c22544b605f94185216dde0465e68b9d57c20dacbc49ca9cccf179b645991664b39d77ef317c71b845b1e30bd509112041d3a19783");
  test.ASSERT_EQUAL (mobius::crypt::pbkdf2_hmac ("sha2_256", "Password", "NaCl", 80000, 64).to_hexstring (), "4ddcd8f60b98be21830cee5ef22701f9641a4418d04c0414aeff08876b34ab56a1d425a1225833549adb841b51c9b3176a272bdebba1d078478f62b397f33c8d");

  test.end ();
}

void
unittest_crypt ()
{
  testcase_cipher_3des ();
  testcase_cipher_aes ();
  testcase_cipher_blowfish ();
  testcase_cipher_des ();
  testcase_cipher_rc2 ();
  testcase_cipher_rc4 ();
  testcase_cipher_rot13 ();
  testcase_cipher_zip ();
  testcase_hash_adler32 ();
  testcase_hash_crc32 ();
  testcase_hash_ed2k ();
  testcase_hash_md4 ();
  testcase_hash_md5 ();
  testcase_hash_sha1 ();
  testcase_hash_sha2_224 ();
  testcase_hash_sha2_256 ();
  testcase_hash_sha2_384 ();
  testcase_hash_sha2_512 ();
  testcase_hash_sha2_512_224 ();
  testcase_hash_sha2_512_256 ();
  testcase_hash_zip ();
  testcase_hmac ();
  testcase_pbkdf1 ();
  testcase_pbkdf2_hmac ();
}
