summaryrefslogtreecommitdiff
path: root/sql/password.c
diff options
context:
space:
mode:
authorunknown <kostja@oak.local>2003-07-01 23:40:59 +0400
committerunknown <kostja@oak.local>2003-07-01 23:40:59 +0400
commitdbb088b034e19e99ec209cbbc4eed3bff64172da (patch)
treecbcae0aeb3eee5a5a448084ae5f0e9b5290fac26 /sql/password.c
parentb871e549eeec215bd40554431de8d21942e596d6 (diff)
downloadmariadb-git-dbb088b034e19e99ec209cbbc4eed3bff64172da.tar.gz
First version of new authentification procedure: now authentification is one-stage (instead of two-stage in 4.1)
For now following tasks have been done: - PASSWORD() function was rewritten. PASSWORD() now returns SHA1 hash_stage2; for new passwords user.password contains '*'hash_stage2; sql_yacc.yy also fixed; - password.c: new functions were implemented, old rolled back to 4.0 state - server code was rewritten to use new authorization algorithm (check_user(), change user, and other stuff in sql/sql_parse.cc) - client code was rewritten to use new authorization algorithm (mysql_real_connect, myslq_authenticate in sql-common/client.c) - now server barks on 45-byte-length 4.1.0 passwords and refuses 4.1.0-style authentification. Users with 4.1.0 passwords are blocked (sql/sql_acl.cc) - mysqladmin.c was fixed to work correctly with new passwords Tests for 4.0-4.1.1, 4.1.1-4.1.1 (with or without db/password) logons was performed; mysqladmin also was tested. Additional check are nevertheless necessary. BitKeeper/etc/ignore: Added start_mysqld.sh mysys/main.cc to the ignore list client/mysqladmin.c: fixed with new password api include/mysql.h: So as scramble_323 accepts only null-terminated message, two scramble buffs are necessary. gotta be fixed include/mysql_com.h: new constants and password.c api changes libmysql/libmysql.c: mysql_change_user rewritten to work with new password api scripts/mysql_create_system_tables.sh: fixed 'Password' column length to 41 scripts/mysql_fix_privilege_tables.sql: fixed 'Password' column length to 41 sql-common/client.c: mysql_real_connect rewritten to support new handshake procedure sql/item_strfunc.cc: Item_func_password and Item_func_old_password rewritten with new password api sql/item_strfunc.h: bit commented, numbers replaced with #defined constants sql/mysql_priv.h: removed unnecessary declaration as now all constants defined is in mysql_com.h sql/mysqld.cc: scramble initialization moved to sql_parce.cc:check_connection sql/password.c: All 4.1 functions were rolled back to 4.0 with attempt to save all possible 4.0-4.1 changes. Names for 4.0 functions were suffixed with '_323' Functions for new handshake were added. sql/slave.cc: Fixed to new constant; Bug #766 remains to be fixed sql/slave.h: fixed to new constant; Buf #766 remains to be fixed sql/sql_acl.cc: rewritten to support new passwords (41 byte-long) and password api sql/sql_acl.h: ditto sql/sql_class.cc: initialization for new members added sql/sql_class.h: same thing as in struct mysql - scramble is used for new family of functions, scramble_323 - for old sql/sql_parse.cc: check_connections was renamed to check_connection as this name reflects better what this function does authorization part of check_connection was rewritten check_user was rewritten with new password and acl api new function 'authenticate', which optionally re-request scramble from client was added fixed some typos COM_CHANGE_USER piece of dipsatch_command() was rewritten sql/sql_repl.h: HASH_PASSWORD_LENGTH replaced with SCRAMBLED_PASSWORD_CHAR_LENGTH bug #766 remains sql/sql_yacc.yy: Two-argument form of PASSWORD() was removed PASSWORD() function was fixed with new password api. BitKeeper/etc/logging_ok: Logging to logging@openlogging.org accepted
Diffstat (limited to 'sql/password.c')
-rw-r--r--sql/password.c828
1 files changed, 330 insertions, 498 deletions
diff --git a/sql/password.c b/sql/password.c
index 257547671e5..be6514d89c6 100644
--- a/sql/password.c
+++ b/sql/password.c
@@ -29,28 +29,33 @@
The password is saved (in user.password) by using the PASSWORD() function in
mysql.
+ This is .c file because it's used in libmysqlclient, which is entirely in C.
+ (we need it to be portable to a variety of systems).
Example:
update user set password=PASSWORD("hello") where user="test"
This saves a hashed number as a string in the password field.
+ The new autentication is performed in following manner:
- New in MySQL 4.1 authentication works even more secure way.
- At the first step client sends user name to the sever, and password if
- it is empty. So in case of empty password authentication is as fast as before.
- At the second stap servers sends scramble to client, which is encoded with
- password stage2 hash stored in the password database as well as salt, needed
- for client to build stage2 password to decrypt scramble.
- Client decrypts the scramble and encrypts it once again with stage1 password.
- This information is sent to server.
- Server decrypts the scramble to get stage1 password and hashes it to get
- stage2 hash. This hash is when compared to hash stored in the database.
+ SERVER: public_seed=create_random_string()
+ send(public_seed)
- This authentication needs 2 packet round trips instead of one but it is much
- stronger. Now if one will steal mysql database content he will not be able
- to break into MySQL.
+ CLIENT: recv(public_seed)
+ hash_stage1=sha1("password")
+ hash_stage2=sha1(hash_stage1)
+ reply=xor(hash_stage1, sha1(public_seed,hash_stage2)
- New Password handling functions by Peter Zaitsev
+ // this three steps are done in scramble()
+ send(reply)
+
+
+ SERVER: recv(reply)
+ hash_stage1=xor(reply, sha1(public_seed,hash_stage2))
+ candidate_hash2=sha1(hash_stage1)
+ check(candidate_hash2==hash_stage2)
+
+ // this three steps are done in check_scramble()
*****************************************************************************/
@@ -60,31 +65,21 @@
#include <sha1.h>
#include "mysql.h"
-
-
-/* Character to use as version identifier for version 4.1 */
-#define PVERSION41_CHAR '*'
-
-/* Scramble length for new password version */
-
+/************ MySQL 3.23-4.0 authentification routines: untouched ***********/
/*
New (MySQL 3.21+) random generation structure initialization
-
SYNOPSIS
randominit()
rand_st OUT Structure to initialize
seed1 IN First initialization parameter
seed2 IN Second initialization parameter
-
- RETURN
- none
*/
-void randominit(struct rand_struct *rand_st,ulong seed1, ulong seed2)
-{ /* For mysql 3.21.# */
+void randominit(struct rand_struct *rand_st, ulong seed1, ulong seed2)
+{ /* For mysql 3.21.# */
#ifdef HAVE_purify
- bzero((char*) rand_st,sizeof(*rand_st)); /* Avoid UMC varnings */
+ bzero((char*) rand_st,sizeof(*rand_st)); /* Avoid UMC varnings */
#endif
rand_st->max_value= 0x3FFFFFFFL;
rand_st->max_value_dbl=(double) rand_st->max_value;
@@ -95,18 +90,15 @@ void randominit(struct rand_struct *rand_st,ulong seed1, ulong seed2)
/*
Old (MySQL 3.20) random generation structure initialization
-
+ XXX: is to be deleted very soon!
SYNOPSIS
old_randominit()
rand_st OUT Structure to initialize
seed1 IN First initialization parameter
-
- RETURN
- none
*/
-static void old_randominit(struct rand_struct *rand_st,ulong seed1)
-{ /* For mysql 3.20.# */
+static void old_randominit(struct rand_struct *rand_st, ulong seed1)
+{ /* For mysql 3.20.# */
rand_st->max_value= 0x01FFFFFFL;
rand_st->max_value_dbl=(double) rand_st->max_value;
seed1%=rand_st->max_value;
@@ -115,14 +107,12 @@ static void old_randominit(struct rand_struct *rand_st,ulong seed1)
/*
- Generate Random number
-
+ Generate random number.
SYNOPSIS
my_rnd()
rand_st INOUT Structure used for number generation
-
- RETURN
- Generated pseudo random number
+ RETURN VALUE
+ generated pseudo random number
*/
double my_rnd(struct rand_struct *rand_st)
@@ -134,63 +124,12 @@ double my_rnd(struct rand_struct *rand_st)
/*
- Generate String of printable random characters of requested length
- String will not be zero terminated.
-
+ Generate binary hash from raw text string
+ Used for Pre-4.1 password handling
SYNOPSIS
- create_random_string()
- length IN Lenght of
- rand_st INOUT Structure used for number generation
- target OUT Buffer for generation
-
- RETURN
- none
-*/
-
-void create_random_string(int length,struct rand_struct *rand_st,char *target)
-{
- char *end=target+length;
- /* Use pointer arithmetics as it is faster way to do so. */
- for (; target<end ; target++)
- *target= (char) (my_rnd(rand_st)*94+33);
-}
-
-
-/*
- Encrypt/Decrypt function used for password encryption in authentication
- Simple XOR is used here but it is OK as we crypt random strings
-
- SYNOPSIS
- password_crypt()
- from IN Data for encryption
- to OUT Encrypt data to the buffer (may be the same)
- password IN Password used for encryption (same length)
- length IN Length of data to encrypt
-
- RETURN
- none
-*/
-
-void password_crypt(const char *from,char *to, const char *password,int length)
-{
- const char *from_end=from+length;
-
- while (from < from_end)
- *to++= *(from++) ^* (password++);
-}
-
-
-/*
- Generate binary hash from raw text password
- Used for Pre-4.1 Password handling
-
- SYNOPSIS
- hash_pasword()
- result OUT Store hash in this location
- password IN Plain text password to build hash
-
- RETURN
- none
+ hash_password()
+ result OUT store hash in this location
+ password IN plain text password to build hash
*/
void hash_password(ulong *result, const char *password)
@@ -200,7 +139,7 @@ void hash_password(ulong *result, const char *password)
for (; *password ; password++)
{
if (*password == ' ' || *password == '\t')
- continue; /* skipp space in password */
+ continue; /* skip space in password */
tmp= (ulong) (uchar) *password;
nr^= (((nr & 63)+add)*tmp)+ (nr << 8);
nr2+=(nr2 << 8) ^ nr;
@@ -213,514 +152,407 @@ void hash_password(ulong *result, const char *password)
/*
- Stage one password hashing.
- Used in MySQL 4.1 password handling
-
+ Create password to be stored in user database from raw string
+ Used for pre-4.1 password handling
SYNOPSIS
- password_hash_stage1()
- to OUT Store stage one hash to this location
- password IN Plain text password to build hash
-
- RETURN
- none
+ make_scrambled_password_323()
+ to OUT store scrambled password here
+ password IN user-supplied password
*/
-void password_hash_stage1(char *to, const char *password)
+void make_scrambled_password_323(char *to, const char *password)
{
- SHA1_CONTEXT context;
- sha1_reset(&context);
- for (; *password ; password++)
- {
- if (*password == ' ' || *password == '\t')
- continue;/* skip space in password */
- sha1_input(&context,(uint8*) &password[0],1);
- }
- sha1_result(&context,(uint8*)to);
+ ulong hash_res[2];
+ hash_password(hash_res, password);
+ sprintf(to, "%08lx%08lx", hash_res[0], hash_res[1]);
}
/*
- Stage two password hashing.
- Used in MySQL 4.1 password handling
-
+ Scramble string with password.
+ Used in pre 4.1 authentication phase.
SYNOPSIS
- password_hash_stage2()
- to INOUT Use this as stage one hash and store stage two hash here
- salt IN Salt used for stage two hashing
-
+ scramble_323()
+ to OUT Store scrambled message here. Buffer must be at least
+ SCRAMBLE_LENGTH_323+1 bytes long
+ message IN Message to scramble. Message must be exactly
+ SRAMBLE_LENGTH_323 long and NULL terminated.
+ password IN Password to use while scrambling
+ old_ver IN Force old version random number generator
RETURN
- none
+ End of scrambled string
*/
-void password_hash_stage2(char *to, const char *salt)
+char *scramble_323(char *to, const char *message, const char *password,
+ my_bool old_ver)
{
- SHA1_CONTEXT context;
- sha1_reset(&context);
- sha1_input(&context,(uint8*) salt, 4);
- sha1_input(&context,(uint8*) to, SHA1_HASH_SIZE);
- sha1_result(&context,(uint8*) to);
+ struct rand_struct rand_st;
+ ulong hash_pass[2], hash_message[2];
+
+ if (password && password[0])
+ {
+ char *to_start=to;
+ hash_password(hash_pass,password);
+ hash_password(hash_message, message);
+ if (old_ver)
+ old_randominit(&rand_st,hash_pass[0] ^ hash_message[0]);
+ else
+ randominit(&rand_st,hash_pass[0] ^ hash_message[0],
+ hash_pass[1] ^ hash_message[1]);
+ while (*message++)
+ *to++= (char) (floor(my_rnd(&rand_st)*31)+64);
+ if (!old_ver)
+ { /* Make it harder to break */
+ char extra=(char) (floor(my_rnd(&rand_st)*31));
+ while (to_start != to)
+ *(to_start++)^=extra;
+ }
+ }
+ *to=0;
+ return to;
}
/*
- Create password to be stored in user database from raw string
- Handles both MySQL 4.1 and Pre-MySQL 4.1 passwords
-
+ Check scrambled message
+ Used in pre 4.1 password handling
SYNOPSIS
- make_scramble_password()
- to OUT Store scrambled password here
- password IN Raw string password
- force_old_scramle
- IN Force generation of old scramble variant
- rand_st INOUT Structure for temporary number generation.
- RETURN
- none
+ check_scramble_323()
+ scrambled IN scrambled message to check.
+ message IN original random message which was used for scrambling; must
+ be exactly SCRAMBLED_LENGTH_323 bytes long and
+ NULL-terminated.
+ hash_pass IN password which should be used for scrambling
+ old_ver IN force old (3.20) version random number generator
+ RETURN VALUE
+ 0 - password correct
+ !0 - password invalid
*/
-void make_scrambled_password(char *to,const char *password,
- my_bool force_old_scramble,
- struct rand_struct *rand_st)
+my_bool
+check_scramble_323(const char *scrambled, const char *message,
+ ulong *hash_pass, my_bool old_ver)
{
- ulong hash_res[2]; /* Used for pre 4.1 password hashing */
- unsigned short salt; /* Salt for 4.1 version password */
- uint8 digest[SHA1_HASH_SIZE];
- if (force_old_scramble) /* Pre 4.1 password encryption */
- {
- hash_password(hash_res,password);
- sprintf(to,"%08lx%08lx",hash_res[0],hash_res[1]);
- }
- else /* New password 4.1 password scrambling */
+ struct rand_struct rand_st;
+ ulong hash_message[2];
+ char buff[16],*to,extra; /* Big enough for check */
+ const char *pos;
+
+ /* Check if this exactly N bytes. Overwise this is something fishy */
+ if (strlen(message) != SCRAMBLE_LENGTH_323)
+ return 1; /* Wrong password */
+
+ hash_password(hash_message,message);
+ if (old_ver)
+ old_randominit(&rand_st,hash_pass[0] ^ hash_message[0]);
+ else
+ randominit(&rand_st,hash_pass[0] ^ hash_message[0],
+ hash_pass[1] ^ hash_message[1]);
+ to=buff;
+ for (pos=scrambled ; *pos ; pos++)
+ *to++=(char) (floor(my_rnd(&rand_st)*31)+64);
+ if (old_ver)
+ extra=0;
+ else
+ extra=(char) (floor(my_rnd(&rand_st)*31));
+ to=buff;
+ while (*scrambled)
{
- to[0]=PVERSION41_CHAR; /* New passwords have version prefix */
- /* Rnd returns number from 0 to 1 so this would be good salt generation.*/
- salt=(unsigned short) (my_rnd(rand_st)*65535+1);
- /* Use only 2 first bytes from it */
- sprintf(to+1,"%04x",salt);
- /* First hasing is done without salt */
- password_hash_stage1((char*) digest, password);
- /* Second stage is done with salt */
- password_hash_stage2((char*) digest,(char*)to+1),
- /* Print resulting hash into the password*/
- sprintf(to+5,
- "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
- digest[0],digest[1],digest[2],digest[3],digest[4],digest[5],digest[6],
- digest[7],digest[8],digest[9],digest[10],digest[11],digest[12],digest[13],
- digest[14],digest[15],digest[16],digest[17],digest[18],digest[19]);
+ if (*scrambled++ != (char) (*to++ ^ extra))
+ return 1; /* Wrong password */
}
+ return 0;
+}
+
+static uint8 char_val(uint8 X)
+{
+ return (uint) (X >= '0' && X <= '9' ? X-'0' :
+ X >= 'A' && X <= 'Z' ? X-'A'+10 : X-'a'+10);
}
/*
- Convert password from binary string form to salt form
- Used for MySQL 4.1 password handling
-
+ Convert password from hex string (as stored in mysql.user) to binary form.
SYNOPSIS
- get_salt_from_bin_password()
- res OUT Store salt form password here
- password IN Binary password to be converted
- salt IN hashing-salt to be used for salt form generation
-
- RETURN
- none
+ get_salt_from_password_323()
+ res OUT store salt here
+ password IN password string as stored in mysql.user
+ NOTE
+ This function does not have length check for passwords. It will just crash
+ Password hashes in old format must have length divisible by 8
*/
-void get_salt_from_bin_password(ulong *res,unsigned char *password,ulong salt)
+void get_salt_from_password_323(ulong *res, const char *password)
{
- unsigned char *password_end=password+SCRAMBLE41_LENGTH;
- *res=salt;
- res++;
-
- /* Process password of known length*/
- while (password<password_end)
+ res[0]= res[1]= 0;
+ if (password)
{
- ulong val=0;
- uint i;
- for (i=0 ; i < 4 ; i++)
- val=(val << 8)+(*password++);
- *res++=val;
+ while (*password)
+ {
+ ulong val=0;
+ uint i;
+ for (i=0 ; i < 8 ; i++)
+ val=(val << 4)+char_val(*password++);
+ *res++=val;
+ }
}
}
/*
- Validate password for MySQL 4.1 password handling.
-
+ Convert scrambled password from binary form to asciiz hex string.
SYNOPSIS
- validate_password()
- password IN Encrypted Scramble which we got from the client
- message IN Original scramble which we have sent to the client before
- salt IN Password in the salted form to match to
-
- RETURN
- 0 for correct password
- !0 for invalid password
+ make_password_from_salt_323()
+ to OUT store resulting string password here, at least 17 bytes
+ salt IN password in salt format, 2 ulongs
*/
-my_bool validate_password(const char *password, const char *message,
- ulong *salt)
+void make_password_from_salt_323(char *to, const ulong *salt)
{
- char buffer[SCRAMBLE41_LENGTH]; /* Used for password validation */
- char tmpsalt[8]; /* Temporary value to convert salt to string form */
- ulong salt_candidate[6]; /* Computed candidate salt */
- ulong *sc=salt_candidate; /* we need to be able to increment */
- ulong *salt_end;
-
- /* Now we shall get stage1 encrypted password in buffer*/
- password_crypt(password,buffer,message,SCRAMBLE41_LENGTH);
-
- /* For compatibility reasons we use ulong to store salt while we need char */
- sprintf(tmpsalt,"%04x",(unsigned short)salt[0]);
-
- password_hash_stage2(buffer,tmpsalt);
- /* Convert password to salt to compare */
- get_salt_from_bin_password(salt_candidate,(uchar*) buffer,salt[0]);
-
- /* Now we shall get exactly the same password as we have stored for user */
- for (salt_end=salt+5 ; salt < salt_end; )
- if (*++salt != *++sc)
- return 1;
-
- /* Or password correct*/
- return 0;
+ sprintf(to,"%08lx%08lx", salt[0], salt[1]);
}
+/******************* MySQL 4.1.1 authentification routines ******************/
/*
- Get length of password string which is stored in mysql.user table
-
+ Generate string of printable random characters of requested length
SYNOPSIS
- get_password_length()
- force_old_scramble IN If we wish to use pre 4.1 scramble format
-
- RETURN
- password length >0
+ create_random_string()
+ to OUT buffer for generation; must be at least length+1 bytes
+ long; result string is always null-terminated
+ length IN how many random characters to put in buffer
+ rand_st INOUT structure used for number generation
*/
-int get_password_length(my_bool force_old_scramble)
+void create_random_string(char *to, uint length, struct rand_struct *rand_st)
{
- return (force_old_scramble) ? 16 : SHA1_HASH_SIZE*2+4+1;
+ char *end= to + length;
+ /* Use pointer arithmetics as it is faster way to do so. */
+ for (; to < end; to++)
+ *to= (char) (my_rnd(rand_st)*94+33);
+ *to= '\0';
}
-/*
- Get version of the password based on mysql.user password string
-
- SYNOPSIS
- get_password_version()
- password IN Password string as stored in mysql.user
-
- RETURN
- 0 for pre 4.1 passwords
- !0 password version char for newer passwords
-*/
+/* Character to use as version identifier for version 4.1 */
-char get_password_version(const char *password)
-{
- if (password==NULL) return 0;
- if (password[0]==PVERSION41_CHAR) return PVERSION41_CHAR;
- return 0;
-}
+#define PVERSION41_CHAR '*'
/*
- Get integer value of Hex character
-
+ Convert given octet sequence to asciiz string of hex characters;
+ str..str+len and 'to' may not overlap.
SYNOPSIS
- char_val()
- X IN Character to find value for
-
- RETURN
- Appropriate integer value
+ octet2hex()
+ buf OUT output buffer. Must be at least 2*len+1 bytes
+ str, len IN the beginning and the length of the input string
*/
-
-
-static inline unsigned int char_val(char X)
+static
+void
+octet2hex(char *to, const uint8 *str, uint len)
{
- return (uint) (X >= '0' && X <= '9' ? X-'0' :
- X >= 'A' && X <= 'Z' ? X-'A'+10 :
- X-'a'+10);
+ static const char alphabet[] = { '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+ const uint8 *str_end= str + len;
+ for (; str != str_end; ++str)
+ {
+ *to++= alphabet[(*str & 0xF0) >> 4];
+ *to++= alphabet[*str & 0x0F];
+ }
+ *to++= '\0';
}
/*
- Get Binary salt from password as in mysql.user format
-
+ Convert given asciiz string of hex (0..9 a..f) characters to octet
+ sequence.
SYNOPSIS
- get_salt_from_password()
- res OUT Store binary salt here
- password IN Password string as stored in mysql.user
-
- RETURN
- none
-
- NOTE
- This function does not have length check for passwords. It will just crash
- Password hashes in old format must have length divisible by 8
-*/
-
-void get_salt_from_password(ulong *res,const char *password)
+ hex2octet()
+ to OUT buffer to place result; must be at least len/2 bytes
+ str, len IN begin, length for character string; str and to may not
+ overlap; len % 2 == 0
+*/
+
+static
+void
+hex2octet(uint8 *to, const char *str, uint len)
{
- if (password) /* zero salt corresponds to empty password */
+ const char *str_end= str + len;
+ while (str < str_end)
{
- if (password[0]==PVERSION41_CHAR) /* if new password */
- {
- uint val=0;
- uint i;
- password++; /* skip version identifier */
-
- /*get hashing salt from password and store in in the start of array */
- for (i=0 ; i < 4 ; i++)
- val=(val << 4)+char_val(*password++);
- *res++=val;
- }
- /* We process old passwords the same way as new ones in other case */
-#ifdef EXTRA_DEBUG
- if (strlen(password)%8!=0)
- fprintf(stderr,"Warning: Incorrect password length for salting: %d\n",
- strlen(password));
-#endif
- while (*password)
- {
- ulong val=0;
- uint i;
- for (i=0 ; i < 8 ; i++)
- val=(val << 4)+char_val(*password++);
- *res++=val;
- }
+ *to= char_val(*str++) << 4;
+ *to++|= char_val(*str++);
}
- return;
}
/*
- Get string version as stored in mysql.user from salt form
-
+ Encrypt/Decrypt function used for password encryption in authentication.
+ Simple XOR is used here but it is OK as we crypt random strings. Note,
+ that XOR(s1, XOR(s1, s2)) == s2, XOR(s1, s2) == XOR(s2, s1)
SYNOPSIS
- make_password_from_salt()
- to OUT Store resulting string password here
- hash_res IN Password in salt format
- password_version
- IN According to which version salt should be treated
-
- RETURN
- none
+ my_crypt()
+ to OUT buffer to hold crypted string; must be at least len bytes
+ long; to and s1 (or s2) may be the same.
+ s1, s2 IN input strings (of equal length)
+ len IN length of s1 and s2
*/
-void make_password_from_salt(char *to, ulong *hash_res,uint8 password_version)
+static
+void
+my_crypt(char *to, const uint8 *s1, const uint8 *s2, uint len)
{
- if (!password_version) /* Handling of old passwords. */
- sprintf(to,"%08lx%08lx",hash_res[0],hash_res[1]);
- else
- if (password_version==PVERSION41_CHAR)
- sprintf(to,"%c%04x%08lx%08lx%08lx%08lx%08lx",PVERSION41_CHAR,(unsigned short)hash_res[0],hash_res[1],
- hash_res[2],hash_res[3],hash_res[4],hash_res[5]);
- else /* Just use empty password if we can't handle it. This should not happen */
- to[0]='\0';
+ const uint8 *s1_end= s1 + len;
+ while (s1 < s1_end)
+ *to++= *s1++ ^ *s2++;
}
/*
- Convert password in salted form to binary string password and hash-salt
- For old password this involes one more hashing
-
+ MySQL 4.1.1 password hashing: SHA conversion (see RFC 2289, 3174) twice
+ applied to the password string, and then produced octet sequence is
+ converted to hex string.
+ The result of this function is used as return value from PASSWORD() and
+ is stored in the database.
SYNOPSIS
- get_hash_and_password()
- salt IN Salt to convert from
- pversion IN Password version to use
- hash OUT Store zero ended hash here
- bin_password OUT Store binary password here (no zero at the end)
-
- RETURN
- 0 for pre 4.1 passwords
- !0 password version char for newer passwords
+ make_scrambled_password()
+ buf OUT buffer of size 2*SHA1_HASH_SIZE + 2 to store hex string
+ password IN NULL-terminated password string
*/
-void get_hash_and_password(ulong *salt, uint8 pversion, char *hash,
- unsigned char *bin_password)
+void
+make_scrambled_password(char *to, const char *password)
{
- int t;
- ulong* salt_end;
- ulong val;
- SHA1_CONTEXT context;
-
- if (pversion) /* New password version assumed */
- {
- salt_end=salt+5;
- sprintf(hash,"%04x",(unsigned short)salt[0]);
- while (salt<salt_end)
- {
- val=*(++salt);
- for (t=3; t>=0; t--)
- {
- bin_password[t]= (char) (val & 255);
- val>>=8; /* Scroll 8 bits to get next part*/
- }
- bin_password+=4; /* Get to next 4 chars*/
- }
- }
- else
- {
- unsigned char *bp= bin_password; /* Binary password loop pointer */
-
- /* Use zero starting hash as an indication of old password */
- hash[0]=0;
- salt_end=salt+2;
- /* Encode salt using SHA1 here */
- sha1_reset(&context);
- while (salt<salt_end) /* Iterate over these elements*/
- {
- val= *salt;
- for (t=3;t>=0;t--)
- {
- bp[t]= (uchar) (val & 255);
- val>>=8; /* Scroll 8 bits to get next part*/
- }
- bp+= 4; /* Get to next 4 chars*/
- salt++;
- }
- /* Use 8 bytes of binary password for hash */
- sha1_input(&context,(uint8*)bin_password,8);
- sha1_result(&context,(uint8*)bin_password);
- }
+ SHA1_CONTEXT sha1_context;
+ uint8 hash_stage2[SHA1_HASH_SIZE];
+
+ sha1_reset(&sha1_context);
+ /* stage 1: hash password */
+ sha1_input(&sha1_context, (uint8 *) password, strlen(password));
+ sha1_result(&sha1_context, (uint8 *) to);
+ /* stage 2: hash stage1 output */
+ sha1_reset(&sha1_context);
+ sha1_input(&sha1_context, (uint8 *) to, SHA1_HASH_SIZE);
+ /* separate buffer is used to pass 'to' in octet2hex */
+ sha1_result(&sha1_context, hash_stage2);
+ /* convert hash_stage2 to hex string */
+ *to++= PVERSION41_CHAR;
+ octet2hex(to, hash_stage2, SHA1_HASH_SIZE);
}
-
+
/*
- Create key from old password to decode scramble
- Used in 4.1 authentication with passwords stored old way
-
+ Produce an obscure octet sequence from password and random
+ string, recieved from the server. This sequence corresponds to the
+ password, but password can not be easily restored from it. The sequence
+ is then sent to the server for validation. Trailing zero is stored in
+ the buf.
+ This function is used by client to create authenticated reply to the
+ server's greeting.
SYNOPSIS
- create_key_from_old_password()
- passwd IN Password used for key generation
- key OUT Created 20 bytes key
-
- RETURN
- None
+ scramble()
+ buf OUT store scrambled string here. The buf must be at least
+ SHA1_HASH_SIZE+1 bytes long.
+ message IN random message, must be exactly SCRAMBLE_LENGTH long and
+ NULL-terminated.
+ password IN users' password
+ RETURN VALUE
+ end of scrambled string
*/
-
-void create_key_from_old_password(const char *passwd, char *key)
+char *
+scramble(char *to, const char *message, const char *password)
{
- char buffer[SCRAMBLE41_LENGTH]; /* Buffer for various needs */
- ulong salt[6]; /* Salt (large for safety) */
- /* At first hash password to the string stored in password */
- make_scrambled_password(buffer,passwd,1,(struct rand_struct *)NULL);
- /* Now convert it to the salt form */
- get_salt_from_password(salt,buffer);
- /* Finally get hash and bin password from salt */
- get_hash_and_password(salt,0,buffer,(unsigned char*) key);
+ SHA1_CONTEXT sha1_context;
+ uint8 hash_stage1[SHA1_HASH_SIZE];
+ uint8 hash_stage2[SHA1_HASH_SIZE];
+
+ sha1_reset(&sha1_context);
+ /* stage 1: hash password */
+ sha1_input(&sha1_context, (uint8 *) password, strlen(password));
+ sha1_result(&sha1_context, hash_stage1);
+ /* stage 2: hash stage 1; note that hash_stage2 is stored in the database */
+ sha1_reset(&sha1_context);
+ sha1_input(&sha1_context, hash_stage1, SHA1_HASH_SIZE);
+ sha1_result(&sha1_context, hash_stage2);
+ /* create crypt string as sha1(message, hash_stage2) */;
+ sha1_reset(&sha1_context);
+ sha1_input(&sha1_context, (const uint8 *) message, SCRAMBLE_LENGTH);
+ sha1_input(&sha1_context, hash_stage2, SHA1_HASH_SIZE);
+ /* xor allows 'from' and 'to' overlap: lets take advantage of it */
+ sha1_result(&sha1_context, (uint8 *) to);
+ my_crypt(to, (const uint8 *) to, hash_stage1, SCRAMBLE_LENGTH);
+ to[SHA1_HASH_SIZE]= '\0';
+ return to + SHA1_HASH_SIZE;
}
/*
- Scramble string with password
- Used at pre 4.1 authentication phase.
-
+ Check that scrambled message corresponds to the password; the function
+ is used by server to check that recieved reply is authentic.
+ This function does not check lengths of given strings: message must be
+ null-terminated, reply and hash_stage2 must be at least SHA1_HASH_SIZE
+ long (if not, something fishy is going on).
SYNOPSIS
- scramble()
- to OUT Store scrambled message here
- message IN Message to scramble
- password IN Password to use while scrambling
- old_ver IN Forse old version random number generator
-
- RETURN
- End of scrambled string
+ check_scramble()
+ scramble IN clients' reply, presumably produced by scramble()
+ message IN original random string, previously sent to client
+ (presumably second argument of scramble()), must be
+ exactly SCRAMBLE_LENGTH long and NULL-terminated.
+ hash_stage2 IN hex2octet-decoded database entry
+ RETURN VALUE
+ 0 password is correct
+ !0 password is invalid
*/
-char *scramble(char *to,const char *message,const char *password,
- my_bool old_ver)
+my_bool
+check_scramble(const char *scramble, const char *message,
+ const uint8 *hash_stage2)
{
- struct rand_struct rand_st;
- ulong hash_pass[2],hash_message[2];
- char message_buffer[9]; /* Real message buffer */
- char *msg=message_buffer;
-
- /* We use special message buffer now as new server can provide longer hash */
-
- memcpy(message_buffer,message,8);
- message_buffer[8]=0;
-
- if (password && password[0])
- {
- char *to_start=to;
- hash_password(hash_pass,password);
- hash_password(hash_message,message_buffer);
- if (old_ver)
- old_randominit(&rand_st,hash_pass[0] ^ hash_message[0]);
- else
- randominit(&rand_st,hash_pass[0] ^ hash_message[0],
- hash_pass[1] ^ hash_message[1]);
- while (*msg++)
- *to++= (char) (floor(my_rnd(&rand_st)*31)+64);
- if (!old_ver)
- { /* Make it harder to break */
- char extra=(char) (floor(my_rnd(&rand_st)*31));
- while (to_start != to)
- *(to_start++)^=extra;
- }
- }
- *to=0;
- return to;
+ SHA1_CONTEXT sha1_context;
+ uint8 buf[SHA1_HASH_SIZE];
+ uint8 hash_stage2_reassured[SHA1_HASH_SIZE];
+
+ sha1_reset(&sha1_context);
+ /* create key to encrypt scramble */
+ sha1_input(&sha1_context, (const uint8 *) message, SCRAMBLE_LENGTH);
+ sha1_input(&sha1_context, hash_stage2, SHA1_HASH_SIZE);
+ sha1_result(&sha1_context, buf);
+ /* encrypt scramble */
+ my_crypt((char *) buf, buf, (const uint8 *) scramble, SCRAMBLE_LENGTH);
+ /* now buf supposedly contains hash_stage1: so we can get hash_stage2 */
+ sha1_reset(&sha1_context);
+ sha1_input(&sha1_context, buf, SHA1_HASH_SIZE);
+ sha1_result(&sha1_context, hash_stage2_reassured);
+ return memcmp(hash_stage2, hash_stage2_reassured, SHA1_HASH_SIZE);
}
/*
- Check scrambled message
- Used for pre 4.1 password handling
-
+ Convert scrambled password from asciiz hex string to binary form.
SYNOPSIS
- scramble()
- scrambled IN Scrambled message to check
- message IN Original message which was scramble
- hash_pass IN Password which should be used for scrambling
- old_ver IN Forse old version random number generator
+ get_salt_from_password()
+ res OUT buf to hold password. Must be at least SHA1_HASH_SIZE
+ bytes long.
+ password IN 4.1.1 version value of user.password
+*/
+
+void get_salt_from_password(uint8 *hash_stage2, const char *password)
+{
+ hex2octet(hash_stage2, password+1 /* skip '*' */, SHA1_HASH_SIZE * 2);
+}
- RETURN
- 0 Password correct
- !0 Password invalid
+/*
+ Convert scrambled password from binary form to asciiz hex string.
+ SYNOPSIS
+ make_password_from_salt()
+ to OUT store resulting string here, 2*SHA1_HASH_SIZE+2 bytes
+ salt IN password in salt format
*/
-my_bool check_scramble(const char *scrambled, const char *message,
- ulong *hash_pass, my_bool old_ver)
+void make_password_from_salt(char *to, const uint8 *hash_stage2)
{
- struct rand_struct rand_st;
- ulong hash_message[2];
- char buff[16],*to,extra; /* Big enough for check */
- const char *pos;
- char message_buffer[SCRAMBLE_LENGTH+1]; /* Copy of message */
-
- /* We need to copy the message as this function can be called for MySQL 4.1
- scramble which is not zero ended and can have zeroes inside
- We could just write zero to proper place in original message but
- this would make it harder to understand code for next generations
- */
-
- memcpy(message_buffer,message,SCRAMBLE_LENGTH); /* Ignore the rest */
- message_buffer[SCRAMBLE_LENGTH]=0;
-
- /* Check if this exactly N bytes. Overwise this is something fishy */
- if (strlen(message_buffer)!=SCRAMBLE_LENGTH)
- return 1; /* Wrong password */
-
- hash_password(hash_message,message_buffer);
- if (old_ver)
- old_randominit(&rand_st,hash_pass[0] ^ hash_message[0]);
- else
- randominit(&rand_st,hash_pass[0] ^ hash_message[0],
- hash_pass[1] ^ hash_message[1]);
- to=buff;
- for (pos=scrambled ; *pos ; pos++)
- *to++=(char) (floor(my_rnd(&rand_st)*31)+64);
- if (old_ver)
- extra=0;
- else
- extra=(char) (floor(my_rnd(&rand_st)*31));
- to=buff;
- while (*scrambled)
- {
- if (*scrambled++ != (char) (*to++ ^ extra))
- return 1; /* Wrong password */
- }
- return 0;
+ *to++= PVERSION41_CHAR;
+ octet2hex(to, hash_stage2, SHA1_HASH_SIZE);
}