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 }