com.luigidragone.net.ntlm
Class NTLM

java.lang.Object
  |
  +--com.luigidragone.net.ntlm.NTLM

public class NTLM
extends java.lang.Object

This class implements the Microsoft NTLM authentication protocol.

NTLM is a Microsoft proprietary network authentication protocol used in many situations (e.g. by the Microsoft Proxy Server to authenticate a browser).

It requires a JCE compatible MD4 hash algorithm implementation and a DES with no-padding ECB cipher to compute the requested value.
An open source JCE compatible library is Cryptix JCE and it is available here. We are assuming that the JCE provider is correctly installed and configured. Notice that the Sun JCE implementation proviedes the DES cipher but doesn't provide the MD4 hashing.

To perform an authentication the following information are needed: Alternatively, the user password can be replaced with its Lan Manager and NT hashed versions. On a Windows system these data can be collected in the registry, otherwise they can be extracted from a SAMBA password file.
Notice that the host and user domain could not be the same.

To start an NTLM authentication procedure (e.g. with a proxy server) build a request message calling formatRequest and send it to the server.
Once the challenge packet has been correctly received extract from it the nonce with getNonce function and use it to compute the reply and build the response message with formatResponse method and send it back to the server.
Repeat the previous steps until the server authenticates the client's identity or a large number of retries has been made. The check of a successful authentication is protocol specific (e.g. code 200 in HTTP), thus it is not performed by this component.

We want to access to the page http://www.server.com/page.html through an NTLM proxy proxy.domain.com that accepts connection on port 80.
We access to proxy from host HOSTDOMAIN\\HOST with the user USERDOMAIN\\user (password "1234567890").
As first step we open a socket connection to proxy server and set up the required object. Notice that we use a keep-alive connection, because NTLM authentication is connection based and the connection must be alive through the whole process.

     Socket s = new Socket("proxy.domain.com", 80);
     s.setKeepAlive(true);
     InputStream is = s.getInputStream();
     OutputStream os = s.getOutputStream();
     BufferedReader r = new BufferedReader(new InputStreamReader(is));
     BufferedWriter w = new BufferedWriter(new OutputStreamWriter(os));

     String host = "HOST";
     String hostDomain = "HOSTDOMAIN";
     String user = "user";
     String userDomain = "USERDOMAIN";
     String password = "1234567890";
 
Then, we format a request message and send it in a HTTP compliant GET message.
     byte[] fstMsg = NTLM.formatRequest(host, hostDomain);
     byte[] fstMsg64 = Codecs.base64Encode(fstMsg);
     System.out.println("NTLM Request Packet: " + new String(fstMsg64));

     w.write("GET http://www.server.com/page.html HTTP/1.0\n");
     w.write("Host: www.server.com\n");
     w.write("Proxy-Connection: Keep-Alive\n");
     w.write("Proxy-Authorization: NTLM " + new String(fstMsg64) + "\n\n");
     w.flush();
     System.out.println("First Message Sent");
 
We wait for the server response and we parse it to extract the nonce.
     String resp = "";
     int contentLength = 0;
     while((line = r.readLine()) != null)
       if(line.length() == 0)
         break;
       if(line.startsWith("Content-Length"))
         contentLength = Integer.parseInt(line.substring(line.indexOf(":") + 1).trim());
       else if(line.startsWith("Proxy-Authenticate"))
         resp = line.substring(line.indexOf(":") + 1).trim();
     r.skip(contentLength);
     System.out.println("Second Message Received");
     System.out.println("Content Length: " + contentLength);
     System.out.println("Proxy-Authenticate: " + resp);
     resp = resp.substring(resp.indexOf(" ")).trim();
     System.out.println("NTLM Chellange Packet: " + resp);
     resp = Codecs.base64Decode(resp);
     byte[] sndMsg = resp.getBytes();
     byte[] nonce = NTLM.getNonce(sndMsg);
 
With the nonce collected in the previous step we create a response message.
     byte[] trdMsg = NTLM.formatResponse(host, user, userDomain,
     	NTLM.computeLMPassword(password), NTLM.computeNTPassword(password),
     	nonce);
     System.out.println(trdMsg.length);
     byte[] trdMsg64 = Codecs.base64Encode(trdMsg);
     System.out.println("NTLM Response Packet: " + new String(trdMsg64));
 
We sent the message to the server.
     w.write("GET http://www.server.com/page.html HTTP/1.0\n");
     w.write("Proxy-Connection: Keep-Alive\n");
     w.write("Host: www.server.com\n");
     w.write("Proxy-Authorization: NTLM " + new String(trdMsg64) + "\n\n");
     w.flush();
     System.out.println("Third Message Sent");
 
Finally we wait the server reply.
     System.out.println("Server response: " + r.readLine());
 
If the reply is like "HTTP/1.0 200 OK" it has worked fine, else the server response is containing a new nonce.

Notice that despite the computing of hashed passwords and of nonce response is exactly the same of the SMB authentication protocol, the message format is slightly different.
Therefore, the methods computeLMPassword, computeNTPassword and computeNTLMResponse can be used to perform a SMB authentication too.

This implementation is based on:

Nevertheless, because there isn't any official protocol specification publicly available there is any warranty that code works correctly and that it is conforming to Microsoft NTLM protocol.

For implementation reasons only the public members perform argument consistency checks. The public members also catch and hide every exceptions that can be throwed (even though interfaces specify otherwise).

Version:
1.0.1
Author:
Luigi Dragone (luigi@luigidragone.com)
See Also:
NTLM Authentication Scheme for HTTP, LanMan and NT Password Encryption in Samba 2.x, "Handbook of Applied Cryptography", JCE, Cryptix

Method Summary
static byte[] computeLMPassword(java.lang.String password)
          Computes the Lan Manager hashed version of a password.
static void computeNTLMResponse(byte[] lmPassword, byte[] ntPassword, byte[] nonce, byte[] lmResponse, byte[] ntResponse)
           Computes the NTLM response to the nonce based on the supplied hashed passwords.
static byte[] computeNTPassword(java.lang.String password)
          Computes the NT hashed version of a password.
static byte[] formatRequest(java.lang.String host, java.lang.String hostDomain)
           Builds a request message for the host of the specified domain that can be send to the server to start the NTLM protocol.
static byte[] formatResponse(java.lang.String host, java.lang.String user, java.lang.String userDomain, byte[] lmPassword, byte[] ntPassword, byte[] nonce)
           Builds the nonce response message.
static byte[] getNonce(byte[] msg)
           Extracts from the server challenge response the nonce required to perform the authentication.
 
Methods inherited from class java.lang.Object
equals, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
 

Method Detail

computeLMPassword

public static byte[] computeLMPassword(java.lang.String password)
                                throws java.lang.IllegalArgumentException,
                                       javax.crypto.NoSuchPaddingException,
                                       java.security.NoSuchAlgorithmException
Computes the Lan Manager hashed version of a password.

Parameters:
password - the user password
Returns:
the Lan Manager hashed version of the password in a 16-bytes array
Throws:
java.lang.IllegalArgumentException - if the supplied password is null
javax.crypto.NoSuchPaddingException - if there isn't any suitable padding method
java.security.NoSuchAlgorithmException - if there isn't any suitable cipher algorithm

computeNTPassword

public static byte[] computeNTPassword(java.lang.String password)
                                throws java.lang.IllegalArgumentException,
                                       java.security.NoSuchAlgorithmException
Computes the NT hashed version of a password.

Parameters:
password - the user password
Returns:
the NT hashed version of the password in a 16-bytes array
Throws:
java.lang.IllegalArgumentException - if the supplied password is null
java.security.NoSuchAlgorithmException - if there isn't any suitable cipher algorithm

computeNTLMResponse

public static void computeNTLMResponse(byte[] lmPassword,
                                       byte[] ntPassword,
                                       byte[] nonce,
                                       byte[] lmResponse,
                                       byte[] ntResponse)
                                throws java.lang.IllegalArgumentException,
                                       javax.crypto.NoSuchPaddingException,
                                       java.security.NoSuchAlgorithmException

Computes the NTLM response to the nonce based on the supplied hashed passwords.

If the hashed password are not available they can be computed from the cleartext password by the means of computeLMPassword and computeNTPassword methods.

Parameters:
lmPassword - a 16-bytes array containing the Lan Manager hashed password
ntPassword - a 16-bytes array containing the Lan Manager hashed password
nonce - a 8-bytes array representing the server's nonce
lmResponse - a 24-bytes array that will contain the Lan Manager response after the method invocation
ntResponse - a 24-bytes array that will contain the NT response after the method invocation
Throws:
java.lang.IllegalArgumentException - if a parameter has an illegal size
javax.crypto.NoSuchPaddingException - if there isn't any suitable padding method
java.security.NoSuchAlgorithmException - if there isn't any suitable cipher algorithm

formatRequest

public static byte[] formatRequest(java.lang.String host,
                                   java.lang.String hostDomain)
                            throws java.io.IOException

Builds a request message for the host of the specified domain that can be send to the server to start the NTLM protocol.

The returned message should be encoded according to protocol specific rules (e.g. base 64 encoding).
The message format is discussed here.

Parameters:
host - the name of the host that is authenticating
hostDomain - the name of the domain to which the host belongs
Returns:
the request message to send to server to open an authentication procedure
Throws:
java.io.IOException - if an error occurs during the message formatting
See Also:
NTLM Authentication Scheme for HTTP

getNonce

public static byte[] getNonce(byte[] msg)
                       throws java.lang.IllegalArgumentException

Extracts from the server challenge response the nonce required to perform the authentication.

The received message should be decoded according to protocol specific rules (e.g. base 64 encoding).
The message format is discussed here.

Parameters:
msg - a byte array containing the server challenge message
Throws:
java.lang.IllegalArgumentException - if a parameter has an illegal size
See Also:
NTLM Authentication Scheme for HTTP

formatResponse

public static byte[] formatResponse(java.lang.String host,
                                    java.lang.String user,
                                    java.lang.String userDomain,
                                    byte[] lmPassword,
                                    byte[] ntPassword,
                                    byte[] nonce)
                             throws java.lang.IllegalArgumentException,
                                    java.io.IOException,
                                    java.security.NoSuchAlgorithmException,
                                    javax.crypto.NoSuchPaddingException

Builds the nonce response message.

It requires the Lan Manager and NT hashed version of user password, that can be computed from the cleartext version by computeNTPassword and computeNTLMResponse, and the nonce obtained from the server by getNonce.
The returned message should be encoded according to protocol specific rules (e.g. base 64 encoding).
The message format is discussed here.

Parameters:
host - the name of the host that is authenticating
user - the name of the user
userDomain - the name of the domain to which the user belongs
lmPassword - a 16-bytes array containing the Lan Manager hashed password
ntPassword - a 16-bytes array containing the NT hashed password
nonce - a 8-byte array containing the nonce sent by server to reply to the request message
Returns:
the challenge response message to send to server to complete the authentication procedure
Throws:
java.io.IOException - if an error occurs during the message formatting
java.lang.IllegalArgumentException - if a parameter has an illegal size
javax.crypto.NoSuchPaddingException - if there isn't any suitable padding method
java.security.NoSuchAlgorithmException - if there isn't any suitable cipher algorithm
See Also:
NTLM Authentication Scheme for HTTP