The Battle for Wesnoth  1.19.10+dev
hash.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2025
3  by Thomas Baumhauer <thomas.baumhauer@NOSPAMgmail.com>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #include "hash.hpp"
17 
18 #include "serialization/base64.hpp"
19 
20 #include <string>
21 #include <sstream>
22 #include <string.h>
23 #include <assert.h>
24 
25 extern "C" {
27 }
28 
29 #ifndef __APPLE__
30 
31 #include <openssl/evp.h>
32 
33 #else
34 
35 #include <CommonCrypto/CommonDigest.h>
36 
37 static_assert(utils::md5::DIGEST_SIZE == CC_MD5_DIGEST_LENGTH, "Constants mismatch");
38 
39 #endif
40 
41 namespace {
42 
43 const std::string hash_prefix = "$H$";
44 
45 template<std::size_t len>
46 std::string encode_hash(const std::array<uint8_t, len>& bytes) {
47  return crypt64::encode(bytes);
48 }
49 
50 template<std::size_t len>
51 std::string hexencode_hash(const std::array<uint8_t, len>& input) {
52  std::ostringstream sout;
53  sout << std::hex;
54  for(uint8_t c : input) {
55  sout << static_cast<int>(c);
56  }
57  return sout.str();
58 }
59 
60 }
61 
62 namespace utils {
63 
64 md5::md5(const std::string& input) {
65 
66 #ifndef __APPLE__
67  EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
68  unsigned int md5_digest_len = EVP_MD_size(EVP_md5());
69  assert(utils::md5::DIGEST_SIZE == md5_digest_len);
70 
71  // MD5_Init
72  EVP_DigestInit_ex(mdctx, EVP_md5(), nullptr);
73 
74  // MD5_Update
75  EVP_DigestUpdate(mdctx, input.c_str(), input.size());
76 
77  // MD5_Final
78  EVP_DigestFinal_ex(mdctx, hash.data(), &md5_digest_len);
79  EVP_MD_CTX_free(mdctx);
80 #else
81  CC_MD5(input.data(), static_cast<CC_LONG>(input.size()), hash.data());
82 #endif
83 
84 }
85 
86 int md5::get_iteration_count(const std::string& hash) {
87  return crypt64::decode(hash[3]);
88 }
89 
90 std::string md5::get_salt(const std::string& hash) {
91  return hash.substr(4,8);
92 }
93 
94 bool md5::is_valid_prefix(const std::string& hash)
95 {
96  return hash.substr(0,3) == hash_prefix;
97 }
98 
99 bool md5::is_valid_hash(const std::string& hash) {
100  if(hash.size() != 34) return false;
101  if(!is_valid_prefix(hash)) return false;
102 
103  const int iteration_count = get_iteration_count(hash);
104  if(iteration_count < 7 || iteration_count > 30) return false;
105 
106  return true;
107 }
108 
109 md5::md5(const std::string& password, const std::string& salt, int iteration_count)
110 {
111  iteration_count = 1 << iteration_count;
112 
113  hash = md5(salt + password).raw_digest();
114  do {
115  hash = md5(std::string(hash.begin(), hash.end()).append(password)).raw_digest();
116  } while(--iteration_count);
117 }
118 
119 std::string md5::hex_digest() const
120 {
121  return hexencode_hash<DIGEST_SIZE>(hash);
122 }
123 
124 std::string md5::base64_digest() const
125 {
126  return encode_hash<DIGEST_SIZE>(hash);
127 }
128 
129 bcrypt::bcrypt(const std::string& input)
130 {
131  assert(is_valid_prefix(input));
132 
133  iteration_count_delim_pos = input.find('$', 4);
134  if(iteration_count_delim_pos == std::string::npos)
135  throw hash_error("hash string malformed");
136 }
137 
138 bcrypt bcrypt::from_salted_salt(const std::string& input)
139 {
140  bcrypt hash { input };
141  std::string bcrypt_salt = input.substr(0, hash.iteration_count_delim_pos + 23);
142  if(bcrypt_salt.size() >= BCRYPT_HASHSIZE)
143  throw hash_error("hash string too large");
144  strcpy(hash.hash.data(), bcrypt_salt.c_str());
145 
146  return hash;
147 }
148 
149 bcrypt bcrypt::from_hash_string(const std::string& input)
150 {
151  bcrypt hash { input };
152  if(input.size() >= BCRYPT_HASHSIZE)
153  throw hash_error("hash string too large");
154  strcpy(hash.hash.data(), input.c_str());
155 
156  return hash;
157 }
158 
159 bcrypt bcrypt::hash_pw(const std::string& password, bcrypt& salt)
160 {
161  bcrypt hash;
162  if(!php_crypt_blowfish_rn(password.c_str(), salt.hash.data(), hash.hash.data(), BCRYPT_HASHSIZE))
163  throw hash_error("failed to hash password");
164 
165  return hash;
166 }
167 
168 bool bcrypt::is_valid_prefix(const std::string& hash) {
169  return ((hash.compare(0, 4, "$2a$") == 0)
170  || (hash.compare(0, 4, "$2b$") == 0)
171  || (hash.compare(0, 4, "$2x$") == 0)
172  || (hash.compare(0, 4, "$2y$") == 0));
173 }
174 
175 std::string bcrypt::get_salt() const
176 {
177  std::size_t salt_pos = iteration_count_delim_pos + 23;
178  if(salt_pos >= BCRYPT_HASHSIZE)
179  throw hash_error("malformed hash");
180  return std::string(hash.data(), salt_pos);
181 }
182 
183 std::string bcrypt::hex_digest() const
184 {
185  return std::string(hash.data());
186 }
187 
188 std::string bcrypt::base64_digest() const
189 {
190  return std::string(hash.data());
191 }
192 
193 } // namespace utils
std::size_t iteration_count_delim_pos
Definition: hash.hpp:75
virtual std::string base64_digest() const override
Definition: hash.cpp:188
static bool is_valid_prefix(const std::string &hash)
Definition: hash.cpp:168
std::string get_salt() const
Definition: hash.cpp:175
static bcrypt from_hash_string(const std::string &input)
Definition: hash.cpp:149
virtual std::string hex_digest() const override
Definition: hash.cpp:183
static bcrypt from_salted_salt(const std::string &input)
Definition: hash.cpp:138
static bcrypt hash_pw(const std::string &password, bcrypt &salt)
Definition: hash.cpp:159
static const unsigned int DIGEST_SIZE
Definition: hash.hpp:48
std::array< uint8_t, sz > hash
Definition: hash.hpp:46
static bool is_valid_prefix(const std::string &hash)
Definition: hash.cpp:94
virtual std::string base64_digest() const override
Definition: hash.cpp:124
static std::string get_salt(const std::string &hash)
Definition: hash.cpp:90
virtual std::string hex_digest() const override
Definition: hash.cpp:119
static int get_iteration_count(const std::string &hash)
Definition: hash.cpp:86
static bool is_valid_hash(const std::string &hash)
Definition: hash.cpp:99
md5(const std::string &input)
Definition: hash.cpp:64
char * php_crypt_blowfish_rn(const char *key, const char *setting, char *output, int size)
static const int BCRYPT_HASHSIZE
Definition: hash.hpp:25
std::string encode(utils::byte_view bytes)
Definition: base64.cpp:235
std::vector< uint8_t > decode(std::string_view in)
Definition: base64.cpp:231
mock_char c