Skip to content

Instantly share code, notes, and snippets.

@jhasse
Created October 7, 2023 00:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jhasse/5080a35bfd36a481fba5a8b34e9b563b to your computer and use it in GitHub Desktop.
Save jhasse/5080a35bfd36a481fba5a8b34e9b563b to your computer and use it in GitHub Desktop.
Standalone SHA1 implementation for C++
// Copyright 2007 Andy Tompkins
// Copyright 2023 Jan Niklas Hasse
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
#pragma once
#include <iomanip>
#include <iostream>
#include <sstream>
namespace boost {
namespace uuids {
namespace detail {
inline unsigned int left_rotate(unsigned int x, std::size_t n) {
return (x << n) ^ (x >> (32 - n));
}
class sha1 {
public:
typedef unsigned int(digest_type)[5];
public:
sha1();
void reset();
void process_byte(unsigned char byte);
void process_block(void const* bytes_begin, void const* bytes_end);
void process_bytes(void const* buffer, std::size_t byte_count);
void get_digest(digest_type& digest);
private:
void process_block();
void process_byte_impl(unsigned char byte);
private:
unsigned int h_[5];
unsigned char block_[64];
std::size_t block_byte_index_;
std::size_t bit_count_low;
std::size_t bit_count_high;
};
inline sha1::sha1() {
reset();
}
inline void sha1::reset() {
h_[0] = 0x67452301;
h_[1] = 0xEFCDAB89;
h_[2] = 0x98BADCFE;
h_[3] = 0x10325476;
h_[4] = 0xC3D2E1F0;
block_byte_index_ = 0;
bit_count_low = 0;
bit_count_high = 0;
}
inline void sha1::process_byte(unsigned char byte) {
process_byte_impl(byte);
// size_t max value = 0xFFFFFFFF
// if (bit_count_low + 8 >= 0x100000000) { // would overflow
// if (bit_count_low >= 0x100000000-8) {
if (bit_count_low < 0xFFFFFFF8) {
bit_count_low += 8;
} else {
bit_count_low = 0;
if (bit_count_high <= 0xFFFFFFFE) {
++bit_count_high;
} else {
throw std::runtime_error("sha1 too many bytes");
}
}
}
inline void sha1::process_byte_impl(unsigned char byte) {
block_[block_byte_index_++] = byte;
if (block_byte_index_ == 64) {
block_byte_index_ = 0;
process_block();
}
}
inline void sha1::process_block(void const* bytes_begin, void const* bytes_end) {
unsigned char const* begin = static_cast<unsigned char const*>(bytes_begin);
unsigned char const* end = static_cast<unsigned char const*>(bytes_end);
for (; begin != end; ++begin) {
process_byte(*begin);
}
}
inline void sha1::process_bytes(void const* buffer, std::size_t byte_count) {
unsigned char const* b = static_cast<unsigned char const*>(buffer);
process_block(b, b + byte_count);
}
inline void sha1::process_block() {
unsigned int w[80];
for (std::size_t i = 0; i < 16; ++i) {
w[i] = (block_[i * 4 + 0] << 24);
w[i] |= (block_[i * 4 + 1] << 16);
w[i] |= (block_[i * 4 + 2] << 8);
w[i] |= (block_[i * 4 + 3]);
}
for (std::size_t i = 16; i < 80; ++i) {
w[i] = left_rotate((w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]), 1);
}
unsigned int a = h_[0];
unsigned int b = h_[1];
unsigned int c = h_[2];
unsigned int d = h_[3];
unsigned int e = h_[4];
for (std::size_t i = 0; i < 80; ++i) {
unsigned int f;
unsigned int k;
if (i < 20) {
f = (b & c) | (~b & d);
k = 0x5A827999;
} else if (i < 40) {
f = b ^ c ^ d;
k = 0x6ED9EBA1;
} else if (i < 60) {
f = (b & c) | (b & d) | (c & d);
k = 0x8F1BBCDC;
} else {
f = b ^ c ^ d;
k = 0xCA62C1D6;
}
unsigned temp = left_rotate(a, 5) + f + e + k + w[i];
e = d;
d = c;
c = left_rotate(b, 30);
b = a;
a = temp;
}
h_[0] += a;
h_[1] += b;
h_[2] += c;
h_[3] += d;
h_[4] += e;
}
inline void sha1::get_digest(digest_type& digest) {
// append the bit '1' to the message
process_byte_impl(0x80);
// append k bits '0', where k is the minimum number >= 0
// such that the resulting message length is congruent to 56 (mod 64)
// check if there is enough space for padding and bit_count
if (block_byte_index_ > 56) {
// finish this block
while (block_byte_index_ != 0) {
process_byte_impl(0);
}
// one more block
while (block_byte_index_ < 56) {
process_byte_impl(0);
}
} else {
while (block_byte_index_ < 56) {
process_byte_impl(0);
}
}
// append length of message (before pre-processing)
// as a 64-bit big-endian integer
process_byte_impl(static_cast<unsigned char>((bit_count_high >> 24) & 0xFF));
process_byte_impl(static_cast<unsigned char>((bit_count_high >> 16) & 0xFF));
process_byte_impl(static_cast<unsigned char>((bit_count_high >> 8) & 0xFF));
process_byte_impl(static_cast<unsigned char>((bit_count_high) & 0xFF));
process_byte_impl(static_cast<unsigned char>((bit_count_low >> 24) & 0xFF));
process_byte_impl(static_cast<unsigned char>((bit_count_low >> 16) & 0xFF));
process_byte_impl(static_cast<unsigned char>((bit_count_low >> 8) & 0xFF));
process_byte_impl(static_cast<unsigned char>((bit_count_low) & 0xFF));
// get final digest
digest[0] = h_[0];
digest[1] = h_[1];
digest[2] = h_[2];
digest[3] = h_[3];
digest[4] = h_[4];
}
} // namespace detail
} // namespace uuids
} // namespace boost
inline std::string sha1(const std::string& input) {
boost::uuids::detail::sha1 sha1;
sha1.process_bytes(input.data(), input.size());
unsigned int digest[5];
sha1.get_digest(digest);
std::ostringstream hash;
hash << std::hex << std::setfill('0');
for (size_t i = 0; i < 5; ++i) {
// Swap byte order because of Little Endian
const char* tmp = reinterpret_cast<char*>(digest);
hash << std::setw(2) << static_cast<int>(static_cast<uint8_t>(tmp[i * 4 + 3]))
<< std::setw(2) << static_cast<int>(static_cast<uint8_t>(tmp[i * 4 + 2]))
<< std::setw(2) << static_cast<int>(static_cast<uint8_t>(tmp[i * 4 + 1]))
<< std::setw(2) << static_cast<int>(static_cast<uint8_t>(tmp[i * 4]));
}
return hash.str();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment