From cba20adff26b3162307ef0c29f6b4a94ddb89577 Mon Sep 17 00:00:00 2001 From: volatilflerovium Date: Sat, 6 Dec 2025 07:42:52 +0000 Subject: [PATCH 1/6] Add CMakeLists.txt Add Compiler flags -g -O3 to MakeFile Ignore build and bin directories Add performance.txt --- .gitignore | 2 ++ CMakeLists.txt | 66 +++++++++++++++++++++++++++++++++++++++++++++++++ Makefile | 4 +-- README.md | 28 +++++++++++++++++++++ README.org | 17 ------------- performance.txt | 8 ++++++ 6 files changed, 106 insertions(+), 19 deletions(-) create mode 100644 CMakeLists.txt create mode 100644 README.md delete mode 100644 README.org create mode 100644 performance.txt diff --git a/.gitignore b/.gitignore index 6416eaa..556cc11 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ /.vscode/ /demo_sha1 /test_sha1 +bin/ +build/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..9a1e6d1 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,66 @@ +cmake_minimum_required(VERSION 3.18) +project("SHA1_tests") + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_FLAGS "-Wall") +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +set(EXECUTABLE_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/bin) + +get_filename_component(PARENT_DIR ../ ABSOLUTE) + + +if(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + add_definitions(-D_WIN32) + add_compile_options("/O2") +else() + add_definitions(-D_LINUX) + if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + message(WARNING "Assuming system in Linux") + endif() + add_compile_options("-O3") + add_compile_options("-g") +endif() + +#--------------------------------------------------------------------- + +add_library(SHA1_lib INTERFACE) + +target_include_directories( + SHA1_lib + INTERFACE + "${CMAKE_SOURCE_DIR}" +) + +#--------------------------------------------------------------------- + +set(TEST test_sha1) + +add_executable( + "${TEST}" + test_sha1.cpp + test_sha1_file.cpp +) + +target_link_libraries( + "${TEST}" + PRIVATE + SHA1_lib +) + +#--------------------------------------------------------------------- + +# Create a 1GB size file for testing performance + +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + message("Creating a 1GB file of random data in /tmp directory...") + execute_process(COMMAND dd if=/dev/urandom of=/tmp/1G_test_file.txt bs=1G count=1 iflag=fullblock + ERROR_VARIABLE error + OUTPUT_VARIABLE output + ) + + add_compile_definitions(TEST_FILE="/tmp/1G_test_file.txt") +endif() + +#--------------------------------------------------------------------- diff --git a/Makefile b/Makefile index 41fc3c9..a5d6704 100644 --- a/Makefile +++ b/Makefile @@ -5,10 +5,10 @@ RM = rm -f all: demo_sha1 test_sha1 demo_sha1: demo_sha1.cpp sha1.cpp sha1.hpp - $(CXX) $(CXXFLAGS) $(CPPFLAGS) -Wall -Wextra -std=c++11 -o $@ demo_sha1.cpp sha1.cpp + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -Wall -Wextra -std=c++11 -g -O3 -o $@ demo_sha1.cpp test_sha1: test_sha1.cpp test_sha1_file.cpp sha1.hpp - $(CXX) $(CXXFLAGS) $(CPPFLAGS) -Wall -Wextra -std=c++11 -o $@ test_sha1.cpp test_sha1_file.cpp sha1.cpp + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -Wall -Wextra -std=c++11 -g -O3 -o $@ test_sha1.cpp test_sha1_file.cpp check: test_sha1 ./test_sha1 diff --git a/README.md b/README.md new file mode 100644 index 0000000..03f3b9f --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# SHA-1 implementation in C++ + +## Warning + +Do not use SHA-1 unless you have to! +[SHA-1 is practically broken](https://en.wikipedia.org/wiki/SHA-1#Birthday-Near-Collision_Attack_%E2%80%93_first_practical_chosen-prefix_attack). Use a hash function from the [SHA-2](https://en.wikipedia.org/wiki/SHA-2) or [SHA-3](https://en.wikipedia.org/wiki/SHA-3) family instead. + +## How to build tests + +Using cmake +``` +mkdir build +cd build +cmake .. +make +``` + +## License + +100% Public Domain + +## Authors + +- Steve Reid (Original C Code) +- [Bruce Guenter](http://untroubled.org/) (Small changes to fit into bglibs) +- [Volker Diels-Grabsch](https://njh.eu/) (Translation to simpler C++ Code) +- [Eugene Hopkinson](https://riot.so/) (Safety improvements) +- [Zlatko Michailov](http://zlatko.michailov.org) (Header-only library) diff --git a/README.org b/README.org deleted file mode 100644 index 9c6cb7f..0000000 --- a/README.org +++ /dev/null @@ -1,17 +0,0 @@ -* SHA-1 implementation in C++ - -** Warning - -Do not use SHA-1 unless you have to! [[https://en.wikipedia.org/wiki/SHA-1#Birthday-Near-Collision_Attack_%E2%80%93_first_practical_chosen-prefix_attack][SHA-1 is practically broken]]. Use a hash function from the [[https://en.wikipedia.org/wiki/SHA-2][SHA-2]] or [[https://en.wikipedia.org/wiki/SHA-3][SHA-3]] family instead. - -** License - -100% Public Domain - -** Authors - -- Steve Reid (Original C Code) -- [[http://untroubled.org/][Bruce Guenter]] (Small changes to fit into bglibs) -- [[https://njh.eu/][Volker Diels-Grabsch]] (Translation to simpler C++ Code) -- [[https://riot.so/][Eugene Hopkinson]] (Safety improvements) -- [[http://zlatko.michailov.org][Zlatko Michailov]] (Header-only library) diff --git a/performance.txt b/performance.txt new file mode 100644 index 0000000..cac5a0e --- /dev/null +++ b/performance.txt @@ -0,0 +1,8 @@ +perf stat ./../bin/test_sha1 +0.011223803 seconds time elapsed + +perf stat ./../bin/test_sha1 --slow +6.446021922 seconds time elapsed + +perf stat ./../bin/test_sha1 /tmp/1G_test_file.txt +1.885112058 seconds time elapsed From 9abd5f0f028130172db683293e7f4892b1b8a3a2 Mon Sep 17 00:00:00 2001 From: volatilflerovium Date: Sat, 6 Dec 2025 07:54:36 +0000 Subject: [PATCH 2/6] Move all the static function to the SHA1 class scope. --- README.md | 1 + sha1.hpp | 130 ++++++++++++++++++++++++++++++------------------------ 2 files changed, 73 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index 03f3b9f..ea98ec8 100644 --- a/README.md +++ b/README.md @@ -26,3 +26,4 @@ make - [Volker Diels-Grabsch](https://njh.eu/) (Translation to simpler C++ Code) - [Eugene Hopkinson](https://riot.so/) (Safety improvements) - [Zlatko Michailov](http://zlatko.michailov.org) (Header-only library) +- [Dan Machado](dan-machado@yandex.com) (Refactoring) diff --git a/sha1.hpp b/sha1.hpp index 67a6b52..1e4b5bb 100644 --- a/sha1.hpp +++ b/sha1.hpp @@ -17,6 +17,8 @@ -- Eugene Hopkinson Header-only library -- Zlatko Michailov + C++ refactoring + -- Dan Machado */ #ifndef SHA1_HPP @@ -40,39 +42,51 @@ class SHA1 std::string final(); static std::string from_file(const std::string &filename); -private: - uint32_t digest[5]; - std::string buffer; +private: + + static const size_t BLOCK_INTS = 16; /* number of 32bit integers per SHA1 block */ + static const size_t BLOCK_BYTES = BLOCK_INTS * 4; + + uint32_t m_digest[5]; + std::string m_buffer; uint64_t transforms; -}; + void reset(uint32_t m_digest[], std::string &m_buffer, uint64_t &transforms); + uint32_t rol(const uint32_t value, const size_t bits); + uint32_t blk(const uint32_t block[BLOCK_INTS], const size_t i); + void R0(const uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i); + void R1(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i); + void R2(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i); + void R3(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i); + void R4(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i); + void transform(uint32_t m_digest[], uint32_t block[BLOCK_INTS], uint64_t &transforms); + void buffer_to_block(const std::string &m_buffer, uint32_t block[BLOCK_INTS]); +}; -static const size_t BLOCK_INTS = 16; /* number of 32bit integers per SHA1 block */ -static const size_t BLOCK_BYTES = BLOCK_INTS * 4; -inline static void reset(uint32_t digest[], std::string &buffer, uint64_t &transforms) +inline void SHA1::reset(uint32_t m_digest[], std::string &m_buffer, uint64_t &transforms) { /* SHA1 initialization constants */ - digest[0] = 0x67452301; - digest[1] = 0xefcdab89; - digest[2] = 0x98badcfe; - digest[3] = 0x10325476; - digest[4] = 0xc3d2e1f0; + m_digest[0] = 0x67452301; + m_digest[1] = 0xefcdab89; + m_digest[2] = 0x98badcfe; + m_digest[3] = 0x10325476; + m_digest[4] = 0xc3d2e1f0; /* Reset counters */ - buffer = ""; + m_buffer = ""; transforms = 0; } -inline static uint32_t rol(const uint32_t value, const size_t bits) +inline uint32_t SHA1::rol(const uint32_t value, const size_t bits) { return (value << bits) | (value >> (32 - bits)); } -inline static uint32_t blk(const uint32_t block[BLOCK_INTS], const size_t i) +inline uint32_t SHA1::blk(const uint32_t block[BLOCK_INTS], const size_t i) { return rol(block[(i+13)&15] ^ block[(i+8)&15] ^ block[(i+2)&15] ^ block[i], 1); } @@ -82,14 +96,14 @@ inline static uint32_t blk(const uint32_t block[BLOCK_INTS], const size_t i) * (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ -inline static void R0(const uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) +inline void SHA1::R0(const uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) { z += ((w&(x^y))^y) + block[i] + 0x5a827999 + rol(v, 5); w = rol(w, 30); } -inline static void R1(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) +inline void SHA1::R1(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) { block[i] = blk(block, i); z += ((w&(x^y))^y) + block[i] + 0x5a827999 + rol(v, 5); @@ -97,7 +111,7 @@ inline static void R1(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, } -inline static void R2(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) +inline void SHA1::R2(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) { block[i] = blk(block, i); z += (w^x^y) + block[i] + 0x6ed9eba1 + rol(v, 5); @@ -105,7 +119,7 @@ inline static void R2(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, } -inline static void R3(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) +inline void SHA1::R3(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) { block[i] = blk(block, i); z += (((w|x)&y)|(w&x)) + block[i] + 0x8f1bbcdc + rol(v, 5); @@ -113,7 +127,7 @@ inline static void R3(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, } -inline static void R4(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) +inline void SHA1::R4(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) { block[i] = blk(block, i); z += (w^x^y) + block[i] + 0xca62c1d6 + rol(v, 5); @@ -125,14 +139,14 @@ inline static void R4(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, * Hash a single 512-bit block. This is the core of the algorithm. */ -inline static void transform(uint32_t digest[], uint32_t block[BLOCK_INTS], uint64_t &transforms) +inline void SHA1::transform(uint32_t m_digest[], uint32_t block[BLOCK_INTS], uint64_t &transforms) { - /* Copy digest[] to working vars */ - uint32_t a = digest[0]; - uint32_t b = digest[1]; - uint32_t c = digest[2]; - uint32_t d = digest[3]; - uint32_t e = digest[4]; + /* Copy m_digest[] to working vars */ + uint32_t a = m_digest[0]; + uint32_t b = m_digest[1]; + uint32_t c = m_digest[2]; + uint32_t d = m_digest[3]; + uint32_t e = m_digest[4]; /* 4 rounds of 20 operations each. Loop unrolled. */ R0(block, a, b, c, d, e, 0); @@ -216,34 +230,34 @@ inline static void transform(uint32_t digest[], uint32_t block[BLOCK_INTS], uint R4(block, c, d, e, a, b, 14); R4(block, b, c, d, e, a, 15); - /* Add the working vars back into digest[] */ - digest[0] += a; - digest[1] += b; - digest[2] += c; - digest[3] += d; - digest[4] += e; + /* Add the working vars back into m_digest[] */ + m_digest[0] += a; + m_digest[1] += b; + m_digest[2] += c; + m_digest[3] += d; + m_digest[4] += e; /* Count the number of transformations */ transforms++; } -inline static void buffer_to_block(const std::string &buffer, uint32_t block[BLOCK_INTS]) +inline void SHA1::buffer_to_block(const std::string &m_buffer, uint32_t block[BLOCK_INTS]) { - /* Convert the std::string (byte buffer) to a uint32_t array (MSB) */ + /* Convert the std::string (byte m_buffer) to a uint32_t array (MSB) */ for (size_t i = 0; i < BLOCK_INTS; i++) { - block[i] = (buffer[4*i+3] & 0xff) - | (buffer[4*i+2] & 0xff)<<8 - | (buffer[4*i+1] & 0xff)<<16 - | (buffer[4*i+0] & 0xff)<<24; + block[i] = (m_buffer[4*i+3] & 0xff) + | (m_buffer[4*i+2] & 0xff)<<8 + | (m_buffer[4*i+1] & 0xff)<<16 + | (m_buffer[4*i+0] & 0xff)<<24; } } inline SHA1::SHA1() { - reset(digest, buffer, transforms); + reset(m_digest, m_buffer, transforms); } @@ -259,43 +273,43 @@ inline void SHA1::update(std::istream &is) while (true) { char sbuf[BLOCK_BYTES]; - is.read(sbuf, BLOCK_BYTES - buffer.size()); - buffer.append(sbuf, (std::size_t)is.gcount()); - if (buffer.size() != BLOCK_BYTES) + is.read(sbuf, BLOCK_BYTES - m_buffer.size()); + m_buffer.append(sbuf, (std::size_t)is.gcount()); + if (m_buffer.size() != BLOCK_BYTES) { return; } uint32_t block[BLOCK_INTS]; - buffer_to_block(buffer, block); - transform(digest, block, transforms); - buffer.clear(); + buffer_to_block(m_buffer, block); + transform(m_digest, block, transforms); + m_buffer.clear(); } } /* - * Add padding and return the message digest. + * Add padding and return the message m_digest. */ inline std::string SHA1::final() { /* Total number of hashed bits */ - uint64_t total_bits = (transforms*BLOCK_BYTES + buffer.size()) * 8; + uint64_t total_bits = (transforms*BLOCK_BYTES + m_buffer.size()) * 8; /* Padding */ - buffer += (char)0x80; - size_t orig_size = buffer.size(); - while (buffer.size() < BLOCK_BYTES) + m_buffer += (char)0x80; + size_t orig_size = m_buffer.size(); + while (m_buffer.size() < BLOCK_BYTES) { - buffer += (char)0x00; + m_buffer += (char)0x00; } uint32_t block[BLOCK_INTS]; - buffer_to_block(buffer, block); + buffer_to_block(m_buffer, block); if (orig_size > BLOCK_BYTES - 8) { - transform(digest, block, transforms); + transform(m_digest, block, transforms); for (size_t i = 0; i < BLOCK_INTS - 2; i++) { block[i] = 0; @@ -305,18 +319,18 @@ inline std::string SHA1::final() /* Append total_bits, split this uint64_t into two uint32_t */ block[BLOCK_INTS - 1] = (uint32_t)total_bits; block[BLOCK_INTS - 2] = (uint32_t)(total_bits >> 32); - transform(digest, block, transforms); + transform(m_digest, block, transforms); /* Hex std::string */ std::ostringstream result; - for (size_t i = 0; i < sizeof(digest) / sizeof(digest[0]); i++) + for (size_t i = 0; i < sizeof(m_digest) / sizeof(m_digest[0]); i++) { result << std::hex << std::setfill('0') << std::setw(8); - result << digest[i]; + result << m_digest[i]; } /* Reset for next run */ - reset(digest, buffer, transforms); + reset(m_digest, m_buffer, transforms); return result.str(); } From db860653dadbde9e65d69d6ad1457178d747049d Mon Sep 17 00:00:00 2001 From: volatilflerovium Date: Sat, 6 Dec 2025 09:19:17 +0000 Subject: [PATCH 3/6] Add SHA1::m_dataBlock Remove parameter uint32_t block from SHA1::blk, SHA1::R0, SHA1::R1, SHA1::R2, SHA1::R3, SHA1::R4 Remove parameters from SHA1::transform and SHA1::buffer_to_block, SHA1::reset --- sha1.hpp | 244 +++++++++++++++++++++++++++---------------------------- 1 file changed, 122 insertions(+), 122 deletions(-) diff --git a/sha1.hpp b/sha1.hpp index 1e4b5bb..c93112e 100644 --- a/sha1.hpp +++ b/sha1.hpp @@ -47,25 +47,25 @@ class SHA1 static const size_t BLOCK_INTS = 16; /* number of 32bit integers per SHA1 block */ static const size_t BLOCK_BYTES = BLOCK_INTS * 4; + uint32_t m_dataBlock[BLOCK_INTS]; uint32_t m_digest[5]; std::string m_buffer; uint64_t transforms; - void reset(uint32_t m_digest[], std::string &m_buffer, uint64_t &transforms); + void reset(); uint32_t rol(const uint32_t value, const size_t bits); - uint32_t blk(const uint32_t block[BLOCK_INTS], const size_t i); - void R0(const uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i); - void R1(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i); - void R2(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i); - void R3(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i); - void R4(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i); - void transform(uint32_t m_digest[], uint32_t block[BLOCK_INTS], uint64_t &transforms); - void buffer_to_block(const std::string &m_buffer, uint32_t block[BLOCK_INTS]); + uint32_t blk(const size_t i); + void R0(const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i); + void R1(const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i); + void R2(const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i); + void R3(const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i); + void R4(const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i); + void transform(); + void buffer_to_block(); }; - -inline void SHA1::reset(uint32_t m_digest[], std::string &m_buffer, uint64_t &transforms) +inline void SHA1::reset() { /* SHA1 initialization constants */ m_digest[0] = 0x67452301; @@ -86,9 +86,9 @@ inline uint32_t SHA1::rol(const uint32_t value, const size_t bits) } -inline uint32_t SHA1::blk(const uint32_t block[BLOCK_INTS], const size_t i) +inline uint32_t SHA1::blk(const size_t i) { - return rol(block[(i+13)&15] ^ block[(i+8)&15] ^ block[(i+2)&15] ^ block[i], 1); + return rol(m_dataBlock[(i+13)&15] ^ m_dataBlock[(i+8)&15] ^ m_dataBlock[(i+2)&15] ^ m_dataBlock[i], 1); } @@ -96,41 +96,41 @@ inline uint32_t SHA1::blk(const uint32_t block[BLOCK_INTS], const size_t i) * (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ -inline void SHA1::R0(const uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) +inline void SHA1::R0(const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) { - z += ((w&(x^y))^y) + block[i] + 0x5a827999 + rol(v, 5); + z += ((w&(x^y))^y) + m_dataBlock[i] + 0x5a827999 + rol(v, 5); w = rol(w, 30); } -inline void SHA1::R1(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) +inline void SHA1::R1(const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) { - block[i] = blk(block, i); - z += ((w&(x^y))^y) + block[i] + 0x5a827999 + rol(v, 5); + m_dataBlock[i] = blk(i); + z += ((w&(x^y))^y) + m_dataBlock[i] + 0x5a827999 + rol(v, 5); w = rol(w, 30); } -inline void SHA1::R2(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) +inline void SHA1::R2(const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) { - block[i] = blk(block, i); - z += (w^x^y) + block[i] + 0x6ed9eba1 + rol(v, 5); + m_dataBlock[i] = blk(i); + z += (w^x^y) + m_dataBlock[i] + 0x6ed9eba1 + rol(v, 5); w = rol(w, 30); } -inline void SHA1::R3(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) +inline void SHA1::R3(const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) { - block[i] = blk(block, i); - z += (((w|x)&y)|(w&x)) + block[i] + 0x8f1bbcdc + rol(v, 5); + m_dataBlock[i] = blk(i); + z += (((w|x)&y)|(w&x)) + m_dataBlock[i] + 0x8f1bbcdc + rol(v, 5); w = rol(w, 30); } -inline void SHA1::R4(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) +inline void SHA1::R4(const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) { - block[i] = blk(block, i); - z += (w^x^y) + block[i] + 0xca62c1d6 + rol(v, 5); + m_dataBlock[i] = blk(i); + z += (w^x^y) + m_dataBlock[i] + 0xca62c1d6 + rol(v, 5); w = rol(w, 30); } @@ -139,7 +139,7 @@ inline void SHA1::R4(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, * Hash a single 512-bit block. This is the core of the algorithm. */ -inline void SHA1::transform(uint32_t m_digest[], uint32_t block[BLOCK_INTS], uint64_t &transforms) +inline void SHA1::transform() { /* Copy m_digest[] to working vars */ uint32_t a = m_digest[0]; @@ -149,86 +149,86 @@ inline void SHA1::transform(uint32_t m_digest[], uint32_t block[BLOCK_INTS], uin uint32_t e = m_digest[4]; /* 4 rounds of 20 operations each. Loop unrolled. */ - R0(block, a, b, c, d, e, 0); - R0(block, e, a, b, c, d, 1); - R0(block, d, e, a, b, c, 2); - R0(block, c, d, e, a, b, 3); - R0(block, b, c, d, e, a, 4); - R0(block, a, b, c, d, e, 5); - R0(block, e, a, b, c, d, 6); - R0(block, d, e, a, b, c, 7); - R0(block, c, d, e, a, b, 8); - R0(block, b, c, d, e, a, 9); - R0(block, a, b, c, d, e, 10); - R0(block, e, a, b, c, d, 11); - R0(block, d, e, a, b, c, 12); - R0(block, c, d, e, a, b, 13); - R0(block, b, c, d, e, a, 14); - R0(block, a, b, c, d, e, 15); - R1(block, e, a, b, c, d, 0); - R1(block, d, e, a, b, c, 1); - R1(block, c, d, e, a, b, 2); - R1(block, b, c, d, e, a, 3); - R2(block, a, b, c, d, e, 4); - R2(block, e, a, b, c, d, 5); - R2(block, d, e, a, b, c, 6); - R2(block, c, d, e, a, b, 7); - R2(block, b, c, d, e, a, 8); - R2(block, a, b, c, d, e, 9); - R2(block, e, a, b, c, d, 10); - R2(block, d, e, a, b, c, 11); - R2(block, c, d, e, a, b, 12); - R2(block, b, c, d, e, a, 13); - R2(block, a, b, c, d, e, 14); - R2(block, e, a, b, c, d, 15); - R2(block, d, e, a, b, c, 0); - R2(block, c, d, e, a, b, 1); - R2(block, b, c, d, e, a, 2); - R2(block, a, b, c, d, e, 3); - R2(block, e, a, b, c, d, 4); - R2(block, d, e, a, b, c, 5); - R2(block, c, d, e, a, b, 6); - R2(block, b, c, d, e, a, 7); - R3(block, a, b, c, d, e, 8); - R3(block, e, a, b, c, d, 9); - R3(block, d, e, a, b, c, 10); - R3(block, c, d, e, a, b, 11); - R3(block, b, c, d, e, a, 12); - R3(block, a, b, c, d, e, 13); - R3(block, e, a, b, c, d, 14); - R3(block, d, e, a, b, c, 15); - R3(block, c, d, e, a, b, 0); - R3(block, b, c, d, e, a, 1); - R3(block, a, b, c, d, e, 2); - R3(block, e, a, b, c, d, 3); - R3(block, d, e, a, b, c, 4); - R3(block, c, d, e, a, b, 5); - R3(block, b, c, d, e, a, 6); - R3(block, a, b, c, d, e, 7); - R3(block, e, a, b, c, d, 8); - R3(block, d, e, a, b, c, 9); - R3(block, c, d, e, a, b, 10); - R3(block, b, c, d, e, a, 11); - R4(block, a, b, c, d, e, 12); - R4(block, e, a, b, c, d, 13); - R4(block, d, e, a, b, c, 14); - R4(block, c, d, e, a, b, 15); - R4(block, b, c, d, e, a, 0); - R4(block, a, b, c, d, e, 1); - R4(block, e, a, b, c, d, 2); - R4(block, d, e, a, b, c, 3); - R4(block, c, d, e, a, b, 4); - R4(block, b, c, d, e, a, 5); - R4(block, a, b, c, d, e, 6); - R4(block, e, a, b, c, d, 7); - R4(block, d, e, a, b, c, 8); - R4(block, c, d, e, a, b, 9); - R4(block, b, c, d, e, a, 10); - R4(block, a, b, c, d, e, 11); - R4(block, e, a, b, c, d, 12); - R4(block, d, e, a, b, c, 13); - R4(block, c, d, e, a, b, 14); - R4(block, b, c, d, e, a, 15); + R0(a, b, c, d, e, 0); + R0(e, a, b, c, d, 1); + R0(d, e, a, b, c, 2); + R0(c, d, e, a, b, 3); + R0(b, c, d, e, a, 4); + R0(a, b, c, d, e, 5); + R0(e, a, b, c, d, 6); + R0(d, e, a, b, c, 7); + R0(c, d, e, a, b, 8); + R0(b, c, d, e, a, 9); + R0(a, b, c, d, e, 10); + R0(e, a, b, c, d, 11); + R0(d, e, a, b, c, 12); + R0(c, d, e, a, b, 13); + R0(b, c, d, e, a, 14); + R0(a, b, c, d, e, 15); + R1(e, a, b, c, d, 0); + R1(d, e, a, b, c, 1); + R1(c, d, e, a, b, 2); + R1(b, c, d, e, a, 3); + R2(a, b, c, d, e, 4); + R2(e, a, b, c, d, 5); + R2(d, e, a, b, c, 6); + R2(c, d, e, a, b, 7); + R2(b, c, d, e, a, 8); + R2(a, b, c, d, e, 9); + R2(e, a, b, c, d, 10); + R2(d, e, a, b, c, 11); + R2(c, d, e, a, b, 12); + R2(b, c, d, e, a, 13); + R2(a, b, c, d, e, 14); + R2(e, a, b, c, d, 15); + R2(d, e, a, b, c, 0); + R2(c, d, e, a, b, 1); + R2(b, c, d, e, a, 2); + R2(a, b, c, d, e, 3); + R2(e, a, b, c, d, 4); + R2(d, e, a, b, c, 5); + R2(c, d, e, a, b, 6); + R2(b, c, d, e, a, 7); + R3(a, b, c, d, e, 8); + R3(e, a, b, c, d, 9); + R3(d, e, a, b, c, 10); + R3(c, d, e, a, b, 11); + R3(b, c, d, e, a, 12); + R3(a, b, c, d, e, 13); + R3(e, a, b, c, d, 14); + R3(d, e, a, b, c, 15); + R3(c, d, e, a, b, 0); + R3(b, c, d, e, a, 1); + R3(a, b, c, d, e, 2); + R3(e, a, b, c, d, 3); + R3(d, e, a, b, c, 4); + R3(c, d, e, a, b, 5); + R3(b, c, d, e, a, 6); + R3(a, b, c, d, e, 7); + R3(e, a, b, c, d, 8); + R3(d, e, a, b, c, 9); + R3(c, d, e, a, b, 10); + R3(b, c, d, e, a, 11); + R4(a, b, c, d, e, 12); + R4(e, a, b, c, d, 13); + R4(d, e, a, b, c, 14); + R4(c, d, e, a, b, 15); + R4(b, c, d, e, a, 0); + R4(a, b, c, d, e, 1); + R4(e, a, b, c, d, 2); + R4(d, e, a, b, c, 3); + R4(c, d, e, a, b, 4); + R4(b, c, d, e, a, 5); + R4(a, b, c, d, e, 6); + R4(e, a, b, c, d, 7); + R4(d, e, a, b, c, 8); + R4(c, d, e, a, b, 9); + R4(b, c, d, e, a, 10); + R4(a, b, c, d, e, 11); + R4(e, a, b, c, d, 12); + R4(d, e, a, b, c, 13); + R4(c, d, e, a, b, 14); + R4(b, c, d, e, a, 15); /* Add the working vars back into m_digest[] */ m_digest[0] += a; @@ -242,12 +242,12 @@ inline void SHA1::transform(uint32_t m_digest[], uint32_t block[BLOCK_INTS], uin } -inline void SHA1::buffer_to_block(const std::string &m_buffer, uint32_t block[BLOCK_INTS]) +inline void SHA1::buffer_to_block() { /* Convert the std::string (byte m_buffer) to a uint32_t array (MSB) */ for (size_t i = 0; i < BLOCK_INTS; i++) { - block[i] = (m_buffer[4*i+3] & 0xff) + m_dataBlock[i] = (m_buffer[4*i+3] & 0xff) | (m_buffer[4*i+2] & 0xff)<<8 | (m_buffer[4*i+1] & 0xff)<<16 | (m_buffer[4*i+0] & 0xff)<<24; @@ -257,7 +257,7 @@ inline void SHA1::buffer_to_block(const std::string &m_buffer, uint32_t block[BL inline SHA1::SHA1() { - reset(m_digest, m_buffer, transforms); + reset(); } @@ -279,9 +279,9 @@ inline void SHA1::update(std::istream &is) { return; } - uint32_t block[BLOCK_INTS]; - buffer_to_block(m_buffer, block); - transform(m_digest, block, transforms); + //uint32_t m_dataBlock[BLOCK_INTS]; + buffer_to_block(); + transform(); m_buffer.clear(); } } @@ -304,22 +304,22 @@ inline std::string SHA1::final() m_buffer += (char)0x00; } - uint32_t block[BLOCK_INTS]; - buffer_to_block(m_buffer, block); + //uint32_t m_dataBlock[BLOCK_INTS]; + buffer_to_block(); if (orig_size > BLOCK_BYTES - 8) { - transform(m_digest, block, transforms); + transform(); for (size_t i = 0; i < BLOCK_INTS - 2; i++) { - block[i] = 0; + m_dataBlock[i] = 0; } } /* Append total_bits, split this uint64_t into two uint32_t */ - block[BLOCK_INTS - 1] = (uint32_t)total_bits; - block[BLOCK_INTS - 2] = (uint32_t)(total_bits >> 32); - transform(m_digest, block, transforms); + m_dataBlock[BLOCK_INTS - 1] = (uint32_t)total_bits; + m_dataBlock[BLOCK_INTS - 2] = (uint32_t)(total_bits >> 32); + transform(); /* Hex std::string */ std::ostringstream result; @@ -330,7 +330,7 @@ inline std::string SHA1::final() } /* Reset for next run */ - reset(m_digest, m_buffer, transforms); + reset(); return result.str(); } From b15118e7734195da22040dded8b94fca750e151c Mon Sep 17 00:00:00 2001 From: volatilflerovium Date: Sat, 6 Dec 2025 10:42:43 +0000 Subject: [PATCH 4/6] Set methods SHA1::rol, SHA1::R0, SHA1::R1, SHA1::R2, SHA1::R3, SHA1::R4 as template --- sha1.hpp | 283 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 165 insertions(+), 118 deletions(-) diff --git a/sha1.hpp b/sha1.hpp index c93112e..07c7822 100644 --- a/sha1.hpp +++ b/sha1.hpp @@ -37,29 +37,75 @@ class SHA1 { public: SHA1(); + /** + * Partially calculate the sha1sum of its argument. The calculation + * is completed by calling final(). Consecutive calls to update() + * will have the effect of calculating the sha1 sum of the concatenated + * string of its arguments. For example: + * SHA1 sha1; + * sha1.update(str1); + * sha1.update(str2); + * sha1.update(str3); + * sha1.final(); + * is the same than + * sha1.update(str1 + str2 + str3); + * sha1.final(); + * + * @param cstr null terminated string + * + * */ void update(const std::string &s); - void update(std::istream &is); + void update(std::istream &is); + + /** + * Complete the calculation of the sha1sum initiated by calls to + * SHA1::update. + * + * @return return the sha1sum of the string (or strings) + * fed by calls to SHA1::update. + * */ std::string final(); - static std::string from_file(const std::string &filename); -private: + /** + * Get the sha1sum of a file/ + * + * @return return the sha1sum of the content of the specified file. + * + * */ + static std::string from_file(const std::string &filename); +private: static const size_t BLOCK_INTS = 16; /* number of 32bit integers per SHA1 block */ static const size_t BLOCK_BYTES = BLOCK_INTS * 4; - uint32_t m_dataBlock[BLOCK_INTS]; + uint32_t m_dataBlock[BLOCK_INTS]; uint32_t m_digest[5]; std::string m_buffer; uint64_t transforms; void reset(); - uint32_t rol(const uint32_t value, const size_t bits); - uint32_t blk(const size_t i); - void R0(const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i); - void R1(const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i); - void R2(const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i); - void R3(const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i); - void R4(const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i); + template + uint32_t rol(uint32_t value); + + void blk(const size_t i); + /* + * R0, R1, R2, R3, R4 are the different operations used in SHA1 + */ + template + void R0(const size_t i); + + template + void R1(const size_t i); + + template + void R2(const size_t i); + + template + void R3(const size_t i); + + template + void R4(const size_t i); + void transform(); void buffer_to_block(); }; @@ -80,15 +126,16 @@ inline void SHA1::reset() } -inline uint32_t SHA1::rol(const uint32_t value, const size_t bits) +template +inline uint32_t SHA1::rol(uint32_t value) { - return (value << bits) | (value >> (32 - bits)); + return (value << N) | (value >> M); } -inline uint32_t SHA1::blk(const size_t i) +inline void SHA1::blk(const size_t i) { - return rol(m_dataBlock[(i+13)&15] ^ m_dataBlock[(i+8)&15] ^ m_dataBlock[(i+2)&15] ^ m_dataBlock[i], 1); + m_dataBlock[i]=rol<1, 31>(m_dataBlock[(i+13)&15] ^ m_dataBlock[(i+8)&15] ^ m_dataBlock[(i+2)&15] ^ m_dataBlock[i]); } @@ -96,42 +143,42 @@ inline uint32_t SHA1::blk(const size_t i) * (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ -inline void SHA1::R0(const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) +template +inline void SHA1::R0(const size_t i) { - z += ((w&(x^y))^y) + m_dataBlock[i] + 0x5a827999 + rol(v, 5); - w = rol(w, 30); + m_digest[z] =m_digest[z]+ ((m_digest[w]&(m_digest[x]^m_digest[y]))^m_digest[y]) + m_dataBlock[i] + 0x5a827999 + rol<5, 27>(m_digest[v]); + m_digest[w] = rol<30, 2>(m_digest[w]); } - -inline void SHA1::R1(const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) +template +inline void SHA1::R1(const size_t i) { - m_dataBlock[i] = blk(i); - z += ((w&(x^y))^y) + m_dataBlock[i] + 0x5a827999 + rol(v, 5); - w = rol(w, 30); + blk(i); + R0(i); } - -inline void SHA1::R2(const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) +template +inline void SHA1::R2(const size_t i) { - m_dataBlock[i] = blk(i); - z += (w^x^y) + m_dataBlock[i] + 0x6ed9eba1 + rol(v, 5); - w = rol(w, 30); + blk(i); + m_digest[z] =m_digest[z]+ (m_digest[w]^m_digest[x]^m_digest[y]) + m_dataBlock[i] + 0x6ed9eba1 + rol<5, 27>(m_digest[v]); + m_digest[w] = rol<30, 2>(m_digest[w]); } - -inline void SHA1::R3(const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) +template +inline void SHA1::R3(const size_t i) { - m_dataBlock[i] = blk(i); - z += (((w|x)&y)|(w&x)) + m_dataBlock[i] + 0x8f1bbcdc + rol(v, 5); - w = rol(w, 30); + blk(i); + m_digest[z] =m_digest[z]+ (((m_digest[w]|m_digest[x])&m_digest[y])|(m_digest[w]&m_digest[x])) + m_dataBlock[i] + 0x8f1bbcdc + rol<5, 27>(m_digest[v]); + m_digest[w] = rol<30, 2>(m_digest[w]); } - -inline void SHA1::R4(const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) +template +inline void SHA1::R4(const size_t i) { - m_dataBlock[i] = blk(i); - z += (w^x^y) + m_dataBlock[i] + 0xca62c1d6 + rol(v, 5); - w = rol(w, 30); + blk(i); + m_digest[z] =m_digest[z]+ (m_digest[w]^m_digest[x]^m_digest[y]) + m_dataBlock[i] + 0xca62c1d6 + rol<5, 27>(m_digest[v]); + m_digest[w] = rol<30, 2>(m_digest[w]); } @@ -149,86 +196,86 @@ inline void SHA1::transform() uint32_t e = m_digest[4]; /* 4 rounds of 20 operations each. Loop unrolled. */ - R0(a, b, c, d, e, 0); - R0(e, a, b, c, d, 1); - R0(d, e, a, b, c, 2); - R0(c, d, e, a, b, 3); - R0(b, c, d, e, a, 4); - R0(a, b, c, d, e, 5); - R0(e, a, b, c, d, 6); - R0(d, e, a, b, c, 7); - R0(c, d, e, a, b, 8); - R0(b, c, d, e, a, 9); - R0(a, b, c, d, e, 10); - R0(e, a, b, c, d, 11); - R0(d, e, a, b, c, 12); - R0(c, d, e, a, b, 13); - R0(b, c, d, e, a, 14); - R0(a, b, c, d, e, 15); - R1(e, a, b, c, d, 0); - R1(d, e, a, b, c, 1); - R1(c, d, e, a, b, 2); - R1(b, c, d, e, a, 3); - R2(a, b, c, d, e, 4); - R2(e, a, b, c, d, 5); - R2(d, e, a, b, c, 6); - R2(c, d, e, a, b, 7); - R2(b, c, d, e, a, 8); - R2(a, b, c, d, e, 9); - R2(e, a, b, c, d, 10); - R2(d, e, a, b, c, 11); - R2(c, d, e, a, b, 12); - R2(b, c, d, e, a, 13); - R2(a, b, c, d, e, 14); - R2(e, a, b, c, d, 15); - R2(d, e, a, b, c, 0); - R2(c, d, e, a, b, 1); - R2(b, c, d, e, a, 2); - R2(a, b, c, d, e, 3); - R2(e, a, b, c, d, 4); - R2(d, e, a, b, c, 5); - R2(c, d, e, a, b, 6); - R2(b, c, d, e, a, 7); - R3(a, b, c, d, e, 8); - R3(e, a, b, c, d, 9); - R3(d, e, a, b, c, 10); - R3(c, d, e, a, b, 11); - R3(b, c, d, e, a, 12); - R3(a, b, c, d, e, 13); - R3(e, a, b, c, d, 14); - R3(d, e, a, b, c, 15); - R3(c, d, e, a, b, 0); - R3(b, c, d, e, a, 1); - R3(a, b, c, d, e, 2); - R3(e, a, b, c, d, 3); - R3(d, e, a, b, c, 4); - R3(c, d, e, a, b, 5); - R3(b, c, d, e, a, 6); - R3(a, b, c, d, e, 7); - R3(e, a, b, c, d, 8); - R3(d, e, a, b, c, 9); - R3(c, d, e, a, b, 10); - R3(b, c, d, e, a, 11); - R4(a, b, c, d, e, 12); - R4(e, a, b, c, d, 13); - R4(d, e, a, b, c, 14); - R4(c, d, e, a, b, 15); - R4(b, c, d, e, a, 0); - R4(a, b, c, d, e, 1); - R4(e, a, b, c, d, 2); - R4(d, e, a, b, c, 3); - R4(c, d, e, a, b, 4); - R4(b, c, d, e, a, 5); - R4(a, b, c, d, e, 6); - R4(e, a, b, c, d, 7); - R4(d, e, a, b, c, 8); - R4(c, d, e, a, b, 9); - R4(b, c, d, e, a, 10); - R4(a, b, c, d, e, 11); - R4(e, a, b, c, d, 12); - R4(d, e, a, b, c, 13); - R4(c, d, e, a, b, 14); - R4(b, c, d, e, a, 15); + R0<0, 1, 2, 3, 4>( 0); + R0<4, 0, 1, 2, 3>( 1); + R0<3, 4, 0, 1, 2>( 2); + R0<2, 3, 4, 0, 1>( 3); + R0<1, 2, 3, 4, 0>( 4); + R0<0, 1, 2, 3, 4>( 5); + R0<4, 0, 1, 2, 3>( 6); + R0<3, 4, 0, 1, 2>( 7); + R0<2, 3, 4, 0, 1>( 8); + R0<1, 2, 3, 4, 0>( 9); + R0<0, 1, 2, 3, 4>(10); + R0<4, 0, 1, 2, 3>(11); + R0<3, 4, 0, 1, 2>(12); + R0<2, 3, 4, 0, 1>(13); + R0<1, 2, 3, 4, 0>(14); + R0<0, 1, 2, 3, 4>(15); + R1<4, 0, 1, 2, 3>( 0); + R1<3, 4, 0, 1, 2>( 1); + R1<2, 3, 4, 0, 1>( 2); + R1<1, 2, 3, 4, 0>( 3); + R2<0, 1, 2, 3, 4>( 4); + R2<4, 0, 1, 2, 3>( 5); + R2<3, 4, 0, 1, 2>( 6); + R2<2, 3, 4, 0, 1>( 7); + R2<1, 2, 3, 4, 0>( 8); + R2<0, 1, 2, 3, 4>( 9); + R2<4, 0, 1, 2, 3>(10); + R2<3, 4, 0, 1, 2>(11); + R2<2, 3, 4, 0, 1>(12); + R2<1, 2, 3, 4, 0>(13); + R2<0, 1, 2, 3, 4>(14); + R2<4, 0, 1, 2, 3>(15); + R2<3, 4, 0, 1, 2>( 0); + R2<2, 3, 4, 0, 1>( 1); + R2<1, 2, 3, 4, 0>( 2); + R2<0, 1, 2, 3, 4>( 3); + R2<4, 0, 1, 2, 3>( 4); + R2<3, 4, 0, 1, 2>( 5); + R2<2, 3, 4, 0, 1>( 6); + R2<1, 2, 3, 4, 0>( 7); + R3<0, 1, 2, 3, 4>( 8); + R3<4, 0, 1, 2, 3>( 9); + R3<3, 4, 0, 1, 2>(10); + R3<2, 3, 4, 0, 1>(11); + R3<1, 2, 3, 4, 0>(12); + R3<0, 1, 2, 3, 4>(13); + R3<4, 0, 1, 2, 3>(14); + R3<3, 4, 0, 1, 2>(15); + R3<2, 3, 4, 0, 1>( 0); + R3<1, 2, 3, 4, 0>( 1); + R3<0, 1, 2, 3, 4>( 2); + R3<4, 0, 1, 2, 3>( 3); + R3<3, 4, 0, 1, 2>( 4); + R3<2, 3, 4, 0, 1>( 5); + R3<1, 2, 3, 4, 0>( 6); + R3<0, 1, 2, 3, 4>( 7); + R3<4, 0, 1, 2, 3>( 8); + R3<3, 4, 0, 1, 2>( 9); + R3<2, 3, 4, 0, 1>(10); + R3<1, 2, 3, 4, 0>(11); + R4<0, 1, 2, 3, 4>(12); + R4<4, 0, 1, 2, 3>(13); + R4<3, 4, 0, 1, 2>(14); + R4<2, 3, 4, 0, 1>(15); + R4<1, 2, 3, 4, 0>( 0); + R4<0, 1, 2, 3, 4>( 1); + R4<4, 0, 1, 2, 3>( 2); + R4<3, 4, 0, 1, 2>( 3); + R4<2, 3, 4, 0, 1>( 4); + R4<1, 2, 3, 4, 0>( 5); + R4<0, 1, 2, 3, 4>( 6); + R4<4, 0, 1, 2, 3>( 7); + R4<3, 4, 0, 1, 2>( 8); + R4<2, 3, 4, 0, 1>( 9); + R4<1, 2, 3, 4, 0>(10); + R4<0, 1, 2, 3, 4>(11); + R4<4, 0, 1, 2, 3>(12); + R4<3, 4, 0, 1, 2>(13); + R4<2, 3, 4, 0, 1>(14); + R4<1, 2, 3, 4, 0>(15); /* Add the working vars back into m_digest[] */ m_digest[0] += a; From 66e86b52b26f5a2f670cd2752ea07af5db585f7e Mon Sep 17 00:00:00 2001 From: volatilflerovium Date: Fri, 12 Dec 2025 17:22:30 +0000 Subject: [PATCH 5/6] Optimization --- CMakeLists.txt | 17 +- README.md | 21 +- performance.txt | 8 - performance/README.md | 20 ++ performance/performance.png | Bin 0 -> 62522 bytes performance/performance_1GB_file.png | Bin 0 -> 48750 bytes sha1.hpp | 519 ++++++++++++++++++--------- 7 files changed, 396 insertions(+), 189 deletions(-) delete mode 100644 performance.txt create mode 100644 performance/README.md create mode 100644 performance/performance.png create mode 100644 performance/performance_1GB_file.png diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a1e6d1..9fc7c18 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,6 +51,21 @@ target_link_libraries( #--------------------------------------------------------------------- +set(TEST demo_sha1) + +add_executable( + "${TEST}" + demo_sha1.cpp +) + +target_link_libraries( + "${TEST}" + PRIVATE + SHA1_lib +) + +#--------------------------------------------------------------------- + # Create a 1GB size file for testing performance if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") @@ -60,7 +75,7 @@ if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") OUTPUT_VARIABLE output ) - add_compile_definitions(TEST_FILE="/tmp/1G_test_file.txt") + #add_compile_definitions(TEST_FILE="/tmp/1G_test_file.txt") endif() #--------------------------------------------------------------------- diff --git a/README.md b/README.md index ea98ec8..c2a1ab8 100644 --- a/README.md +++ b/README.md @@ -2,17 +2,24 @@ ## Warning -Do not use SHA-1 unless you have to! -[SHA-1 is practically broken](https://en.wikipedia.org/wiki/SHA-1#Birthday-Near-Collision_Attack_%E2%80%93_first_practical_chosen-prefix_attack). Use a hash function from the [SHA-2](https://en.wikipedia.org/wiki/SHA-2) or [SHA-3](https://en.wikipedia.org/wiki/SHA-3) family instead. +Do not use SHA-1 unless you have to. [SHA-1 is practically broken](https://en.wikipedia.org/wiki/SHA-1#Birthday-Near-Collision_Attack_%E2%80%93_first_practical_chosen-prefix_attack). +Use a hash function from the [SHA-2](https://en.wikipedia.org/wiki/SHA-2) or [SHA-3](https://en.wikipedia.org/wiki/SHA-3) family instead. -## How to build tests +- Despite been proven that SHA-1 has collision attacks, SHA-1 is still extensively used, for example in +websocket protocol. -Using cmake +- For security related purposes one should use a combination of at least two hashes. For example in pseudocode: + - sha2(sha1(data)) not very good + - sha1(data)+sha2(data) better + - sha1(data.part1)+sha2(data.part2)+sha3(data) strong (where data=data.part1+data.part2) + + +## To build the tests ``` +cd tests mkdir build cd build -cmake .. -make +cmake .. ``` ## License @@ -26,4 +33,4 @@ make - [Volker Diels-Grabsch](https://njh.eu/) (Translation to simpler C++ Code) - [Eugene Hopkinson](https://riot.so/) (Safety improvements) - [Zlatko Michailov](http://zlatko.michailov.org) (Header-only library) -- [Dan Machado](dan-machado@yandex.com) (Refactoring) +- [Dan Machado](dan-machado@yandex.com) (Optimization) diff --git a/performance.txt b/performance.txt deleted file mode 100644 index cac5a0e..0000000 --- a/performance.txt +++ /dev/null @@ -1,8 +0,0 @@ -perf stat ./../bin/test_sha1 -0.011223803 seconds time elapsed - -perf stat ./../bin/test_sha1 --slow -6.446021922 seconds time elapsed - -perf stat ./../bin/test_sha1 /tmp/1G_test_file.txt -1.885112058 seconds time elapsed diff --git a/performance/README.md b/performance/README.md new file mode 100644 index 0000000..f45d77c --- /dev/null +++ b/performance/README.md @@ -0,0 +1,20 @@ +# Profiling + +Charts were generated using (time_profiler_visualizer)[https://github.com/volatilflerovium/time_profiler_visualizer] + +## Performance + +Performace tests run under Debian 11 with AMD Ryzen7 processor. + +For a file of 1GB + - SHA1 previous version: ~1870ms + - Linux command sha1sum: ~1471ms + - SHA1 optimized: ~1340ms (under Linux with mmap) and ~1530ms (for Linux and Windows using std::fread) + +On a 1 GB file (in seconds) + +[![performance-1GB-file.png](https://i.postimg.cc/GpPT4tPZ/performance-1GB-file.png)](https://postimg.cc/7C6ZcxWV) + +On incremental string (in microseconds) + +[![performance.png](https://i.postimg.cc/J4dN8sK0/performance.png)](https://postimg.cc/YhYGfq5B) diff --git a/performance/performance.png b/performance/performance.png new file mode 100644 index 0000000000000000000000000000000000000000..12296fb0bdb7fea4f50f057b3f5e7906e02b102a GIT binary patch literal 62522 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 literal 0 HcmV?d00001 diff --git a/performance/performance_1GB_file.png b/performance/performance_1GB_file.png new file mode 100644 index 0000000000000000000000000000000000000000..460c0366de7238a2c20e953028bc7eaff90bb153 GIT binary patch literal 48750 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 literal 0 HcmV?d00001 diff --git a/sha1.hpp b/sha1.hpp index 07c7822..e3354ca 100644 --- a/sha1.hpp +++ b/sha1.hpp @@ -17,112 +17,173 @@ -- Eugene Hopkinson Header-only library -- Zlatko Michailov - C++ refactoring + C++ optimization -- Dan Machado */ #ifndef SHA1_HPP #define SHA1_HPP - -#include -#include -#include -#include -#include #include +#include + +#ifdef _LINUX + #if __has_include() + // for mmap: + #define MMAN_HEADER + + #include + #include + #include + #endif + #include +#else + #include +#endif + +//==================================================================== class SHA1 { -public: - SHA1(); - /** - * Partially calculate the sha1sum of its argument. The calculation - * is completed by calling final(). Consecutive calls to update() - * will have the effect of calculating the sha1 sum of the concatenated - * string of its arguments. For example: - * SHA1 sha1; - * sha1.update(str1); - * sha1.update(str2); - * sha1.update(str3); - * sha1.final(); - * is the same than - * sha1.update(str1 + str2 + str3); - * sha1.final(); - * - * @param cstr null terminated string - * - * */ - void update(const std::string &s); - void update(std::istream &is); - - /** - * Complete the calculation of the sha1sum initiated by calls to - * SHA1::update. - * - * @return return the sha1sum of the string (or strings) - * fed by calls to SHA1::update. - * */ - std::string final(); - - /** - * Get the sha1sum of a file/ - * - * @return return the sha1sum of the content of the specified file. - * - * */ - static std::string from_file(const std::string &filename); - -private: - static const size_t BLOCK_INTS = 16; /* number of 32bit integers per SHA1 block */ - static const size_t BLOCK_BYTES = BLOCK_INTS * 4; - - uint32_t m_dataBlock[BLOCK_INTS]; - uint32_t m_digest[5]; - std::string m_buffer; - uint64_t transforms; - - void reset(); - template - uint32_t rol(uint32_t value); - - void blk(const size_t i); - /* - * R0, R1, R2, R3, R4 are the different operations used in SHA1 - */ - template - void R0(const size_t i); - - template - void R1(const size_t i); - - template - void R2(const size_t i); - - template - void R3(const size_t i); - - template - void R4(const size_t i); - - void transform(); - void buffer_to_block(); + public: + SHA1(); + + /** + * Partially calculate the sha1sum of its argument. The calculation + * is completed by calling final(). Consecutive calls to update() + * will have the effect of calculating the sha1 sum of the concatenated + * string of its arguments. For example: + * SHA1 sha1; + * sha1.update(str1); + * sha1.update(str2); + * sha1.update(str3); + * sha1.final(); + * is the same than + * sha1.update(str1 + str2 + str3); + * sha1.final(); + * which is the same as (using the function operator) + * sha1(str1 + str2 + str3) + * + * @param cstr null terminated string + * + * */ + void update(const char* cstr); + void update(const std::string& str); + void update(const std::string&& str); + + /** + * Complete the calculation of the sha1sum initiated by calls to + * SHA1::update. + * + * @return return the sha1sum of the string (or strings) + * fed by calls to SHA1::update. + * */ + std::string final(); + + static std::string from_file(const std::string&); + + /** + * Function call operator to calculate the sha1sum of + * a string. Return the sha1sum of the given string + * + * @param str a null terminated string or a std::string. + * + * @return sha1 sum of the str. + * + * */ + std::string operator()(const char* str); + std::string operator()(const std::string& str); + + /** + * Function call operator to calculate the sha1sum of a file. + * It returns false if an error occurs while opening the file and + * set the paramenter hashSum as the empty string, otherwise it return + * true and it sets the sha1sum of the file in the paramenter hashSum. + * + * @param fileName the name of a valid file + * + * @param[out] hashSum reference to a std::string where to copy the hashsum + * of the file. If a error occurred, it is set as the empty string + * + * @return true if the sha1sum was calculated, false if an error occurs + * while opening the file. + * */ + bool operator()(const char* fileName, std::string& hashSum); + bool operator()(const std::string& fileName, std::string& hashSum); + + /** + * Retrieve the description of the error that occurred when trying + * to get the sha1sum of a file. + * + * */ + std::string getError(); + + private: + static const size_t BLOCK_INTS = 16; /* number of 32bit integers per SHA1 block */ + static const size_t BLOCK_BYTES = BLOCK_INTS * 4; + static const size_t DIGEST_SIZE = 5; + + uint32_t m_dataBlock[BLOCK_INTS]; + uint8_t m_buffer[BLOCK_BYTES]; + uint32_t m_digestB[DIGEST_SIZE]; + uint32_t m_digest[DIGEST_SIZE]; + uint64_t m_transforms; + size_t m_dataSize; + int m_lastError; + const uint8_t* m_bufferPtr; + + void reset(); + + template + uint32_t rol(uint32_t value); + + void blk(size_t i); + + /* + * R0, R1, R2, R3, R4 are the different operations used in SHA1 + */ + template + void R0(const size_t i); + + template + void R1(const size_t i); + + template + void R2(const size_t i); + + template + void R3(const size_t i); + + template + void R4(const size_t i); + + void computeHash(const char* dataPtr, const size_t dataSize); + + std::string fileSha1Sum(const char* fileName); + + /* + * Hash a single 512-bit block. This is the core of the algorithm. + */ + void transform(); + void buffer_to_block(); }; +//==================================================================== inline void SHA1::reset() { /* SHA1 initialization constants */ - m_digest[0] = 0x67452301; - m_digest[1] = 0xefcdab89; - m_digest[2] = 0x98badcfe; - m_digest[3] = 0x10325476; - m_digest[4] = 0xc3d2e1f0; + m_digestB[0] = 0x67452301; + m_digestB[1] = 0xefcdab89; + m_digestB[2] = 0x98badcfe; + m_digestB[3] = 0x10325476; + m_digestB[4] = 0xc3d2e1f0; /* Reset counters */ - m_buffer = ""; - transforms = 0; + m_transforms = 0; + m_dataSize=0; + m_bufferPtr=m_buffer; } @@ -132,17 +193,11 @@ inline uint32_t SHA1::rol(uint32_t value) return (value << N) | (value >> M); } - -inline void SHA1::blk(const size_t i) +inline void SHA1::blk(size_t i) { m_dataBlock[i]=rol<1, 31>(m_dataBlock[(i+13)&15] ^ m_dataBlock[(i+8)&15] ^ m_dataBlock[(i+2)&15] ^ m_dataBlock[i]); } - -/* - * (R0+R1), R2, R3, R4 are the different operations used in SHA1 - */ - template inline void SHA1::R0(const size_t i) { @@ -182,18 +237,9 @@ inline void SHA1::R4(const size_t i) } -/* - * Hash a single 512-bit block. This is the core of the algorithm. - */ - inline void SHA1::transform() { - /* Copy m_digest[] to working vars */ - uint32_t a = m_digest[0]; - uint32_t b = m_digest[1]; - uint32_t c = m_digest[2]; - uint32_t d = m_digest[3]; - uint32_t e = m_digest[4]; + std::memcpy(m_digest, m_digestB, DIGEST_SIZE*sizeof(uint32_t)); /* 4 rounds of 20 operations each. Loop unrolled. */ R0<0, 1, 2, 3, 4>( 0); @@ -277,119 +323,246 @@ inline void SHA1::transform() R4<2, 3, 4, 0, 1>(14); R4<1, 2, 3, 4, 0>(15); - /* Add the working vars back into m_digest[] */ - m_digest[0] += a; - m_digest[1] += b; - m_digest[2] += c; - m_digest[3] += d; - m_digest[4] += e; + /* Add the working vars back into m_digestB[] */ + m_digestB[0]=m_digestB[0]+m_digest[0]; + m_digestB[1]=m_digestB[1]+m_digest[1]; + m_digestB[2]=m_digestB[2]+m_digest[2]; + m_digestB[3]=m_digestB[3]+m_digest[3]; + m_digestB[4]=m_digestB[4]+m_digest[4]; /* Count the number of transformations */ - transforms++; + m_transforms++; } inline void SHA1::buffer_to_block() { - /* Convert the std::string (byte m_buffer) to a uint32_t array (MSB) */ - for (size_t i = 0; i < BLOCK_INTS; i++) - { - m_dataBlock[i] = (m_buffer[4*i+3] & 0xff) - | (m_buffer[4*i+2] & 0xff)<<8 - | (m_buffer[4*i+1] & 0xff)<<16 - | (m_buffer[4*i+0] & 0xff)<<24; - } + m_dataBlock[0] = m_bufferPtr[3] | m_bufferPtr[2]<<8 | m_bufferPtr[1]<<16 | m_bufferPtr[0]<<24; + m_dataBlock[1] = m_bufferPtr[7] | m_bufferPtr[6]<<8 | m_bufferPtr[5]<<16 | m_bufferPtr[4]<<24; + m_dataBlock[2] = m_bufferPtr[11] | m_bufferPtr[10]<<8 | m_bufferPtr[9]<<16 | m_bufferPtr[8]<<24; + m_dataBlock[3] = m_bufferPtr[15] | m_bufferPtr[14]<<8 | m_bufferPtr[13]<<16 | m_bufferPtr[12]<<24; + m_dataBlock[4] = m_bufferPtr[19] | m_bufferPtr[18]<<8 | m_bufferPtr[17]<<16 | m_bufferPtr[16]<<24; + m_dataBlock[5] = m_bufferPtr[23] | m_bufferPtr[22]<<8 | m_bufferPtr[21]<<16 | m_bufferPtr[20]<<24; + m_dataBlock[6] = m_bufferPtr[27] | m_bufferPtr[26]<<8 | m_bufferPtr[25]<<16 | m_bufferPtr[24]<<24; + m_dataBlock[7] = m_bufferPtr[31] | m_bufferPtr[30]<<8 | m_bufferPtr[29]<<16 | m_bufferPtr[28]<<24; + m_dataBlock[8] = m_bufferPtr[35] | m_bufferPtr[34]<<8 | m_bufferPtr[33]<<16 | m_bufferPtr[32]<<24; + m_dataBlock[9] = m_bufferPtr[39] | m_bufferPtr[38]<<8 | m_bufferPtr[37]<<16 | m_bufferPtr[36]<<24; + m_dataBlock[10] = m_bufferPtr[43] | m_bufferPtr[42]<<8 | m_bufferPtr[41]<<16 | m_bufferPtr[40]<<24; + m_dataBlock[11] = m_bufferPtr[47] | m_bufferPtr[46]<<8 | m_bufferPtr[45]<<16 | m_bufferPtr[44]<<24; + m_dataBlock[12] = m_bufferPtr[51] | m_bufferPtr[50]<<8 | m_bufferPtr[49]<<16 | m_bufferPtr[48]<<24; + m_dataBlock[13] = m_bufferPtr[55] | m_bufferPtr[54]<<8 | m_bufferPtr[53]<<16 | m_bufferPtr[52]<<24; + m_dataBlock[14] = m_bufferPtr[59] | m_bufferPtr[58]<<8 | m_bufferPtr[57]<<16 | m_bufferPtr[56]<<24; + m_dataBlock[15] = m_bufferPtr[63] | m_bufferPtr[62]<<8 | m_bufferPtr[61]<<16 | m_bufferPtr[60]<<24; } - inline SHA1::SHA1() { reset(); } - -inline void SHA1::update(const std::string &s) +inline void SHA1::update(const char* cstr) { - std::istringstream is(s); - update(is); + computeHash(cstr, std::strlen(cstr)); } - -inline void SHA1::update(std::istream &is) +inline void SHA1::update(const std::string& str) { - while (true) - { - char sbuf[BLOCK_BYTES]; - is.read(sbuf, BLOCK_BYTES - m_buffer.size()); - m_buffer.append(sbuf, (std::size_t)is.gcount()); - if (m_buffer.size() != BLOCK_BYTES) - { - return; - } - //uint32_t m_dataBlock[BLOCK_INTS]; - buffer_to_block(); - transform(); - m_buffer.clear(); - } + computeHash(str.c_str(), str.length()); } +inline void SHA1::update(const std::string&& str) +{ + computeHash(str.c_str(), str.length()); +} /* - * Add padding and return the message m_digest. + * Add padding and return the message m_digestB. */ - inline std::string SHA1::final() { /* Total number of hashed bits */ - uint64_t total_bits = (transforms*BLOCK_BYTES + m_buffer.size()) * 8; - - /* Padding */ - m_buffer += (char)0x80; - size_t orig_size = m_buffer.size(); - while (m_buffer.size() < BLOCK_BYTES) - { - m_buffer += (char)0x00; - } + uint64_t total_bits = (m_transforms*BLOCK_BYTES + m_dataSize) * 8; - //uint32_t m_dataBlock[BLOCK_INTS]; - buffer_to_block(); + if(m_dataSize(0x80); + buffer_to_block(); + } - if (orig_size > BLOCK_BYTES - 8) - { + if (m_dataSize+1 > BLOCK_BYTES - 8){ transform(); - for (size_t i = 0; i < BLOCK_INTS - 2; i++) - { - m_dataBlock[i] = 0; - } + std::memset(m_dataBlock, 0, sizeof(uint32_t)*(BLOCK_INTS - 2)); } /* Append total_bits, split this uint64_t into two uint32_t */ - m_dataBlock[BLOCK_INTS - 1] = (uint32_t)total_bits; - m_dataBlock[BLOCK_INTS - 2] = (uint32_t)(total_bits >> 32); + m_dataBlock[BLOCK_INTS - 1] = static_cast(total_bits); + m_dataBlock[BLOCK_INTS - 2] = static_cast(total_bits >> 32); transform(); /* Hex std::string */ - std::ostringstream result; - for (size_t i = 0; i < sizeof(m_digest) / sizeof(m_digest[0]); i++) - { - result << std::hex << std::setfill('0') << std::setw(8); - result << m_digest[i]; + char resultBuffer[41]; + for(size_t i = 0; i < DIGEST_SIZE; i++){ + sprintf(resultBuffer+8*i, "%08x", m_digestB[i]); } + reset(); + return resultBuffer; +} + +inline std::string SHA1::from_file(const std::string& filename) +{ + std::string hash; + SHA1 sha1; + sha1(filename, hash); + return hash; +} - /* Reset for next run */ +inline std::string SHA1::operator()(const char* cstr) +{ reset(); + computeHash(cstr, std::strlen(cstr)); + return final(); +} - return result.str(); +inline std::string SHA1::operator()(const std::string& str) +{ + reset(); + computeHash(str.c_str(), str.length()); + return final(); } -inline std::string SHA1::from_file(const std::string &filename) +inline bool SHA1::operator()(const char* fileName, std::string& hashSum) +{ + reset(); + hashSum=fileSha1Sum(fileName); + return m_lastError==0; +} + +inline bool SHA1::operator()(const std::string& fileName, std::string& hashSum) { - std::ifstream stream(filename.c_str(), std::ios::binary); - SHA1 checksum; - checksum.update(stream); - return checksum.final(); + return operator()(fileName.c_str(), hashSum); } +inline std::string SHA1::getError() +{ + return std::strerror(m_lastError); +} + + +inline void SHA1::computeHash(const char* dataPtr, size_t dataSize) +{ + size_t idx=0; + size_t newDataSize=dataSize; + size_t copyB=BLOCK_BYTES-m_dataSize; + + auto update=[&idx, ©B, &newDataSize, this](){ + idx=idx+copyB; + newDataSize=newDataSize-copyB; + m_dataSize=m_dataSize+copyB; + }; + + while(true){ + if(copyB>newDataSize){ + copyB=newDataSize; + m_bufferPtr=m_buffer; + std::memcpy(m_buffer+m_dataSize, dataPtr+idx, copyB); + + if(m_dataSize!=BLOCK_BYTES){ + update(); + break; + } + } + else{//copyB==BLOCK_BYTES + m_bufferPtr=reinterpret_cast(dataPtr+idx); + } + update(); + buffer_to_block(); + transform(); + + copyB=BLOCK_BYTES; + m_dataSize=0; + } +} + +#ifdef MMAN_HEADER + +inline std::string SHA1::fileSha1Sum(const char* fileName) +{ + m_lastError=0; + + if(access(fileName, F_OK)<0){ + m_lastError=errno; + return ""; + } + if(access(fileName, R_OK)<0){ + m_lastError=errno; + return ""; + } + + int fd= open(fileName, O_RDONLY); + struct stat sb; + if(fstat(fd, &sb) == -1) { + close(fd); + m_lastError=errno; + return ""; + } + + if(sb.st_size>0){ + void* data=mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if(data==MAP_FAILED){ + close(fd); + m_lastError=errno; + return ""; + } + else{ + computeHash(static_cast(data), sb.st_size); + } + munmap(data, sb.st_size); + } + close(fd); + return final(); +} + +#else // for Windows or Linux + +inline std::string SHA1::fileSha1Sum(const char* fileName) +{ + m_lastError=0; + + if(access(fileName, F_OK)<0){ + m_lastError=errno; + return ""; + } + + if(access(fileName, R_OK)<0){ + m_lastError=errno; + return ""; + } + + std::FILE* fd = std::fopen(fileName, "r"); + if(fd==nullptr){ + m_lastError=errno; + return ""; + } + + const int p=3; + char buffer[p*BLOCK_BYTES]; + + while(true){ + auto dataSize = std::fread(buffer, 1, p*BLOCK_BYTES, fd); + if(dataSize==0){ + break; + } + computeHash(buffer, dataSize); + } + + std::fclose(fd); + return final(); +} + +#endif + +//==================================================================== + #endif /* SHA1_HPP */ From 49a71d108ca680155218a50627f184833b44b977 Mon Sep 17 00:00:00 2001 From: volatilflerovium Date: Fri, 12 Dec 2025 17:43:26 +0000 Subject: [PATCH 6/6] Add performance test --- CMakeLists.txt | 14 ------ performance/CMakeLists.txt | 88 +++++++++++++++++++++++++++++++++++ performance/big_file_test.cpp | 47 +++++++++++++++++++ 3 files changed, 135 insertions(+), 14 deletions(-) create mode 100644 performance/CMakeLists.txt create mode 100644 performance/big_file_test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 9fc7c18..52a474f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,17 +65,3 @@ target_link_libraries( ) #--------------------------------------------------------------------- - -# Create a 1GB size file for testing performance - -if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") - message("Creating a 1GB file of random data in /tmp directory...") - execute_process(COMMAND dd if=/dev/urandom of=/tmp/1G_test_file.txt bs=1G count=1 iflag=fullblock - ERROR_VARIABLE error - OUTPUT_VARIABLE output - ) - - #add_compile_definitions(TEST_FILE="/tmp/1G_test_file.txt") -endif() - -#--------------------------------------------------------------------- diff --git a/performance/CMakeLists.txt b/performance/CMakeLists.txt new file mode 100644 index 0000000..e418581 --- /dev/null +++ b/performance/CMakeLists.txt @@ -0,0 +1,88 @@ +cmake_minimum_required(VERSION 3.18) +project("SHA1_tests") + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_FLAGS "-Wall") +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +set(EXECUTABLE_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/bin) + +get_filename_component(PARENT_DIR ../ ABSOLUTE) + + +if(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + add_definitions(-D_WIN32) + add_compile_options("/O2") +else() + add_definitions(-D_LINUX) + if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + message(WARNING "Assuming system in Linux") + endif() + add_compile_options("-O3") + add_compile_options("-g") +endif() + +#--------------------------------------------------------------------- + +include(FetchContent) + +FetchContent_Declare(TimeProfiler + GIT_REPOSITORY "https://github.com/volatilflerovium/time_profiler_visualizer" + GIT_TAG "origin/main" +) + +FetchContent_MakeAvailable(TimeProfiler) + +#--------------------------------------------------------------------- + +add_library(SHA1_lib INTERFACE) + +target_include_directories( + SHA1_lib + INTERFACE + "${CMAKE_SOURCE_DIR}/.." +) + +#--------------------------------------------------------------------- + +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + + execute_process(COMMAND ls /tmp/1G_sha1_test_file.txt + ERROR_VARIABLE error + OUTPUT_VARIABLE output + ) + + if(NOT ("${error}" STREQUAL "")) + message("Creating a 1G file of random data in /tmp directory...") + execute_process(COMMAND dd if=/dev/urandom of=/tmp/1G_sha1_test_file.txt bs=1G count=1 iflag=fullblock + ERROR_VARIABLE error + OUTPUT_VARIABLE output + ) + endif() + add_compile_definitions(TEST_FILE="/tmp/1G_sha1_test_file.txt") +endif() + +#--------------------------------------------------------------------- + +set(TEST "big_file_test") + +add_executable( + "${TEST}" + big_file_test.cpp +) + +target_link_libraries( + "${TEST}" + PRIVATE + TimeProfiler + SHA1_lib +) + +target_compile_definitions( + "${TEST}" + PRIVATE + -DENABLE_STOPWATCH +) + +#--------------------------------------------------------------------- diff --git a/performance/big_file_test.cpp b/performance/big_file_test.cpp new file mode 100644 index 0000000..9967e58 --- /dev/null +++ b/performance/big_file_test.cpp @@ -0,0 +1,47 @@ +#include + +#include "sha1.hpp" + +#include "time_profiler/time_profiler.h" + +using Profiler=tprofiler::TimeProfiler; + +//==================================================================== + +int main() +{ + const char* testFile=""; + #ifndef TEST_FILE + std::cout<