1 package org.metricshub.winrm.service.client.encryption;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
35
36
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
50 private byte[] clientChallenge;
51 private byte[] clientChallenge2;
52 private byte[] secondaryKey;
53 private byte[] timestamp;
54
55
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
94 private byte[] getClientChallenge() {
95 if (clientChallenge == null) {
96 clientChallenge = makeRandomChallenge(random);
97 }
98 return clientChallenge;
99 }
100
101
102 private byte[] getClientChallenge2() {
103 if (clientChallenge2 == null) {
104 clientChallenge2 = makeRandomChallenge(random);
105 }
106 return clientChallenge2;
107 }
108
109
110 public byte[] getSecondaryKey() {
111 if (secondaryKey == null) {
112 secondaryKey = makeSecondaryKey(random);
113 }
114 return secondaryKey;
115 }
116
117
118 private byte[] getLMHash() throws NTLMEngineException {
119 if (lmHash == null) {
120 lmHash = lmHash(password);
121 }
122 return lmHash;
123 }
124
125
126 public byte[] getLMResponse() throws NTLMEngineException {
127 if (lmResponse == null) {
128 lmResponse = lmResponse(getLMHash(), challenge);
129 }
130 return lmResponse;
131 }
132
133
134 private byte[] getNTLMHash() throws NTLMEngineException {
135 if (ntlmHash == null) {
136 ntlmHash = ntlmHash(password);
137 }
138 return ntlmHash;
139 }
140
141
142 public byte[] getNTLMResponse() throws NTLMEngineException {
143 if (ntlmResponse == null) {
144 ntlmResponse = lmResponse(getNTLMHash(), challenge);
145 }
146 return ntlmResponse;
147 }
148
149
150 private byte[] getLMv2Hash() throws NTLMEngineException {
151 if (lmv2Hash == null) {
152 lmv2Hash = lmv2Hash(domain, user, getNTLMHash());
153 }
154 return lmv2Hash;
155 }
156
157
158 private byte[] getNTLMv2Hash() throws NTLMEngineException {
159 if (ntlmv2Hash == null) {
160 ntlmv2Hash = ntlmv2Hash(domain, user, getNTLMHash());
161 }
162 return ntlmv2Hash;
163 }
164
165
166 private byte[] getTimestamp() {
167 if (timestamp == null) {
168 long time = this.currentTime;
169 time += 11644473600000l;
170 time *= 10000;
171
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
182 private byte[] getNTLMv2Blob() {
183 if (ntlmv2Blob == null) {
184 ntlmv2Blob = createBlob(getClientChallenge2(), targetInformation, getTimestamp());
185 }
186 return ntlmv2Blob;
187 }
188
189
190
191
192
193
194
195
196
197
198
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
235 public byte[] getNTLMv2Response() throws NTLMEngineException {
236 if (ntlmv2Response == null) {
237 ntlmv2Response = lmv2Response(getNTLMv2Hash(), challenge, getNTLMv2Blob());
238 }
239 return ntlmv2Response;
240 }
241
242
243 public byte[] getLMv2Response() throws NTLMEngineException {
244 if (lmv2Response == null) {
245 lmv2Response = lmv2Response(getLMv2Hash(), challenge, getClientChallenge());
246 }
247 return lmv2Response;
248 }
249
250
251 public byte[] getNTLM2SessionResponse() throws NTLMEngineException {
252 if (ntlm2SessionResponse == null) {
253 ntlm2SessionResponse = ntlm2SessionResponse(getNTLMHash(), challenge, getClientChallenge());
254 }
255 return ntlm2SessionResponse;
256 }
257
258
259
260
261
262
263
264
265
266
267
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
292
293
294
295
296
297
298
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
326
327
328
329
330
331
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
359
360
361
362
363
364
365
366
367
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
387
388
389
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
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
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
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
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
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
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
487
488
489
490
491
492
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
506
507
508
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
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
526
527
528
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
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
546
547
548
549
550
551
552
553
554
555
556
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
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
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
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 }