@@ -81,7 +81,7 @@ def update_progress(progress):
8181 sys .stderr .flush ()
8282
8383
84- def serve (remote_addr , local_addr , remote_port , local_port , password , filename , command = FLASH ): # noqa: C901
84+ def serve (remote_addr , local_addr , remote_port , local_port , password , md5_target , filename , command = FLASH ): # noqa: C901
8585 # Create a TCP/IP socket
8686 sock = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
8787 server_address = (local_addr , local_port )
@@ -119,7 +119,10 @@ def serve(remote_addr, local_addr, remote_port, local_port, password, filename,
119119 return 1
120120 sock2 .settimeout (TIMEOUT )
121121 try :
122- data = sock2 .recv (69 ).decode () # "AUTH " + 64-char SHA256 nonce
122+ if md5_target :
123+ data = sock2 .recv (37 ).decode () # "AUTH " + 32-char MD5 nonce
124+ else :
125+ data = sock2 .recv (69 ).decode () # "AUTH " + 64-char SHA256 nonce
123126 break
124127 except : # noqa: E722
125128 sys .stderr .write ("." )
@@ -133,32 +136,47 @@ def serve(remote_addr, local_addr, remote_port, local_port, password, filename,
133136 if data != "OK" :
134137 if data .startswith ("AUTH" ):
135138 nonce = data .split ()[1 ]
136-
137- # Generate client nonce (cnonce)
138139 cnonce_text = "%s%u%s%s" % (filename , content_size , file_md5 , remote_addr )
139- cnonce = hashlib .sha256 (cnonce_text .encode ()).hexdigest ()
140140
141- # PBKDF2-HMAC-SHA256 challenge/response protocol
142- # The ESP32 stores the password as SHA256 hash, so we need to hash the password first
143- # 1. Hash the password with SHA256 (to match ESP32 storage)
144- password_hash = hashlib .sha256 (password .encode ()).hexdigest ()
141+ if md5_target :
142+ # Generate client nonce (cnonce)
143+ cnonce = hashlib .md5 (cnonce_text .encode ()).hexdigest ()
144+
145+ # MD5 challenge/response protocol (insecure, use only for compatibility with old firmwares)
146+ # 1. Hash the password with MD5 (to match ESP32 storage)
147+ password_hash = hashlib .md5 (password .encode ()).hexdigest ()
148+
149+ # 2. Create challenge response
150+ challenge = "%s:%s:%s" % (password_hash , nonce , cnonce )
151+ response = hashlib .md5 (challenge .encode ()).hexdigest ()
152+ else :
153+ # Generate client nonce (cnonce)
154+ cnonce = hashlib .sha256 (cnonce_text .encode ()).hexdigest ()
155+
156+ # PBKDF2-HMAC-SHA256 challenge/response protocol
157+ # The ESP32 stores the password as SHA256 hash, so we need to hash the password first
158+ # 1. Hash the password with SHA256 (to match ESP32 storage)
159+ password_hash = hashlib .sha256 (password .encode ()).hexdigest ()
145160
146- # 2. Derive key using PBKDF2-HMAC-SHA256 with the password hash
147- salt = nonce + ":" + cnonce
148- derived_key = hashlib .pbkdf2_hmac ("sha256" , password_hash .encode (), salt .encode (), 10000 )
149- derived_key_hex = derived_key .hex ()
161+ # 2. Derive key using PBKDF2-HMAC-SHA256 with the password hash
162+ salt = nonce + ":" + cnonce
163+ derived_key = hashlib .pbkdf2_hmac ("sha256" , password_hash .encode (), salt .encode (), 10000 )
164+ derived_key_hex = derived_key .hex ()
150165
151- # 3. Create challenge response
152- challenge = derived_key_hex + ":" + nonce + ":" + cnonce
153- response = hashlib .sha256 (challenge .encode ()).hexdigest ()
166+ # 3. Create challenge response
167+ challenge = derived_key_hex + ":" + nonce + ":" + cnonce
168+ response = hashlib .sha256 (challenge .encode ()).hexdigest ()
154169
155170 sys .stderr .write ("Authenticating..." )
156171 sys .stderr .flush ()
157172 message = "%d %s %s\n " % (AUTH , cnonce , response )
158173 sock2 .sendto (message .encode (), remote_address )
159174 sock2 .settimeout (10 )
160175 try :
161- data = sock2 .recv (64 ).decode () # SHA256 produces 64 character response
176+ if md5_target :
177+ data = sock2 .recv (32 ).decode () # MD5 produces 32 character response
178+ else :
179+ data = sock2 .recv (64 ).decode () # SHA256 produces 64 character response
162180 except : # noqa: E722
163181 sys .stderr .write ("FAIL\n " )
164182 logging .error ("No Answer to our Authentication" )
@@ -269,6 +287,14 @@ def parse_args(unparsed_args):
269287
270288 # authentication
271289 parser .add_argument ("-a" , "--auth" , dest = "auth" , help = "Set authentication password." , action = "store" , default = "" )
290+ parser .add_argument (
291+ "-m" ,
292+ "--md5-target" ,
293+ dest = "md5_target" ,
294+ help = "Target device is using MD5 checksum. This is insecure, use only for compatibility with old firmwares." ,
295+ action = "store_true" ,
296+ default = False ,
297+ )
272298
273299 # image
274300 parser .add_argument ("-f" , "--file" , dest = "image" , help = "Image file." , metavar = "FILE" , default = None )
@@ -335,7 +361,14 @@ def main(args):
335361 command = SPIFFS
336362
337363 return serve (
338- options .esp_ip , options .host_ip , options .esp_port , options .host_port , options .auth , options .image , command
364+ options .esp_ip ,
365+ options .host_ip ,
366+ options .esp_port ,
367+ options .host_port ,
368+ options .auth ,
369+ options .md5_target ,
370+ options .image ,
371+ command
339372 )
340373
341374
0 commit comments