Java tutorial
/** * Donated by Jarapac (http://jarapac.sourceforge.net/) and released under EPL. * * j-Interop (Pure Java implementation of DCOM protocol) * * Copyright (c) 2013 Vikram Roopchand * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Vikram Roopchand - Moving to EPL from LGPL v1. * */ package rpc.security.ntlm; //import gnu.crypto.hash.MD4; //import gnu.crypto.hash.MD5; //import gnu.crypto.prng.ARCFour; //import gnu.crypto.prng.IRandom; //import gnu.crypto.prng.LimitReachedException; import java.io.UnsupportedEncodingException; import java.security.DigestException; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.HashMap; import java.util.Random; import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.Digest; import org.bouncycastle.crypto.StreamCipher; import org.bouncycastle.crypto.digests.MD4Digest; import org.bouncycastle.crypto.digests.MD5Digest; import org.bouncycastle.crypto.engines.RC4Engine; import org.bouncycastle.crypto.params.KeyParameter; class NTLMKeyFactory { Random random = new Random(); private static final byte[] clientSigningMagicConstant = new byte[] { 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x74, 0x6f, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2d, 0x74, 0x6f, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x6d, 0x61, 0x67, 0x69, 0x63, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x00 };//"session key to client-to-server signing key magic constant"; private static final byte[] serverSigningMagicConstant = new byte[] { 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x74, 0x6f, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2d, 0x74, 0x6f, 0x2d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x6d, 0x61, 0x67, 0x69, 0x63, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x00 };//"session key to server-to-client signing key magic constant"; private static final byte[] clientSealingMagicConstant = new byte[] { 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x74, 0x6f, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2d, 0x74, 0x6f, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x65, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x6d, 0x61, 0x67, 0x69, 0x63, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x00 };//"session key to client-to-server sealing key magic constant"; private static final byte[] serverSealingMagicConstant = new byte[] { 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x74, 0x6f, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2d, 0x74, 0x6f, 0x2d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x73, 0x65, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x6d, 0x61, 0x67, 0x69, 0x63, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x00 };//"session key to server-to-client sealing key magic constant"; NTLMKeyFactory() { } /** NTLMv1 User Session Key. Cases where LMcompatibilitylevel is 0,1,2. For 3,4,5 the logic is different * and based upon the reponses being sent back (either LMv2 or NTLMv2) * * @param password * @return * @throws UnsupportedEncodingException * @throws DigestException */ byte[] getNTLMUserSessionKey(String password) throws UnsupportedEncodingException, DigestException { //look at NTLMPasswordAuthentication in jcifs. It supports only the NTLMUserSessionKey and the LMv2UserSessionKey...we need more :( // byte key[] = new byte[16]; byte[] ntlmHash = Responses.ntlmHash(password); // MD4 md4 = new MD4(); // md4.update(ntlmHash,0,ntlmHash.length); // key = md4.digest(); // return key; Digest md4 = new MD4Digest(); byte[] ret = new byte[md4.getDigestSize()]; md4.update(ntlmHash, 0, ntlmHash.length); md4.doFinal(ret, 0); return ret; } byte[] getNTLMv2UserSessionKey(String target, String user, String password, byte[] challenge, byte[] blob) throws Exception { byte key[] = new byte[16]; byte[] ntlm2Hash = Responses.ntlmv2Hash(target, user, password); byte[] data = new byte[challenge.length + blob.length]; System.arraycopy(challenge, 0, data, 0, challenge.length); System.arraycopy(blob, 0, data, challenge.length, blob.length); byte[] mac = Responses.hmacMD5(data, ntlm2Hash); key = Responses.hmacMD5(mac, ntlm2Hash); return key; } /** Password of the user * * @param password * @param servernonce challenge + nonce from NTLM2 Session Response * @return * @throws DigestException * @throws UnsupportedEncodingException * @throws NoSuchAlgorithmException */ byte[] getNTLM2SessionResponseUserSessionKey(String password, byte[] servernonce) throws NoSuchAlgorithmException, UnsupportedEncodingException, DigestException { return Responses.hmacMD5(servernonce, getNTLMUserSessionKey(password)); } /** Randomly generated 16 bytes * * @return */ byte[] getSecondarySessionKey() { byte[] key = new byte[16]; random.nextBytes(key); return key; } StreamCipher getARCFOUR(byte[] key) { HashMap attrib = new HashMap(); // IRandom keystream = new ARCFour(); // attrib.put(ARCFour.ARCFOUR_KEY_MATERIAL, key); // keystream.init(attrib); StreamCipher keystream = new RC4Engine(); CipherParameters params = new KeyParameter(key); keystream.init(true, params); return keystream; } byte[] applyARCFOUR(StreamCipher keystream, byte[] data) { byte[] retData = new byte[data.length]; keystream.processBytes(data, 0, data.length, retData, 0); // for (int i = 0; i < data.length; i++) { // retData[i] = (byte) (data[i] ^ keystream.nextByte()); // } return retData; } byte[] decryptSecondarySessionKey(byte[] encryptedData, byte[] key) throws IllegalStateException { return applyARCFOUR(getARCFOUR(key), encryptedData); } byte[] encryptSecondarySessionKey(byte[] plainData, byte[] key) throws IllegalStateException { return applyARCFOUR(getARCFOUR(key), plainData); } byte[] generateClientSigningKeyUsingNegotiatedSecondarySessionKey(byte[] secondarySessionKey) { //TODO this can be moved out of here... byte[] dataforhash = new byte[secondarySessionKey.length + clientSigningMagicConstant.length]; System.arraycopy(secondarySessionKey, 0, dataforhash, 0, secondarySessionKey.length); System.arraycopy(clientSigningMagicConstant, 0, dataforhash, secondarySessionKey.length, clientSigningMagicConstant.length); // MD5 md5 = new MD5(); // md5.update(dataforhash, 0, dataforhash.length); // return md5.digest(); Digest md5 = new MD5Digest(); byte[] ret = new byte[md5.getDigestSize()]; md5.update(dataforhash, 0, dataforhash.length); md5.doFinal(ret, 0); return ret; } byte[] generateClientSealingKeyUsingNegotiatedSecondarySessionKey(byte[] secondarySessionKey) { //TODO this can be moved out of here... byte[] dataforhash = new byte[secondarySessionKey.length + clientSealingMagicConstant.length]; System.arraycopy(secondarySessionKey, 0, dataforhash, 0, secondarySessionKey.length); System.arraycopy(clientSealingMagicConstant, 0, dataforhash, secondarySessionKey.length, clientSealingMagicConstant.length); // MD5 md5 = new MD5(); // md5.update(dataforhash, 0, dataforhash.length); // return md5.digest(); Digest md5 = new MD5Digest(); byte[] ret = new byte[md5.getDigestSize()]; md5.update(dataforhash, 0, dataforhash.length); md5.doFinal(ret, 0); return ret; } byte[] generateServerSigningKeyUsingNegotiatedSecondarySessionKey(byte[] secondarySessionKey) { //TODO this can be moved out of here... byte[] dataforhash = new byte[secondarySessionKey.length + serverSigningMagicConstant.length]; System.arraycopy(secondarySessionKey, 0, dataforhash, 0, secondarySessionKey.length); System.arraycopy(serverSigningMagicConstant, 0, dataforhash, secondarySessionKey.length, serverSigningMagicConstant.length); // MD5 md5 = new MD5(); // md5.update(dataforhash, 0, dataforhash.length); // return md5.digest(); Digest md5 = new MD5Digest(); byte[] ret = new byte[md5.getDigestSize()]; md5.update(dataforhash, 0, dataforhash.length); md5.doFinal(ret, 0); return ret; } byte[] generateServerSealingKeyUsingNegotiatedSecondarySessionKey(byte[] secondarySessionKey) { //TODO this can be moved out of here... byte[] dataforhash = new byte[secondarySessionKey.length + serverSealingMagicConstant.length]; System.arraycopy(secondarySessionKey, 0, dataforhash, 0, secondarySessionKey.length); System.arraycopy(serverSealingMagicConstant, 0, dataforhash, secondarySessionKey.length, serverSealingMagicConstant.length); // MD5 md5 = new MD5(); // md5.update(dataforhash, 0, dataforhash.length); // return md5.digest(); Digest md5 = new MD5Digest(); byte[] ret = new byte[md5.getDigestSize()]; md5.update(dataforhash, 0, dataforhash.length); md5.doFinal(ret, 0); return ret; } //TODO merge the signing routine for both client and server all that they differ by are keys...as expected byte[] signingPt1(int sequenceNumber, byte[] signingKey, byte[] data, int lengthOfBuffer) throws NoSuchAlgorithmException, IllegalStateException { byte[] seqNumPlusData = new byte[4 + lengthOfBuffer]; seqNumPlusData[0] = (byte) (sequenceNumber & 0xFF); seqNumPlusData[1] = (byte) ((sequenceNumber >> 8) & 0xFF); seqNumPlusData[2] = (byte) ((sequenceNumber >> 16) & 0xFF); seqNumPlusData[3] = (byte) ((sequenceNumber >> 24) & 0xFF); System.arraycopy(data, 0, seqNumPlusData, 4, lengthOfBuffer); byte[] retval = new byte[16]; retval[0] = 0x01; //Version number LE 1. byte[] sign = Responses.hmacMD5(seqNumPlusData, signingKey); for (int i = 0; i < 8; i++) { retval[i + 4] = sign[i]; } retval[12] = (byte) (sequenceNumber & 0xFF); retval[13] = (byte) ((sequenceNumber >> 8) & 0xFF); retval[14] = (byte) ((sequenceNumber >> 16) & 0xFF); retval[15] = (byte) ((sequenceNumber >> 24) & 0xFF); return retval; } void signingPt2(byte[] verifier, StreamCipher rc4) throws IllegalStateException { for (int i = 0; i < 8; i++) { // verifier[i+4] = (byte) (verifier[i+4] ^ rc4.nextByte()); verifier[i + 4] = (byte) (rc4.returnByte(verifier[i + 4])); } } boolean compareSignature(byte[] src, byte[] target) { return Arrays.equals(src, target); } //TODO merge the signing routine for both client and server all that they differ by are keys...as expected // byte[] serverSigning(int sequenceNumber, byte[] serverSigningKey, byte[] data, IRandom rc4) throws NoSuchAlgorithmException, IllegalStateException, LimitReachedException // { // byte[] seqNumPlusData = new byte[4 + data.length]; // // seqNumPlusData[0] = (byte)(sequenceNumber & 0xFF); // seqNumPlusData[1] = (byte)((sequenceNumber >> 8) & 0xFF); // seqNumPlusData[2] = (byte)((sequenceNumber >> 16) & 0xFF); // seqNumPlusData[3] = (byte)((sequenceNumber >> 24) & 0xFF); // // System.arraycopy(data, 0, seqNumPlusData, 4, data.length); // // byte[] retval = new byte[16]; // retval[0] = 0x01; //Version number LE 1. // // byte[] sign = Responses.hmacMD5(seqNumPlusData, serverSigningKey); // // for (int i = 0; i < 8; i++) { // retval[i+4] = (byte) (sign[i] ^ rc4.nextByte()); // } // // retval[12] = (byte)(sequenceNumber & 0xFF); // retval[13] = (byte)((sequenceNumber >> 8) & 0xFF); // retval[14] = (byte)((sequenceNumber >> 16) & 0xFF); // retval[15] = (byte)((sequenceNumber >> 24) & 0xFF); // // return retval; // } // byte[] clientSealing(int sequenceNumber, byte[] clientSealingKey, byte[] clientSigningKey, byte[] data,IRandom rc4) throws IllegalStateException, LimitReachedException, NoSuchAlgorithmException // { // //TODO..Imp... this implementation is not correct and should work for sequence 0, for the rest of the // // sequences the arcfour state has to be maintained and not a new one used everytime... // byte[] cipheredData = applyARCFOUR(rc4, data); // byte[] signature = clientSigning(sequenceNumber, clientSigningKey, data, rc4); // byte[] retval = new byte[cipheredData.length + signature.length]; // System.arraycopy(cipheredData, 0, retval, 0, cipheredData.length); // System.arraycopy(signature, 0, retval, cipheredData.length,signature.length); // return retval; // } // // byte[] serverSealing(int sequenceNumber, byte[] serverSealingKey, byte[] serverSigningKey, byte[] data, IRandom rc4) throws IllegalStateException, LimitReachedException, NoSuchAlgorithmException // { // //TODO..Imp... this implementation is not correct and should work for sequence 0, for the rest of the // // sequences the arcfour state has to be maintained and not a new one used everytime... // byte[] cipheredData = applyARCFOUR(rc4, data); // byte[] signature = clientSigning(sequenceNumber, serverSigningKey, data, rc4); // byte[] retval = new byte[cipheredData.length + signature.length]; // System.arraycopy(cipheredData, 0, retval, 0, cipheredData.length); // System.arraycopy(signature, 0, retval, cipheredData.length,signature.length); // return retval; // } // static void testFromDavenportPaper() // { // try // { // // NTLMKeyFactory keyFactory = new NTLMKeyFactory(); // byte[] challengePlusclientNonce = Util.toBytesFromString("677f1c557a5ee96c404d1b6f69152580"); // byte [] ntlm2UserSessionReponseKey = keyFactory.getNTLM2SessionResponseUserSessionKey("test1234", challengePlusclientNonce); // // System.out.println(Util.dumpString(ntlm2UserSessionReponseKey)); // // byte[] secondaryEncryptedKey = Util.toBytesFromString("727a5240822ec7af4e9100c43e6fee7f"); // // byte[] decryptedSecondaryKey = keyFactory.decryptSecondarySessionKey(secondaryEncryptedKey, ntlm2UserSessionReponseKey); // System.out.println(Util.dumpString(decryptedSecondaryKey)); // // //now lets try signature from server // byte[] data = new byte[]{0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08}; // byte[] serverSigningKey = keyFactory.generateServerSigningKeyUsingNegotiatedSecondarySessionKey(decryptedSecondaryKey); // System.out.println(Util.dumpString(serverSigningKey)); // byte[] serverSealingKey = keyFactory.generateServerSealingKeyUsingNegotiatedSecondarySessionKey(decryptedSecondaryKey); // System.out.println(Util.dumpString(serverSealingKey)); // IRandom rc4 = keyFactory.getARCFOUR(serverSealingKey); // System.out.println(Util.dumpString(keyFactory.serverSigning(0, serverSigningKey, data, rc4))); // byte[] cipheredPack = keyFactory.serverSealing(1, serverSealingKey, serverSigningKey,data, rc4); // System.out.println(Util.dumpString(cipheredPack)); // // IRandom rc4fordecipher = keyFactory.getARCFOUR(serverSealingKey); // keyFactory.serverSigning(0, serverSigningKey, data, rc4fordecipher);//just like that for increasing rc4fordecipher state...will not be like this // //in the actual implementation... // byte[] cipheredData = new byte[8]; // System.arraycopy(cipheredPack, 0, cipheredData, 0, 8); // // System.out.println(Util.dumpString(keyFactory.applyARCFOUR(rc4fordecipher, cipheredData))); // int i = 0; // }catch(Exception e) // { // e.printStackTrace(); // } // // } // // /** // * @param args // */ // static void main(String[] args) { // // try // { // // NTLMKeyFactory keyFactory = new NTLMKeyFactory(); // byte[] challengePlusclientNonce = Util.toBytesFromString("38c2c82866a284b6a2d45d0f58feb085"); // byte [] ntlm2UserSessionReponseKey = keyFactory.getNTLM2SessionResponseUserSessionKey("enterprise", challengePlusclientNonce); // // System.out.println(Util.dumpString(ntlm2UserSessionReponseKey)); // // byte[] secondaryEncryptedKey = Util.toBytesFromString("fa650f59feb62161fc08defeb9e5f5d2"); // // byte[] decryptedSecondaryKey = keyFactory.decryptSecondarySessionKey(secondaryEncryptedKey, ntlm2UserSessionReponseKey); // System.out.println(Util.dumpString(decryptedSecondaryKey)); // // //now lets try signature from server // byte[] data = new byte[]{0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08}; // byte[] clientSigningKey = keyFactory.generateClientSigningKeyUsingNegotiatedSecondarySessionKey(decryptedSecondaryKey); // System.out.println(Util.dumpString(clientSigningKey)); // byte[] clientSealingKey = keyFactory.generateClientSealingKeyUsingNegotiatedSecondarySessionKey(decryptedSecondaryKey); // System.out.println(Util.dumpString(clientSealingKey)); //// IRandom rc4 = keyFactory.getARCFOUR(serverSealingKey); // //// byte[] cipheredPack = keyFactory.serverSealing(0, serverSealingKey, serverSigningKey,data, rc4); //// System.out.println(Util.dumpString(cipheredPack)); // // IRandom rc4fordecipher = keyFactory.getARCFOUR(clientSealingKey); //// keyFactory.serverSigning(0, serverSigningKey, data, rc4fordecipher);//just like that for increasing rc4fordecipher state...will not be like this // //in the actual implementation... // byte[] cipheredData = new byte[496]; // FileInputStream stream = new FileInputStream("c:/temp/encrypted"); // stream.read(cipheredData, 0, 496); //// System.arraycopy(cipheredPack, 0, cipheredData, 0, 8); // cipheredData = keyFactory.applyARCFOUR(rc4fordecipher, cipheredData); // Hexdump.hexdump(System.out, cipheredData, 0, cipheredData.length); // int i = 0; // }catch(Exception e) // { // e.printStackTrace(); // } // // } }