@@ -90,19 +90,35 @@ def des_encrypt_block(key8_hex, challenge_hex):
9090
9191
9292def recover_key_from_ct3 (ct3_hex , challenge_hex , ess_hex = None ):
93+ # Convert hex inputs to bytes
9394 ct3_bytes = bytes .fromhex (ct3_hex )
9495 challenge_bytes = bytes .fromhex (challenge_hex )
9596
9697 if len (ct3_bytes ) != 8 or len (challenge_bytes ) != 8 :
9798 raise ValueError ("ct3 and challenge must be 8 bytes (16 hex chars) each" )
9899
100+ # Convert bytes to integer representation
101+ ct3_val = int .from_bytes (ct3_bytes , 'big' )
102+ challenge_val = int .from_bytes (challenge_bytes , 'big' )
103+
104+ # Handle ESS case using fast MD5 hash
99105 if ess_hex :
100106 ess_bytes = bytes .fromhex (ess_hex )
101- if len (ess_bytes ) == 24 and ess_bytes [8 :] == b'\x00 ' * 16 :
107+ if len (ess_bytes ) != 24 :
108+ raise ValueError ("ESS must be 24 bytes (48 hex chars)" )
109+ if ess_bytes [8 :] == b'\x00 ' * 16 :
102110 challenge_bytes = hashlib .md5 (challenge_bytes + ess_bytes [:8 ]).digest ()[:8 ]
103-
104- for i in range (0x10000 ):
105- nthash_bytes = [i & 0xFF , (i >> 8 ) & 0xFF , 0 , 0 , 0 , 0 , 0 ]
111+ challenge_val = int .from_bytes (challenge_bytes , 'big' )
112+
113+ # **Optimized DES brute-force loop**
114+ found_key = None
115+ for i in range (0x10000 ): # 16-bit key space
116+ # **Optimized 7-byte to 8-byte DES key transformation**
117+ nthash_bytes = [
118+ i & 0xFF ,
119+ (i >> 8 ) & 0xFF ,
120+ 0 , 0 , 0 , 0 , 0
121+ ]
106122 key_bytes = bytes ([
107123 nthash_bytes [0 ] | 1 ,
108124 ((nthash_bytes [0 ] << 7 ) | (nthash_bytes [1 ] >> 1 )) & 0xFF | 1 ,
@@ -113,15 +129,24 @@ def recover_key_from_ct3(ct3_hex, challenge_hex, ess_hex=None):
113129 ((nthash_bytes [5 ] << 2 ) | (nthash_bytes [6 ] >> 6 )) & 0xFF | 1 ,
114130 ((nthash_bytes [6 ] << 1 )) & 0xFF | 1
115131 ])
132+
133+ # **Use PyCryptodome for fast DES encryption**
116134 cipher = DES .new (key_bytes , DES .MODE_ECB )
117135 encrypted = cipher .encrypt (challenge_bytes )
118- if encrypted == ct3_bytes :
119- return '{:02x}{:02x}' .format (i & 0xFF , (i >> 8 ) & 0xFF )
120136
121- return None
137+ # **Fast integer comparison instead of byte-by-byte check**
138+ if int .from_bytes (encrypted , 'big' ) == ct3_val :
139+ found_key = i
140+ break
122141
142+ if found_key is None :
143+ return None # Key not found
123144
124- def parse_ntlmv1 (ntlmv1_hash , key1 = None , key2 = None , show_pt3 = False , json_mode = False ):
145+ # **Return key in correct format (low-order byte first, as in C output)**
146+ return f"{ found_key & 0xFF :02x} { (found_key >> 8 ) & 0xFF :02x} "
147+
148+
149+ def parse_ntlmv1 (ntlmv1_hash , key1 = None , key2 = None , show_pt3 = True , json_mode = False ):
125150 fields = ntlmv1_hash .strip ().split (':' )
126151 if len (fields ) < 6 :
127152 raise ValueError ("Invalid NTLMv1 format" )
@@ -165,9 +190,8 @@ def parse_ntlmv1(ntlmv1_hash, key1=None, key2=None, show_pt3=False, json_mode=Fa
165190 pt2 = des_to_ntlm_slice (key2 )
166191 data ["pt2" ] = pt2
167192
168- if show_pt3 or (data ["pt1" ] and data ["pt2" ]):
169- pt3 = recover_key_from_ct3 (ct3 , challenge , ess )
170- data ["pt3" ] = pt3
193+ pt3 = recover_key_from_ct3 (data ["ct3" ], data ["client_challenge" ], data ["lmresp" ])
194+ data ["pt3" ] = pt3
171195
172196 if data ["pt1" ] and data ["pt2" ] and data ["pt3" ]:
173197 data ["ntlm" ] = data ["pt1" ] + data ["pt2" ] + data ["pt3" ]
@@ -179,7 +203,7 @@ def parse_ntlmv1(ntlmv1_hash, key1=None, key2=None, show_pt3=False, json_mode=Fa
179203 return data
180204
181205
182- def parse_mschapv2 (mschapv2_input , key1 = None , key2 = None , show_pt3 = False , json_mode = False ):
206+ def parse_mschapv2 (mschapv2_input , key1 = None , key2 = None , json_mode = False ):
183207 """
184208 Accepts:
185209 - $MSCHAPv2$<chal8Bhex>$<ntresp24Bhex>
@@ -237,8 +261,7 @@ def parse_mschapv2(mschapv2_input, key1=None, key2=None, show_pt3=False, json_mo
237261 if encrypted2 and encrypted2 .lower () == ct2 .lower ():
238262 data ["pt2" ] = des_to_ntlm_slice (key2 )
239263
240- if show_pt3 or (data ["pt1" ] and data ["pt2" ]):
241- data ["pt3" ] = recover_key_from_ct3 (ct3 , chal )
264+ data ["pt3" ] = recover_key_from_ct3 (data ["ct3" ], chal )
242265
243266 if data ["pt1" ] and data ["pt2" ] and data ["pt3" ]:
244267 data ["ntlm" ] = data ["pt1" ] + data ["pt2" ] + data ["pt3" ]
@@ -288,7 +311,6 @@ def main():
288311 parser .add_argument ("--99" , dest = "hash_99" , help = "$99$ style base64 blob" )
289312 parser .add_argument ("--key1" , help = "16-char DES key hex for CT1" )
290313 parser .add_argument ("--key2" , help = "16-char DES key hex for CT2" )
291- parser .add_argument ("--ct3" , action = "store_true" , help = "Brute-force CT3 key" )
292314 parser .add_argument ("--json" , action = "store_true" , help = "Output JSON only" )
293315 parser .add_argument ("--to99" , action = "store_true" , help = "Convert NTLMv1 hash to $99$ format" )
294316 parser .add_argument ("--hashcat" , action = "store_true" , help = "Generate hashcat format strings for ct1/ct2" )
@@ -346,7 +368,6 @@ def main():
346368 args .ntlmv1 ,
347369 key1 = args .key1 ,
348370 key2 = args .key2 ,
349- show_pt3 = args .ct3 ,
350371 json_mode = args .json
351372 )
352373
@@ -362,7 +383,6 @@ def main():
362383 args .ntlmv1 ,
363384 key1 = args .key1 ,
364385 key2 = args .key2 ,
365- show_pt3 = args .ct3 , # not required for conversion, but harmless
366386 json_mode = True # suppress prints; we'll control output below
367387 )
368388 mschapv2_str = ntlmv1_to_mschapv2 (parsed_ntlm )
@@ -402,7 +422,7 @@ def main():
402422 args .mschapv2 ,
403423 key1 = args .key1 ,
404424 key2 = args .key2 ,
405- show_pt3 = args . ct3 ,
425+ show_pt3 = True ,
406426 json_mode = args .json
407427 )
408428 except Exception as e :
0 commit comments