diff --git a/Makefile.am b/Makefile.am index d2f456c0..0b321d6e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -48,6 +48,8 @@ UTIL_FILES = \ hash/md4.hpp \ hash/md5.cpp \ hash/md5.hpp \ + hash/sha1.cpp \ + hash/sha1.hpp \ image.cpp \ image.hpp \ io.cpp \ diff --git a/hash/sha1.cpp b/hash/sha1.cpp new file mode 100644 index 00000000..c6a6c6cc --- /dev/null +++ b/hash/sha1.cpp @@ -0,0 +1,228 @@ +/* + * This file is part of libgim. + * + * libgim 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 3 of the License, or (at your option) any later + * version. + * + * libgim 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 libgim. If not, see . + * + * Copyright 2013 Danny Robson + */ + +#include "sha1.hpp" + +#include "bitwise.hpp" +#include "endian.hpp" +#include "types.hpp" +#include "types/casts.hpp" + +#include +#include +#include +#include + + +using util::hash::SHA1; +using std::numeric_limits; +using std::begin; +using std::end; + + +// Logical function for sequence of rounds +static inline uint32_t +f_00 (uint32_t B, uint32_t C, uint32_t D) + { return (B & C) | (~B & D); } + + +static inline uint32_t +f_20 (uint32_t B, uint32_t C, uint32_t D) + { return B ^ C ^ D; } + + +static inline uint32_t +f_40 (uint32_t B, uint32_t C, uint32_t D) + { return (B & C) | (B & D) | (C & D); } + + +static inline uint32_t +f_60 (uint32_t B, uint32_t C, uint32_t D) + { return B ^ C ^ D; } + + +// Constant words for sequence of rounds +static const uint32_t K_00 = 0x5A827999; +static const uint32_t K_20 = 0x6ED9EBA1; +static const uint32_t K_40 = 0x8F1BBCDC; +static const uint32_t K_60 = 0xCA62C1D6; + + +static const uint32_t DEFAULT_H0 = 0x67452301; +static const uint32_t DEFAULT_H1 = 0xEFCDAB89; +static const uint32_t DEFAULT_H2 = 0x98BADCFE; +static const uint32_t DEFAULT_H3 = 0x10325476; +static const uint32_t DEFAULT_H4 = 0xC3D2E1F0; + + +static const size_t BLOCK_SIZE = 16; + + +SHA1::SHA1() +{ + reset (); +} + + +void +SHA1::reset (void) { + total = 0; + + H[0] = DEFAULT_H0; + H[1] = DEFAULT_H1; + H[2] = DEFAULT_H2; + H[3] = DEFAULT_H3; + H[4] = DEFAULT_H4; + + std::fill (begin (W), end (W), 0); + + state = READY; +} + + +void +SHA1::update (const uint8_t *data, size_t size) { + assert (state == READY); + assert (numeric_limits::max () - total >= size); + + + while (size > 0) { + size_t offset = total % BLOCK_SIZE; + size_t tocopy = std::min (BLOCK_SIZE - offset, size); + + for (size_t i = 0; i < tocopy; ++i) { + size_t octet = sizeof(W[0]) - (offset + i) % sizeof (W[0]); + size_t index = (offset / sizeof (W[0]) + i) / sizeof (W[0]); + + size_t shift = (octet - 1) * 8u; + uint32_t byte = *data++; + + W[index] |= byte << shift; + } + + total += tocopy; + + if (total % BLOCK_SIZE == 0) { + process (); + std::fill (begin (W), end (W), 0); + } + size -= tocopy; + } +} + + +void +SHA1::process (void) { + // Shuffle the work buffer a bit and initialise the state variables + for (size_t t = 16; t < 80; ++t) + W[t] = rotatel (W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16], 1); + + uint32_t A = H[0], + B = H[1], + C = H[2], + D = H[3], + E = H[4]; + + // Perform each of the four rounds + #define ROTATE_STATE(i) do { \ + uint32_t temp = rotatel (A, 5) + f_##i (B, C, D) + E + W[t] + K_##i; \ + E = D; \ + D = C; \ + C = rotatel (B, 30); \ + B = A; \ + A = temp; \ + } while (0) + + for (size_t t = 0; t < 20; ++t) ROTATE_STATE(00); + for (size_t t = 20; t < 40; ++t) ROTATE_STATE(20); + for (size_t t = 40; t < 60; ++t) ROTATE_STATE(40); + for (size_t t = 60; t < 80; ++t) ROTATE_STATE(60); + + // Save out the intermediate hash again + H[0] += A; + H[1] += B; + H[2] += C; + H[3] += D; + H[4] += E; +} + + +void +SHA1::finish (void) { + size_t index = (total / sizeof (W[0])) % BLOCK_SIZE; + size_t octet = sizeof (W[0]) - total % sizeof (W[0]) - 1; + + W[index] |= 0x80 << octet * 8; + if (index >= BLOCK_SIZE - 2) { + W[elems(W) - 2] = 0; + W[elems(W) - 1] = 0; + process(); + std::fill (begin (W), end (W), 0); + index = 0; + } else { + ++index; + } + + std::fill (begin (W) + index, end (W), 0); + + union { + uint32_t full; + uint8_t part[4]; + } swapper; + + swapper.full = 0; + swapper.part[3] = uint8_t(total); + + total *= 8; + + W[BLOCK_SIZE - 2] = 0x00000000; + W[BLOCK_SIZE - 1] = total << 24; + process (); + + state = FINISHED; +} + + +SHA1::digest_t +SHA1::digest (void) const { + assert (state == FINISHED); + + return { { + size_cast ((H[0] >> 24u) & 0xFF), + size_cast ((H[0] >> 16u) & 0xFF), + size_cast ((H[0] >> 8u) & 0xFF), + size_cast ((H[0] ) & 0xFF), + size_cast ((H[1] >> 24u) & 0xFF), + size_cast ((H[1] >> 16u) & 0xFF), + size_cast ((H[1] >> 8u) & 0xFF), + size_cast ((H[1] ) & 0xFF), + size_cast ((H[2] >> 24u) & 0xFF), + size_cast ((H[2] >> 16u) & 0xFF), + size_cast ((H[2] >> 8u) & 0xFF), + size_cast ((H[2] ) & 0xFF), + size_cast ((H[3] >> 24u) & 0xFF), + size_cast ((H[3] >> 16u) & 0xFF), + size_cast ((H[3] >> 8u) & 0xFF), + size_cast ((H[3] ) & 0xFF), + size_cast ((H[4] >> 24u) & 0xFF), + size_cast ((H[4] >> 16u) & 0xFF), + size_cast ((H[4] >> 8u) & 0xFF), + size_cast ((H[4] ) & 0xFF) + } }; +} diff --git a/hash/sha1.hpp b/hash/sha1.hpp new file mode 100644 index 00000000..f28878ee --- /dev/null +++ b/hash/sha1.hpp @@ -0,0 +1,62 @@ +/* + * This file is part of libgim. + * + * libgim 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 3 of the License, or (at your option) any later + * version. + * + * libgim 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 libgim. If not, see . + * + * Copyright 2013 Danny Robson + */ + +#ifndef __UTIL_HASH_SHA1_HPP +#define __UTIL_HASH_SHA1_HPP + +#include +#include + +#include + + +namespace util { + namespace hash { + class SHA1 { + public: + typedef std::array digest_t; + + public: + SHA1(); + + void update (const uint8_t *, size_t); + void finish (void); + digest_t digest (void) const; + void reset (void); + + protected: + void process (void); + + enum { + READY, + FINISHED + } state; + + uint64_t total; + uint32_t H[5]; + + union { + uint8_t c[16*4+64*4]; + uint32_t W[16+64]; + }; + }; + } +} + +#endif diff --git a/test/.gitignore b/test/.gitignore index ef5df41d..c49530e7 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -10,11 +10,12 @@ /*.log /maths* /matrix* -/md[24]* +/md[245]* /option /pool* /range* /region* +/sha1* /signal* /*.trs /version* diff --git a/test/Makefile.am b/test/Makefile.am index efb80673..875d6721 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -25,6 +25,7 @@ TEST_BIN = \ range \ region \ signal \ + sha1 \ version TESTS = $(TEST_BIN) json.pl @@ -83,5 +84,8 @@ region_SOURCES = region.cpp signal_LDADD = $(builddir)/../libutil.la signal_SOURCES = signal.cpp +sha1_LDADD = $(builddir)/../libutil.la +sha1_SOURCES = sha1.cpp + version_LDADD = $(builddir)/../libutil.la version_SOURCES = version.cpp diff --git a/test/sha1.cpp b/test/sha1.cpp new file mode 100644 index 00000000..1835214e --- /dev/null +++ b/test/sha1.cpp @@ -0,0 +1,68 @@ +#include "../hash/sha1.hpp" + +#include "../types.hpp" + +#include +#include + +#include + + +using util::hash::SHA1; + + +int +main (int, char**) { + static const struct { + const char *input; + SHA1::digest_t output; + } TESTS[] = { + { "", + { { 0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, + 0xbf, 0xef, 0x95, 0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09 } } + }, + + { + "a", + { { 0x86, 0xf7, 0xe4, 0x37, 0xfa, 0xa5, 0xa7, 0xfc, 0xe1, 0x5d, + 0x1d, 0xdc, 0xb9, 0xea, 0xea, 0xea, 0x37, 0x76, 0x67, 0xb8 } } + }, + + { "abc", + { { 0xA9, 0x99, 0x3E, 0x36, 0x47, 0x06, 0x81, 0x6A, 0xBA, 0x3E, + 0x25, 0x71, 0x78, 0x50, 0xC2, 0x6C, 0x9C, 0xD0, 0xD8, 0x9D } } + }, + + { "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + { { 0x84, 0x98, 0x3E, 0x44, 0x1C, 0x3B, 0xD2, 0x6E, 0xBA, 0xAE, + 0x4A, 0xA1, 0xF9, 0x51, 0x29, 0xE5, 0xE5, 0x46, 0x70, 0xF1 } } + }, + + { "a", + { { 0x34, 0xAA, 0x97, 0x3C, 0xD4, 0xC4, 0xDA, 0xA4, 0xF6, 0x1E, + 0xEB, 0x2B, 0xDB, 0xAD, 0x27, 0x31, 0x65, 0x34, 0x01, 0x6F } } + }, + + { "0123456701234567012345670123456701234567012345670123456701234567", + { { 0xDE, 0xA3, 0x56, 0xA2, 0xCD, 0xDD, 0x90, 0xC7, 0xA7, 0xEC, + 0xED, 0xC5, 0xEB, 0xB5, 0x63, 0x93, 0x4F, 0x46, 0x04, 0x52 } } + } + }; + + for (size_t i = 0; i < elems (TESTS); ++i) { + util::hash::SHA1 obj; + obj.update (reinterpret_cast (TESTS[i].input), + strlen (TESTS[i].input)); + obj.finish (); + + for (uint8_t c: obj.digest ()) { + unsigned hi = c >> 4u; + unsigned lo = c & 0xF; + + std::cout << std::hex << hi << lo << " "; + } + std::cout << "\n"; + } + + return 0; +}