aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib/passhash/bcrypt/bcrypt.cpp
blob: e16f9935b26776bb40cd46fed32c4f2b8f0a0657 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
/*
* Bcrypt Password Hashing
* (C) 2010,2018 Jack Lloyd
*
* Botan is released under the Simplified BSD License (see license.txt)
*/

#include <botan/bcrypt.h>
#include <botan/rng.h>
#include <botan/internal/blowfish.h>
#include <botan/base64.h>
#include <botan/internal/parsing.h>

namespace Botan {

namespace {

// Bcrypt uses a non-standard base64 alphabet
alignas(256) const uint8_t OPENBSD_BASE64_SUB[256] = {
   0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
   0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
   0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
   0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x38, 0x80, 0x80, 0x80, 0x39,
   0x79, 0x7A, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x80, 0x80,
   0x80, 0x80, 0x80, 0x80, 0x80, 0x2E, 0x2F, 0x41, 0x42, 0x43, 0x44, 0x45,
   0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51,
   0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x80, 0x80, 0x80, 0x80, 0x80,
   0x80, 0x59, 0x5A, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
   0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75,
   0x76, 0x77, 0x78, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
   0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
   0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
   0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
   0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
   0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
   0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
   0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
   0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
   0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
   0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
   0x80, 0x80, 0x80, 0x80
};

alignas(256) const uint8_t OPENBSD_BASE64_SUB_INV[256] = {
   0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
   0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
   0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
   0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x41, 0x42,
   0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2B, 0x2F, 0x80, 0x80,
   0x80, 0x80, 0x80, 0x80, 0x80, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
   0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55,
   0x56, 0x57, 0x58, 0x59, 0x5A, 0x61, 0x62, 0x80, 0x80, 0x80, 0x80, 0x80,
   0x80, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D,
   0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
   0x7A, 0x30, 0x31, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
   0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
   0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
   0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
   0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
   0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
   0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
   0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
   0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
   0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
   0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
   0x80, 0x80, 0x80, 0x80
};

std::string bcrypt_base64_encode(const uint8_t input[], size_t length)
   {
   std::string b64 = base64_encode(input, length);

   while(b64.size() && b64[b64.size()-1] == '=')
      b64 = b64.substr(0, b64.size() - 1);

   for(size_t i = 0; i != b64.size(); ++i)
      b64[i] = OPENBSD_BASE64_SUB[static_cast<uint8_t>(b64[i])];

   return b64;
   }

std::vector<uint8_t> bcrypt_base64_decode(std::string input)
   {
   for(size_t i = 0; i != input.size(); ++i)
      input[i] = OPENBSD_BASE64_SUB_INV[static_cast<uint8_t>(input[i])];

   return unlock(base64_decode(input));
   }

std::string make_bcrypt(const std::string& pass,
                        const std::vector<uint8_t>& salt,
                        uint16_t work_factor,
                        char version)
   {
   /*
   * On a 4 GHz Skylake, workfactor == 18 takes about 15 seconds to
   * hash a password. This seems like a reasonable upper bound for the
   * time being.
   * Bcrypt allows up to work factor 31 (2^31 iterations)
   */
   BOTAN_ARG_CHECK(work_factor >= 4 && work_factor <= 18,
                   "Invalid bcrypt work factor");

   static const uint8_t BCRYPT_MAGIC[8*3] = {
      0x4F, 0x72, 0x70, 0x68, 0x65, 0x61, 0x6E, 0x42,
      0x65, 0x68, 0x6F, 0x6C, 0x64, 0x65, 0x72, 0x53,
      0x63, 0x72, 0x79, 0x44, 0x6F, 0x75, 0x62, 0x74
   };

   Blowfish blowfish;

   // Include the trailing NULL byte, so we need c_str() not data()
   blowfish.salted_set_key(cast_char_ptr_to_uint8(pass.c_str()),
                           pass.length() + 1,
                           salt.data(),
                           salt.size(),
                           work_factor);

   std::vector<uint8_t> ctext(BCRYPT_MAGIC, BCRYPT_MAGIC + 8*3);

   for(size_t i = 0; i != 64; ++i)
      blowfish.encrypt_n(ctext.data(), ctext.data(), 3);

   std::string salt_b64 = bcrypt_base64_encode(salt.data(), salt.size());

   std::string work_factor_str = std::to_string(work_factor);
   if(work_factor_str.length() == 1)
      work_factor_str = "0" + work_factor_str;

   return "$2" + std::string(1, version) + "$" + work_factor_str +
          "$" + salt_b64.substr(0, 22) +
          bcrypt_base64_encode(ctext.data(), ctext.size() - 1);
   }

}

std::string generate_bcrypt(const std::string& pass,
                            RandomNumberGenerator& rng,
                            uint16_t work_factor,
                            char version)
   {
   /*
   2a, 2b and 2y are identical for our purposes because our implementation of 2a
   never had the truncation or signed char bugs in the first place.
   */

   if(version != 'a' && version != 'b' && version != 'y')
      throw Invalid_Argument("Unknown bcrypt version '" + std::string(1, version) + "'");

   std::vector<uint8_t> salt;
   rng.random_vec(salt, 16);
   return make_bcrypt(pass, salt, work_factor, version);
   }

bool check_bcrypt(const std::string& pass, const std::string& hash)
   {
   if(hash.size() != 60 ||
      hash[0] != '$' || hash[1] != '2' || hash[3] != '$' || hash[6] != '$')
      {
      return false;
      }

   const char bcrypt_version = hash[2];

   if(bcrypt_version != 'a' && bcrypt_version != 'b' && bcrypt_version != 'y')
      {
      return false;
      }

   const uint16_t workfactor = to_uint16(hash.substr(4, 2));

   const std::vector<uint8_t> salt = bcrypt_base64_decode(hash.substr(7, 22));
   if(salt.size() != 16)
      return false;

   const std::string compare = make_bcrypt(pass, salt, workfactor, bcrypt_version);

   return same_mem(hash.data(), compare.data(), compare.size());
   }

}