alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*
 * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/Attic/NTLM.java,v 1.12.2.2 2004/02/22 18:21:13 olegk Exp $
 * $Revision: 1.12.2.2 $
 * $Date: 2004/02/22 18:21:13 $
 *
 * ====================================================================
 *
 *  Copyright 2002-2004 The Apache Software Foundation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * .
 *
 * [Additional notices, if required by prior licensing conditions]
 *
 */

package org.apache.commons.httpclient;

import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.httpclient.util.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Provides an implementation of the NTLM authentication protocol.
 * 

* This class provides methods for generating authentication * challenge responses for the NTLM authentication protocol. The NTLM * protocol is a proprietary Microsoft protocol and as such no RFC * exists for it. This class is based upon the reverse engineering * efforts of a wide range of people.

* *

Please note that an implementation of JCE must be correctly installed and configured when * using NTLM support.

* *

This class should not be used externally to HttpClient as it's API is specifically * designed to work with HttpClient's use case, in particular it's connection management.

* * @deprecated this class will be made package access for 2.0beta2 * * @author Adrian Sutton * @author Jeff Dever * @author Mike Bowler * * @version $Revision: 1.12.2.2 $ $Date: 2004/02/22 18:21:13 $ * @since 2.0alpha2 */ public final class NTLM { /** The current response */ private byte[] currentResponse; /** The current position */ private int currentPosition = 0; /** Log object for this class. */ private static final Log LOG = LogFactory.getLog(NTLM.class); /** Character encoding */ public static final String DEFAULT_CHARSET = "ASCII"; /** * Returns the response for the given message. * * @param message the message that was received from the server. * @param username the username to authenticate with. * @param password the password to authenticate with. * @param host The host. * @param domain the NT domain to authenticate in. * @return The response. * @throws HttpException If the messages cannot be retrieved. */ public final String getResponseFor(String message, String username, String password, String host, String domain) throws HttpException { final String response; if (message == null || message.trim().equals("")) { response = getType1Message(host, domain); } else { response = getType3Message(username, password, host, domain, parseType2Message(message)); } return response; } /** * Return the cipher for the specified key. * @param key The key. * @return Cipher The cipher. * @throws HttpException If the cipher cannot be retrieved. */ private Cipher getCipher(byte[] key) throws HttpException { try { final Cipher ecipher = Cipher.getInstance("DES/ECB/NoPadding"); key = setupKey(key); ecipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "DES")); return ecipher; } catch (NoSuchAlgorithmException e) { throw new HttpException("DES encryption is not available."); } catch (InvalidKeyException e) { throw new HttpException("Invalid key for DES encryption."); } catch (NoSuchPaddingException e) { throw new HttpException( "NoPadding option for DES is not available."); } } /** * Adds parity bits to the key. * @param key56 The key * @return The modified key. */ private byte[] setupKey(byte[] key56) { byte[] key = new byte[8]; key[0] = (byte) ((key56[0] >> 1) & 0xff); key[1] = (byte) ((((key56[0] & 0x01) << 6) | (((key56[1] & 0xff) >> 2) & 0xff)) & 0xff); key[2] = (byte) ((((key56[1] & 0x03) << 5) | (((key56[2] & 0xff) >> 3) & 0xff)) & 0xff); key[3] = (byte) ((((key56[2] & 0x07) << 4) | (((key56[3] & 0xff) >> 4) & 0xff)) & 0xff); key[4] = (byte) ((((key56[3] & 0x0f) << 3) | (((key56[4] & 0xff) >> 5) & 0xff)) & 0xff); key[5] = (byte) ((((key56[4] & 0x1f) << 2) | (((key56[5] & 0xff) >> 6) & 0xff)) & 0xff); key[6] = (byte) ((((key56[5] & 0x3f) << 1) | (((key56[6] & 0xff) >> 7) & 0xff)) & 0xff); key[7] = (byte) (key56[6] & 0x7f); for (int i = 0; i < key.length; i++) { key[i] = (byte) (key[i] << 1); } return key; } /** * Encrypt the data. * @param key The key. * @param bytes The data * @return byte[] The encrypted data * @throws HttpException If {@link Cipher.doFinal(byte[])} fails */ private byte[] encrypt(byte[] key, byte[] bytes) throws HttpException { Cipher ecipher = getCipher(key); try { byte[] enc = ecipher.doFinal(bytes); return enc; } catch (IllegalBlockSizeException e) { throw new HttpException("Invalid block size for DES encryption."); } catch (BadPaddingException e) { throw new HttpException( "Data not padded correctly for DES encryption."); } } /** * Prepares the object to create a response of the given length. * @param length the length of the response to prepare. */ private void prepareResponse(int length) { currentResponse = new byte[length]; currentPosition = 0; } /** * Adds the given byte to the response. * @param b the byte to add. */ private void addByte(byte b) { currentResponse[currentPosition] = b; currentPosition++; } /** * Adds the given bytes to the response. * @param bytes the bytes to add. */ private void addBytes(byte[] bytes) { for (int i = 0; i < bytes.length; i++) { currentResponse[currentPosition] = bytes[i]; currentPosition++; } } /** * Returns the response that has been generated after shrinking the array if * required and base64 encodes the response. * @return The response as above. */ private String getResponse() { byte[] resp; if (currentResponse.length > currentPosition) { byte[] tmp = new byte[currentPosition]; for (int i = 0; i < currentPosition; i++) { tmp[i] = currentResponse[i]; } resp = tmp; } else { resp = currentResponse; } return HttpConstants.getString(Base64.encode(resp)); } /** * Creates the first message (type 1 message) in the NTLM authentication sequence. * This message includes the user name, domain and host for the authentication session. * * @param host the computer name of the host requesting authentication. * @param domain The domain to authenticate with. * @return String the message to add to the HTTP request header. */ private String getType1Message(String host, String domain) { host = host.toUpperCase(); domain = domain.toUpperCase(); byte[] hostBytes = getBytes(host); byte[] domainBytes = getBytes(domain); int finalLength = 32 + hostBytes.length + domainBytes.length; prepareResponse(finalLength); // The initial id string. byte[] protocol = getBytes("NTLMSSP"); addBytes(protocol); addByte((byte) 0); // Type addByte((byte) 1); addByte((byte) 0); addByte((byte) 0); addByte((byte) 0); // Flags addByte((byte) 6); addByte((byte) 82); addByte((byte) 0); addByte((byte) 0); // Domain length (first time). int iDomLen = domainBytes.length; byte[] domLen = convertShort(iDomLen); addByte(domLen[0]); addByte(domLen[1]); // Domain length (second time). addByte(domLen[0]); addByte(domLen[1]); // Domain offset. byte[] domOff = convertShort(hostBytes.length + 32); addByte(domOff[0]); addByte(domOff[1]); addByte((byte) 0); addByte((byte) 0); // Host length (first time). byte[] hostLen = convertShort(hostBytes.length); addByte(hostLen[0]); addByte(hostLen[1]); // Host length (second time). addByte(hostLen[0]); addByte(hostLen[1]); // Host offset (always 32). byte[] hostOff = convertShort(32); addByte(hostOff[0]); addByte(hostOff[1]); addByte((byte) 0); addByte((byte) 0); // Host String. addBytes(hostBytes); // Domain String. addBytes(domainBytes); return getResponse(); } /** * Extracts the server nonce out of the given message type 2. * * @param message the String containing the base64 encoded message. * @return an array of 8 bytes that the server sent to be used when * hashing the password. */ private byte[] parseType2Message(String message) { // Decode the message first. byte[] msg = Base64.decode(getBytes(message)); byte[] nonce = new byte[8]; // The nonce is the 8 bytes starting from the byte in position 24. for (int i = 0; i < 8; i++) { nonce[i] = msg[i + 24]; } return nonce; } /** * Creates the type 3 message using the given server nonce. The type 3 message includes all the * information for authentication, host, domain, username and the result of encrypting the * nonce sent by the server using the user's password as the key. * * @param user The user name. This should not include the domain name. * @param password The password. * @param host The host that is originating the authentication request. * @param domain The domain to authenticate within. * @param nonce the 8 byte array the server sent. * @return The type 3 message. * @throws HttpException If {@encrypt(byte[],byte[])} fails. */ private String getType3Message(String user, String password, String host, String domain, byte[] nonce) throws HttpException { int ntRespLen = 0; int lmRespLen = 24; domain = domain.toUpperCase(); host = host.toUpperCase(); user = user.toUpperCase(); byte[] domainBytes = getBytes(domain); byte[] hostBytes = getBytes(host); byte[] userBytes = getBytes(user); int domainLen = domainBytes.length; int hostLen = hostBytes.length; int userLen = userBytes.length; int finalLength = 64 + ntRespLen + lmRespLen + domainLen + userLen + hostLen; prepareResponse(finalLength); byte[] ntlmssp = getBytes("NTLMSSP"); addBytes(ntlmssp); addByte((byte) 0); addByte((byte) 3); addByte((byte) 0); addByte((byte) 0); addByte((byte) 0); // LM Resp Length (twice) addBytes(convertShort(24)); addBytes(convertShort(24)); // LM Resp Offset addBytes(convertShort(finalLength - 24)); addByte((byte) 0); addByte((byte) 0); // NT Resp Length (twice) addBytes(convertShort(0)); addBytes(convertShort(0)); // NT Resp Offset addBytes(convertShort(finalLength)); addByte((byte) 0); addByte((byte) 0); // Domain length (twice) addBytes(convertShort(domainLen)); addBytes(convertShort(domainLen)); // Domain offset. addBytes(convertShort(64)); addByte((byte) 0); addByte((byte) 0); // User Length (twice) addBytes(convertShort(userLen)); addBytes(convertShort(userLen)); // User offset addBytes(convertShort(64 + domainLen)); addByte((byte) 0); addByte((byte) 0); // Host length (twice) addBytes(convertShort(hostLen)); addBytes(convertShort(hostLen)); // Host offset addBytes(convertShort(64 + domainLen + userLen)); for (int i = 0; i < 6; i++) { addByte((byte) 0); } // Message length addBytes(convertShort(finalLength)); addByte((byte) 0); addByte((byte) 0); // Flags addByte((byte) 6); addByte((byte) 82); addByte((byte) 0); addByte((byte) 0); addBytes(domainBytes); addBytes(userBytes); addBytes(hostBytes); addBytes(hashPassword(password, nonce)); return getResponse(); } /** * Creates the LANManager and NT response for the given password using the * given nonce. * @param password the password to create a hash for. * @param nonce the nonce sent by the server. * @return The response. * @throws HttpException If {@link #encrypt(byte[],byte[])} fails. */ private byte[] hashPassword(String password, byte[] nonce) throws HttpException { byte[] passw = getBytes(password.toUpperCase()); byte[] lmPw1 = new byte[7]; byte[] lmPw2 = new byte[7]; int len = passw.length; if (len > 7) { len = 7; } int idx; for (idx = 0; idx < len; idx++) { lmPw1[idx] = passw[idx]; } for (; idx < 7; idx++) { lmPw1[idx] = (byte) 0; } len = passw.length; if (len > 14) { len = 14; } for (idx = 7; idx < len; idx++) { lmPw2[idx - 7] = passw[idx]; } for (; idx < 14; idx++) { lmPw2[idx - 7] = (byte) 0; } // Create LanManager hashed Password byte[] magic = { (byte) 0x4B, (byte) 0x47, (byte) 0x53, (byte) 0x21, (byte) 0x40, (byte) 0x23, (byte) 0x24, (byte) 0x25 }; byte[] lmHpw1; lmHpw1 = encrypt(lmPw1, magic); byte[] lmHpw2 = encrypt(lmPw2, magic); byte[] lmHpw = new byte[21]; for (int i = 0; i < lmHpw1.length; i++) { lmHpw[i] = lmHpw1[i]; } for (int i = 0; i < lmHpw2.length; i++) { lmHpw[i + 8] = lmHpw2[i]; } for (int i = 0; i < 5; i++) { lmHpw[i + 16] = (byte) 0; } // Create the responses. byte[] lmResp = new byte[24]; calcResp(lmHpw, nonce, lmResp); return lmResp; } /** * Takes a 21 byte array and treats it as 3 56-bit DES keys. The 8 byte * plaintext is encrypted with each key and the resulting 24 bytes are * stored in the results array. * * @param keys The keys. * @param plaintext The plain text to encrypt. * @param results Where the results are stored. * @throws HttpException If {@link #encrypt(byte[],byte[])} fails. */ private void calcResp(byte[] keys, byte[] plaintext, byte[] results) throws HttpException { byte[] keys1 = new byte[7]; byte[] keys2 = new byte[7]; byte[] keys3 = new byte[7]; for (int i = 0; i < 7; i++) { keys1[i] = keys[i]; } for (int i = 0; i < 7; i++) { keys2[i] = keys[i + 7]; } for (int i = 0; i < 7; i++) { keys3[i] = keys[i + 14]; } byte[] results1 = encrypt(keys1, plaintext); byte[] results2 = encrypt(keys2, plaintext); byte[] results3 = encrypt(keys3, plaintext); for (int i = 0; i < 8; i++) { results[i] = results1[i]; } for (int i = 0; i < 8; i++) { results[i + 8] = results2[i]; } for (int i = 0; i < 8; i++) { results[i + 16] = results3[i]; } } /** * Converts a given number to a two byte array in little endian order. * @param num the number to convert. * @return The byte representation of num in little endian order. */ private byte[] convertShort(int num) { byte[] val = new byte[2]; String hex = Integer.toString(num, 16); while (hex.length() < 4) { hex = "0" + hex; } String low = hex.substring(2, 4); String high = hex.substring(0, 2); val[0] = (byte) Integer.parseInt(low, 16); val[1] = (byte) Integer.parseInt(high, 16); return val; } /** * Convert a string to a byte array. * @param s The string * @return byte[] The resulting byte array. */ private static byte[] getBytes(final String s) { if (s == null) { throw new IllegalArgumentException("Parameter may not be null"); } try { return s.getBytes(DEFAULT_CHARSET); } catch (UnsupportedEncodingException unexpectedEncodingException) { throw new RuntimeException("NTLM requires ASCII support"); } } }
... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.