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