NAWA 0.9
Web Application Framework for C++
Argon2HashingEngine.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2019-2022 Tobias Flaig.
3 *
4 * This file is part of nawa.
5 *
6 * nawa is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License,
8 * version 3, as published by the Free Software Foundation.
9 *
10 * nawa is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with nawa. If not, see <https://www.gnu.org/licenses/>.
17 */
18
24#include <argon2.h>
25#include <cstring>
26#include <nawa/Exception.h>
28#include <nawa/util/encoding.h>
29#include <random>
30#include <regex>
31
32using namespace nawa;
33using namespace std;
34
35struct hashing::Argon2HashingEngine::Data {
36 Algorithm algorithm;
37 uint32_t timeCost;
38 uint32_t memoryCost;
39 uint32_t parallelism;
40 string salt;
41 size_t hashLen;
43 Data(Algorithm algorithm, uint32_t timeCost, uint32_t memoryCost, uint32_t parallelism, string salt, size_t hashLen)
44 : algorithm(algorithm), timeCost(timeCost), memoryCost(memoryCost), parallelism(parallelism),
45 salt(std::move(salt)), hashLen(hashLen) {}
46};
47
48NAWA_DEFAULT_DESTRUCTOR_IMPL_WITH_NS(hashing, Argon2HashingEngine)
49
51 uint32_t timeCost, uint32_t memoryCost, uint32_t parallelism,
52 std::string salt, size_t hashLen) {
53 data = make_unique<Data>(algorithm, timeCost, memoryCost, parallelism, std::move(salt), hashLen);
54}
55
56std::string hashing::Argon2HashingEngine::generateHash(std::string input) const {
57
58 // check validity of parameters
59 if (!data->salt.empty() && data->salt.length() < ARGON2_MIN_SALT_LENGTH) {
60 throw Exception(__PRETTY_FUNCTION__, 10,
61 "Provided user-defined salt is not long enough");
62 }
63
64 string actualSalt = data->salt;
65 if (data->salt.empty()) {
66 // generate random salt (16 bytes)
67 random_device rd;
68 stringstream sstr;
69 if (rd.entropy() == 32) {
70 uniform_int_distribution<uint32_t> distribution(0, 0xffffffff);
71 for (int i = 0; i < 4; ++i) {
72 uint32_t val = distribution(rd);
73 sstr << (char) (val & 0xff);
74 sstr << (char) ((val >> 8) & 0xff);
75 sstr << (char) ((val >> 16) & 0xff);
76 sstr << (char) ((val >> 24) & 0xff);
77 }
78 } else {
79 uniform_int_distribution<uint16_t> distribution(0, 0xffff);
80 for (int i = 0; i < 8; ++i) {
81 uint16_t val = distribution(rd);
82 sstr << (char) (val & 0xff);
83 sstr << (char) ((val >> 8) & 0xff);
84 }
85 }
86 actualSalt = sstr.str();
87 }
88
89 int errorCode = 0;
90 size_t encodedHashCeil = 50 + (actualSalt.length() * 4) / 3 + (data->hashLen * 4) / 3;
91 char c_hash[encodedHashCeil];
92 memset(c_hash, '\0', encodedHashCeil);
93
94 switch (data->algorithm) {
96 errorCode = argon2i_hash_encoded(data->timeCost, data->memoryCost, data->parallelism,
97 (void*) input.c_str(), input.length(),
98 (void*) actualSalt.c_str(), actualSalt.length(), data->hashLen, c_hash,
99 encodedHashCeil);
100 break;
102 errorCode = argon2d_hash_encoded(data->timeCost, data->memoryCost, data->parallelism,
103 (void*) input.c_str(), input.length(),
104 (void*) actualSalt.c_str(), actualSalt.length(), data->hashLen, c_hash,
105 encodedHashCeil);
106 break;
108 errorCode = argon2id_hash_encoded(data->timeCost, data->memoryCost, data->parallelism,
109 (void*) input.c_str(), input.length(),
110 (void*) actualSalt.c_str(), actualSalt.length(), data->hashLen, c_hash,
111 encodedHashCeil);
112 break;
113 }
114
115 // error handling
116 if (errorCode != ARGON2_OK) {
117 throw Exception(__PRETTY_FUNCTION__, 10,
118 string("Argon2 error: ") + argon2_error_message(errorCode));
119 }
120
121 return {c_hash};
122}
123
124bool hashing::Argon2HashingEngine::verifyHash(std::string input, std::string hash) const {
125
126 // split the hash and create a new object with the properties of the hash
127 regex rgx(
128 R"(\$argon2(i|d|id)\$(v=([0-9]+))?\$m=([0-9]+),t=([0-9]+),p=([0-9]+)\$([A-Za-z0-9+\/]+={0,2})\$([A-Za-z0-9+\/]+={0,2}))");
129 smatch matches;
130 regex_match(hash, matches, rgx);
131 Algorithm algorithm1;
132 uint32_t version1;
133 uint32_t memoryCost1;
134 uint32_t timeCost1;
135 uint32_t parallelism1;
136 string salt1;
137 string hash1;
138 if (matches.size() == 9) {
139 if (matches[1] == "d")
140 algorithm1 = Algorithm::ARGON2D;
141 else if (matches[1] == "id")
142 algorithm1 = Algorithm::ARGON2ID;
143 else
144 algorithm1 = Algorithm::ARGON2I;
145 try {
146 version1 = stoul(matches[3]);
147 memoryCost1 = stoul(matches[4]);
148 timeCost1 = stoul(matches[5]);
149 parallelism1 = stoul(matches[6]);
150 salt1 = encoding::base64Decode(matches[7]);
151 hash1 = encoding::base64Decode(matches[8]);
152 } catch (...) {
153 return false;
154 }
155 } else {
156 return false;
157 }
158 if (version1 != 19) {
159 return false;
160 }
161
162 auto engine1 = Argon2HashingEngine(algorithm1, timeCost1, memoryCost1, parallelism1, salt1, hash1.length());
163 string inputHash;
164 try {
165 inputHash = engine1.generateHash(input);
166 inputHash = encoding::base64Decode(inputHash.substr(inputHash.find_last_of('$') + 1));
167 } catch (Exception const&) {
168 return false;
169 }
170
171 if (hash1.length() != inputHash.length())
172 return false;
173
174 auto u1 = (const unsigned char*) hash1.c_str();
175 auto u2 = (const unsigned char*) inputHash.c_str();
176
177 int ret = 0;
178 for (int i = 0; i < hash1.length(); ++i)
179 ret |= (u1[i] ^ u2[i]);
180
181 return ret == 0;
182}
Exception class that can be used by apps to catch errors resulting from nawa function calls.
bool verifyHash(std::string input, std::string hash) const override
Namespace containing functions for text encoding and decoding.
#define NAWA_DEFAULT_DESTRUCTOR_IMPL_WITH_NS(Namespace, Class)
Definition: macros.h:37
std::string base64Decode(std::string const &input)
Definition: encoding.cpp:286
Definition: AppInit.h:31