The Battle for Wesnoth  1.19.0+dev
hash.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2024
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  utils::byte_string_view view{bytes.data(), len};
48  return crypt64::encode(view);
49 }
50 
51 template<std::size_t len>
52 std::string hexencode_hash(const std::array<uint8_t, len>& input) {
53  std::ostringstream sout;
54  sout << std::hex;
55  for(uint8_t c : input) {
56  sout << static_cast<int>(c);
57  }
58  return sout.str();
59 }
60 
61 }
62 
63 namespace utils {
64 
65 md5::md5(const std::string& input) {
66 
67 #ifndef __APPLE__
68  EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
69  unsigned int md5_digest_len = EVP_MD_size(EVP_md5());
70  assert(utils::md5::DIGEST_SIZE == md5_digest_len);
71 
72  // MD5_Init
73  EVP_DigestInit_ex(mdctx, EVP_md5(), NULL);
74 
75  // MD5_Update
76  EVP_DigestUpdate(mdctx, input.c_str(), input.size());
77 
78  // MD5_Final
79  EVP_DigestFinal_ex(mdctx, hash.data(), &md5_digest_len);
80  EVP_MD_CTX_free(mdctx);
81 #else
82  CC_MD5(input.data(), static_cast<CC_LONG>(input.size()), hash.data());
83 #endif
84 
85 }
86 
87 int md5::get_iteration_count(const std::string& hash) {
88  return crypt64::decode(hash[3]);
89 }
90 
91 std::string md5::get_salt(const std::string& hash) {
92  return hash.substr(4,8);
93 }
94 
95 bool md5::is_valid_prefix(const std::string& hash)
96 {
97  return hash.substr(0,3) == hash_prefix;
98 }
99 
100 bool md5::is_valid_hash(const std::string& hash) {
101  if(hash.size() != 34) return false;
102  if(!is_valid_prefix(hash)) return false;
103 
104  const int iteration_count = get_iteration_count(hash);
105  if(iteration_count < 7 || iteration_count > 30) return false;
106 
107  return true;
108 }
109 
110 md5::md5(const std::string& password, const std::string& salt, int iteration_count)
111 {
112  iteration_count = 1 << iteration_count;
113 
114  hash = md5(salt + password).raw_digest();
115  do {
116  hash = md5(std::string(hash.begin(), hash.end()).append(password)).raw_digest();
117  } while(--iteration_count);
118 }
119 
120 std::string md5::hex_digest() const
121 {
122  return hexencode_hash<DIGEST_SIZE>(hash);
123 }
124 
125 std::string md5::base64_digest() const
126 {
127  return encode_hash<DIGEST_SIZE>(hash);
128 }
129 
130 bcrypt::bcrypt(const std::string& input)
131 {
132  assert(is_valid_prefix(input));
133 
134  iteration_count_delim_pos = input.find('$', 4);
135  if(iteration_count_delim_pos == std::string::npos)
136  throw hash_error("hash string malformed");
137 }
138 
139 bcrypt bcrypt::from_salted_salt(const std::string& input)
140 {
141  bcrypt hash { input };
142  std::string bcrypt_salt = input.substr(0, hash.iteration_count_delim_pos + 23);
143  if(bcrypt_salt.size() >= BCRYPT_HASHSIZE)
144  throw hash_error("hash string too large");
145  strcpy(hash.hash.data(), bcrypt_salt.c_str());
146 
147  return hash;
148 }
149 
150 bcrypt bcrypt::from_hash_string(const std::string& input)
151 {
152  bcrypt hash { input };
153  if(input.size() >= BCRYPT_HASHSIZE)
154  throw hash_error("hash string too large");
155  strcpy(hash.hash.data(), input.c_str());
156 
157  return hash;
158 }
159 
160 bcrypt bcrypt::hash_pw(const std::string& password, bcrypt& salt)
161 {
162  bcrypt hash;
163  if(!php_crypt_blowfish_rn(password.c_str(), salt.hash.data(), hash.hash.data(), BCRYPT_HASHSIZE))
164  throw hash_error("failed to hash password");
165 
166  return hash;
167 }
168 
169 bool bcrypt::is_valid_prefix(const std::string& hash) {
170  return ((hash.compare(0, 4, "$2a$") == 0)
171  || (hash.compare(0, 4, "$2b$") == 0)
172  || (hash.compare(0, 4, "$2x$") == 0)
173  || (hash.compare(0, 4, "$2y$") == 0));
174 }
175 
176 std::string bcrypt::get_salt() const
177 {
178  std::size_t salt_pos = iteration_count_delim_pos + 23;
179  if(salt_pos >= BCRYPT_HASHSIZE)
180  throw hash_error("malformed hash");
181  return std::string(hash.data(), salt_pos);
182 }
183 
184 std::string bcrypt::hex_digest() const
185 {
186  return std::string(hash.data());
187 }
188 
189 std::string bcrypt::base64_digest() const
190 {
191  return std::string(hash.data());
192 }
193 
194 } // namespace utils
std::size_t iteration_count_delim_pos
Definition: hash.hpp:75
virtual std::string base64_digest() const override
Definition: hash.cpp:189
static bool is_valid_prefix(const std::string &hash)
Definition: hash.cpp:169
std::string get_salt() const
Definition: hash.cpp:176
static bcrypt from_hash_string(const std::string &input)
Definition: hash.cpp:150
virtual std::string hex_digest() const override
Definition: hash.cpp:184
static bcrypt from_salted_salt(const std::string &input)
Definition: hash.cpp:139
static bcrypt hash_pw(const std::string &password, bcrypt &salt)
Definition: hash.cpp:160
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:95
virtual std::string base64_digest() const override
Definition: hash.cpp:125
static std::string get_salt(const std::string &hash)
Definition: hash.cpp:91
virtual std::string hex_digest() const override
Definition: hash.cpp:120
static int get_iteration_count(const std::string &hash)
Definition: hash.cpp:87
static bool is_valid_hash(const std::string &hash)
Definition: hash.cpp:100
md5(const std::string &input)
Definition: hash.cpp:65
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_string_view bytes)
Definition: base64.cpp:235
std::vector< uint8_t > decode(std::string_view in)
Definition: base64.cpp:231
std::basic_string_view< uint8_t > byte_string_view
Definition: base64.hpp:24
mock_char c