/*
 * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/auth/NTLM.java,v 1.11 2004/05/13 04:02:00 mbecke Exp $
 * $Revision: 480424 $
 * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
 *
 * ====================================================================
 *
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You 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
 * <http://www.apache.org/>.
 *
 */

package org.apache.commons.httpclient.auth;

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.codec.binary.Base64;
import org.apache.commons.httpclient.util.EncodingUtil;

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.

Author:Adrian Sutton, Jeff Dever, Mike Bowler
Version:$Revision: 480424 $ $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
Since:3.0
/** * Provides an implementation of the NTLM authentication protocol. * <p> * 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.</p> * * <p>Please note that an implementation of JCE must be correctly installed and configured when * using NTLM support.</p> * * <p>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.</p> * * @author <a href="mailto:adrian@ephox.com">Adrian Sutton</a> * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a> * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> * * @version $Revision: 480424 $ $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $ * @since 3.0 */
final class NTLM {
Character encoding
/** Character encoding */
public static final String DEFAULT_CHARSET = "ASCII";
The current response
/** The current response */
private byte[] currentResponse;
The current position
/** The current position */
private int currentPosition = 0;
The character set to use for encoding the credentials
/** The character set to use for encoding the credentials */
private String credentialCharset = DEFAULT_CHARSET;
Returns the response for the given message.
Params:
  • message – the message that was received from the server.
  • username – the username to authenticate with.
  • password – the password to authenticate with.
  • host – The host.
  • domain – the NT domain to authenticate in.
Throws:
  • HttpException – If the messages cannot be retrieved.
Returns:The response.
/** * 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 AuthenticationException { 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.
Params:
  • key – The key.
Throws:
Returns:Cipher The cipher.
/** * Return the cipher for the specified key. * @param key The key. * @return Cipher The cipher. * @throws AuthenticationException If the cipher cannot be retrieved. */
private Cipher getCipher(byte[] key) throws AuthenticationException { 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 AuthenticationException("DES encryption is not available.", e); } catch (InvalidKeyException e) { throw new AuthenticationException("Invalid key for DES encryption.", e); } catch (NoSuchPaddingException e) { throw new AuthenticationException( "NoPadding option for DES is not available.", e); } }
Adds parity bits to the key.
Params:
  • key56 – The key
Returns:The modified key.
/** * 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.
Params:
  • key – The key.
  • bytes – The data
Throws:
  • HttpException – If Cipher.doFinal(byte[]) fails
Returns:byte[] The encrypted data
/** * 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 AuthenticationException { Cipher ecipher = getCipher(key); try { byte[] enc = ecipher.doFinal(bytes); return enc; } catch (IllegalBlockSizeException e) { throw new AuthenticationException("Invalid block size for DES encryption.", e); } catch (BadPaddingException e) { throw new AuthenticationException("Data not padded correctly for DES encryption.", e); } }
Prepares the object to create a response of the given length.
Params:
  • length – the length of the response to prepare.
/** * 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.
Params:
  • b – the byte to add.
/** * 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.
Params:
  • bytes – the bytes to add.
/** * 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.
Returns:The response as above.
/** * 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 EncodingUtil.getAsciiString(Base64.encodeBase64(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.
Params:
  • host – the computer name of the host requesting authentication.
  • domain – The domain to authenticate with.
Returns:String the message to add to the HTTP request header.
/** * 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. */
public String getType1Message(String host, String domain) { host = host.toUpperCase(); domain = domain.toUpperCase(); byte[] hostBytes = EncodingUtil.getBytes(host, DEFAULT_CHARSET); byte[] domainBytes = EncodingUtil.getBytes(domain, DEFAULT_CHARSET); int finalLength = 32 + hostBytes.length + domainBytes.length; prepareResponse(finalLength); // The initial id string. byte[] protocol = EncodingUtil.getBytes("NTLMSSP", DEFAULT_CHARSET); 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.
Params:
  • message – the String containing the base64 encoded message.
Returns:an array of 8 bytes that the server sent to be used when hashing the password.
/** * 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. */
public byte[] parseType2Message(String message) { // Decode the message first. byte[] msg = Base64.decodeBase64(EncodingUtil.getBytes(message, DEFAULT_CHARSET)); 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.
Params:
  • user – The user name. This should not include the domain name.
  • password – The password.
  • host – The host that is originating the authentication request.
  • domain – The domain to authenticate within.
  • nonce – the 8 byte array the server sent.
Throws:
Returns:The type 3 message.
/** * 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 AuthenticationException If {@encrypt(byte[],byte[])} fails. */
public String getType3Message(String user, String password, String host, String domain, byte[] nonce) throws AuthenticationException { int ntRespLen = 0; int lmRespLen = 24; domain = domain.toUpperCase(); host = host.toUpperCase(); user = user.toUpperCase(); byte[] domainBytes = EncodingUtil.getBytes(domain, DEFAULT_CHARSET); byte[] hostBytes = EncodingUtil.getBytes(host, DEFAULT_CHARSET); byte[] userBytes = EncodingUtil.getBytes(user, credentialCharset); int domainLen = domainBytes.length; int hostLen = hostBytes.length; int userLen = userBytes.length; int finalLength = 64 + ntRespLen + lmRespLen + domainLen + userLen + hostLen; prepareResponse(finalLength); byte[] ntlmssp = EncodingUtil.getBytes("NTLMSSP", DEFAULT_CHARSET); 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.
Params:
  • password – the password to create a hash for.
  • nonce – the nonce sent by the server.
Throws:
Returns:The response.
/** * 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 AuthenticationException { byte[] passw = EncodingUtil.getBytes(password.toUpperCase(), credentialCharset); 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.
Params:
  • keys – The keys.
  • plaintext – The plain text to encrypt.
  • results – Where the results are stored.
Throws:
/** * 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 AuthenticationException If {@link #encrypt(byte[],byte[])} fails. */
private void calcResp(byte[] keys, byte[] plaintext, byte[] results) throws AuthenticationException { 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.
Params:
  • num – the number to convert.
Returns:The byte representation of num in little endian order.
/** * Converts a given number to a two byte array in little endian order. * @param num the number to convert. * @return The byte representation of <i>num</i> 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; }
Returns:Returns the credentialCharset.
/** * @return Returns the credentialCharset. */
public String getCredentialCharset() { return credentialCharset; }
Params:
  • credentialCharset – The credentialCharset to set.
/** * @param credentialCharset The credentialCharset to set. */
public void setCredentialCharset(String credentialCharset) { this.credentialCharset = credentialCharset; } }