Add the MD4 message digest algorithm draft implementation
This commit is contained in:
17
CMakeLists.txt
Normal file
17
CMakeLists.txt
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.20)
|
||||||
|
|
||||||
|
project(
|
||||||
|
Chaos
|
||||||
|
LANGUAGES CXX
|
||||||
|
)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
add_library(Chaos INTERFACE)
|
||||||
|
|
||||||
|
target_include_directories(Chaos INTERFACE
|
||||||
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/Chaos>
|
||||||
|
)
|
||||||
|
|
||||||
|
add_subdirectory(ChaosTests)
|
||||||
277
Chaos/Md4.hpp
Normal file
277
Chaos/Md4.hpp
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
#ifndef CHAOS_MD4HASHER_HPP
|
||||||
|
#define CHAOS_MD4HASHER_HPP
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
namespace Chaos::Md4
|
||||||
|
{
|
||||||
|
|
||||||
|
struct Buffer
|
||||||
|
{
|
||||||
|
uint32_t Regs_[4] = { 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 };
|
||||||
|
};
|
||||||
|
|
||||||
|
using Block = std::array<uint32_t, 16>;
|
||||||
|
|
||||||
|
struct Algorithm
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static void UpdateBuffer(Buffer & buffer, const Block & block)
|
||||||
|
{
|
||||||
|
uint32_t a = buffer.Regs_[0];
|
||||||
|
uint32_t b = buffer.Regs_[1];
|
||||||
|
uint32_t c = buffer.Regs_[2];
|
||||||
|
uint32_t d = buffer.Regs_[3];
|
||||||
|
|
||||||
|
FF(a, b, c, d, block[ 0], 3);
|
||||||
|
FF(d, a, b, c, block[ 1], 7);
|
||||||
|
FF(c, d, a, b, block[ 2], 11);
|
||||||
|
FF(b, c, d, a, block[ 3], 19);
|
||||||
|
FF(a, b, c, d, block[ 4], 3);
|
||||||
|
FF(d, a, b, c, block[ 5], 7);
|
||||||
|
FF(c, d, a, b, block[ 6], 11);
|
||||||
|
FF(b, c, d, a, block[ 7], 19);
|
||||||
|
FF(a, b, c, d, block[ 8], 3);
|
||||||
|
FF(d, a, b, c, block[ 9], 7);
|
||||||
|
FF(c, d, a, b, block[10], 11);
|
||||||
|
FF(b, c, d, a, block[11], 19);
|
||||||
|
FF(a, b, c, d, block[12], 3);
|
||||||
|
FF(d, a, b, c, block[13], 7);
|
||||||
|
FF(c, d, a, b, block[14], 11);
|
||||||
|
FF(b, c, d, a, block[15], 19);
|
||||||
|
|
||||||
|
GG(a, b, c, d, block[ 0], 3);
|
||||||
|
GG(d, a, b, c, block[ 4], 5);
|
||||||
|
GG(c, d, a, b, block[ 8], 9);
|
||||||
|
GG(b, c, d, a, block[12], 13);
|
||||||
|
GG(a, b, c, d, block[ 1], 3);
|
||||||
|
GG(d, a, b, c, block[ 5], 5);
|
||||||
|
GG(c, d, a, b, block[ 9], 9);
|
||||||
|
GG(b, c, d, a, block[13], 13);
|
||||||
|
GG(a, b, c, d, block[ 2], 3);
|
||||||
|
GG(d, a, b, c, block[ 6], 5);
|
||||||
|
GG(c, d, a, b, block[10], 9);
|
||||||
|
GG(b, c, d, a, block[14], 13);
|
||||||
|
GG(a, b, c, d, block[ 3], 3);
|
||||||
|
GG(d, a, b, c, block[ 7], 5);
|
||||||
|
GG(c, d, a, b, block[11], 9);
|
||||||
|
GG(b, c, d, a, block[15], 13);
|
||||||
|
|
||||||
|
HH(a, b, c, d, block[ 0], 3);
|
||||||
|
HH(d, a, b, c, block[ 8], 9);
|
||||||
|
HH(c, d, a, b, block[ 4], 11);
|
||||||
|
HH(b, c, d, a, block[12], 15);
|
||||||
|
HH(a, b, c, d, block[ 2], 3);
|
||||||
|
HH(d, a, b, c, block[10], 9);
|
||||||
|
HH(c, d, a, b, block[ 6], 11);
|
||||||
|
HH(b, c, d, a, block[14], 15);
|
||||||
|
HH(a, b, c, d, block[ 1], 3);
|
||||||
|
HH(d, a, b, c, block[ 9], 9);
|
||||||
|
HH(c, d, a, b, block[ 5], 11);
|
||||||
|
HH(b, c, d, a, block[13], 15);
|
||||||
|
HH(a, b, c, d, block[ 3], 3);
|
||||||
|
HH(d, a, b, c, block[11], 9);
|
||||||
|
HH(c, d, a, b, block[ 7], 11);
|
||||||
|
HH(b, c, d, a, block[15], 15);
|
||||||
|
|
||||||
|
buffer.Regs_[0] += a;
|
||||||
|
buffer.Regs_[1] += b;
|
||||||
|
buffer.Regs_[2] += c;
|
||||||
|
buffer.Regs_[3] += d;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static uint32_t Rotl(uint32_t v, int_fast8_t s)
|
||||||
|
{
|
||||||
|
return (v << s) | (v >> (32 - s));
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t F(uint32_t x, uint32_t y, uint32_t z)
|
||||||
|
{
|
||||||
|
return (x & y) | ((~x) & z);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t G(uint32_t x, uint32_t y, uint32_t z)
|
||||||
|
{
|
||||||
|
return (x & y) | (x & z) | (y & z);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t H(uint32_t x, uint32_t y, uint32_t z)
|
||||||
|
{
|
||||||
|
return x ^ y ^ z;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void FF(uint32_t & a, uint32_t b, uint32_t c, uint32_t d,
|
||||||
|
uint32_t x, int_fast8_t s)
|
||||||
|
{
|
||||||
|
a = Rotl(a + F(b, c, d) + x, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void GG(uint32_t & a, uint32_t b, uint32_t c, uint32_t d,
|
||||||
|
uint32_t x, int_fast8_t s)
|
||||||
|
{
|
||||||
|
constexpr uint32_t C2 = 0x5a827999;
|
||||||
|
|
||||||
|
a = Rotl(a + G(b, c, d) + x + C2, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void HH(uint32_t & a, uint32_t b, uint32_t c, uint32_t d,
|
||||||
|
uint32_t x, int_fast8_t s)
|
||||||
|
{
|
||||||
|
constexpr uint32_t C3 = 0x6ed9eba1;
|
||||||
|
|
||||||
|
a = Rotl(a + H(b, c, d) + x + C3, s);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Hash
|
||||||
|
{
|
||||||
|
std::string ToHexString() const
|
||||||
|
{
|
||||||
|
char buf[33];
|
||||||
|
|
||||||
|
std::sprintf(buf,
|
||||||
|
"%02x%02x%02x%02x%02x%02x%02x%02x"
|
||||||
|
"%02x%02x%02x%02x%02x%02x%02x%02x",
|
||||||
|
RawDigest[ 0], RawDigest[ 1], RawDigest[ 2], RawDigest[ 3],
|
||||||
|
RawDigest[ 4], RawDigest[ 5], RawDigest[ 6], RawDigest[ 7],
|
||||||
|
RawDigest[ 8], RawDigest[ 9], RawDigest[10], RawDigest[11],
|
||||||
|
RawDigest[12], RawDigest[13], RawDigest[14], RawDigest[15]);
|
||||||
|
|
||||||
|
return std::string(buf, buf + 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<uint8_t, 16> RawDigest;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Hasher
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Hasher()
|
||||||
|
: BlockSize_(0)
|
||||||
|
, Word_(0)
|
||||||
|
, WordBytesPacked_(0)
|
||||||
|
, MessageSizeBytes_(0)
|
||||||
|
{
|
||||||
|
Block_.fill(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename InputIt>
|
||||||
|
void Update(InputIt begin, InputIt end)
|
||||||
|
{
|
||||||
|
MessageSizeBytes_ += UpdateImpl(begin, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
Hash Finish()
|
||||||
|
{
|
||||||
|
uint64_t messageSizeBytesMod64 = MessageSizeBytes_ % 64;
|
||||||
|
|
||||||
|
int_fast8_t paddingNeededBytes;
|
||||||
|
|
||||||
|
if (messageSizeBytesMod64 < 56)
|
||||||
|
{
|
||||||
|
paddingNeededBytes = 56 - messageSizeBytesMod64;
|
||||||
|
}
|
||||||
|
else if (messageSizeBytesMod64 > 56)
|
||||||
|
{
|
||||||
|
paddingNeededBytes = 120 - messageSizeBytesMod64;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
paddingNeededBytes = 64;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateImpl(PAD_, PAD_ + paddingNeededBytes);
|
||||||
|
|
||||||
|
// TODO: does not comply with the standard:
|
||||||
|
// "In the unlikely event that b is greater than pow(2, 64),
|
||||||
|
// then only the low-order 64 bits of b are used."
|
||||||
|
const uint64_t messageSizeBits = MessageSizeBytes_ * 8;
|
||||||
|
|
||||||
|
uint8_t extracted[] =
|
||||||
|
{
|
||||||
|
static_cast<uint8_t>((messageSizeBits >> 0) & 0xFF),
|
||||||
|
static_cast<uint8_t>((messageSizeBits >> 8) & 0xFF),
|
||||||
|
static_cast<uint8_t>((messageSizeBits >> 16) & 0xFF),
|
||||||
|
static_cast<uint8_t>((messageSizeBits >> 24) & 0xFF),
|
||||||
|
static_cast<uint8_t>((messageSizeBits >> 32) & 0xFF),
|
||||||
|
static_cast<uint8_t>((messageSizeBits >> 40) & 0xFF),
|
||||||
|
static_cast<uint8_t>((messageSizeBits >> 48) & 0xFF),
|
||||||
|
static_cast<uint8_t>((messageSizeBits >> 56) & 0xFF),
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(std::size(extracted) == 8);
|
||||||
|
|
||||||
|
UpdateImpl(extracted, extracted + std::size(extracted));
|
||||||
|
|
||||||
|
Hash result;
|
||||||
|
|
||||||
|
int_fast8_t i = 0;
|
||||||
|
for (int_fast8_t reg = 0; reg < 4; ++reg)
|
||||||
|
{
|
||||||
|
for (int_fast8_t shift = 0; shift < 32; shift += 8)
|
||||||
|
{
|
||||||
|
result.RawDigest[i++] = (Buffer_.Regs_[reg] >> shift) & 0xFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr uint8_t PAD_[] =
|
||||||
|
{
|
||||||
|
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(std::size(PAD_) == 64);
|
||||||
|
|
||||||
|
Buffer Buffer_;
|
||||||
|
|
||||||
|
Block Block_;
|
||||||
|
int_fast8_t BlockSize_;
|
||||||
|
|
||||||
|
uint32_t Word_;
|
||||||
|
int_fast8_t WordBytesPacked_;
|
||||||
|
|
||||||
|
uint64_t MessageSizeBytes_;
|
||||||
|
|
||||||
|
template<typename InputIt>
|
||||||
|
uint64_t UpdateImpl(InputIt begin, InputIt end)
|
||||||
|
{
|
||||||
|
uint64_t written = 0;
|
||||||
|
|
||||||
|
for (InputIt it = begin; it != end; ++it, ++written)
|
||||||
|
{
|
||||||
|
Word_ |= (static_cast<uint32_t>(*it) << (WordBytesPacked_ * 8));
|
||||||
|
++WordBytesPacked_;
|
||||||
|
|
||||||
|
if (WordBytesPacked_ == 4)
|
||||||
|
{
|
||||||
|
Block_[BlockSize_++] = Word_;
|
||||||
|
WordBytesPacked_ = 0;
|
||||||
|
Word_ = 0;
|
||||||
|
|
||||||
|
if (BlockSize_ == 16)
|
||||||
|
{
|
||||||
|
Algorithm::UpdateBuffer(Buffer_, Block_);
|
||||||
|
BlockSize_ = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return written;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Chaos
|
||||||
|
|
||||||
|
#endif
|
||||||
20
ChaosTests/CMakeLists.txt
Normal file
20
ChaosTests/CMakeLists.txt
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
include(FetchContent)
|
||||||
|
|
||||||
|
FetchContent_Declare(
|
||||||
|
googletest
|
||||||
|
GIT_REPOSITORY https://github.com/google/googletest.git
|
||||||
|
GIT_TAG v1.17.0
|
||||||
|
)
|
||||||
|
|
||||||
|
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
||||||
|
FetchContent_MakeAvailable(googletest)
|
||||||
|
|
||||||
|
add_executable(ChaosTests Md4HasherTests.cpp)
|
||||||
|
target_link_libraries(ChaosTests gtest gtest_main)
|
||||||
|
target_include_directories(ChaosTests PRIVATE
|
||||||
|
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/Chaos>
|
||||||
|
)
|
||||||
|
|
||||||
|
include(GoogleTest)
|
||||||
|
|
||||||
|
gtest_discover_tests(ChaosTests)
|
||||||
177
ChaosTests/MD4HasherTests.cpp
Normal file
177
ChaosTests/MD4HasherTests.cpp
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include "Md4.hpp"
|
||||||
|
|
||||||
|
using namespace Chaos;
|
||||||
|
|
||||||
|
TEST(Md4Tests, RFCTest)
|
||||||
|
{
|
||||||
|
struct Helper
|
||||||
|
{
|
||||||
|
std::string operator()(const char * in) const
|
||||||
|
{
|
||||||
|
Md4::Hasher hasher;
|
||||||
|
hasher.Update(in, in + strlen(in));
|
||||||
|
return hasher.Finish().ToHexString();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Helper hash;
|
||||||
|
|
||||||
|
ASSERT_EQ("31d6cfe0d16ae931b73c59d7e0c089c0", hash(""));
|
||||||
|
ASSERT_EQ("bde52cb31de33e46245e05fbdbd6fb24", hash("a"));
|
||||||
|
ASSERT_EQ("a448017aaf21d8525fc10ae87aa6729d", hash("abc"));
|
||||||
|
ASSERT_EQ("d9130a8164549fe818874806e1c7014b", hash("message digest"));
|
||||||
|
ASSERT_EQ("d79e1c308aa5bbcdeea8ed63df412da9", hash("abcdefghijklmnopqrstuvwxyz"));
|
||||||
|
ASSERT_EQ("043f8582f241db351ce627e153e7f0e4", hash("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"));
|
||||||
|
ASSERT_EQ("e33b4ddc9c38f2199c3e7b164fcc0536", hash("12345678901234567890123456789012345678901234567890123456789012345678901234567890"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Md4Tests, PartialUpdateTest)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
// "a"
|
||||||
|
Md4::Hasher hasher;
|
||||||
|
|
||||||
|
{
|
||||||
|
const char * in = "a";
|
||||||
|
hasher.Update(in, in + strlen(in));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const char * in = "";
|
||||||
|
hasher.Update(in, in + strlen(in));
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_EQ("bde52cb31de33e46245e05fbdbd6fb24", hasher.Finish().ToHexString());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// "abc"
|
||||||
|
Md4::Hasher hasher;
|
||||||
|
|
||||||
|
{
|
||||||
|
const char * in = "ab";
|
||||||
|
hasher.Update(in, in + strlen(in));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const char * in = "c";
|
||||||
|
hasher.Update(in, in + strlen(in));
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_EQ("a448017aaf21d8525fc10ae87aa6729d", hasher.Finish().ToHexString());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// "message digest"
|
||||||
|
Md4::Hasher hasher;
|
||||||
|
|
||||||
|
{
|
||||||
|
const char * in = "me";
|
||||||
|
hasher.Update(in, in + strlen(in));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const char * in = "ssage ";
|
||||||
|
hasher.Update(in, in + strlen(in));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const char * in = "diges";
|
||||||
|
hasher.Update(in, in + strlen(in));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const char * in = "t";
|
||||||
|
hasher.Update(in, in + strlen(in));
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_EQ("d9130a8164549fe818874806e1c7014b", hasher.Finish().ToHexString());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// "12345678901234567890123456789012345678901234567890123456789012345678901234567890"
|
||||||
|
Md4::Hasher hasher;
|
||||||
|
|
||||||
|
{
|
||||||
|
const char * in = "12345678901234567890";
|
||||||
|
hasher.Update(in, in + strlen(in));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const char * in = "12345678901234567890";
|
||||||
|
hasher.Update(in, in + strlen(in));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const char * in = "12345678901234567890";
|
||||||
|
hasher.Update(in, in + strlen(in));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const char * in = "12345678901234567890";
|
||||||
|
hasher.Update(in, in + strlen(in));
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_EQ("e33b4ddc9c38f2199c3e7b164fcc0536", hasher.Finish().ToHexString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Md4Tests, LongInputTest)
|
||||||
|
{
|
||||||
|
struct Helper
|
||||||
|
{
|
||||||
|
std::string operator()(const char * in) const
|
||||||
|
{
|
||||||
|
Md4::Hasher hasher;
|
||||||
|
hasher.Update(in, in + strlen(in));
|
||||||
|
return hasher.Finish().ToHexString();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Helper hash;
|
||||||
|
|
||||||
|
// 2500 zeros ('0').
|
||||||
|
ASSERT_EQ("20de61fc6dc2134f7a7bbcf43fd923e6", hash(std::string(2500, '0').c_str()));
|
||||||
|
// 1000 'a' followed by 1000 'b'.
|
||||||
|
ASSERT_EQ("cbabb47a57b10e0028ec7f519c66f229", hash((std::string(1000, 'a') +
|
||||||
|
std::string(1000, 'b')).c_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Md4Tests, LongInputPartialUpdateTest)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
// 2500 zeros ('0').
|
||||||
|
Md4::Hasher hasher;
|
||||||
|
|
||||||
|
std::string in(750, '0');
|
||||||
|
|
||||||
|
hasher.Update(in.begin(), in.begin() + 250);
|
||||||
|
hasher.Update(in.begin(), in.begin() + 500);
|
||||||
|
hasher.Update(in.begin(), in.begin() + 500);
|
||||||
|
hasher.Update(in.begin(), in.begin() + 750);
|
||||||
|
hasher.Update(in.begin(), in.begin() + 333);
|
||||||
|
hasher.Update(in.begin(), in.begin() + 167);
|
||||||
|
|
||||||
|
ASSERT_EQ("20de61fc6dc2134f7a7bbcf43fd923e6", hasher.Finish().ToHexString());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// 1000 'a' followed by 1000 'b'.
|
||||||
|
Md4::Hasher hasher;
|
||||||
|
|
||||||
|
std::string inA(1000, 'a');
|
||||||
|
std::string inB(1000, 'b');
|
||||||
|
|
||||||
|
hasher.Update(inA.begin(), inA.begin() + 100);
|
||||||
|
hasher.Update(inA.begin(), inA.begin() + 255);
|
||||||
|
hasher.Update(inA.begin(), inA.begin() + 645);
|
||||||
|
|
||||||
|
hasher.Update(inB.begin(), inB.begin() + 33);
|
||||||
|
hasher.Update(inB.begin(), inB.begin() + 701);
|
||||||
|
hasher.Update(inB.begin(), inB.begin() + 266);
|
||||||
|
|
||||||
|
ASSERT_EQ("cbabb47a57b10e0028ec7f519c66f229", hasher.Finish().ToHexString());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user