Add a Hash base class to Azure::Core and redesign Md5 to derive from it. (#1632)

* Add MD5 hashing APIs to Azure::Core available from azure/core/md5.hpp.

* Add simplified header test for md5 and base64.

* Add changelog entry.

* Remove unnecessary include.

* Address feedback - add back ptr, length APIs.

* Address PR feedback - docs and typo fixes.

* Add a Hash base class and redesign Md5 to derive from it.

* Add test for call to final on empty instance.

* Remove old file which got renamed to hash.hpp.

* Remove md5.hpp file references.

* Address PR feedback - move to crypto, remove virtual, and misc.
This commit is contained in:
Ahson Khan 2021-02-08 18:31:12 -08:00 committed by GitHub
parent 1f849ec384
commit b3497f8c16
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 208 additions and 103 deletions

View File

@ -5,7 +5,7 @@
### New Features
- Added support for HTTP conditional requests `MatchConditions` and `RequestConditions`.
- Added MD5 hashing APIs to the `Azure::Core` namespace available from `azure/core/md5.hpp`.
- Added the `Hash` base class and MD5 hashing APIs to the `Azure::Core::Cryptography` namespace available from `azure/core/cryptography/hash.hpp`.
### Breaking Changes

View File

@ -45,6 +45,7 @@ set(
AZURE_CORE_HEADER
${CURL_TRANSPORT_ADAPTER_INC}
${WIN_TRANSPORT_ADAPTER_INC}
inc/azure/core/cryptography/hash.hpp
inc/azure/core/http/body_stream.hpp
inc/azure/core/http/http.hpp
inc/azure/core/http/pipeline.hpp
@ -64,7 +65,6 @@ set(
inc/azure/core/etag.hpp
inc/azure/core/exception.hpp
inc/azure/core/match_conditions.hpp
inc/azure/core/md5.hpp
inc/azure/core/nullable.hpp
inc/azure/core/operation.hpp
inc/azure/core/operation_status.hpp
@ -80,6 +80,7 @@ set(
AZURE_CORE_SOURCE
${CURL_TRANSPORT_ADAPTER_SRC}
${WIN_TRANSPORT_ADAPTER_SRC}
src/cryptography/md5.cpp
src/http/bearer_token_authentication_policy.cpp
src/http/body_stream.cpp
src/http/http.cpp
@ -95,7 +96,6 @@ set(
src/base64.cpp
src/context.cpp
src/datetime.cpp
src/md5.cpp
src/operation_status.cpp
src/strings.cpp
src/version.cpp

View File

@ -18,13 +18,15 @@
#include "azure/core/dll_import_export.hpp"
#include "azure/core/etag.hpp"
#include "azure/core/match_conditions.hpp"
#include "azure/core/md5.hpp"
#include "azure/core/nullable.hpp"
#include "azure/core/request_conditions.hpp"
#include "azure/core/response.hpp"
#include "azure/core/uuid.hpp"
#include "azure/core/version.hpp"
// azure/core/cryptography
#include "azure/core/cryptography/hash.hpp"
// azure/core/http
#include "azure/core/http/body_stream.hpp"
#include "azure/core/http/http.hpp"

View File

@ -0,0 +1,149 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/**
* @file
* @brief Utility functions to help compute the hash value for the input binary data, using
* algorithms such as MD5.
*/
#pragma once
#include <cstdint>
#include <stdexcept>
#include <string>
#include <vector>
namespace Azure { namespace Core { namespace Cryptography {
/**
* @brief Represents the base class for hash algorithms which map binary data of an arbitrary
* length to small binary data of a fixed length.
*/
class Hash {
private:
/**
* @brief Used to append partial binary input data to compute the hash in a streaming fashion.
* @remark Once all the data has been added, call #Final() to get the computed hash value.
* @param data The pointer to the current block of binary data that is used for hash
* calculation.
* @param length The size of the data provided.
*/
virtual void OnAppend(const uint8_t* data, std::size_t length) = 0;
/**
* @brief Computes the hash value of the specified binary input data, including any previously
* appended.
* @param data The pointer to binary data to compute the hash value for.
* @param length The size of the data provided.
* @return The computed hash value corresponding to the input provided including any previously
* appended.
*/
virtual std::vector<uint8_t> OnFinal(const uint8_t* data, std::size_t length) = 0;
public:
/**
* @brief Used to append partial binary input data to compute the hash in a streaming fashion.
* @remark Once all the data has been added, call #Final() to get the computed hash value.
* @remark Do not call this function after a call to #Final().
* @param data The pointer to the current block of binary data that is used for hash
* calculation.
* @param length The size of the data provided.
*/
void Append(const uint8_t* data, std::size_t length)
{
if (!data && length != 0)
{
throw std::invalid_argument(
"Length cannot be " + std::to_string(length) + " if the data pointer is null.");
}
if (m_isdone)
{
throw std::runtime_error("Cannot call Append after calling Final().");
};
OnAppend(data, length);
}
/**
* @brief Computes the hash value of the specified binary input data, including any previously
* appended.
* @remark Do not call this function multiple times.
* @param data The pointer to the last block of binary data to compute the hash value for.
* @param length The size of the data provided.
* @return The computed hash value corresponding to the input provided, including any previously
* appended.
*/
std::vector<uint8_t> Final(const uint8_t* data, std::size_t length)
{
if (!data && length != 0)
{
throw std::invalid_argument(
"Length cannot be " + std::to_string(length) + " if the data pointer is null.");
}
if (m_isdone)
{
throw std::runtime_error("Cannot call Final() multiple times.");
};
m_isdone = true;
return OnFinal(data, length);
}
/**
* @brief Computes the hash value of all the binary input data appended to the instance so far.
* @remark Use #Append() to add more partial data before calling this function.
* @remark Do not call this function multiple times.
* @return The computed hash value corresponding to the input provided.
*/
std::vector<uint8_t> Final() { return Final(nullptr, 0); };
/**
* @brief Cleanup any state when destroying the instance of @Hash.
*/
virtual ~Hash(){};
private:
bool m_isdone = false;
};
/**
* @brief Represents the class for the MD5 hash function which maps binary data of an arbitrary
* length to small binary data of a fixed length.
*/
class Md5Hash : public Hash {
public:
/**
* @brief Construct a default instance of @Md5Hash.
*/
explicit Md5Hash();
/**
* @brief Cleanup any state when destroying the instance of @Md5Hash.
*/
~Md5Hash();
private:
void* m_md5Context;
/**
* @brief Computes the hash value of the specified binary input data, including any previously
* appended.
* @param data The pointer to binary data to compute the hash value for.
* @param length The size of the data provided.
* @return The computed MD5 hash value corresponding to the input provided including any
* previously appended.
*/
std::vector<uint8_t> OnFinal(const uint8_t* data, std::size_t length) override;
/**
* @brief Used to append partial binary input data to compute the MD5 hash in a streaming
* fashion.
* @remark Once all the data has been added, call #Final() to get the computed hash value.
* @param data The pointer to the current block of binary data that is used for hash
* calculation.
* @param length The size of the data provided.
*/
void OnAppend(const uint8_t* data, std::size_t length) override;
};
}}} // namespace Azure::Core::Cryptography

View File

@ -1,75 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/**
* @file
* @brief Utility functions to help compute the MD5 hash value for the input binary data.
*/
#pragma once
#include <cstdint>
#include <vector>
namespace Azure { namespace Core {
/**
* @brief Represents the class for the MD5 hash function which maps binary data of an arbitrary
* length to small binary data of a fixed length.
*/
class Md5 {
public:
/**
* @brief Construct a default instance of @Md5.
*/
explicit Md5();
/**
* @brief Cleanup any state when destroying the instance of @Md5.
*/
~Md5();
/**
* @brief Used to append partial binary input data to compute the hash in a streaming fashion.
* @remark Once all the data has been added, call #Digest() to get the computed hash value.
* @param data The pointer to the current block of binary data that is used for hash
* calculation.
* @param length The size of the data provided.
*/
void Update(const uint8_t* data, std::size_t length);
/**
* @brief Computes the hash value of all the binary input data appended to the instance so far.
* @remark Use #Update() to add more partial data before calling this function.
* @return The computed MD5 hash value corresponding to the input provided.
*/
std::vector<uint8_t> Digest() const;
/**
* @brief Computes the hash value of the specified binary input data.
* @param data The pointer to binary data to compute the hash value for.
* @param length The size of the data provided.
* @return The computed MD5 hash value corresponding to the input provided.
*/
static std::vector<uint8_t> Hash(const uint8_t* data, std::size_t length)
{
Md5 instance;
instance.Update(data, length);
return instance.Digest();
}
/**
* @brief Computes the hash value of the specified binary input data.
* @param data The input vector to compute the hash value for.
* @return The computed MD5 hash value corresponding to the input provided.
*/
static std::vector<uint8_t> Hash(const std::vector<uint8_t>& data)
{
return Hash(data.data(), data.size());
}
private:
void* m_md5Context;
};
}} // namespace Azure::Core

View File

@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include "azure/core/md5.hpp"
#include "azure/core/cryptography/hash.hpp"
#include "azure/core/platform.hpp"
#if defined(AZ_PLATFORM_WINDOWS)
@ -16,7 +16,7 @@
#include <stdexcept>
#include <vector>
namespace Azure { namespace Core {
namespace Azure { namespace Core { namespace Cryptography {
#if defined(AZ_PLATFORM_WINDOWS)
@ -74,7 +74,7 @@ namespace Azure { namespace Core {
};
} // namespace Details
Md5::Md5()
Md5Hash::Md5Hash()
{
static Details::AlgorithmProviderInstance AlgorithmProvider{};
@ -97,14 +97,14 @@ namespace Azure { namespace Core {
}
}
Md5::~Md5()
Md5Hash::~Md5Hash()
{
Details::Md5HashContext* md5Context = static_cast<Details::Md5HashContext*>(m_md5Context);
BCryptDestroyHash(md5Context->hashHandle);
delete md5Context;
}
void Md5::Update(const uint8_t* data, std::size_t length)
void Md5Hash::OnAppend(const uint8_t* data, std::size_t length)
{
Details::Md5HashContext* md5Context = static_cast<Details::Md5HashContext*>(m_md5Context);
@ -119,8 +119,9 @@ namespace Azure { namespace Core {
}
}
std::vector<uint8_t> Md5::Digest() const
std::vector<uint8_t> Md5Hash::OnFinal(const uint8_t* data, std::size_t length)
{
OnAppend(data, length);
Details::Md5HashContext* md5Context = static_cast<Details::Md5HashContext*>(m_md5Context);
std::vector<uint8_t> hash;
hash.resize(md5Context->hashLength);
@ -138,27 +139,28 @@ namespace Azure { namespace Core {
#elif defined(AZ_PLATFORM_POSIX)
Md5::Md5()
Md5Hash::Md5Hash()
{
MD5_CTX* md5Context = new MD5_CTX;
m_md5Context = md5Context;
MD5_Init(md5Context);
}
Md5::~Md5()
Md5Hash::~Md5Hash()
{
MD5_CTX* md5Context = static_cast<MD5_CTX*>(m_md5Context);
delete md5Context;
}
void Md5::Update(const uint8_t* data, std::size_t length)
void Md5Hash::OnAppend(const uint8_t* data, std::size_t length)
{
MD5_CTX* md5Context = static_cast<MD5_CTX*>(m_md5Context);
MD5_Update(md5Context, data, length);
}
std::vector<uint8_t> Md5::Digest() const
std::vector<uint8_t> Md5Hash::OnFinal(const uint8_t* data, std::size_t length)
{
OnAppend(data, length);
MD5_CTX* md5Context = static_cast<MD5_CTX*>(m_md5Context);
unsigned char hash[MD5_DIGEST_LENGTH];
MD5_Final(hash, md5Context);
@ -166,4 +168,4 @@ namespace Azure { namespace Core {
}
#endif
}} // namespace Azure::Core
}}} // namespace Azure::Core::Cryptography

View File

@ -3,19 +3,19 @@
#include <algorithm>
#include <azure/core/base64.hpp>
#include <azure/core/md5.hpp>
#include <azure/core/cryptography/hash.hpp>
#include <gtest/gtest.h>
#include <random>
#include <string>
#include <vector>
using namespace Azure::Core;
using namespace Azure::Core::Cryptography;
static std::vector<uint8_t> Hash(const std::string& data)
static std::vector<uint8_t> ComputeHash(const std::string& data)
{
const uint8_t* ptr = reinterpret_cast<const uint8_t*>(data.data());
std::vector<uint8_t> v(ptr, ptr + data.length());
return Md5::Hash(v);
Md5Hash instance;
return instance.Final(ptr, data.length());
}
static thread_local std::mt19937_64 random_generator(std::random_device{}());
@ -62,13 +62,17 @@ uint64_t RandomInt(uint64_t minNumber, uint64_t maxNumber)
return distribution(random_generator);
}
TEST(Md5, Basic)
TEST(Md5Hash, Basic)
{
EXPECT_EQ(Base64Encode(Hash("")), "1B2M2Y8AsgTpgAmY7PhCfg==");
EXPECT_EQ(Base64Encode(Hash("Hello Azure!")), "Pz8543xut4RVSbb2g52Mww==");
Md5Hash md5empty;
EXPECT_EQ(Azure::Core::Base64Encode(md5empty.Final()), "1B2M2Y8AsgTpgAmY7PhCfg==");
EXPECT_EQ(Azure::Core::Base64Encode(ComputeHash("")), "1B2M2Y8AsgTpgAmY7PhCfg==");
EXPECT_EQ(Azure::Core::Base64Encode(ComputeHash("Hello Azure!")), "Pz8543xut4RVSbb2g52Mww==");
auto data = RandomBuffer(static_cast<std::size_t>(16777216));
Md5 md5Instance;
Md5Hash md5Single;
Md5Hash md5Streaming;
// There are two ways to get the hash value, a "single-shot" static API called `Hash()` and one
// where you can stream partial data blocks with multiple calls to `Update()` and then once you
@ -83,9 +87,32 @@ TEST(Md5, Basic)
{
std::size_t s = static_cast<std::size_t>(RandomInt(0, 4194304));
s = std::min(s, data.size() - length);
md5Instance.Update(&data[length], s);
md5Instance.Update(&data[length], 0);
md5Streaming.Append(&data[length], s);
md5Streaming.Append(&data[length], 0);
length += s;
}
EXPECT_EQ(md5Instance.Digest(), Md5::Hash(data));
EXPECT_EQ(md5Streaming.Final(), md5Single.Final(data.data(), data.size()));
}
TEST(Md5Hash, ExpectThrow)
{
std::string data = "";
const uint8_t* ptr = reinterpret_cast<const uint8_t*>(data.data());
Md5Hash instance;
EXPECT_THROW(instance.Final(nullptr, 1), std::invalid_argument);
EXPECT_THROW(instance.Append(nullptr, 1), std::invalid_argument);
EXPECT_EQ(
Azure::Core::Base64Encode(instance.Final(ptr, data.length())), "1B2M2Y8AsgTpgAmY7PhCfg==");
EXPECT_THROW(instance.Final(), std::runtime_error);
EXPECT_THROW(instance.Final(ptr, data.length()), std::runtime_error);
EXPECT_THROW(instance.Append(ptr, data.length()), std::runtime_error);
}
TEST(Md5Hash, CtorDtor)
{
{
Md5Hash instance;
}
}

View File

@ -23,7 +23,7 @@ TEST(SimplifiedHeader, core)
EXPECT_NO_THROW(Azure::Core::DateTime(2020, 11, 03, 15, 30, 44));
EXPECT_NO_THROW(Azure::Core::ETag e);
EXPECT_NO_THROW(Azure::Core::Base64Decode("foo"));
EXPECT_NO_THROW(Azure::Core::Md5 m);
EXPECT_NO_THROW(Azure::Core::Cryptography::Md5Hash m);
EXPECT_NO_THROW(Azure::Core::Http::RawResponse r(
1, 1, Azure::Core::Http::HttpStatusCode::Accepted, "phrase"));
EXPECT_NO_THROW(Azure::Core::MatchConditions mc);