19
19
#include " ArduinoOTA.h"
20
20
#include " NetworkClient.h"
21
21
#include " ESPmDNS.h"
22
- #include " MD5Builder.h"
22
+ #include " SHA256Builder.h"
23
+ #include " PBKDF2_HMACBuilder.h"
23
24
#include " Update.h"
24
25
25
26
// #define OTA_DEBUG Serial
@@ -72,18 +73,20 @@ String ArduinoOTAClass::getHostname() {
72
73
73
74
ArduinoOTAClass &ArduinoOTAClass::setPassword (const char *password) {
74
75
if (_state == OTA_IDLE && password) {
75
- MD5Builder passmd5;
76
- passmd5.begin ();
77
- passmd5.add (password);
78
- passmd5.calculate ();
76
+ // Hash the password with SHA256 for storage (not plain text)
77
+ SHA256Builder pass_hash;
78
+ pass_hash.begin ();
79
+ pass_hash.add (password);
80
+ pass_hash.calculate ();
79
81
_password.clear ();
80
- _password = passmd5 .toString ();
82
+ _password = pass_hash .toString ();
81
83
}
82
84
return *this ;
83
85
}
84
86
85
87
ArduinoOTAClass &ArduinoOTAClass::setPasswordHash (const char *password) {
86
88
if (_state == OTA_IDLE && password) {
89
+ // Store the pre-hashed password directly
87
90
_password.clear ();
88
91
_password = password;
89
92
}
@@ -188,17 +191,18 @@ void ArduinoOTAClass::_onRx() {
188
191
_udp_ota.read ();
189
192
_md5 = readStringUntil (' \n ' );
190
193
_md5.trim ();
191
- if (_md5.length () != 32 ) {
194
+ if (_md5.length () != 32 ) { // MD5 produces 32 character hex string for firmware integrity
192
195
log_e (" bad md5 length" );
193
196
return ;
194
197
}
195
198
196
199
if (_password.length ()) {
197
- MD5Builder nonce_md5;
198
- nonce_md5.begin ();
199
- nonce_md5.add (String (micros ()));
200
- nonce_md5.calculate ();
201
- _nonce = nonce_md5.toString ();
200
+ // Generate a random challenge (nonce)
201
+ SHA256Builder nonce_sha256;
202
+ nonce_sha256.begin ();
203
+ nonce_sha256.add (String (micros ()) + String (random (1000000 )));
204
+ nonce_sha256.calculate ();
205
+ _nonce = nonce_sha256.toString ();
202
206
203
207
_udp_ota.beginPacket (_udp_ota.remoteIP (), _udp_ota.remotePort ());
204
208
_udp_ota.printf (" AUTH %s" , _nonce.c_str ());
@@ -222,20 +226,37 @@ void ArduinoOTAClass::_onRx() {
222
226
_udp_ota.read ();
223
227
String cnonce = readStringUntil (' ' );
224
228
String response = readStringUntil (' \n ' );
225
- if (cnonce.length () != 32 || response.length () != 32 ) {
229
+ if (cnonce.length () != 64 || response.length () != 64 ) { // SHA256 produces 64 character hex string
226
230
log_e (" auth param fail" );
227
231
_state = OTA_IDLE;
228
232
return ;
229
233
}
230
234
231
- String challenge = _password + " :" + String (_nonce) + " :" + cnonce;
232
- MD5Builder _challengemd5;
233
- _challengemd5.begin ();
234
- _challengemd5.add (challenge);
235
- _challengemd5.calculate ();
236
- String result = _challengemd5.toString ();
237
-
238
- if (result.equals (response)) {
235
+ // Verify the challenge/response using PBKDF2-HMAC-SHA256
236
+ // The client should derive a key using PBKDF2-HMAC-SHA256 with:
237
+ // - password: the OTA password (or its hash if using setPasswordHash)
238
+ // - salt: nonce + cnonce
239
+ // - iterations: 10000 (or configurable)
240
+ // Then hash the challenge with the derived key
241
+
242
+ String salt = _nonce + " :" + cnonce;
243
+ SHA256Builder sha256;
244
+ // Use the stored password hash for PBKDF2 derivation
245
+ PBKDF2_HMACBuilder pbkdf2 (&sha256, _password, salt, 10000 );
246
+
247
+ pbkdf2.begin ();
248
+ pbkdf2.calculate ();
249
+ String derived_key = pbkdf2.toString ();
250
+
251
+ // Create challenge: derived_key + nonce + cnonce
252
+ String challenge = derived_key + " :" + _nonce + " :" + cnonce;
253
+ SHA256Builder challenge_sha256;
254
+ challenge_sha256.begin ();
255
+ challenge_sha256.add (challenge);
256
+ challenge_sha256.calculate ();
257
+ String expected_response = challenge_sha256.toString ();
258
+
259
+ if (expected_response.equals (response)) {
239
260
_udp_ota.beginPacket (_udp_ota.remoteIP (), _udp_ota.remotePort ());
240
261
_udp_ota.print (" OK" );
241
262
_udp_ota.endPacket ();
@@ -266,7 +287,8 @@ void ArduinoOTAClass::_runUpdate() {
266
287
_state = OTA_IDLE;
267
288
return ;
268
289
}
269
- Update.setMD5 (_md5.c_str ());
290
+
291
+ Update.setMD5 (_md5.c_str ()); // Note: Update library still uses MD5 for firmware integrity, this is separate from authentication
270
292
271
293
if (_start_callback) {
272
294
_start_callback ();
0 commit comments