/*
 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.security.ntlm;

import static com.sun.security.ntlm.Version.*;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.util.Locale;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.SecretKeySpec;

NTLM authentication implemented according to MS-NLMP, version 12.1
Since:1.7
/** * NTLM authentication implemented according to MS-NLMP, version 12.1 * @since 1.7 */
class NTLM { private final SecretKeyFactory fac; private final Cipher cipher; private final MessageDigest md4; private final Mac hmac; private final MessageDigest md5; private static final boolean DEBUG = System.getProperty("ntlm.debug") != null; final Version v; final boolean writeLM; final boolean writeNTLM; protected NTLM(String version) throws NTLMException { if (version == null) version = "LMv2/NTLMv2"; switch (version) { case "LM": v = NTLM; writeLM = true; writeNTLM = false; break; case "NTLM": v = NTLM; writeLM = false; writeNTLM = true; break; case "LM/NTLM": v = NTLM; writeLM = writeNTLM = true; break; case "NTLM2": v = NTLM2; writeLM = writeNTLM = true; break; case "LMv2": v = NTLMv2; writeLM = true; writeNTLM = false; break; case "NTLMv2": v = NTLMv2; writeLM = false; writeNTLM = true; break; case "LMv2/NTLMv2": v = NTLMv2; writeLM = writeNTLM = true; break; default: throw new NTLMException(NTLMException.BAD_VERSION, "Unknown version " + version); } try { fac = SecretKeyFactory.getInstance ("DES"); cipher = Cipher.getInstance ("DES/ECB/NoPadding"); md4 = sun.security.provider.MD4.getInstance(); hmac = Mac.getInstance("HmacMD5"); md5 = MessageDigest.getInstance("MD5"); } catch (NoSuchPaddingException e) { throw new AssertionError(); } catch (NoSuchAlgorithmException e) { throw new AssertionError(); } }
Prints out a formatted string, called in various places inside then NTLM implementation for debugging/logging purposes. When the system property "ntlm.debug" is set, System.out.printf(format, args) is called. This method is designed to be overridden by child classes to match their own debugging/logging mechanisms.
Params:
  • format – a format string
  • args – the arguments referenced by format
See Also:
/** * Prints out a formatted string, called in various places inside then NTLM * implementation for debugging/logging purposes. When the system property * "ntlm.debug" is set, <code>System.out.printf(format, args)</code> is * called. This method is designed to be overridden by child classes to * match their own debugging/logging mechanisms. * @param format a format string * @param args the arguments referenced by <code>format</code> * @see java.io.PrintStream#printf(java.lang.String, java.lang.Object[]) */
public void debug(String format, Object... args) { if (DEBUG) { System.out.printf(format, args); } }
Prints out the content of a byte array, called in various places inside the NTLM implementation for debugging/logging purposes. When the system property "ntlm.debug" is set, the hexdump of the array is printed into System.out. This method is designed to be overridden by child classes to match their own debugging/logging mechanisms.
Params:
  • bytes – the byte array to print out
/** * Prints out the content of a byte array, called in various places inside * the NTLM implementation for debugging/logging purposes. When the system * property "ntlm.debug" is set, the hexdump of the array is printed into * System.out. This method is designed to be overridden by child classes to * match their own debugging/logging mechanisms. * @param bytes the byte array to print out */
public void debug(byte[] bytes) { if (DEBUG) { try { new sun.misc.HexDumpEncoder().encodeBuffer(bytes, System.out); } catch (IOException ioe) { // Impossible } } }
Reading an NTLM packet
/** * Reading an NTLM packet */
static class Reader { private final byte[] internal; Reader(byte[] data) { internal = data; } int readInt(int offset) throws NTLMException { try { return (internal[offset] & 0xff) + ((internal[offset+1] & 0xff) << 8) + ((internal[offset+2] & 0xff) << 16) + ((internal[offset+3] & 0xff) << 24); } catch (ArrayIndexOutOfBoundsException ex) { throw new NTLMException(NTLMException.PACKET_READ_ERROR, "Input message incorrect size"); } } int readShort(int offset) throws NTLMException { try { return (internal[offset] & 0xff) + ((internal[offset+1] & 0xff << 8)); } catch (ArrayIndexOutOfBoundsException ex) { throw new NTLMException(NTLMException.PACKET_READ_ERROR, "Input message incorrect size"); } } byte[] readBytes(int offset, int len) throws NTLMException { try { return Arrays.copyOfRange(internal, offset, offset + len); } catch (ArrayIndexOutOfBoundsException ex) { throw new NTLMException(NTLMException.PACKET_READ_ERROR, "Input message incorrect size"); } } byte[] readSecurityBuffer(int offset) throws NTLMException { int pos = readInt(offset+4); if (pos == 0) return null; try { return Arrays.copyOfRange( internal, pos, pos + readShort(offset)); } catch (ArrayIndexOutOfBoundsException ex) { throw new NTLMException(NTLMException.PACKET_READ_ERROR, "Input message incorrect size"); } } String readSecurityBuffer(int offset, boolean unicode) throws NTLMException { byte[] raw = readSecurityBuffer(offset); try { return raw == null ? null : new String( raw, unicode ? "UnicodeLittleUnmarked" : "ISO8859_1"); } catch (UnsupportedEncodingException ex) { throw new NTLMException(NTLMException.PACKET_READ_ERROR, "Invalid input encoding"); } } }
Writing an NTLM packet
/** * Writing an NTLM packet */
static class Writer { private byte[] internal; // buffer private int current; // current written content interface buffer
Starts writing a NTLM packet
Params:
  • type – NEGOTIATE || CHALLENGE || AUTHENTICATE
  • len – the base length, without security buffers
/** * Starts writing a NTLM packet * @param type NEGOTIATE || CHALLENGE || AUTHENTICATE * @param len the base length, without security buffers */
Writer(int type, int len) { assert len < 256; internal = new byte[256]; current = len; System.arraycopy ( new byte[] {'N','T','L','M','S','S','P',0,(byte)type}, 0, internal, 0, 9); } void writeShort(int offset, int number) { internal[offset] = (byte)(number); internal[offset+1] = (byte)(number >> 8); } void writeInt(int offset, int number) { internal[offset] = (byte)(number); internal[offset+1] = (byte)(number >> 8); internal[offset+2] = (byte)(number >> 16); internal[offset+3] = (byte)(number >> 24); } void writeBytes(int offset, byte[] data) { System.arraycopy(data, 0, internal, offset, data.length); } void writeSecurityBuffer(int offset, byte[] data) { if (data == null) { writeShort(offset+4, current); } else { int len = data.length; if (current + len > internal.length) { internal = Arrays.copyOf(internal, current + len + 256); } writeShort(offset, len); writeShort(offset+2, len); writeShort(offset+4, current); System.arraycopy(data, 0, internal, current, len); current += len; } } void writeSecurityBuffer(int offset, String str, boolean unicode) { try { writeSecurityBuffer(offset, str == null ? null : str.getBytes( unicode ? "UnicodeLittleUnmarked" : "ISO8859_1")); } catch (UnsupportedEncodingException ex) { assert false; } } byte[] getBytes() { return Arrays.copyOf(internal, current); } } // LM/NTLM /* Convert a 7 byte array to an 8 byte array (for a des key with parity) * input starts at offset off */ byte[] makeDesKey (byte[] input, int off) { int[] in = new int [input.length]; for (int i=0; i<in.length; i++ ) { in[i] = input[i]<0 ? input[i]+256: input[i]; } byte[] out = new byte[8]; out[0] = (byte)in[off+0]; out[1] = (byte)(((in[off+0] << 7) & 0xFF) | (in[off+1] >> 1)); out[2] = (byte)(((in[off+1] << 6) & 0xFF) | (in[off+2] >> 2)); out[3] = (byte)(((in[off+2] << 5) & 0xFF) | (in[off+3] >> 3)); out[4] = (byte)(((in[off+3] << 4) & 0xFF) | (in[off+4] >> 4)); out[5] = (byte)(((in[off+4] << 3) & 0xFF) | (in[off+5] >> 5)); out[6] = (byte)(((in[off+5] << 2) & 0xFF) | (in[off+6] >> 6)); out[7] = (byte)((in[off+6] << 1) & 0xFF); return out; } byte[] calcLMHash (byte[] pwb) { byte[] magic = {0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25}; byte[] pwb1 = new byte [14]; int len = pwb.length; if (len > 14) len = 14; System.arraycopy (pwb, 0, pwb1, 0, len); /* Zero padded */ try { DESKeySpec dks1 = new DESKeySpec (makeDesKey (pwb1, 0)); DESKeySpec dks2 = new DESKeySpec (makeDesKey (pwb1, 7)); SecretKey key1 = fac.generateSecret (dks1); SecretKey key2 = fac.generateSecret (dks2); cipher.init (Cipher.ENCRYPT_MODE, key1); byte[] out1 = cipher.doFinal (magic, 0, 8); cipher.init (Cipher.ENCRYPT_MODE, key2); byte[] out2 = cipher.doFinal (magic, 0, 8); byte[] result = new byte [21]; System.arraycopy (out1, 0, result, 0, 8); System.arraycopy (out2, 0, result, 8, 8); return result; } catch (InvalidKeyException ive) { // Will not happen, all key material are 8 bytes assert false; } catch (InvalidKeySpecException ikse) { // Will not happen, we only feed DESKeySpec to DES factory assert false; } catch (IllegalBlockSizeException ibse) { // Will not happen, we encrypt 8 bytes assert false; } catch (BadPaddingException bpe) { // Will not happen, this is encryption assert false; } return null; // will not happen, we returned already } byte[] calcNTHash (byte[] pw) { byte[] out = md4.digest (pw); byte[] result = new byte [21]; System.arraycopy (out, 0, result, 0, 16); return result; } /* key is a 21 byte array. Split it into 3 7 byte chunks, * Convert each to 8 byte DES keys, encrypt the text arg with * each key and return the three results in a sequential [] */ byte[] calcResponse (byte[] key, byte[] text) { try { assert key.length == 21; DESKeySpec dks1 = new DESKeySpec(makeDesKey(key, 0)); DESKeySpec dks2 = new DESKeySpec(makeDesKey(key, 7)); DESKeySpec dks3 = new DESKeySpec(makeDesKey(key, 14)); SecretKey key1 = fac.generateSecret(dks1); SecretKey key2 = fac.generateSecret(dks2); SecretKey key3 = fac.generateSecret(dks3); cipher.init(Cipher.ENCRYPT_MODE, key1); byte[] out1 = cipher.doFinal(text, 0, 8); cipher.init(Cipher.ENCRYPT_MODE, key2); byte[] out2 = cipher.doFinal(text, 0, 8); cipher.init(Cipher.ENCRYPT_MODE, key3); byte[] out3 = cipher.doFinal(text, 0, 8); byte[] result = new byte[24]; System.arraycopy(out1, 0, result, 0, 8); System.arraycopy(out2, 0, result, 8, 8); System.arraycopy(out3, 0, result, 16, 8); return result; } catch (IllegalBlockSizeException ex) { // None will happen assert false; } catch (BadPaddingException ex) { assert false; } catch (InvalidKeySpecException ex) { assert false; } catch (InvalidKeyException ex) { assert false; } return null; } // LMv2/NTLMv2 byte[] hmacMD5(byte[] key, byte[] text) { try { SecretKeySpec skey = new SecretKeySpec(Arrays.copyOf(key, 16), "HmacMD5"); hmac.init(skey); return hmac.doFinal(text); } catch (InvalidKeyException ex) { assert false; } catch (RuntimeException e) { assert false; } return null; } byte[] calcV2(byte[] nthash, String text, byte[] blob, byte[] challenge) { try { byte[] ntlmv2hash = hmacMD5(nthash, text.getBytes("UnicodeLittleUnmarked")); byte[] cn = new byte[blob.length+8]; System.arraycopy(challenge, 0, cn, 0, 8); System.arraycopy(blob, 0, cn, 8, blob.length); byte[] result = new byte[16+blob.length]; System.arraycopy(hmacMD5(ntlmv2hash, cn), 0, result, 0, 16); System.arraycopy(blob, 0, result, 16, blob.length); return result; } catch (UnsupportedEncodingException ex) { assert false; } return null; } // NTLM2 LM/NTLM static byte[] ntlm2LM(byte[] nonce) { return Arrays.copyOf(nonce, 24); } byte[] ntlm2NTLM(byte[] ntlmHash, byte[] nonce, byte[] challenge) { byte[] b = Arrays.copyOf(challenge, 16); System.arraycopy(nonce, 0, b, 8, 8); byte[] sesshash = Arrays.copyOf(md5.digest(b), 8); return calcResponse(ntlmHash, sesshash); } // Password in ASCII and UNICODE static byte[] getP1(char[] password) { try { return new String(password).toUpperCase( Locale.ENGLISH).getBytes("ISO8859_1"); } catch (UnsupportedEncodingException ex) { return null; } } static byte[] getP2(char[] password) { try { return new String(password).getBytes("UnicodeLittleUnmarked"); } catch (UnsupportedEncodingException ex) { return null; } } }