Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: com/mysql/jdbc/Security.java


1   /*
2    Copyright (C) 2002-2004 MySQL AB
3   
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of version 2 of the GNU General Public License as
6    published by the Free Software Foundation.
7    
8   
9    There are special exceptions to the terms and conditions of the GPL 
10   as it is applied to this software. View the full text of the 
11   exception exception in file EXCEPTIONS-CONNECTOR-J in the directory of this 
12   software distribution.
13  
14   This program is distributed in the hope that it will be useful,
15   but WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   GNU General Public License for more details.
18  
19   You should have received a copy of the GNU General Public License
20   along with this program; if not, write to the Free Software
21   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  
23   */
24  package com.mysql.jdbc;
25  
26  import java.security.MessageDigest;
27  import java.security.NoSuchAlgorithmException;
28  
29  
30  /**
31   * Methods for doing secure authentication with MySQL-4.1
32   * and newer.
33   *
34   * @author Mark Matthews
35   *
36   * @version $Id: Security.java,v 1.1.4.6 2004/08/09 22:15:12 mmatthew Exp $
37   */
38  class Security {
39      private static final int SHA1_HASH_SIZE = 20;
40      private static final char PVERSION41_CHAR = '*';
41  
42      /**
43       * Prevent construction.
44       */
45      private Security() {
46          super();
47      }
48  
49      /*
50        Convert password in salted form to binary string password and hash-salt
51        For old password this involes one more hashing
52  
53        SYNOPSIS
54              get_hash_and_password()
55              salt         IN  Salt to convert from
56              pversion     IN  Password version to use
57              hash         OUT Store zero ended hash here
58              bin_password OUT Store binary password here (no zero at the end)
59  
60        RETURN
61              0 for pre 4.1 passwords
62         !0 password version char for newer passwords
63      */
64  
65      /**
66       * DOCUMENT ME!
67       *
68       * @param salt DOCUMENT ME!
69       * @param usingNewPasswords DOCUMENT ME!
70       *
71       * @return DOCUMENT ME!
72       *
73       * @throws NoSuchAlgorithmException if the message digest 'SHA-1' is not
74       * available.
75       */
76      static byte[] getBinaryPassword(int[] salt, boolean usingNewPasswords)
77          throws NoSuchAlgorithmException {
78          int val = 0;
79  
80          byte[] binaryPassword = new byte[SHA1_HASH_SIZE]; /* Binary password loop pointer */
81  
82          if (usingNewPasswords) /* New password version assumed */ {
83              int pos = 0;
84  
85              for (int i = 0; i < 4; i++) /* Iterate over these elements*/ {
86                  val = salt[i];
87  
88                  for (int t = 3; t >= 0; t--) {
89                      binaryPassword[pos++] = (byte) (val & 255);
90                      val >>= 8; /* Scroll 8 bits to get next part*/
91                  }
92              }
93  
94              return binaryPassword;
95          } else {
96              int offset = 0;
97  
98              for (int i = 0; i < 2; i++) /* Iterate over these elements*/ {
99                  val = salt[i];
100 
101                 for (int t = 3; t >= 0; t--) {
102                     binaryPassword[t + offset] = (byte) (val % 256);
103                     val >>= 8; /* Scroll 8 bits to get next part*/
104                 }
105 
106                 offset += 4;
107             }
108 
109             MessageDigest md = MessageDigest.getInstance("SHA-1");
110 
111             md.update(binaryPassword, 0, 8);
112 
113             return md.digest();
114         }
115     }
116 
117     /**
118      * Creates key from old password to decode scramble
119      * Used in 4.1 authentication with passwords stored
120      * pre-4.1 hashing.
121      *
122      * @param passwd the password to create the key from
123      *
124      * @return 20 byte generated key
125      *
126      * @throws NoSuchAlgorithmException if the message digest 'SHA-1'
127      *          is not available.
128      */
129     static byte[] createKeyFromOldPassword(String passwd)
130         throws NoSuchAlgorithmException {
131         /* At first hash password to the string stored in password */
132         passwd = makeScrambledPassword(passwd);
133 
134         /* Now convert it to the salt form */
135         int[] salt = getSaltFromPassword(passwd);
136 
137         /* Finally get hash and bin password from salt */
138         return getBinaryPassword(salt, false);
139     }
140 
141     /**
142      * Creates password to be stored in user database
143      * from raw string.
144      *
145      * Handles Pre-MySQL 4.1 passwords.
146      *
147      * @param password plaintext password
148      *
149      * @return scrambled password
150      *
151      * @throws NoSuchAlgorithmException if the message digest 'SHA-1' is not
152      *          available.
153      */
154     static String makeScrambledPassword(String password)
155         throws NoSuchAlgorithmException {
156         long[] passwordHash = Util.newHash(password);
157         StringBuffer scramble = new StringBuffer();
158 
159         scramble.append(longToHex(passwordHash[0]));
160         scramble.append(longToHex(passwordHash[1]));
161 
162         return scramble.toString();
163     }
164 
165     /**
166      * Encrypt/Decrypt function used for password encryption in authentication
167      *
168      * Simple XOR is used here but it is OK as we crypt random strings
169      *
170      * @param from     IN     Data for encryption
171      * @param to       OUT    Encrypt data to the buffer (may be the same)
172      * @param password IN     Password used for encryption (same length)
173      * @param length   IN     Length of data to encrypt
174      */
175     static void passwordCrypt(byte[] from, byte[] to, byte[] password,
176         int length) {
177         int pos = 0;
178 
179         while ((pos < from.length) && (pos < length)) {
180             to[pos] = (byte) (from[pos] ^ password[pos]);
181             pos++;
182         }
183     }
184 
185     /**
186      * Stage one password hashing, used in MySQL 4.1 password handling
187      *
188      * @param password plaintext password
189      *
190      * @return stage one hash of password
191      *
192      * @throws NoSuchAlgorithmException if the message digest 'SHA-1' is not
193      *          available.
194      */
195     static byte[] passwordHashStage1(String password)
196         throws NoSuchAlgorithmException {
197         MessageDigest md = MessageDigest.getInstance("SHA-1");
198         StringBuffer cleansedPassword = new StringBuffer();
199 
200         int passwordLength = password.length();
201 
202         for (int i = 0; i < passwordLength; i++) {
203             char c = password.charAt(i);
204 
205             if ((c == ' ') || (c == '\t')) {
206                 continue; /* skip space in password */
207             }
208 
209             cleansedPassword.append(c);
210         }
211 
212         return md.digest(cleansedPassword.toString().getBytes());
213     }
214 
215     /**
216      * Stage two password hashing used in MySQL 4.1
217      * password handling
218      *
219      * @param hash from passwordHashStage1
220      * @param salt salt used for stage two hashing
221      *
222      * @return result of stage two password hash
223      *
224      * @throws NoSuchAlgorithmException if the message digest 'SHA-1' is not
225      * available.
226      */
227     static byte[] passwordHashStage2(byte[] hashedPassword, byte[] salt)
228         throws NoSuchAlgorithmException {
229         MessageDigest md = MessageDigest.getInstance("SHA-1");
230 
231         // hash 4 bytes of salt
232         md.update(salt, 0, 4);
233 
234         md.update(hashedPassword, 0, SHA1_HASH_SIZE);
235 
236         return md.digest();
237     }
238 
239     private static int[] getSaltFromPassword(String password) {
240         int[] result = new int[6];
241 
242         if ((password == null) || (password.length() == 0)) {
243             return result;
244         }
245 
246         if (password.charAt(0) == PVERSION41_CHAR) {
247             // new password
248             String saltInHex = password.substring(1, 5);
249 
250             int val = 0;
251 
252             for (int i = 0; i < 4; i++) {
253                 val = (val << 4) + charVal(saltInHex.charAt(i));
254             }
255 
256             return result;
257         } else {
258             int resultPos = 0;
259             int pos = 0;
260             int length = password.length();
261 
262             while (pos < length) {
263                 int val = 0;
264 
265                 for (int i = 0; i < 8; i++) {
266                     val = (val << 4) + charVal(password.charAt(pos++));
267                 }
268 
269                 result[resultPos++] = val;
270             }
271 
272             return result;
273         }
274     }
275 
276     /**
277      * Returns hex value for given char
278      */
279     private static int charVal(char c) {
280         return (int) (((c >= '0') && (c <= '9')) ? (c - '0')
281                                                  : (((c >= 'A') && (c <= 'Z'))
282         ? (c - 'A' + 10) : (c - 'a' + 10)));
283     }
284 
285     private static String longToHex(long val) {
286         String longHex = Long.toHexString(val);
287 
288         int length = longHex.length();
289 
290         if (length < 8) {
291             int padding = 8 - length;
292             StringBuffer buf = new StringBuffer();
293 
294             for (int i = 0; i < padding; i++) {
295                 buf.append("0");
296             }
297 
298             buf.append(longHex);
299 
300             return buf.toString();
301         } else {
302             return longHex.substring(0, 8);
303         }
304     }
305 
306   //  SERVER:  public_seed=create_random_string()
307     //       send(public_seed)
308     //
309     //  CLIENT:  recv(public_seed)
310     //       hash_stage1=sha1("password")
311     //       hash_stage2=sha1(hash_stage1)
312     //       reply=xor(hash_stage1, sha1(public_seed,hash_stage2)
313     //
314     //       // this three steps are done in scramble()
315     //
316     //       send(reply)
317     //
318     //
319     //  SERVER:  recv(reply)
320     //       hash_stage1=xor(reply, sha1(public_seed,hash_stage2))
321     //       candidate_hash2=sha1(hash_stage1)
322     //       check(candidate_hash2==hash_stage2)
323     
324   static byte[] scramble411(String password, String seed) throws NoSuchAlgorithmException {
325     MessageDigest md = MessageDigest.getInstance("SHA-1");
326     
327     byte[] passwordHashStage1 = md.digest(password.getBytes());
328     md.reset();
329     byte[] passwordHashStage2 = md.digest(passwordHashStage1);
330     md.reset();
331     byte[] seedAsBytes = seed.getBytes(); // for debugging
332     md.update(seedAsBytes);
333     md.update(passwordHashStage2);
334     
335     byte[] toBeXord = md.digest();
336     
337     int numToXor = toBeXord.length;
338     
339     for (int i = 0; i < numToXor; i++) {
340       toBeXord[i] = (byte)(toBeXord[i] ^ passwordHashStage1[i]);
341     }
342     
343     return toBeXord;
344     
345   }
346 }