1585: Use OpenSSL md5 if it is available r=def- a=heinrich5991

Fixes #1582.

Co-authored-by: heinrich5991 <heinrich5991@gmail.com>
This commit is contained in:
bors[bot] 2019-04-06 15:43:24 +00:00
commit b25c5275d5
16 changed files with 229 additions and 113 deletions

View file

@ -503,7 +503,10 @@ add_library(md5 EXCLUDE_FROM_ALL OBJECT ${DEP_MD5_SRC})
list(APPEND TARGETS_DEP json md5)
set(DEP_JSON $<TARGET_OBJECTS:json>)
set(DEP_MD5 $<TARGET_OBJECTS:md5>)
set(DEP_MD5)
if(NOT CRYPTO_FOUND)
set(DEP_MD5 $<TARGET_OBJECTS:md5>)
endif()
########################################################################
# COPY DATA AND DLLS
@ -575,6 +578,7 @@ set_glob(BASE GLOB_RECURSE src/base
detect.h
hash.c
hash.h
hash_bundled.c
hash_ctxt.h
hash_libtomcrypt.c
hash_openssl.c

View file

@ -3,6 +3,30 @@
#include "system.h"
static void digest_str(const unsigned char *digest, size_t digest_len, char *str, size_t max_len)
{
unsigned i;
if(max_len > digest_len * 2 + 1)
{
max_len = digest_len * 2 + 1;
}
str[max_len - 1] = 0;
max_len -= 1;
for(i = 0; i < max_len; i++)
{
static const char HEX[] = "0123456789abcdef";
int index = i / 2;
if(i % 2 == 0)
{
str[i] = HEX[digest[index] >> 4];
}
else
{
str[i] = HEX[digest[index] & 0xf];
}
}
}
SHA256_DIGEST sha256(const void *message, size_t message_len)
{
SHA256_CTX ctxt;
@ -13,26 +37,7 @@ SHA256_DIGEST sha256(const void *message, size_t message_len)
void sha256_str(SHA256_DIGEST digest, char *str, size_t max_len)
{
unsigned i;
if(max_len > SHA256_MAXSTRSIZE)
{
max_len = SHA256_MAXSTRSIZE;
}
str[max_len - 1] = 0;
max_len -= 1;
for(i = 0; i < max_len; i++)
{
static const char HEX[] = "0123456789abcdef";
int index = i / 2;
if(i % 2 == 0)
{
str[i] = HEX[digest.data[index] >> 4];
}
else
{
str[i] = HEX[digest.data[index] & 0xf];
}
}
digest_str(digest.data, sizeof(digest.data), str, max_len);
}
int sha256_from_str(SHA256_DIGEST *out, const char *str)
@ -44,3 +49,26 @@ int sha256_comp(SHA256_DIGEST digest1, SHA256_DIGEST digest2)
{
return mem_comp(digest1.data, digest2.data, sizeof(digest1.data));
}
MD5_DIGEST md5(const void *message, size_t message_len)
{
MD5_CTX ctxt;
md5_init(&ctxt);
md5_update(&ctxt, message, message_len);
return md5_finish(&ctxt);
}
void md5_str(MD5_DIGEST digest, char *str, size_t max_len)
{
digest_str(digest.data, sizeof(digest.data), str, max_len);
}
int md5_from_str(MD5_DIGEST *out, const char *str)
{
return str_hex_decode(out->data, sizeof(out->data), str);
}
int md5_comp(MD5_DIGEST digest1, MD5_DIGEST digest2)
{
return mem_comp(digest1.data, digest2.data, sizeof(digest1.data));
}

View file

@ -11,6 +11,8 @@ enum
{
SHA256_DIGEST_LENGTH=256/8,
SHA256_MAXSTRSIZE=2*SHA256_DIGEST_LENGTH+1,
MD5_DIGEST_LENGTH=128/8,
MD5_MAXSTRSIZE=2*MD5_DIGEST_LENGTH+1,
};
typedef struct
@ -18,12 +20,23 @@ typedef struct
unsigned char data[SHA256_DIGEST_LENGTH];
} SHA256_DIGEST;
typedef struct
{
unsigned char data[MD5_DIGEST_LENGTH];
} MD5_DIGEST;
SHA256_DIGEST sha256(const void *message, size_t message_len);
void sha256_str(SHA256_DIGEST digest, char *str, size_t max_len);
int sha256_from_str(SHA256_DIGEST *out, const char *str);
int sha256_comp(SHA256_DIGEST digest1, SHA256_DIGEST digest2);
MD5_DIGEST md5(const void *message, size_t message_len);
void md5_str(MD5_DIGEST digest, char *str, size_t max_len);
int md5_from_str(MD5_DIGEST *out, const char *str);
int md5_comp(MD5_DIGEST digest1, MD5_DIGEST digest2);
static const SHA256_DIGEST SHA256_ZEROED = {{0}};
static const MD5_DIGEST MD5_ZEROED = {{0}};
#ifdef __cplusplus
}
@ -38,6 +51,14 @@ inline bool operator!=(const SHA256_DIGEST &that, const SHA256_DIGEST &other)
{
return !(that == other);
}
inline bool operator==(const MD5_DIGEST &that, const MD5_DIGEST &other)
{
return md5_comp(that, other) == 0;
}
inline bool operator!=(const MD5_DIGEST &that, const MD5_DIGEST &other)
{
return !(that == other);
}
#endif
#endif // BASE_HASH_H

19
src/base/hash_bundled.c Normal file
View file

@ -0,0 +1,19 @@
#if !defined(CONF_OPENSSL)
#include "hash_ctxt.h"
#include <engine/external/md5/md5.h>
void md5_update(MD5_CTX *ctxt, const void *data, size_t data_len)
{
md5_append(ctxt, data, data_len);
}
MD5_DIGEST md5_finish(MD5_CTX *ctxt)
{
MD5_DIGEST result;
md5_finish_(ctxt, result.data);
return result;
}
#endif

View file

@ -5,7 +5,10 @@
#include <stdint.h>
#if defined(CONF_OPENSSL)
#include <openssl/md5.h>
#include <openssl/sha.h>
#else
#include <engine/external/md5/md5.h>
#endif
#ifdef __cplusplus
@ -22,12 +25,17 @@ typedef struct
uint32_t curlen;
unsigned char buf[64];
} SHA256_CTX;
typedef md5_state_t MD5_CTX;
#endif
void sha256_init(SHA256_CTX *ctxt);
void sha256_update(SHA256_CTX *ctxt, const void *data, size_t data_len);
SHA256_DIGEST sha256_finish(SHA256_CTX *ctxt);
void md5_init(MD5_CTX *ctxt);
void md5_update(MD5_CTX *ctxt, const void *data, size_t data_len);
MD5_DIGEST md5_finish(MD5_CTX *ctxt);
#ifdef __cplusplus
}
#endif

View file

@ -17,4 +17,21 @@ SHA256_DIGEST sha256_finish(SHA256_CTX *ctxt)
SHA256_Final(result.data, ctxt);
return result;
}
void md5_init(MD5_CTX *ctxt)
{
MD5_Init(ctxt);
}
void md5_update(MD5_CTX *ctxt, const void *data, size_t data_len)
{
MD5_Update(ctxt, data, data_len);
}
MD5_DIGEST md5_finish(MD5_CTX *ctxt)
{
MD5_DIGEST result;
MD5_Final(result.data, ctxt);
return result;
}
#endif

View file

@ -11,6 +11,7 @@
#include <climits>
#include <tuple>
#include <base/hash_ctxt.h>
#include <base/math.h>
#include <base/vmath.h>
#include <base/system.h>
@ -34,8 +35,6 @@
#include <engine/storage.h>
#include <engine/textrender.h>
#include <engine/external/md5/md5.h>
#include <engine/client/http.h>
#include <engine/shared/config.h>
#include <engine/shared/compression.h>
@ -637,19 +636,16 @@ void CClient::EnterGame()
void GenerateTimeoutCode(char *pBuffer, unsigned Size, char *pSeed, const NETADDR &Addr, bool Dummy)
{
md5_state_t Md5;
md5_byte_t aDigest[16];
MD5_CTX Md5;
md5_init(&Md5);
const char *pDummy = Dummy ? "dummy" : "normal";
md5_append(&Md5, (unsigned char *)pDummy, str_length(pDummy) + 1);
md5_append(&Md5, (unsigned char *)pSeed, str_length(pSeed) + 1);
md5_append(&Md5, (unsigned char *)&Addr, sizeof(Addr));
md5_finish(&Md5, aDigest);
md5_update(&Md5, (unsigned char *)pDummy, str_length(pDummy) + 1);
md5_update(&Md5, (unsigned char *)pSeed, str_length(pSeed) + 1);
md5_update(&Md5, (unsigned char *)&Addr, sizeof(Addr));
MD5_DIGEST Digest = md5_finish(&Md5);
unsigned short Random[8];
mem_copy(Random, aDigest, sizeof(Random));
mem_copy(Random, Digest.data, sizeof(Random));
generate_password(pBuffer, Size, Random, 8);
}

View file

@ -2,10 +2,10 @@
/* If you are missing that file, acquire a complete release at teeworlds.com. */
#include <algorithm> // sort TODO: remove this
#include <base/hash_ctxt.h>
#include <base/math.h>
#include <base/system.h>
#include <engine/external/md5/md5.h>
#include <engine/shared/config.h>
#include <engine/shared/memheap.h>
#include <engine/shared/network.h>
@ -92,15 +92,12 @@ const CServerInfo *CServerBrowser::SortedGet(int Index) const
int CServerBrowser::GenerateToken(const NETADDR &Addr) const
{
md5_state_t Md5;
md5_byte_t aDigest[16];
md5_init(&Md5);
md5_append(&Md5, m_aTokenSeed, sizeof(m_aTokenSeed));
md5_append(&Md5, (unsigned char *)&Addr, sizeof(Addr));
md5_finish(&Md5, aDigest);
return (aDigest[0] << 16) | (aDigest[1] << 8) | aDigest[2];
SHA256_CTX Sha256;
sha256_init(&Sha256);
sha256_update(&Sha256, m_aTokenSeed, sizeof(m_aTokenSeed));
sha256_update(&Sha256, (unsigned char *)&Addr, sizeof(Addr));
SHA256_DIGEST Digest = sha256_finish(&Sha256);
return (Digest.data[0] << 16) | (Digest.data[1] << 8) | Digest.data[2];
}
int CServerBrowser::GetBasicToken(int Token)

View file

@ -358,7 +358,7 @@ md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes)
}
void
md5_finish(md5_state_t *pms, md5_byte_t digest[16])
md5_finish_(md5_state_t *pms, md5_byte_t digest[16])
{
static const md5_byte_t pad[64] = {
0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

View file

@ -82,7 +82,7 @@ void md5_init(md5_state_t *pms);
void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes);
/* Finish the message and return the digest. */
void md5_finish(md5_state_t *pms, md5_byte_t digest[16]);
void md5_finish_(md5_state_t *pms, md5_byte_t digest[16]);
#ifdef __cplusplus
} /* end extern "C" */

View file

@ -1,5 +1,4 @@
#include <engine/external/md5/md5.h>
#include <base/hash_ctxt.h>
#include <engine/shared/config.h>
#include "authmanager.h"
@ -7,6 +6,16 @@
#define MOD_IDENT "default_mod"
#define HELPER_IDENT "default_helper"
static MD5_DIGEST HashPassword(const char *pPassword, const unsigned char aSalt[SALT_BYTES])
{
// Hash the password and the salt
MD5_CTX Md5;
md5_init(&Md5);
md5_update(&Md5, (unsigned char *)pPassword, str_length(pPassword));
md5_update(&Md5, aSalt, SALT_BYTES);
return md5_finish(&Md5);
}
CAuthManager::CAuthManager()
{
m_aDefault[0] = -1;
@ -32,14 +41,14 @@ void CAuthManager::Init()
}
}
int CAuthManager::AddKeyHash(const char *pIdent, const unsigned char *pHash, const unsigned char *pSalt, int AuthLevel)
int CAuthManager::AddKeyHash(const char *pIdent, MD5_DIGEST Hash, const unsigned char *pSalt, int AuthLevel)
{
if(FindKey(pIdent) >= 0)
return -1;
CKey Key;
str_copy(Key.m_aIdent, pIdent, sizeof(Key.m_aIdent));
mem_copy(Key.m_aPw, pHash, MD5_BYTES);
Key.m_Pw = Hash;
mem_copy(Key.m_aSalt, pSalt, SALT_BYTES);
Key.m_Level = AuthLevel;
@ -48,20 +57,10 @@ int CAuthManager::AddKeyHash(const char *pIdent, const unsigned char *pHash, con
int CAuthManager::AddKey(const char *pIdent, const char *pPw, int AuthLevel)
{
md5_state_t ctx;
unsigned char aHash[MD5_BYTES];
unsigned char aSalt[SALT_BYTES];
// Generate random salt
unsigned char aSalt[SALT_BYTES];
secure_random_fill(aSalt, SALT_BYTES);
// Hash the password and the salt
md5_init(&ctx);
md5_append(&ctx, (unsigned char *)pPw, str_length(pPw));
md5_append(&ctx, aSalt, SALT_BYTES);
md5_finish(&ctx, aHash);
return AddKeyHash(pIdent, aHash, aSalt, AuthLevel);
return AddKeyHash(pIdent, HashPassword(pPw, aSalt), aSalt, AuthLevel);
}
int CAuthManager::RemoveKey(int Slot)
@ -94,17 +93,7 @@ bool CAuthManager::CheckKey(int Slot, const char *pPw)
{
if(Slot < 0 || Slot >= m_aKeys.size())
return false;
md5_state_t ctx;
unsigned char aHash[MD5_BYTES];
// Hash the password and the salt
md5_init(&ctx);
md5_append(&ctx, (unsigned char*)pPw, str_length(pPw));
md5_append(&ctx, m_aKeys[Slot].m_aSalt, SALT_BYTES);
md5_finish(&ctx, aHash);
return !mem_comp(m_aKeys[Slot].m_aPw, aHash, MD5_BYTES);
return m_aKeys[Slot].m_Pw == HashPassword(pPw, m_aKeys[Slot].m_aSalt);
}
int CAuthManager::DefaultKey(int AuthLevel)
@ -128,13 +117,13 @@ const char *CAuthManager::KeyIdent(int Slot)
return m_aKeys[Slot].m_aIdent;
}
void CAuthManager::UpdateKeyHash(int Slot, const unsigned char *pHash, const unsigned char *pSalt, int AuthLevel)
void CAuthManager::UpdateKeyHash(int Slot, MD5_DIGEST Hash, const unsigned char *pSalt, int AuthLevel)
{
if(Slot < 0 || Slot >= m_aKeys.size())
return;
CKey *pKey = &m_aKeys[Slot];
mem_copy(pKey->m_aPw, pHash, MD5_BYTES);
pKey->m_Pw = Hash;
mem_copy(pKey->m_aSalt, pSalt, SALT_BYTES);
pKey->m_Level = AuthLevel;
}
@ -144,20 +133,10 @@ void CAuthManager::UpdateKey(int Slot, const char *pPw, int AuthLevel)
if(Slot < 0 || Slot >= m_aKeys.size())
return;
md5_state_t ctx;
unsigned char aHash[MD5_BYTES];
unsigned char aSalt[SALT_BYTES];
// Generate random salt
unsigned char aSalt[SALT_BYTES];
secure_random_fill(aSalt, SALT_BYTES);
// Hash the password and the salt
md5_init(&ctx);
md5_append(&ctx, (unsigned char *)pPw, str_length(pPw));
md5_append(&ctx, aSalt, SALT_BYTES);
md5_finish(&ctx, aHash);
UpdateKeyHash(Slot, aHash, aSalt, AuthLevel);
UpdateKeyHash(Slot, HashPassword(pPw, aSalt), aSalt, AuthLevel);
}
void CAuthManager::ListKeys(FListCallback pfnListCallback, void *pUser)

View file

@ -1,9 +1,9 @@
#ifndef ENGINE_SERVER_AUTHMANAGER_H
#define ENGINE_SERVER_AUTHMANAGER_H
#include <base/hash.h>
#include <base/tl/array.h>
#define MD5_BYTES 16
#define SALT_BYTES 8
class CAuthManager
@ -19,7 +19,7 @@ private:
struct CKey
{
char m_aIdent[64];
unsigned char m_aPw[MD5_BYTES];
MD5_DIGEST m_Pw;
unsigned char m_aSalt[SALT_BYTES];
int m_Level;
};
@ -33,7 +33,7 @@ public:
CAuthManager();
void Init();
int AddKeyHash(const char *pIdent, const unsigned char *pHash, const unsigned char *pSalt, int AuthLevel);
int AddKeyHash(const char *pIdent, MD5_DIGEST Hash, const unsigned char *pSalt, int AuthLevel);
int AddKey(const char *pIdent, const char *pPw, int AuthLevel);
int RemoveKey(int Slot); // Returns the old key slot that is now in the named one.
int FindKey(const char *pIdent);
@ -41,7 +41,7 @@ public:
int DefaultKey(int AuthLevel);
int KeyLevel(int Slot);
const char *KeyIdent(int Slot);
void UpdateKeyHash(int Slot, const unsigned char *pHash, const unsigned char *pSalt, int AuthLevel);
void UpdateKeyHash(int Slot, MD5_DIGEST Hash, const unsigned char *pSalt, int AuthLevel);
void UpdateKey(int Slot, const char *pPw, int AuthLevel);
void ListKeys(FListCallback pfnListCallbac, void *pUser);
void AddDefaultKey(int Level, const char *pPw);

View file

@ -2249,10 +2249,10 @@ void CServer::ConAuthAddHashed(IConsole::IResult *pResult, void *pUser)
return;
}
unsigned char aHash[MD5_BYTES];
MD5_DIGEST Hash;
unsigned char aSalt[SALT_BYTES];
if(str_hex_decode(aHash, sizeof(aHash), pPw))
if(md5_from_str(&Hash, pPw))
{
pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "auth", "Malformed password hash");
return;
@ -2265,7 +2265,7 @@ void CServer::ConAuthAddHashed(IConsole::IResult *pResult, void *pUser)
bool NeedUpdate = !pManager->NumNonDefaultKeys();
if(pManager->AddKeyHash(pIdent, aHash, aSalt, Level) < 0)
if(pManager->AddKeyHash(pIdent, Hash, aSalt, Level) < 0)
pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "auth", "ident already exists");
else
{
@ -2328,10 +2328,10 @@ void CServer::ConAuthUpdateHashed(IConsole::IResult *pResult, void *pUser)
return;
}
unsigned char aHash[MD5_BYTES];
MD5_DIGEST Hash;
unsigned char aSalt[SALT_BYTES];
if(str_hex_decode(aHash, sizeof(aHash), pPw))
if(md5_from_str(&Hash, pPw))
{
pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "auth", "Malformed password hash");
return;
@ -2342,7 +2342,7 @@ void CServer::ConAuthUpdateHashed(IConsole::IResult *pResult, void *pUser)
return;
}
pManager->UpdateKeyHash(KeySlot, aHash, aSalt, Level);
pManager->UpdateKeyHash(KeySlot, Hash, aSalt, Level);
pThis->LogoutKey(KeySlot, "key update");
pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "auth", "key updated");

View file

@ -1,13 +1,13 @@
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
/* If you are missing that file, acquire a complete release at teeworlds.com. */
#include <base/system.h>
#include <base/hash_ctxt.h>
#include <engine/console.h>
#include "config.h"
#include "netban.h"
#include "network.h"
#include <engine/external/md5/md5.h>
#include <engine/message.h>
#include <engine/shared/protocol.h>
@ -145,16 +145,12 @@ int CNetServer::Update()
SECURITY_TOKEN CNetServer::GetToken(const NETADDR &Addr)
{
md5_state_t md5;
md5_byte_t digest[16];
SECURITY_TOKEN SecurityToken;
md5_init(&md5);
SHA256_CTX Sha256;
sha256_init(&Sha256);
sha256_update(&Sha256, (unsigned char*)m_SecurityTokenSeed, sizeof(m_SecurityTokenSeed));
sha256_update(&Sha256, (unsigned char*)&Addr, sizeof(Addr));
md5_append(&md5, (unsigned char*)m_SecurityTokenSeed, sizeof(m_SecurityTokenSeed));
md5_append(&md5, (unsigned char*)&Addr, sizeof(Addr));
md5_finish(&md5, digest);
SecurityToken = ToSecurityToken(digest);
SECURITY_TOKEN SecurityToken = ToSecurityToken(sha256_finish(&Sha256).data);
if (SecurityToken == NET_SECURITY_TOKEN_UNKNOWN ||
SecurityToken == NET_SECURITY_TOKEN_UNSUPPORTED)

View file

@ -1,6 +1,6 @@
#include "uuid_manager.h"
#include <engine/external/md5/md5.h>
#include <base/hash_ctxt.h>
#include <engine/shared/packer.h>
#include <stdio.h>
@ -26,19 +26,17 @@ CUuid RandomUuid()
CUuid CalculateUuid(const char *pName)
{
md5_state_t Md5;
md5_byte_t aDigest[16];
MD5_CTX Md5;
md5_init(&Md5);
md5_append(&Md5, TEEWORLDS_NAMESPACE.m_aData, sizeof(TEEWORLDS_NAMESPACE.m_aData));
md5_update(&Md5, TEEWORLDS_NAMESPACE.m_aData, sizeof(TEEWORLDS_NAMESPACE.m_aData));
// Without terminating NUL.
md5_append(&Md5, (const unsigned char *)pName, str_length(pName));
md5_finish(&Md5, aDigest);
md5_update(&Md5, (const unsigned char *)pName, str_length(pName));
MD5_DIGEST Digest = md5_finish(&Md5);
CUuid Result;
for(unsigned i = 0; i < sizeof(Result.m_aData); i++)
{
Result.m_aData[i] = aDigest[i];
Result.m_aData[i] = Digest.data[i];
}
Result.m_aData[6] &= 0x0f;

View file

@ -58,3 +58,56 @@ TEST(Hash, Sha256FromStr)
EXPECT_TRUE(sha256_from_str(&Sha256, "012345678901234567890123456789012345678901234567890123456789012x"));
EXPECT_TRUE(sha256_from_str(&Sha256, "x123456789012345678901234567890123456789012345678901234567890123"));
}
static void Expect2(MD5_DIGEST Actual, const char *pWanted)
{
char aActual[MD5_MAXSTRSIZE];
md5_str(Actual, aActual, sizeof(aActual));
EXPECT_STREQ(aActual, pWanted);
}
TEST(Hash, Md5)
{
// https://en.wikipedia.org/w/index.php?title=MD5&oldid=889664074#MD5_hashes
Expect2(md5("", 0), "d41d8cd98f00b204e9800998ecf8427e");
MD5_CTX ctxt;
md5_init(&ctxt);
Expect2(md5_finish(&ctxt), "d41d8cd98f00b204e9800998ecf8427e");
char QUICK_BROWN_FOX[] = "The quick brown fox jumps over the lazy dog.";
Expect2(md5(QUICK_BROWN_FOX, str_length(QUICK_BROWN_FOX)), "e4d909c290d0fb1ca068ffaddf22cbd0");
md5_init(&ctxt);
md5_update(&ctxt, "The ", 4);
md5_update(&ctxt, "quick ", 6);
md5_update(&ctxt, "brown ", 6);
md5_update(&ctxt, "fox ", 4);
md5_update(&ctxt, "jumps ", 6);
md5_update(&ctxt, "over ", 5);
md5_update(&ctxt, "the ", 4);
md5_update(&ctxt, "lazy ", 5);
md5_update(&ctxt, "dog.", 4);
Expect2(md5_finish(&ctxt), "e4d909c290d0fb1ca068ffaddf22cbd0");
}
TEST(Hash, Md5Eq)
{
EXPECT_EQ(md5("", 0), md5("", 0));
}
TEST(Hash, Md5FromStr)
{
MD5_DIGEST Expected = {{
0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89,
0x01, 0x23, 0x45, 0x67, 0x89, 0x01,
}};
MD5_DIGEST Md5;
EXPECT_FALSE(md5_from_str(&Md5, "01234567890123456789012345678901"));
EXPECT_EQ(Md5, Expected);
EXPECT_TRUE(md5_from_str(&Md5, "0123456789012345678901234567890"));
EXPECT_TRUE(md5_from_str(&Md5, "012345678901234567890123456789012"));
EXPECT_TRUE(md5_from_str(&Md5, ""));
EXPECT_TRUE(md5_from_str(&Md5, "0123456789012345678901234567890x"));
EXPECT_TRUE(md5_from_str(&Md5, "x1234567890123456789012345678901"));
}