001: // jTDS JDBC Driver for Microsoft SQL Server and Sybase
002: // Copyright (C) 2004 The jTDS Project
003: //
004: // This library is free software; you can redistribute it and/or
005: // modify it under the terms of the GNU Lesser General Public
006: // License as published by the Free Software Foundation; either
007: // version 2.1 of the License, or (at your option) any later version.
008: //
009: // This library is distributed in the hope that it will be useful,
010: // but WITHOUT ANY WARRANTY; without even the implied warranty of
011: // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012: // Lesser General Public License for more details.
013: //
014: // You should have received a copy of the GNU Lesser General Public
015: // License along with this library; if not, write to the Free Software
016: // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: //
018: package net.sourceforge.jtds.jdbc;
019:
020: import net.sourceforge.jtds.util.MD4Digest;
021: import net.sourceforge.jtds.util.DESEngine;
022: import net.sourceforge.jtds.util.MD5Digest;
023:
024: import java.io.UnsupportedEncodingException;
025: import java.util.Arrays;
026:
027: /**
028: * This class calculates the two "responses" to the nonce supplied by the server
029: * as a part of NTLM authentication.
030: *
031: * Much gratitude to the authors of this page, esp. for NTLMv2 info:
032: * http://davenport.sourceforge.net/ntlm.html
033: *
034: * @author Matt Brinkley
035: * @version $Id: NtlmAuth.java,v 1.7 2006/06/23 18:00:56 matt_brinkley Exp $
036: */
037: public class NtlmAuth {
038:
039: //-------------------------------------------------------------------------
040: // LM/NTLM - public interface
041: //-------------------------------------------------------------------------
042:
043: public static byte[] answerNtChallenge(String password, byte[] nonce)
044: throws UnsupportedEncodingException {
045: return encryptNonce(ntHash(password), nonce);
046: }
047:
048: public static byte[] answerLmChallenge(String pwd, byte[] nonce)
049: throws UnsupportedEncodingException {
050: byte[] password = convertPassword(pwd);
051:
052: DESEngine d1 = new DESEngine(true, makeDESkey(password, 0));
053: DESEngine d2 = new DESEngine(true, makeDESkey(password, 7));
054: byte[] encrypted = new byte[21];
055: Arrays.fill(encrypted, (byte) 0);
056:
057: d1.processBlock(nonce, 0, encrypted, 0);
058: d2.processBlock(nonce, 0, encrypted, 8);
059:
060: return encryptNonce(encrypted, nonce);
061: }
062:
063: //-------------------------------------------------------------------------
064: // LM/NTLM v2 - public interface
065: //-------------------------------------------------------------------------
066: public static byte[] answerNtlmv2Challenge(String domain,
067: String user, String password, byte[] nonce,
068: byte[] targetInfo, byte[] clientNonce)
069: throws UnsupportedEncodingException {
070: return answerNtlmv2Challenge(domain, user, password, nonce,
071: targetInfo, clientNonce, System.currentTimeMillis());
072: }
073:
074: public static byte[] answerNtlmv2Challenge(String domain,
075: String user, String password, byte[] nonce,
076: byte[] targetInfo, byte[] clientNonce, byte[] timestamp)
077: throws UnsupportedEncodingException {
078: byte[] hash = ntv2Hash(domain, user, password);
079: byte[] blob = createBlob(targetInfo, clientNonce, timestamp);
080: return lmv2Response(hash, blob, nonce);
081: }
082:
083: public static byte[] answerNtlmv2Challenge(String domain,
084: String user, String password, byte[] nonce,
085: byte[] targetInfo, byte[] clientNonce, long now)
086: throws UnsupportedEncodingException {
087: return answerNtlmv2Challenge(domain, user, password, nonce,
088: targetInfo, clientNonce, createTimestamp(now));
089: }
090:
091: public static byte[] answerLmv2Challenge(String domain,
092: String user, String password, byte[] nonce,
093: byte[] clientNonce) throws UnsupportedEncodingException {
094: byte[] hash = ntv2Hash(domain, user, password);
095: return lmv2Response(hash, clientNonce, nonce);
096: }
097:
098: //-------------------------------------------------------------------------
099: // LMv2/NTLMv2 impl helpers
100: //-------------------------------------------------------------------------
101:
102: private static byte[] ntv2Hash(String domain, String user,
103: String password) throws UnsupportedEncodingException {
104: byte[] hash = ntHash(password);
105: String identity = user.toUpperCase() + domain.toUpperCase();
106: byte[] identityBytes = identity
107: .getBytes("UnicodeLittleUnmarked");
108:
109: return hmacMD5(identityBytes, hash);
110: }
111:
112: /**
113: * Creates the LMv2 Response from the given hash, client data, and
114: * Type 2 challenge.
115: *
116: * @param hash The NTLMv2 Hash.
117: * @param clientData The client data (blob or client challenge).
118: * @param challenge The server challenge from the Type 2 message.
119: *
120: * @return The response (either NTLMv2 or LMv2, depending on the
121: * client data).
122: */
123: private static byte[] lmv2Response(byte[] hash, byte[] clientData,
124: byte[] challenge) {
125: byte[] data = new byte[challenge.length + clientData.length];
126: System.arraycopy(challenge, 0, data, 0, challenge.length);
127: System.arraycopy(clientData, 0, data, challenge.length,
128: clientData.length);
129: byte[] mac = hmacMD5(data, hash);
130: byte[] lmv2Response = new byte[mac.length + clientData.length];
131: System.arraycopy(mac, 0, lmv2Response, 0, mac.length);
132: System.arraycopy(clientData, 0, lmv2Response, mac.length,
133: clientData.length);
134: return lmv2Response;
135: }
136:
137: /**
138: * Calculates the HMAC-MD5 hash of the given data using the specified
139: * hashing key.
140: *
141: * @param data The data for which the hash will be calculated.
142: * @param key The hashing key.
143: *
144: * @return The HMAC-MD5 hash of the given data.
145: */
146: private static byte[] hmacMD5(byte[] data, byte[] key) {
147: byte[] ipad = new byte[64];
148: byte[] opad = new byte[64];
149: for (int i = 0; i < 64; i++) {
150: ipad[i] = (byte) 0x36;
151: opad[i] = (byte) 0x5c;
152: }
153: for (int i = key.length - 1; i >= 0; i--) {
154: ipad[i] ^= key[i];
155: opad[i] ^= key[i];
156: }
157: byte[] content = new byte[data.length + 64];
158: System.arraycopy(ipad, 0, content, 0, 64);
159: System.arraycopy(data, 0, content, 64, data.length);
160: data = md5(content);
161: content = new byte[data.length + 64];
162: System.arraycopy(opad, 0, content, 0, 64);
163: System.arraycopy(data, 0, content, 64, data.length);
164: return md5(content);
165: }
166:
167: private static byte[] md5(byte[] data) {
168: MD5Digest md5 = new MD5Digest();
169: md5.update(data, 0, data.length);
170: byte[] hash = new byte[16];
171: md5.doFinal(hash, 0);
172: return hash;
173: }
174:
175: /**
176: * Implementation of HMAC-MD5 that uses the JDK's crypto API
177: * We don't use this because of JTDS's support of JDK 1.3.
178: */
179: /*
180: private static byte[] hmacMD5(byte[] data, byte[] key)
181: throws NoSuchAlgorithmException, InvalidKeyException
182: {
183: SecretKey md5key = new SecretKeySpec( key, "HmacMD5" );
184: Mac mac = Mac.getInstance(md5key.getAlgorithm());
185: mac.init(md5key);
186: return mac.doFinal(data);
187: }
188: */
189:
190: /**
191: * Creates a timestamp in the format used in NTLMv2 responses.
192: * Public so it could be unit tested.
193: * @param time current time, as returned from System.currentTimeMillis
194: * @return little-endian byte array of number of tenths of microseconds since
195: * Jan 1, 1601
196: */
197: public static byte[] createTimestamp(long time) {
198: time += 11644473600000l; // milliseconds from January 1, 1601 -> epoch.
199: time *= 10000; // tenths of a microsecond.
200:
201: // convert to little-endian byte array.
202: byte[] timestamp = new byte[8];
203: for (int i = 0; i < 8; i++) {
204: timestamp[i] = (byte) time;
205: time >>>= 8;
206: }
207:
208: return timestamp;
209: }
210:
211: /**
212: * Creates the NTLMv2 blob from the given target information block and
213: * client challenge.
214: *
215: * @param targetInformation The target information block from the Type 2
216: * message.
217: * @param clientChallenge The random 8-byte client challenge.
218: *
219: * @return The blob, used in the calculation of the NTLMv2 Response.
220: */
221: private static byte[] createBlob(byte[] targetInformation,
222: byte[] clientChallenge, byte[] timestamp) {
223: byte[] blobSignature = new byte[] { (byte) 0x01, (byte) 0x01,
224: (byte) 0x00, (byte) 0x00 };
225: byte[] reserved = new byte[] { (byte) 0x00, (byte) 0x00,
226: (byte) 0x00, (byte) 0x00 };
227: byte[] unknown1 = new byte[] { (byte) 0x00, (byte) 0x00,
228: (byte) 0x00, (byte) 0x00 };
229: byte[] unknown2 = new byte[] { (byte) 0x00, (byte) 0x00,
230: (byte) 0x00, (byte) 0x00 };
231:
232: byte[] blob = new byte[blobSignature.length + reserved.length
233: + timestamp.length + clientChallenge.length
234: + unknown1.length + targetInformation.length
235: + unknown2.length];
236: int offset = 0;
237: System.arraycopy(blobSignature, 0, blob, offset,
238: blobSignature.length);
239: offset += blobSignature.length;
240: System.arraycopy(reserved, 0, blob, offset, reserved.length);
241: offset += reserved.length;
242: System.arraycopy(timestamp, 0, blob, offset, timestamp.length);
243: offset += timestamp.length;
244: System.arraycopy(clientChallenge, 0, blob, offset,
245: clientChallenge.length);
246: offset += clientChallenge.length;
247: System.arraycopy(unknown1, 0, blob, offset, unknown1.length);
248: offset += unknown1.length;
249: System.arraycopy(targetInformation, 0, blob, offset,
250: targetInformation.length);
251: offset += targetInformation.length;
252: System.arraycopy(unknown2, 0, blob, offset, unknown2.length);
253: return blob;
254: }
255:
256: //-------------------------------------------------------------------------
257: // LM/NTLM impl helpers
258: //-------------------------------------------------------------------------
259:
260: private static byte[] encryptNonce(byte[] key, byte[] nonce) {
261: byte[] out = new byte[24];
262:
263: DESEngine d1 = new DESEngine(true, makeDESkey(key, 0));
264: DESEngine d2 = new DESEngine(true, makeDESkey(key, 7));
265: DESEngine d3 = new DESEngine(true, makeDESkey(key, 14));
266:
267: d1.processBlock(nonce, 0, out, 0);
268: d2.processBlock(nonce, 0, out, 8);
269: d3.processBlock(nonce, 0, out, 16);
270:
271: return out;
272: }
273:
274: /**
275: * Creates the md4 hash of the unicode password. This is used as the DES
276: * key when encrypting the nonce for NTLM challenge-response
277: */
278: private static byte[] ntHash(String password)
279: throws UnsupportedEncodingException {
280: byte[] key = new byte[21];
281: Arrays.fill(key, (byte) 0);
282: byte[] pwd = password.getBytes("UnicodeLittleUnmarked");
283:
284: // do the md4 hash of the unicode passphrase...
285: MD4Digest md4 = new MD4Digest();
286: md4.update(pwd, 0, pwd.length);
287: md4.doFinal(key, 0);
288: return key;
289: }
290:
291: /**
292: * Used by answerNtlmChallenge. We need the password converted to caps,
293: * narrowed and padded/truncated to 14 chars...
294: */
295: private static byte[] convertPassword(String password)
296: throws UnsupportedEncodingException {
297: byte[] pwd = password.toUpperCase().getBytes("UTF8");
298:
299: byte[] rtn = new byte[14];
300: Arrays.fill(rtn, (byte) 0);
301: System.arraycopy(pwd, 0, // src
302: rtn, 0, // dst
303: pwd.length > 14 ? 14 : pwd.length); // length
304:
305: return rtn;
306: }
307:
308: /**
309: * Turns a 7-byte DES key into an 8-byte one by adding parity bits. All
310: * implementations of DES seem to want an 8-byte key.
311: */
312: private static byte[] makeDESkey(byte[] buf, int off) {
313: byte[] ret = new byte[8];
314:
315: ret[0] = (byte) ((buf[off + 0] >> 1) & 0xff);
316: ret[1] = (byte) ((((buf[off + 0] & 0x01) << 6) | (((buf[off + 1] & 0xff) >> 2) & 0xff)) & 0xff);
317: ret[2] = (byte) ((((buf[off + 1] & 0x03) << 5) | (((buf[off + 2] & 0xff) >> 3) & 0xff)) & 0xff);
318: ret[3] = (byte) ((((buf[off + 2] & 0x07) << 4) | (((buf[off + 3] & 0xff) >> 4) & 0xff)) & 0xff);
319: ret[4] = (byte) ((((buf[off + 3] & 0x0F) << 3) | (((buf[off + 4] & 0xff) >> 5) & 0xff)) & 0xff);
320: ret[5] = (byte) ((((buf[off + 4] & 0x1F) << 2) | (((buf[off + 5] & 0xff) >> 6) & 0xff)) & 0xff);
321: ret[6] = (byte) ((((buf[off + 5] & 0x3F) << 1) | (((buf[off + 6] & 0xff) >> 7) & 0xff)) & 0xff);
322: ret[7] = (byte) (buf[off + 6] & 0x7F);
323:
324: for (int i = 0; i < 8; i++) {
325: ret[i] = (byte) (ret[i] << 1);
326: }
327:
328: return ret;
329: }
330: }
|