File: password_hasher.cc

package info (click to toggle)
mysql-8.0 8.0.43-3
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 1,273,924 kB
  • sloc: cpp: 4,684,605; ansic: 412,450; pascal: 108,398; java: 83,641; perl: 30,221; cs: 27,067; sql: 26,594; sh: 24,181; python: 21,816; yacc: 17,169; php: 11,522; xml: 7,388; javascript: 7,076; makefile: 2,194; lex: 1,075; awk: 670; asm: 520; objc: 183; ruby: 97; lisp: 86
file content (172 lines) | stat: -rw-r--r-- 5,943 bytes parent folder | download
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
/*
 * Copyright (c) 2015, 2025, Oracle and/or its affiliates.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, version 2.0,
 * as published by the Free Software Foundation.
 *
 * This program is designed to work with certain software (including
 * but not limited to OpenSSL) that is licensed under separate terms,
 * as designated in a particular file or component or in included license
 * documentation.  The authors of MySQL hereby grant you an additional
 * permission to link the program and your derivative works with the
 * separately licensed software that they have either included with
 * the program or referenced in the documentation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License, version 2.0, for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
 */

#include "plugin/x/client/authentication/password_hasher.h"

#include <assert.h>
#include <openssl/rand.h>
#include <openssl/sha.h>
#include <sys/types.h>

#include <cstdint>
#include <cstring>
#include <stdexcept>

#include "plugin/x/client/authentication/mysql41_hash.h"

#define PVERSION41_CHAR '*'
#define SCRAMBLE_LENGTH 20

namespace xcl {
namespace password_hasher {
namespace {

const char *_dig_vec_upper = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

void compute_two_stage_mysql41_hash(const std::string &password,
                                    uint8_t *hash_stage1,
                                    uint8_t *hash_stage2) {
  /* Stage 1: hash pwd */
  compute_mysql41_hash(hash_stage1, password.c_str(), password.size());

  /* Stage 2 : hash first stage's output. */
  compute_mysql41_hash(hash_stage2, reinterpret_cast<const char *>(hash_stage1),
                       MYSQL41_HASH_SIZE);
}

void my_crypt(char *to, const uint8_t *s1, const uint8_t *s2, size_t len) {
  const uint8_t *s1_end = s1 + len;

  while (s1 < s1_end) *to++ = *s1++ ^ *s2++;
}

}  // namespace

/**
  Convert given octet sequence to asciiz string of hex characters;
  str..str+len and 'to' may not overlap.

  @param [out] to Output buffer. Must be at least 2*len+1 bytes
  @param [in] str Input string
  @param [in] len Length of the input string

  @return End of output buffer at position buf+len*2
 */
char *octet2hex(char *to, const char *str, size_t len) {
  const char *str_end = str + len;
  for (; str != str_end; ++str) {
    *to++ = _dig_vec_upper[((uint8_t)*str) >> 4];
    *to++ = _dig_vec_upper[((uint8_t)*str) & 0x0F];
  }
  *to = '\0';
  return to;
}

/** Generate human readable string from the binary
 *  result from hashing function.
 *
 *  @return empty string when invalid hash was set, else
 *          human readable version of hash_stage2.
 */
std::string get_password_from_salt(const std::string &hash_stage2) {
  const uint8_t result_size =
      2 * MYSQL41_HASH_SIZE + 1 /* '\0' sign */ + 1 /* '*' sign */;
  char result[result_size] = {0};

  if (hash_stage2.length() != MYSQL41_HASH_SIZE) return "";

  result[0] = PVERSION41_CHAR;
  octet2hex(&result[1], &hash_stage2[0], MYSQL41_HASH_SIZE);

  // Skip the additional \0 sign added by octet2hex
  return {std::begin(result), std::end(result) - 1};
}

std::string generate_user_salt() {
  std::string result(SCRAMBLE_LENGTH, '\0');
  char *buffer = &result[0];
  char *end = buffer + result.length() - 1;

  RAND_bytes(reinterpret_cast<unsigned char *>(buffer), SCRAMBLE_LENGTH);

  /* Sequence must be a legal UTF8 string */
  for (; buffer < end; buffer++) {
    *buffer &= 0x7f;
    if (*buffer == '\0' || *buffer == '$') *buffer = *buffer + 1;
  }

  return result;
}

bool check_scramble_mysql41_hash(const std::string &scramble_arg,
                                 const std::string &message,
                                 const uint8_t *hash_stage2) {
  char buf[MYSQL41_HASH_SIZE];
  uint8_t hash_stage2_reassured[MYSQL41_HASH_SIZE];

  assert(MYSQL41_HASH_SIZE == SCRAMBLE_LENGTH);
  /* create key to encrypt scramble */
  compute_mysql41_hash_multi(
      reinterpret_cast<uint8_t *>(buf), message.c_str(), message.size(),
      reinterpret_cast<const char *>(hash_stage2), MYSQL41_HASH_SIZE);

  /* encrypt scramble */
  my_crypt(buf, reinterpret_cast<const uint8_t *>(buf),
           reinterpret_cast<const uint8_t *>(scramble_arg.c_str()),
           SCRAMBLE_LENGTH);

  /* now buf supposedly contains hash_stage1: so we can get hash_stage2 */
  compute_mysql41_hash(reinterpret_cast<uint8_t *>(hash_stage2_reassured),
                       reinterpret_cast<const char *>(buf), MYSQL41_HASH_SIZE);

  return 0 == memcmp(hash_stage2, hash_stage2_reassured, MYSQL41_HASH_SIZE);
}

std::string scramble(const std::string &message, const std::string &password) {
  uint8_t hash_stage1[MYSQL41_HASH_SIZE];
  uint8_t hash_stage2[MYSQL41_HASH_SIZE];
  std::string result(SCRAMBLE_LENGTH, '\0');

  result.at(SCRAMBLE_LENGTH - 1) = '\0';

  assert(MYSQL41_HASH_SIZE == SCRAMBLE_LENGTH);

  /* Two stage SHA1 hash of the pwd */
  compute_two_stage_mysql41_hash(password,
                                 reinterpret_cast<uint8_t *>(hash_stage1),
                                 reinterpret_cast<uint8_t *>(hash_stage2));

  /* create crypt string as sha1(message, hash_stage2) */
  compute_mysql41_hash_multi(
      reinterpret_cast<uint8_t *>(&result[0]), message.c_str(), message.size(),
      reinterpret_cast<const char *>(hash_stage2), MYSQL41_HASH_SIZE);
  my_crypt(&result[0], reinterpret_cast<const uint8_t *>(&result[0]),
           hash_stage1, SCRAMBLE_LENGTH);

  return result;
}

}  // namespace password_hasher
}  // namespace xcl