@@ -81,7 +81,7 @@ def update_progress(progress):
81
81
sys .stderr .flush ()
82
82
83
83
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
85
85
# Create a TCP/IP socket
86
86
sock = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
87
87
server_address = (local_addr , local_port )
@@ -119,7 +119,10 @@ def serve(remote_addr, local_addr, remote_port, local_port, password, filename,
119
119
return 1
120
120
sock2 .settimeout (TIMEOUT )
121
121
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
123
126
break
124
127
except : # noqa: E722
125
128
sys .stderr .write ("." )
@@ -133,32 +136,47 @@ def serve(remote_addr, local_addr, remote_port, local_port, password, filename,
133
136
if data != "OK" :
134
137
if data .startswith ("AUTH" ):
135
138
nonce = data .split ()[1 ]
136
-
137
- # Generate client nonce (cnonce)
138
139
cnonce_text = "%s%u%s%s" % (filename , content_size , file_md5 , remote_addr )
139
- cnonce = hashlib .sha256 (cnonce_text .encode ()).hexdigest ()
140
140
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 ()
145
160
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 ()
150
165
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 ()
154
169
155
170
sys .stderr .write ("Authenticating..." )
156
171
sys .stderr .flush ()
157
172
message = "%d %s %s\n " % (AUTH , cnonce , response )
158
173
sock2 .sendto (message .encode (), remote_address )
159
174
sock2 .settimeout (10 )
160
175
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
162
180
except : # noqa: E722
163
181
sys .stderr .write ("FAIL\n " )
164
182
logging .error ("No Answer to our Authentication" )
@@ -269,6 +287,14 @@ def parse_args(unparsed_args):
269
287
270
288
# authentication
271
289
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
+ )
272
298
273
299
# image
274
300
parser .add_argument ("-f" , "--file" , dest = "image" , help = "Image file." , metavar = "FILE" , default = None )
@@ -335,7 +361,14 @@ def main(args):
335
361
command = SPIFFS
336
362
337
363
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
339
372
)
340
373
341
374
0 commit comments