|
| 1 | +/* |
| 2 | + * IRC - Internet Relay Chat, ircd/ircd_crypt_bcrypt.c |
| 3 | + * |
| 4 | + * This program is free software; you can redistribute it and/or modify |
| 5 | + * it under the terms of the GNU General Public License as published by |
| 6 | + * the Free Software Foundation; either version 1, or (at your option) |
| 7 | + * any later version. |
| 8 | + * |
| 9 | + * This program is distributed in the hope that it will be useful, |
| 10 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 12 | + * GNU General Public License for more details. |
| 13 | + * |
| 14 | + * You should have received a copy of the GNU General Public License |
| 15 | + * along with this program; if not, write to the Free Software |
| 16 | + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
| 17 | + */ |
| 18 | +/** |
| 19 | + * @file |
| 20 | + * @brief Bcrypt password hashing routines |
| 21 | + * |
| 22 | + * Provides bcrypt ($2y$) password hashing using the system's crypt() function. |
| 23 | + * Requires a system with bcrypt support in libcrypt (glibc 2.7+ or libxcrypt). |
| 24 | + */ |
| 25 | +#define _XOPEN_SOURCE 500 |
| 26 | + |
| 27 | +#include "config.h" |
| 28 | +#include "ircd_crypt.h" |
| 29 | +#include "ircd_crypt_bcrypt.h" |
| 30 | +#include "ircd_log.h" |
| 31 | +#include "s_debug.h" |
| 32 | +#include "ircd_alloc.h" |
| 33 | + |
| 34 | +/* #include <assert.h> -- Now using assert in ircd_log.h */ |
| 35 | +#include <unistd.h> |
| 36 | +#include <string.h> |
| 37 | +#include <stdlib.h> |
| 38 | +#include <time.h> |
| 39 | +#include <fcntl.h> |
| 40 | +#ifdef HAVE_CRYPT_H |
| 41 | +#include <crypt.h> |
| 42 | +#endif |
| 43 | + |
| 44 | +/* Bcrypt uses a custom base64 alphabet */ |
| 45 | +static const char bcrypt_base64[] = |
| 46 | + "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; |
| 47 | + |
| 48 | +/* Default cost factor (2^12 = 4096 iterations) */ |
| 49 | +#define BCRYPT_DEFAULT_COST 12 |
| 50 | + |
| 51 | +/** Generate random bytes from /dev/urandom |
| 52 | + * @param buf Buffer to fill |
| 53 | + * @param len Number of bytes to generate |
| 54 | + * @return 0 on success, -1 on failure |
| 55 | + */ |
| 56 | +static int get_random_bytes(unsigned char* buf, size_t len) |
| 57 | +{ |
| 58 | + int fd; |
| 59 | + ssize_t n; |
| 60 | + |
| 61 | + fd = open("/dev/urandom", O_RDONLY); |
| 62 | + if (fd < 0) |
| 63 | + return -1; |
| 64 | + |
| 65 | + n = read(fd, buf, len); |
| 66 | + close(fd); |
| 67 | + |
| 68 | + return (n == (ssize_t)len) ? 0 : -1; |
| 69 | +} |
| 70 | + |
| 71 | +/** Generate a bcrypt salt string |
| 72 | + * @param salt Buffer to store the salt (must be at least 30 bytes) |
| 73 | + * @param cost Cost factor (4-31, recommend 10-12) |
| 74 | + * @return Pointer to salt, or NULL on failure |
| 75 | + */ |
| 76 | +static char* generate_bcrypt_salt(char* salt, int cost) |
| 77 | +{ |
| 78 | + unsigned char raw[16]; |
| 79 | + int i; |
| 80 | + unsigned long v; |
| 81 | + |
| 82 | + if (cost < 4) cost = 4; |
| 83 | + if (cost > 31) cost = 31; |
| 84 | + |
| 85 | + if (get_random_bytes(raw, 16) < 0) |
| 86 | + return NULL; |
| 87 | + |
| 88 | + /* Format: $2y$XX$ followed by 22 base64 characters */ |
| 89 | + sprintf(salt, "$2y$%02d$", cost); |
| 90 | + |
| 91 | + /* Encode 16 bytes (128 bits) into 22 base64 characters */ |
| 92 | + /* Each group of 3 bytes becomes 4 base64 chars, with padding handled specially */ |
| 93 | + for (i = 0; i < 5; i++) { |
| 94 | + v = (raw[i*3] << 16) | (raw[i*3+1] << 8) | raw[i*3+2]; |
| 95 | + salt[7 + i*4] = bcrypt_base64[(v >> 18) & 0x3f]; |
| 96 | + salt[7 + i*4 + 1] = bcrypt_base64[(v >> 12) & 0x3f]; |
| 97 | + salt[7 + i*4 + 2] = bcrypt_base64[(v >> 6) & 0x3f]; |
| 98 | + salt[7 + i*4 + 3] = bcrypt_base64[v & 0x3f]; |
| 99 | + } |
| 100 | + /* Last byte */ |
| 101 | + v = raw[15]; |
| 102 | + salt[27] = bcrypt_base64[(v >> 2) & 0x3f]; |
| 103 | + salt[28] = bcrypt_base64[(v << 4) & 0x3f]; |
| 104 | + salt[29] = '\0'; |
| 105 | + |
| 106 | + return salt; |
| 107 | +} |
| 108 | + |
| 109 | +/** Bcrypt password hashing function |
| 110 | + * @param key The password to hash |
| 111 | + * @param salt The salt (if starts with $2, use as-is; otherwise generate new) |
| 112 | + * @return The hashed password, or NULL on failure |
| 113 | + * |
| 114 | + * When called with an existing bcrypt hash as salt, extracts and uses that salt. |
| 115 | + * When called with a simple salt (for new password generation), generates a |
| 116 | + * proper bcrypt salt. |
| 117 | + */ |
| 118 | +const char* ircd_crypt_bcrypt(const char* key, const char* salt) |
| 119 | +{ |
| 120 | + static char newsalt[30]; |
| 121 | + const char* result; |
| 122 | + |
| 123 | + assert(NULL != key); |
| 124 | + assert(NULL != salt); |
| 125 | + |
| 126 | + Debug((DEBUG_DEBUG, "ircd_crypt_bcrypt: key = %s", key)); |
| 127 | + Debug((DEBUG_DEBUG, "ircd_crypt_bcrypt: salt = %s", salt)); |
| 128 | + |
| 129 | + /* If salt is already a bcrypt hash/salt, use it directly */ |
| 130 | + if (strlen(salt) >= 28 && salt[0] == '$' && salt[1] == '2' && |
| 131 | + (salt[2] == 'a' || salt[2] == 'b' || salt[2] == 'y') && salt[3] == '$') |
| 132 | + { |
| 133 | + result = crypt(key, salt); |
| 134 | + } |
| 135 | + else |
| 136 | + { |
| 137 | + /* Generate a new bcrypt salt */ |
| 138 | + if (generate_bcrypt_salt(newsalt, BCRYPT_DEFAULT_COST) == NULL) |
| 139 | + { |
| 140 | + Debug((DEBUG_DEBUG, "ircd_crypt_bcrypt: failed to generate salt")); |
| 141 | + return NULL; |
| 142 | + } |
| 143 | + Debug((DEBUG_DEBUG, "ircd_crypt_bcrypt: generated salt = %s", newsalt)); |
| 144 | + result = crypt(key, newsalt); |
| 145 | + } |
| 146 | + |
| 147 | + if (result == NULL) |
| 148 | + { |
| 149 | + Debug((DEBUG_DEBUG, "ircd_crypt_bcrypt: crypt() returned NULL")); |
| 150 | + return NULL; |
| 151 | + } |
| 152 | + |
| 153 | + /* Verify it's actually a bcrypt result (starts with $2) */ |
| 154 | + if (result[0] != '$' || result[1] != '2') |
| 155 | + { |
| 156 | + Debug((DEBUG_DEBUG, "ircd_crypt_bcrypt: crypt() did not return bcrypt hash")); |
| 157 | + return NULL; |
| 158 | + } |
| 159 | + |
| 160 | + Debug((DEBUG_DEBUG, "ircd_crypt_bcrypt: result = %s", result)); |
| 161 | + return result; |
| 162 | +} |
| 163 | + |
| 164 | +/** Register the bcrypt mechanism */ |
| 165 | +void ircd_register_crypt_bcrypt(void) |
| 166 | +{ |
| 167 | + crypt_mech_t* crypt_mech; |
| 168 | + |
| 169 | + if ((crypt_mech = (crypt_mech_t*)MyMalloc(sizeof(crypt_mech_t))) == NULL) |
| 170 | + { |
| 171 | + Debug((DEBUG_MALLOC, "Could not allocate space for crypt_bcrypt")); |
| 172 | + return; |
| 173 | + } |
| 174 | + |
| 175 | + crypt_mech->mechname = "bcrypt"; |
| 176 | + crypt_mech->shortname = "crypt_bcrypt"; |
| 177 | + crypt_mech->description = "Bcrypt password hash ($2y$)."; |
| 178 | + crypt_mech->crypt_function = &ircd_crypt_bcrypt; |
| 179 | + /* Note: We use an empty token because bcrypt hashes are detected |
| 180 | + * directly by their $2y$ prefix in ircd_crypt(), not via the |
| 181 | + * normal token mechanism. This registration is primarily for |
| 182 | + * umkpasswd to generate bcrypt passwords. */ |
| 183 | + crypt_mech->crypt_token = ""; |
| 184 | + crypt_mech->crypt_token_size = 0; |
| 185 | + |
| 186 | + ircd_crypt_register_mech(crypt_mech); |
| 187 | +} |
0 commit comments