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