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:
parent
1f849ec384
commit
b3497f8c16
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
149
sdk/core/azure-core/inc/azure/core/cryptography/hash.hpp
Normal file
149
sdk/core/azure-core/inc/azure/core/cryptography/hash.hpp
Normal 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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user