View Javadoc
1   package org.metricshub.winrm.service.client.encryption;
2   
3   /*-
4    * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
5    * WinRM Java Client
6    * ჻჻჻჻჻჻
7    * Copyright 2023 - 2024 Metricshub
8    * ჻჻჻჻჻჻
9    * Licensed under the Apache License, Version 2.0 (the "License");
10   * you may not use this file except in compliance with the License.
11   * You may obtain a copy of the License at
12   *
13   *      http://www.apache.org/licenses/LICENSE-2.0
14   *
15   * Unless required by applicable law or agreed to in writing, software
16   * distributed under the License is distributed on an "AS IS" BASIS,
17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18   * See the License for the specific language governing permissions and
19   * limitations under the License.
20   * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
21   */
22  
23  import java.security.Key;
24  import java.security.MessageDigest;
25  import java.util.Arrays;
26  import java.util.Locale;
27  import java.util.Random;
28  import javax.crypto.Cipher;
29  import javax.crypto.spec.SecretKeySpec;
30  import org.apache.http.impl.auth.NTLMEngineException;
31  import org.metricshub.winrm.service.client.auth.ntlm.NTLMEngineUtils;
32  
33  /**
34   * Code from io.cloudsoft.winrm4j.client.ntlm.forks.httpclient.NTLMEngineImpl
35   * release 0.12.3 @link https://github.com/cloudsoft/winrm4j
36   * io.cloudsoft.winrm4j.client.ntlm.forks.httpclient is a fork of apache-httpclient 4.5.13
37   */
38  public class CipherGen {
39  
40  	private final Random random;
41  	private final long currentTime;
42  
43  	private final String domain;
44  	private final String user;
45  	private final String password;
46  	private final byte[] challenge;
47  	private final byte[] targetInformation;
48  
49  	// Information we can generate but may be passed in (for testing)
50  	private byte[] clientChallenge;
51  	private byte[] clientChallenge2;
52  	private byte[] secondaryKey;
53  	private byte[] timestamp;
54  
55  	// Stuff we always generate
56  	private byte[] lmHash = null;
57  	private byte[] lmResponse = null;
58  	private byte[] ntlmHash = null;
59  	private byte[] ntlmResponse = null;
60  	private byte[] ntlmv2Hash = null;
61  	private byte[] lmv2Hash = null;
62  	private byte[] lmv2Response = null;
63  	private byte[] ntlmv2Blob = null;
64  	private byte[] ntlmv2Response = null;
65  	private byte[] ntlm2SessionResponse = null;
66  	private byte[] lm2SessionResponse = null;
67  	private byte[] lmUserSessionKey = null;
68  	private byte[] ntlmUserSessionKey = null;
69  	private byte[] ntlmv2UserSessionKey = null;
70  	private byte[] ntlm2SessionResponseUserSessionKey = null;
71  	private byte[] lanManagerSessionKey = null;
72  
73  	public CipherGen(
74  		final Random random,
75  		final long currentTime,
76  		final String domain,
77  		final String user,
78  		final String password,
79  		final byte[] challenge,
80  		final String target,
81  		final byte[] targetInformation
82  	) {
83  		this.random = random;
84  		this.currentTime = currentTime;
85  
86  		this.domain = domain;
87  		this.user = user;
88  		this.password = password;
89  		this.challenge = challenge;
90  		this.targetInformation = targetInformation;
91  	}
92  
93  	/** Calculate and return client challenge */
94  	private byte[] getClientChallenge() {
95  		if (clientChallenge == null) {
96  			clientChallenge = makeRandomChallenge(random);
97  		}
98  		return clientChallenge;
99  	}
100 
101 	/** Calculate and return second client challenge */
102 	private byte[] getClientChallenge2() {
103 		if (clientChallenge2 == null) {
104 			clientChallenge2 = makeRandomChallenge(random);
105 		}
106 		return clientChallenge2;
107 	}
108 
109 	/** Calculate and return random secondary key */
110 	public byte[] getSecondaryKey() {
111 		if (secondaryKey == null) {
112 			secondaryKey = makeSecondaryKey(random);
113 		}
114 		return secondaryKey;
115 	}
116 
117 	/** Calculate and return the LMHash */
118 	private byte[] getLMHash() throws NTLMEngineException {
119 		if (lmHash == null) {
120 			lmHash = lmHash(password);
121 		}
122 		return lmHash;
123 	}
124 
125 	/** Calculate and return the LMResponse */
126 	public byte[] getLMResponse() throws NTLMEngineException {
127 		if (lmResponse == null) {
128 			lmResponse = lmResponse(getLMHash(), challenge);
129 		}
130 		return lmResponse;
131 	}
132 
133 	/** Calculate and return the NTLMHash */
134 	private byte[] getNTLMHash() throws NTLMEngineException {
135 		if (ntlmHash == null) {
136 			ntlmHash = ntlmHash(password);
137 		}
138 		return ntlmHash;
139 	}
140 
141 	/** Calculate and return the NTLMResponse */
142 	public byte[] getNTLMResponse() throws NTLMEngineException {
143 		if (ntlmResponse == null) {
144 			ntlmResponse = lmResponse(getNTLMHash(), challenge);
145 		}
146 		return ntlmResponse;
147 	}
148 
149 	/** Calculate the LMv2 hash */
150 	private byte[] getLMv2Hash() throws NTLMEngineException {
151 		if (lmv2Hash == null) {
152 			lmv2Hash = lmv2Hash(domain, user, getNTLMHash());
153 		}
154 		return lmv2Hash;
155 	}
156 
157 	/** Calculate the NTLMv2 hash */
158 	private byte[] getNTLMv2Hash() throws NTLMEngineException {
159 		if (ntlmv2Hash == null) {
160 			ntlmv2Hash = ntlmv2Hash(domain, user, getNTLMHash());
161 		}
162 		return ntlmv2Hash;
163 	}
164 
165 	/** Calculate a timestamp */
166 	private byte[] getTimestamp() {
167 		if (timestamp == null) {
168 			long time = this.currentTime;
169 			time += 11644473600000l; // milliseconds from January 1, 1601 -> epoch.
170 			time *= 10000; // tenths of a microsecond.
171 			// convert to little-endian byte array.
172 			timestamp = new byte[8];
173 			for (int i = 0; i < 8; i++) {
174 				timestamp[i] = (byte) time;
175 				time >>>= 8;
176 			}
177 		}
178 		return timestamp;
179 	}
180 
181 	/** Calculate the NTLMv2Blob */
182 	private byte[] getNTLMv2Blob() {
183 		if (ntlmv2Blob == null) {
184 			ntlmv2Blob = createBlob(getClientChallenge2(), targetInformation, getTimestamp());
185 		}
186 		return ntlmv2Blob;
187 	}
188 
189 	/**
190 	 * Creates the NTLMv2 blob from the given target information block and
191 	 * client challenge.
192 	 *
193 	 * @param targetInformation
194 	 *			The target information block from the Type 2 message.
195 	 * @param clientChallenge
196 	 *			The random 8-byte client challenge.
197 	 *
198 	 * @return The blob, used in the calculation of the NTLMv2 Response.
199 	 */
200 	private static byte[] createBlob(
201 		final byte[] clientChallenge,
202 		final byte[] targetInformation,
203 		final byte[] timestamp
204 	) {
205 		final byte[] blobSignature = new byte[] { (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x00 };
206 		final byte[] reserved = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 };
207 		final byte[] unknown1 = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 };
208 		final byte[] unknown2 = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 };
209 		final byte[] blob = new byte[blobSignature.length +
210 		reserved.length +
211 		timestamp.length +
212 		8 +
213 		unknown1.length +
214 		targetInformation.length +
215 		unknown2.length];
216 		int offset = 0;
217 		System.arraycopy(blobSignature, 0, blob, offset, blobSignature.length);
218 		offset += blobSignature.length;
219 		System.arraycopy(reserved, 0, blob, offset, reserved.length);
220 		offset += reserved.length;
221 		System.arraycopy(timestamp, 0, blob, offset, timestamp.length);
222 		offset += timestamp.length;
223 		System.arraycopy(clientChallenge, 0, blob, offset, 8);
224 		offset += 8;
225 		System.arraycopy(unknown1, 0, blob, offset, unknown1.length);
226 		offset += unknown1.length;
227 		System.arraycopy(targetInformation, 0, blob, offset, targetInformation.length);
228 		offset += targetInformation.length;
229 		System.arraycopy(unknown2, 0, blob, offset, unknown2.length);
230 		offset += unknown2.length;
231 		return blob;
232 	}
233 
234 	/** Calculate the NTLMv2Response */
235 	public byte[] getNTLMv2Response() throws NTLMEngineException {
236 		if (ntlmv2Response == null) {
237 			ntlmv2Response = lmv2Response(getNTLMv2Hash(), challenge, getNTLMv2Blob());
238 		}
239 		return ntlmv2Response;
240 	}
241 
242 	/** Calculate the LMv2Response */
243 	public byte[] getLMv2Response() throws NTLMEngineException {
244 		if (lmv2Response == null) {
245 			lmv2Response = lmv2Response(getLMv2Hash(), challenge, getClientChallenge());
246 		}
247 		return lmv2Response;
248 	}
249 
250 	/** Get NTLM2SessionResponse */
251 	public byte[] getNTLM2SessionResponse() throws NTLMEngineException {
252 		if (ntlm2SessionResponse == null) {
253 			ntlm2SessionResponse = ntlm2SessionResponse(getNTLMHash(), challenge, getClientChallenge());
254 		}
255 		return ntlm2SessionResponse;
256 	}
257 
258 	/**
259 	 * Calculates the NTLM2 Session Response for the given challenge, using the
260 	 * specified password and client challenge.
261 	 *
262 	 * @param ntlmHash
263 	 * @param challenge
264 	 * @param clientChallenge
265 	 * @return The NTLM2 Session Response. This is placed in the NTLM response
266 	 *		 field of the Type 3 message; the LM response field contains the
267 	 *		 client challenge, null-padded to 24 bytes.
268 	 */
269 	private static byte[] ntlm2SessionResponse(
270 		final byte[] ntlmHash,
271 		final byte[] challenge,
272 		final byte[] clientChallenge
273 	) throws NTLMEngineException {
274 		try {
275 			final MessageDigest md5 = EncryptionUtils.getMD5();
276 			md5.update(challenge);
277 			md5.update(clientChallenge);
278 			final byte[] digest = md5.digest();
279 
280 			final byte[] sessionHash = new byte[8];
281 			System.arraycopy(digest, 0, sessionHash, 0, 8);
282 			return lmResponse(ntlmHash, sessionHash);
283 		} catch (final NTLMEngineException e) {
284 			throw (NTLMEngineException) e;
285 		} catch (final Exception e) {
286 			throw new NTLMEngineException(e.getMessage(), e);
287 		}
288 	}
289 
290 	/**
291 	 * Creates the LM Response from the given hash and Type 2 challenge.
292 	 *
293 	 * @param hash
294 	 *			The LM or NTLM Hash.
295 	 * @param challenge
296 	 *			The server challenge from the Type 2 message.
297 	 *
298 	 * @return The response (either LM or NTLM, depending on the provided hash).
299 	 */
300 	private static byte[] lmResponse(final byte[] hash, final byte[] challenge) throws NTLMEngineException {
301 		try {
302 			final byte[] keyBytes = new byte[21];
303 			System.arraycopy(hash, 0, keyBytes, 0, 16);
304 			final Key lowKey = createDESKey(keyBytes, 0);
305 			final Key middleKey = createDESKey(keyBytes, 7);
306 			final Key highKey = createDESKey(keyBytes, 14);
307 			final Cipher des = Cipher.getInstance("DES/ECB/NoPadding");
308 			des.init(Cipher.ENCRYPT_MODE, lowKey);
309 			final byte[] lowResponse = des.doFinal(challenge);
310 			des.init(Cipher.ENCRYPT_MODE, middleKey);
311 			final byte[] middleResponse = des.doFinal(challenge);
312 			des.init(Cipher.ENCRYPT_MODE, highKey);
313 			final byte[] highResponse = des.doFinal(challenge);
314 			final byte[] lmResponse = new byte[24];
315 			System.arraycopy(lowResponse, 0, lmResponse, 0, 8);
316 			System.arraycopy(middleResponse, 0, lmResponse, 8, 8);
317 			System.arraycopy(highResponse, 0, lmResponse, 16, 8);
318 			return lmResponse;
319 		} catch (final Exception e) {
320 			throw new NTLMEngineException(e.getMessage(), e);
321 		}
322 	}
323 
324 	/**
325 	 * Creates the LM Hash of the user's password.
326 	 *
327 	 * @param password
328 	 *			The password.
329 	 *
330 	 * @return The LM Hash of the given password, used in the calculation of the
331 	 *		 LM Response.
332 	 */
333 	private static byte[] lmHash(final String password) throws NTLMEngineException {
334 		try {
335 			final byte[] oemPassword = password.toUpperCase(Locale.ROOT).getBytes(NTLMEngineUtils.DEFAULT_CHARSET);
336 
337 			final int length = Math.min(oemPassword.length, 14);
338 			final byte[] keyBytes = new byte[14];
339 			System.arraycopy(oemPassword, 0, keyBytes, 0, length);
340 			final Key lowKey = createDESKey(keyBytes, 0);
341 			final Key highKey = createDESKey(keyBytes, 7);
342 			final byte[] magicConstant = "KGS!@#$%".getBytes(NTLMEngineUtils.DEFAULT_CHARSET);
343 			final Cipher des = Cipher.getInstance("DES/ECB/NoPadding");
344 			des.init(Cipher.ENCRYPT_MODE, lowKey);
345 			final byte[] lowHash = des.doFinal(magicConstant);
346 			des.init(Cipher.ENCRYPT_MODE, highKey);
347 			final byte[] highHash = des.doFinal(magicConstant);
348 			final byte[] lmHash = new byte[16];
349 			System.arraycopy(lowHash, 0, lmHash, 0, 8);
350 			System.arraycopy(highHash, 0, lmHash, 8, 8);
351 			return lmHash;
352 		} catch (final Exception e) {
353 			throw new NTLMEngineException(e.getMessage(), e);
354 		}
355 	}
356 
357 	/**
358 	 * Creates a DES encryption key from the given key material.
359 	 *
360 	 * @param bytes
361 	 *			A byte array containing the DES key material.
362 	 * @param offset
363 	 *			The offset in the given byte array at which the 7-byte key
364 	 *			material starts.
365 	 *
366 	 * @return A DES encryption key created from the key material starting at
367 	 *		 the specified offset in the given byte array.
368 	 */
369 	private static Key createDESKey(final byte[] bytes, final int offset) {
370 		final byte[] keyBytes = new byte[7];
371 		System.arraycopy(bytes, offset, keyBytes, 0, 7);
372 		final byte[] material = new byte[8];
373 		material[0] = keyBytes[0];
374 		material[1] = (byte) ((keyBytes[0] << 7) | ((keyBytes[1] & 0xff) >>> 1));
375 		material[2] = (byte) ((keyBytes[1] << 6) | ((keyBytes[2] & 0xff) >>> 2));
376 		material[3] = (byte) ((keyBytes[2] << 5) | ((keyBytes[3] & 0xff) >>> 3));
377 		material[4] = (byte) ((keyBytes[3] << 4) | ((keyBytes[4] & 0xff) >>> 4));
378 		material[5] = (byte) ((keyBytes[4] << 3) | ((keyBytes[5] & 0xff) >>> 5));
379 		material[6] = (byte) ((keyBytes[5] << 2) | ((keyBytes[6] & 0xff) >>> 6));
380 		material[7] = (byte) (keyBytes[6] << 1);
381 		oddParity(material);
382 		return new SecretKeySpec(material, "DES");
383 	}
384 
385 	/**
386 	 * Applies odd parity to the given byte array.
387 	 *
388 	 * @param bytes
389 	 *			The data whose parity bits are to be adjusted for odd parity.
390 	 */
391 	private static void oddParity(final byte[] bytes) {
392 		for (int i = 0; i < bytes.length; i++) {
393 			final byte b = bytes[i];
394 			final boolean needsParity =
395 				(((b >>> 7) ^ (b >>> 6) ^ (b >>> 5) ^ (b >>> 4) ^ (b >>> 3) ^ (b >>> 2) ^ (b >>> 1)) & 0x01) == 0;
396 			if (needsParity) {
397 				bytes[i] |= (byte) 0x01;
398 			} else {
399 				bytes[i] &= (byte) 0xfe;
400 			}
401 		}
402 	}
403 
404 	/** Calculate and return LM2 session response */
405 	public byte[] getLM2SessionResponse() {
406 		if (lm2SessionResponse == null) {
407 			final byte[] clntChallenge = getClientChallenge();
408 			lm2SessionResponse = new byte[24];
409 			System.arraycopy(clntChallenge, 0, lm2SessionResponse, 0, clntChallenge.length);
410 			Arrays.fill(lm2SessionResponse, clntChallenge.length, lm2SessionResponse.length, (byte) 0x00);
411 		}
412 		return lm2SessionResponse;
413 	}
414 
415 	/** Get LMUserSessionKey */
416 	public byte[] getLMUserSessionKey() throws NTLMEngineException {
417 		if (lmUserSessionKey == null) {
418 			lmUserSessionKey = new byte[16];
419 			System.arraycopy(getLMHash(), 0, lmUserSessionKey, 0, 8);
420 			Arrays.fill(lmUserSessionKey, 8, 16, (byte) 0x00);
421 		}
422 		return lmUserSessionKey;
423 	}
424 
425 	/** Get NTLMUserSessionKey */
426 	public byte[] getNTLMUserSessionKey() throws NTLMEngineException {
427 		if (ntlmUserSessionKey == null) {
428 			final MD4 md4 = new MD4();
429 			md4.update(getNTLMHash());
430 			ntlmUserSessionKey = md4.getOutput();
431 		}
432 		return ntlmUserSessionKey;
433 	}
434 
435 	/** GetNTLMv2UserSessionKey */
436 	public byte[] getNTLMv2UserSessionKey() throws NTLMEngineException {
437 		if (ntlmv2UserSessionKey == null) {
438 			final byte[] ntlmv2hash = getNTLMv2Hash();
439 			final byte[] truncatedResponse = new byte[16];
440 			System.arraycopy(getNTLMv2Response(), 0, truncatedResponse, 0, 16);
441 			ntlmv2UserSessionKey = hmacMD5(truncatedResponse, ntlmv2hash);
442 		}
443 		return ntlmv2UserSessionKey;
444 	}
445 
446 	/** Get NTLM2SessionResponseUserSessionKey */
447 	public byte[] getNTLM2SessionResponseUserSessionKey() throws NTLMEngineException {
448 		if (ntlm2SessionResponseUserSessionKey == null) {
449 			final byte[] ntlm2SessionResponseNonce = getLM2SessionResponse();
450 			final byte[] sessionNonce = new byte[challenge.length + ntlm2SessionResponseNonce.length];
451 			System.arraycopy(challenge, 0, sessionNonce, 0, challenge.length);
452 			System.arraycopy(ntlm2SessionResponseNonce, 0, sessionNonce, challenge.length, ntlm2SessionResponseNonce.length);
453 			ntlm2SessionResponseUserSessionKey = hmacMD5(sessionNonce, getNTLMUserSessionKey());
454 		}
455 		return ntlm2SessionResponseUserSessionKey;
456 	}
457 
458 	/** Get LAN Manager session key */
459 	public byte[] getLanManagerSessionKey() throws NTLMEngineException {
460 		if (lanManagerSessionKey == null) {
461 			try {
462 				final byte[] keyBytes = new byte[14];
463 				System.arraycopy(getLMHash(), 0, keyBytes, 0, 8);
464 				Arrays.fill(keyBytes, 8, keyBytes.length, (byte) 0xbd);
465 				final Key lowKey = createDESKey(keyBytes, 0);
466 				final Key highKey = createDESKey(keyBytes, 7);
467 				final byte[] truncatedResponse = new byte[8];
468 				System.arraycopy(getLMResponse(), 0, truncatedResponse, 0, truncatedResponse.length);
469 				Cipher des = Cipher.getInstance("DES/ECB/NoPadding");
470 				des.init(Cipher.ENCRYPT_MODE, lowKey);
471 				final byte[] lowPart = des.doFinal(truncatedResponse);
472 				des = Cipher.getInstance("DES/ECB/NoPadding");
473 				des.init(Cipher.ENCRYPT_MODE, highKey);
474 				final byte[] highPart = des.doFinal(truncatedResponse);
475 				lanManagerSessionKey = new byte[16];
476 				System.arraycopy(lowPart, 0, lanManagerSessionKey, 0, lowPart.length);
477 				System.arraycopy(highPart, 0, lanManagerSessionKey, lowPart.length, highPart.length);
478 			} catch (final Exception e) {
479 				throw new NTLMEngineException(e.getMessage(), e);
480 			}
481 		}
482 		return lanManagerSessionKey;
483 	}
484 
485 	/**
486 	 * Creates the NTLM Hash of the user's password.
487 	 *
488 	 * @param password
489 	 *			The password.
490 	 *
491 	 * @return The NTLM Hash of the given password, used in the calculation of
492 	 *		 the NTLM Response and the NTLMv2 and LMv2 Hashes.
493 	 */
494 	private static byte[] ntlmHash(final String password) throws NTLMEngineException {
495 		if (NTLMEngineUtils.UNICODE_LITTLE_UNMARKED == null) {
496 			throw new NTLMEngineException("Unicode not supported");
497 		}
498 		final byte[] unicodePassword = password.getBytes(NTLMEngineUtils.UNICODE_LITTLE_UNMARKED);
499 		final MD4 md4 = new MD4();
500 		md4.update(unicodePassword);
501 		return md4.getOutput();
502 	}
503 
504 	/**
505 	 * Creates the LMv2 Hash of the user's password.
506 	 *
507 	 * @return The LMv2 Hash, used in the calculation of the NTLMv2 and LMv2
508 	 *		 Responses.
509 	 */
510 	private static byte[] lmv2Hash(final String domain, final String user, final byte[] ntlmHash)
511 		throws NTLMEngineException {
512 		if (NTLMEngineUtils.UNICODE_LITTLE_UNMARKED == null) {
513 			throw new NTLMEngineException("Unicode not supported");
514 		}
515 		final HMACMD5 hmacMD5 = new HMACMD5(ntlmHash);
516 		// Upper case username, upper case domain!
517 		hmacMD5.update(user.toUpperCase(Locale.ROOT).getBytes(NTLMEngineUtils.UNICODE_LITTLE_UNMARKED));
518 		if (domain != null) {
519 			hmacMD5.update(domain.toUpperCase(Locale.ROOT).getBytes(NTLMEngineUtils.UNICODE_LITTLE_UNMARKED));
520 		}
521 		return hmacMD5.getOutput();
522 	}
523 
524 	/**
525 	 * Creates the NTLMv2 Hash of the user's password.
526 	 *
527 	 * @return The NTLMv2 Hash, used in the calculation of the NTLMv2 and LMv2
528 	 *		 Responses.
529 	 */
530 	private static byte[] ntlmv2Hash(final String domain, final String user, final byte[] ntlmHash)
531 		throws NTLMEngineException {
532 		if (NTLMEngineUtils.UNICODE_LITTLE_UNMARKED == null) {
533 			throw new NTLMEngineException("Unicode not supported");
534 		}
535 		final HMACMD5 hmacMD5 = new HMACMD5(ntlmHash);
536 		// Upper case username, mixed case target!!
537 		hmacMD5.update(user.toUpperCase(Locale.ROOT).getBytes(NTLMEngineUtils.UNICODE_LITTLE_UNMARKED));
538 		if (domain != null) {
539 			hmacMD5.update(domain.getBytes(NTLMEngineUtils.UNICODE_LITTLE_UNMARKED));
540 		}
541 		return hmacMD5.getOutput();
542 	}
543 
544 	/**
545 	 * Creates the LMv2 Response from the given hash, client data, and Type 2
546 	 * challenge.
547 	 *
548 	 * @param hash
549 	 *			The NTLMv2 Hash.
550 	 * @param clientData
551 	 *			The client data (blob or client challenge).
552 	 * @param challenge
553 	 *			The server challenge from the Type 2 message.
554 	 *
555 	 * @return The response (either NTLMv2 or LMv2, depending on the client
556 	 *		 data).
557 	 */
558 	private static byte[] lmv2Response(final byte[] hash, final byte[] challenge, final byte[] clientData) {
559 		final HMACMD5 hmacMD5 = new HMACMD5(hash);
560 		hmacMD5.update(challenge);
561 		hmacMD5.update(clientData);
562 		final byte[] mac = hmacMD5.getOutput();
563 		final byte[] lmv2Response = new byte[mac.length + clientData.length];
564 		System.arraycopy(mac, 0, lmv2Response, 0, mac.length);
565 		System.arraycopy(clientData, 0, lmv2Response, mac.length, clientData.length);
566 		return lmv2Response;
567 	}
568 
569 	/** Calculate a challenge block */
570 	private static byte[] makeRandomChallenge(final Random random) {
571 		final byte[] rval = new byte[8];
572 		synchronized (random) {
573 			random.nextBytes(rval);
574 		}
575 		return rval;
576 	}
577 
578 	/** Calculate a 16-byte secondary key */
579 	private static byte[] makeSecondaryKey(final Random random) {
580 		final byte[] rval = new byte[16];
581 		synchronized (random) {
582 			random.nextBytes(rval);
583 		}
584 		return rval;
585 	}
586 
587 	/** Calculates HMAC-MD5 */
588 	private static byte[] hmacMD5(final byte[] value, final byte[] key) {
589 		final HMACMD5 hmacMD5 = new HMACMD5(key);
590 		hmacMD5.update(value);
591 		return hmacMD5.getOutput();
592 	}
593 }