Skip to content

Commit 9f3771d

Browse files
committed
add bcrypt support
1 parent e773310 commit 9f3771d

5 files changed

Lines changed: 240 additions & 3 deletions

File tree

include/ircd_crypt_bcrypt.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* IRC - Internet Relay Chat, include/ircd_crypt_bcrypt.h
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+
/** @file
19+
* @brief Bcrypt password hashing APIs.
20+
*/
21+
#ifndef INCLUDED_ircd_crypt_bcrypt_h
22+
#define INCLUDED_ircd_crypt_bcrypt_h
23+
24+
extern void ircd_register_crypt_bcrypt(void);
25+
extern const char* ircd_crypt_bcrypt(const char* key, const char* salt);
26+
27+
#endif /* INCLUDED_ircd_crypt_bcrypt_h */

ircd/Makefile.in

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ CRYPTO_SRC = \
7777
ircd_md5.c \
7878
ircd_crypt_plain.c \
7979
ircd_crypt_smd5.c \
80-
ircd_crypt_native.c
80+
ircd_crypt_native.c \
81+
ircd_crypt_bcrypt.c
8182

8283
UMKPASSWD_SRC = ${CRYPTO_SRC} \
8384
ircd_alloc.c \
@@ -486,7 +487,7 @@ ircd_crypt.o: ircd_crypt.c ../config.h ../include/ircd_crypt.h \
486487
../include/ircd_alloc.h ../include/ircd_features.h ../include/ircd_log.h \
487488
../include/ircd_string.h ../include/s_debug.h \
488489
../include/ircd_crypt_native.h ../include/ircd_crypt_plain.h \
489-
../include/ircd_crypt_smd5.h
490+
../include/ircd_crypt_smd5.h ../include/ircd_crypt_bcrypt.h
490491
ircd_crypt_native.o: ircd_crypt_native.c ../config.h \
491492
../include/ircd_crypt.h ../include/ircd_crypt_native.h \
492493
../include/ircd_log.h ../include/s_debug.h ../include/ircd_alloc.h
@@ -496,6 +497,9 @@ ircd_crypt_plain.o: ircd_crypt_plain.c ../config.h ../include/ircd_crypt.h \
496497
ircd_crypt_smd5.o: ircd_crypt_smd5.c ../config.h ../include/ircd_crypt.h \
497498
../include/ircd_crypt_smd5.h ../include/ircd_log.h ../include/ircd_md5.h \
498499
../include/s_debug.h ../include/ircd_alloc.h
500+
ircd_crypt_bcrypt.o: ircd_crypt_bcrypt.c ../config.h ../include/ircd_crypt.h \
501+
../include/ircd_crypt_bcrypt.h ../include/ircd_log.h ../include/s_debug.h \
502+
../include/ircd_alloc.h
499503
ircd_events.o: ircd_events.c ../config.h ../include/ircd_events.h \
500504
../include/ircd.h ../include/ircd_alloc.h ../include/ircd_log.h \
501505
../include/ircd_snprintf.h ../include/s_debug.h
@@ -1306,7 +1310,7 @@ umkpasswd.o: umkpasswd.c ../config.h ../include/ircd_alloc.h \
13061310
../include/ircd_log.h ../include/ircd_string.h ../include/umkpasswd.h \
13071311
../include/s_debug.h ../include/ircd_md5.h ../include/ircd_crypt.h \
13081312
../include/ircd_crypt_smd5.h ../include/ircd_crypt_native.h \
1309-
../include/ircd_crypt_plain.h
1313+
../include/ircd_crypt_plain.h ../include/ircd_crypt_bcrypt.h
13101314
uping.o: uping.c ../config.h ../include/uping.h ../include/client.h \
13111315
../include/ircd.h ../include/ircd_alloc.h ../include/ircd_events.h \
13121316
../include/ircd_log.h ../include/ircd_osdep.h ../include/ircd_string.h \

ircd/ircd_crypt.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
#include "ircd_crypt_native.h"
5656
#include "ircd_crypt_plain.h"
5757
#include "ircd_crypt_smd5.h"
58+
#include "ircd_crypt_bcrypt.h"
5859

5960
/* #include <assert.h> -- Now using assert in ircd_log.h */
6061
#include <unistd.h>
@@ -199,6 +200,21 @@ crypt_mechs_t* crypt_mech;
199200
return hashed_pass;
200201
}
201202

203+
/* try bcrypt ($2a$, $2b$, $2y$) - pass directly to system crypt */
204+
if (strlen(salt) > 4 && salt[0] == '$' && salt[1] == '2' &&
205+
(salt[2] == 'a' || salt[2] == 'b' || salt[2] == 'y') && salt[3] == '$')
206+
{
207+
char *s;
208+
Debug((DEBUG_DEBUG, "ircd_crypt: detected bcrypt hash"));
209+
if (NULL == (temp_hashed_pass = (char*)ircd_crypt_native(key, salt)))
210+
return NULL;
211+
if (!ircd_strcmp(temp_hashed_pass, salt))
212+
{
213+
DupString(s, temp_hashed_pass);
214+
return s;
215+
}
216+
}
217+
202218
/* try to use native crypt for an old-style (untagged) password */
203219
if (strlen(salt) > 2)
204220
{
@@ -242,6 +258,7 @@ void ircd_crypt_init(void)
242258
ircd_register_crypt_smd5();
243259
ircd_register_crypt_plain();
244260
ircd_register_crypt_native();
261+
ircd_register_crypt_bcrypt();
245262

246263
return;
247264
}

ircd/ircd_crypt_bcrypt.c

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
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+
}

ircd/umkpasswd.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
#include "ircd_crypt_smd5.h"
4343
#include "ircd_crypt_native.h"
4444
#include "ircd_crypt_plain.h"
45+
#include "ircd_crypt_bcrypt.h"
4546

4647
/* bleah, evil globals */
4748
umkpasswd_conf_t* umkpasswd_conf;
@@ -256,6 +257,7 @@ void load_mechs(void)
256257
ircd_register_crypt_native();
257258
ircd_register_crypt_smd5();
258259
ircd_register_crypt_plain(); /* yes I know it's slightly pointless */
260+
ircd_register_crypt_bcrypt();
259261

260262
return;
261263
}

0 commit comments

Comments
 (0)