📄 ntlm.java
字号:
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*
* NTLM.java
* Copyright (C) 2002 Luigi Dragone
*
*/
package com.luigidragone.net.ntlm;
import java.security.*;
import java.security.spec.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import java.io.*;
/**
* <p>
* This class implements the Microsoft NTLM authentication protocol.
* </p>
* <p>
* NTLM is a Microsoft proprietary network authentication protocol used in many
* situations (e.g. by the Microsoft Proxy Server to authenticate a browser).
* </p>
* <p>
* It requires a JCE compatible MD4 hash algorithm implementation and
* a DES with no-padding ECB cipher to compute the requested value. <br>
* An open source JCE compatible library is Cryptix JCE and it is available
* <a href="http://www.cryptix.org/">here</a>. 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.
* </p>
* To perform an authentication the following information are needed:
* <ul>
* <li>the host name (with its own domain);</li>
* <li>the user name (with its own domain);</li>
* <li>the user password.</li>
* </ul>
* 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.<br>
* Notice that the host and user domain could not be the same.
* </p>
* <p>
* To start an NTLM authentication procedure (e.g. with a proxy server) build a
* request message calling {@link #formatRequest(String, String) formatRequest}
* and send it to the server.<br>
* Once the challenge packet has been correctly received extract from it the nonce
* with {@link #getNonce(byte[]) getNonce} function and use it to compute the
* reply and build the response message with
* {@link #formatResponse(String, String, String, byte[], byte[], byte[]) formatResponse}
* method and send it back to the server.<br>
* 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.
* </p>
* <p>
* We want to access to the page <code>http://www.server.com/page.html</code>
* through an NTLM proxy <code>proxy.domain.com</code> that accepts connection on
* port 80.<br>
* We access to proxy from host <code>HOSTDOMAIN\\HOST<code> with the user
* <code>USERDOMAIN\\user</code> (password <code>"1234567890"</code>).<br>
* 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.<br>
* <pre>
* 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";
* </pre>
* Then, we format a request message and send it in a HTTP compliant GET message.<br>
* <pre>
* 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");
* </pre>
* We wait for the server response and we parse it to extract the nonce.<br>
* <pre>
* 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);
* </pre>
* With the nonce collected in the previous step we create a response message.<br>
* <pre>
* 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));
* </pre>
* We sent the message to the server.<br>
* <pre>
* 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");
* </pre>
* Finally we wait the server reply.<br>
* <pre>
* System.out.println("Server response: " + r.readLine());
* </pre>
* If the reply is like <code>"HTTP/1.0 200 OK"</code> it has worked fine, else
* the server response is containing a new nonce.
* </p>
* <p>
* 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. <br>
* Therefore, the methods {@link #computeLMPassword(String) computeLMPassword},
* {@link #computeNTPassword(String) computeNTPassword} and
* {@link #computeNTLMResponse(byte[], byte[], byte[], byte[], byte[]) computeNTLMResponse}
* can be used to perform a SMB authentication too.
* </p>
* <p>
* This implementation is based on:
* <ul>
* <li>the reverse engineering of the NTLM protocol made by Ronald Tschalär
* and available <a href="http://www.innovation.ch/java/ntlm.html">here</a>;</li>
* <li>the documentation about NTLM provided with <a href="http://www.atstake.com/research/lc3/">
* L0phtCrack 1.5</a>;</li>
* <li>the "Handbook of Applied Cryptography", freely available
* <a href="http://www.cacr.math.uwaterloo.ca/hac/">here</a>;</li>
* <li>the C source code of NTLM library in the <a href="http://www.samba.org/">
* SAMBA Project</a>.</li>
* </ul>
* 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.
* </p>
* <p>
* 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).
* </p>
*
* @author Luigi Dragone (<a href="mailto:luigi@luigidragone.com">luigi@luigidragone.com</a>)
*
* @version 1.0.1
*
* @see <a href="http://www.innovation.ch/java/ntlm.html">NTLM Authentication Scheme for HTTP</a>
* @see <a href="ftp://ftp.samba.org/pub/samba/docs/textdocs/ENCRYPTION.txt">LanMan and NT Password Encryption in Samba 2.x</a>
* @see <a href="http://www.cacr.math.uwaterloo.ca/hac/">"Handbook of Applied Cryptography"</a>
* @see <a href="http://java.sun.com/products/jce/">JCE</a>
* @see <a href="http://www.cryptix.org/">Cryptix</a>
*/
public class NTLM {
protected NTLM() {}
/**
* The magic number used to compute the Lan Manager hashed password.
*/
protected static final byte[] MAGIC = new byte[] {0x4B, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25};
/**
* <p>
* Converts an unsigned byte to an unsigned integer.
* </p>
* <p>
* Notice that Java bytes are always signed, but the cryptographic algorithms
* rely on unsigned ones, that can be simulated in this way.<br>
* A bit mask is employed to prevent that the signum bit is extended to MSBs.
* </p>
*/
protected static int unsignedByteToInt(byte b) {
return (int)b & 0xFF;
}
protected static byte getLoByte(char c) {
return (byte)c;
}
protected static byte getHiByte(char c) {
return (byte)((c >>> 8) & 0xFF);
}
protected static short swapBytes(short s) {
return (short)(((s << 8) & 0xFF00) | ((s >>> 8) & 0x00FF));
}
/**
* <p>
* Computes an odd DES key from 56 bits represented as a 7-bytes array.
* </p>
* <p>
* Keeps elements from index <code>offset</code> to index <code>offset + 7</code> of
* supplied array.
* </p>
*
* @param keyData a byte array containing the 56 bits used to compute the DES key
* @param offset the offset of the first element of the 56-bits key data
*
* @return the odd DES key generated
*
* @exception InvalidKeyException
* @exception NoSuchAlgorithmException
* @exception InvalidKeySpecException
*/
protected static Key computeDESKey(byte[] keyData, int offset) throws InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException {
byte[] desKeyData = new byte[8];
int[] k = new int[7];
for(int i = 0; i < 7; i++)
k[i] = unsignedByteToInt(keyData[offset + i]);
desKeyData[0] = (byte)(k[0] >>> 1);
desKeyData[1] = (byte)(((k[0] & 0x01) << 6) | (k[1] >>> 2));
desKeyData[2] = (byte)(((k[1] & 0x03) << 5) | (k[2] >>> 3));
desKeyData[3] = (byte)(((k[2] & 0x07) << 4) | (k[3] >>> 4));
desKeyData[4] = (byte)(((k[3] & 0x0F) << 3) | (k[4] >>> 5));
desKeyData[5] = (byte)(((k[4] & 0x1F) << 2) | (k[5] >>> 6));
desKeyData[6] = (byte)(((k[5] & 0x3F) << 1) | (k[6] >>> 7));
desKeyData[7] = (byte)(k[6] & 0x7F);
for(int i=0; i<8; i++)
desKeyData[i] = (byte)(unsignedByteToInt(desKeyData[i]) << 1);
KeySpec desKeySpec = new DESKeySpec(desKeyData);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
return secretKey;
}
/**
* Encrypts the 8-bytes plain text three times with the 3 56-bits DES keys and
* puts the result in a 24-bytes array.
*
* @param keys a 21-bytes array containing 3 56-bits DES keys
* @param plaintext a 8-bytes array to be encrypted
*
* @return a 24-bytes array containing the plaintext DES encrypted with the supplied keys
*
* @exception InvalidKeyException
* @exception NoSuchAlgorithmException
* @exception javax.crypto.NoSuchPaddingException
* @exception InvalidKeySpecException
* @exception BadPaddingException
* @exception IllegalBlockSizeException
* @exception ShortBufferException
*/
protected static byte[] encrypt(byte[] keys, byte[] plaintext) throws InvalidKeyException, NoSuchAlgorithmException, javax.crypto.NoSuchPaddingException, InvalidKeySpecException, BadPaddingException, IllegalBlockSizeException, ShortBufferException {
byte[] ciphertext = new byte[24];
Cipher c = Cipher.getInstance("DES/ECB/NoPadding");
Key k = computeDESKey(keys, 0);
c.init(Cipher.ENCRYPT_MODE, k);
c.doFinal(plaintext, 0, 8, ciphertext, 0);
k = computeDESKey(keys, 7);
c.init(Cipher.ENCRYPT_MODE, k);
c.doFinal(plaintext, 0, 8, ciphertext, 8);
k = computeDESKey(keys, 14);
c.init(Cipher.ENCRYPT_MODE, k);
c.doFinal(plaintext, 0, 8, ciphertext, 16);
return ciphertext;
}
/**
* Computes the Lan Manager hashed version of a password.
*
* @param password the user password
*
* @return the Lan Manager hashed version of the password in a 16-bytes array
*
* @exception IllegalArgumentException if the supplied password is null
* @exception javax.crypto.NoSuchPaddingException if there isn't any suitable padding method
* @exception NoSuchAlgorithmException if there isn't any suitable cipher algorithm
*/
public static byte[] computeLMPassword(String password) throws IllegalArgumentException, NoSuchPaddingException, NoSuchAlgorithmException {
if(password == null)
throw new IllegalArgumentException("password : null value not allowed");
try {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -