KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > mysql > jdbc > Security


1 /*
2    Copyright (C) 2002 MySQL AB
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 2 of the License, or
7       (at your option) 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
18  */

19 package com.mysql.jdbc;
20
21 import java.security.MessageDigest JavaDoc;
22 import java.security.NoSuchAlgorithmException JavaDoc;
23
24
25 /**
26  * Methods for doing secure authentication with MySQL-4.1
27  * and newer.
28  *
29  * @author Mark Matthews
30  *
31  * @version $Id: Security.java,v 1.1.4.4 2003/09/11 19:34:01 mmatthew Exp $
32  */

33 class Security {
34     private static final int SHA1_HASH_SIZE = 20;
35     private static final char PVERSION41_CHAR = '*';
36
37     /**
38      * Prevent construction.
39      */

40     private Security() {
41         super();
42     }
43
44     /*
45       Convert password in salted form to binary string password and hash-salt
46       For old password this involes one more hashing
47
48       SYNOPSIS
49             get_hash_and_password()
50             salt IN Salt to convert from
51             pversion IN Password version to use
52             hash OUT Store zero ended hash here
53             bin_password OUT Store binary password here (no zero at the end)
54
55       RETURN
56             0 for pre 4.1 passwords
57        !0 password version char for newer passwords
58     */

59
60     /**
61      * DOCUMENT ME!
62      *
63      * @param salt DOCUMENT ME!
64      * @param usingNewPasswords DOCUMENT ME!
65      *
66      * @return DOCUMENT ME!
67      *
68      * @throws NoSuchAlgorithmException if the message digest 'SHA-1' is not
69      * available.
70      */

71     static byte[] getBinaryPassword(int[] salt, boolean usingNewPasswords)
72         throws NoSuchAlgorithmException JavaDoc {
73         int val = 0;
74
75         byte[] binaryPassword = new byte[SHA1_HASH_SIZE]; /* Binary password loop pointer */
76
77         if (usingNewPasswords) /* New password version assumed */ {
78             int pos = 0;
79
80             for (int i = 0; i < 4; i++) /* Iterate over these elements*/ {
81                 val = salt[i];
82
83                 for (int t = 3; t >= 0; t--) {
84                     binaryPassword[pos++] = (byte) (val & 255);
85                     val >>= 8; /* Scroll 8 bits to get next part*/
86                 }
87             }
88
89             return binaryPassword;
90         } else {
91             int offset = 0;
92
93             for (int i = 0; i < 2; i++) /* Iterate over these elements*/ {
94                 val = salt[i];
95
96                 for (int t = 3; t >= 0; t--) {
97                     binaryPassword[t + offset] = (byte) (val % 256);
98                     val >>= 8; /* Scroll 8 bits to get next part*/
99                 }
100
101                 offset += 4;
102             }
103
104             MessageDigest JavaDoc md = MessageDigest.getInstance("SHA-1");
105
106             md.update(binaryPassword, 0, 8);
107
108             return md.digest();
109         }
110     }
111
112     /**
113      * Creates key from old password to decode scramble
114      * Used in 4.1 authentication with passwords stored
115      * pre-4.1 hashing.
116      *
117      * @param passwd the password to create the key from
118      *
119      * @return 20 byte generated key
120      *
121      * @throws NoSuchAlgorithmException if the message digest 'SHA-1'
122      * is not available.
123      */

124     static byte[] createKeyFromOldPassword(String JavaDoc passwd)
125         throws NoSuchAlgorithmException JavaDoc {
126         /* At first hash password to the string stored in password */
127         passwd = makeScrambledPassword(passwd);
128
129         /* Now convert it to the salt form */
130         int[] salt = getSaltFromPassword(passwd);
131
132         /* Finally get hash and bin password from salt */
133         return getBinaryPassword(salt, false);
134     }
135
136     /**
137      * Creates password to be stored in user database
138      * from raw string.
139      *
140      * Handles Pre-MySQL 4.1 passwords.
141      *
142      * @param password plaintext password
143      *
144      * @return scrambled password
145      *
146      * @throws NoSuchAlgorithmException if the message digest 'SHA-1' is not
147      * available.
148      */

149     static String JavaDoc makeScrambledPassword(String JavaDoc password)
150         throws NoSuchAlgorithmException JavaDoc {
151         long[] passwordHash = Util.newHash(password);
152         StringBuffer JavaDoc scramble = new StringBuffer JavaDoc();
153
154         scramble.append(longToHex(passwordHash[0]));
155         scramble.append(longToHex(passwordHash[1]));
156
157         return scramble.toString();
158     }
159
160     /**
161      * Encrypt/Decrypt function used for password encryption in authentication
162      *
163      * Simple XOR is used here but it is OK as we crypt random strings
164      *
165      * @param from IN Data for encryption
166      * @param to OUT Encrypt data to the buffer (may be the same)
167      * @param password IN Password used for encryption (same length)
168      * @param length IN Length of data to encrypt
169      */

170     static void passwordCrypt(byte[] from, byte[] to, byte[] password,
171         int length) {
172         int pos = 0;
173
174         while ((pos < from.length) && (pos < length)) {
175             to[pos] = (byte) (from[pos] ^ password[pos]);
176             pos++;
177         }
178     }
179
180     /**
181      * Stage one password hashing, used in MySQL 4.1 password handling
182      *
183      * @param password plaintext password
184      *
185      * @return stage one hash of password
186      *
187      * @throws NoSuchAlgorithmException if the message digest 'SHA-1' is not
188      * available.
189      */

190     static byte[] passwordHashStage1(String JavaDoc password)
191         throws NoSuchAlgorithmException JavaDoc {
192         MessageDigest JavaDoc md = MessageDigest.getInstance("SHA-1");
193         StringBuffer JavaDoc cleansedPassword = new StringBuffer JavaDoc();
194
195         int passwordLength = password.length();
196
197         for (int i = 0; i < passwordLength; i++) {
198             char c = password.charAt(i);
199
200             if ((c == ' ') || (c == '\t')) {
201                 continue; /* skip space in password */
202             }
203
204             cleansedPassword.append(c);
205         }
206
207         return md.digest(cleansedPassword.toString().getBytes());
208     }
209
210     /**
211      * Stage two password hashing used in MySQL 4.1
212      * password handling
213      *
214      * @param hash from passwordHashStage1
215      * @param salt salt used for stage two hashing
216      *
217      * @return result of stage two password hash
218      *
219      * @throws NoSuchAlgorithmException if the message digest 'SHA-1' is not
220      * available.
221      */

222     static byte[] passwordHashStage2(byte[] hashedPassword, byte[] salt)
223         throws NoSuchAlgorithmException JavaDoc {
224         MessageDigest JavaDoc md = MessageDigest.getInstance("SHA-1");
225
226         // hash 4 bytes of salt
227
md.update(salt, 0, 4);
228
229         md.update(hashedPassword, 0, SHA1_HASH_SIZE);
230
231         return md.digest();
232     }
233
234     private static int[] getSaltFromPassword(String JavaDoc password) {
235         int[] result = new int[6];
236
237         if ((password == null) || (password.length() == 0)) {
238             return result;
239         }
240
241         if (password.charAt(0) == PVERSION41_CHAR) {
242             // new password
243
String JavaDoc saltInHex = password.substring(1, 5);
244
245             int val = 0;
246
247             for (int i = 0; i < 4; i++) {
248                 val = (val << 4) + charVal(saltInHex.charAt(i));
249             }
250
251             return result;
252         } else {
253             int resultPos = 0;
254             int pos = 0;
255             int length = password.length();
256
257             while (pos < length) {
258                 int val = 0;
259
260                 for (int i = 0; i < 8; i++) {
261                     val = (val << 4) + charVal(password.charAt(pos++));
262                 }
263
264                 result[resultPos++] = val;
265             }
266
267             return result;
268         }
269     }
270
271     /**
272      * Returns hex value for given char
273      */

274     private static int charVal(char c) {
275         return (int) (((c >= '0') && (c <= '9')) ? (c - '0')
276                                                  : (((c >= 'A') && (c <= 'Z'))
277         ? (c - 'A' + 10) : (c - 'a' + 10)));
278     }
279
280     private static String JavaDoc longToHex(long val) {
281         String JavaDoc longHex = Long.toHexString(val);
282
283         int length = longHex.length();
284
285         if (length < 8) {
286             int padding = 8 - length;
287             StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
288
289             for (int i = 0; i < padding; i++) {
290                 buf.append("0");
291             }
292
293             buf.append(longHex);
294
295             return buf.toString();
296         } else {
297             return longHex.substring(0, 8);
298         }
299     }
300
301     // SERVER: public_seed=create_random_string()
302
// send(public_seed)
303
//
304
// CLIENT: recv(public_seed)
305
// hash_stage1=sha1("password")
306
// hash_stage2=sha1(hash_stage1)
307
// reply=xor(hash_stage1, sha1(public_seed,hash_stage2)
308
//
309
// // this three steps are done in scramble()
310
//
311
// send(reply)
312
//
313
//
314
// SERVER: recv(reply)
315
// hash_stage1=xor(reply, sha1(public_seed,hash_stage2))
316
// candidate_hash2=sha1(hash_stage1)
317
// check(candidate_hash2==hash_stage2)
318

319     static byte[] scramble411(String JavaDoc password, String JavaDoc seed) throws NoSuchAlgorithmException JavaDoc {
320         MessageDigest JavaDoc md = MessageDigest.getInstance("SHA-1");
321         
322         byte[] passwordHashStage1 = md.digest(password.getBytes());
323         md.reset();
324         byte[] passwordHashStage2 = md.digest(passwordHashStage1);
325         md.reset();
326         byte[] seedAsBytes = seed.getBytes(); // for debugging
327
md.update(seedAsBytes);
328         md.update(passwordHashStage2);
329         
330         byte[] toBeXord = md.digest();
331         
332         int numToXor = toBeXord.length;
333         
334         for (int i = 0; i < numToXor; i++) {
335             toBeXord[i] = (byte)(toBeXord[i] ^ passwordHashStage1[i]);
336         }
337         
338         return toBeXord;
339         
340     }
341 }
342
Popular Tags