1919#include " ArduinoOTA.h"
2020#include " NetworkClient.h"
2121#include " ESPmDNS.h"
22- #include " MD5Builder.h"
22+ #include " SHA2Builder.h"
23+ #include " PBKDF2_HMACBuilder.h"
2324#include " Update.h"
2425
2526// #define OTA_DEBUG Serial
@@ -72,18 +73,20 @@ String ArduinoOTAClass::getHostname() {
7273
7374ArduinoOTAClass &ArduinoOTAClass::setPassword (const char *password) {
7475 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 ();
7981 _password.clear ();
80- _password = passmd5 .toString ();
82+ _password = pass_hash .toString ();
8183 }
8284 return *this ;
8385}
8486
8587ArduinoOTAClass &ArduinoOTAClass::setPasswordHash (const char *password) {
8688 if (_state == OTA_IDLE && password) {
89+ // Store the pre-hashed password directly
8790 _password.clear ();
8891 _password = password;
8992 }
@@ -188,17 +191,18 @@ void ArduinoOTAClass::_onRx() {
188191 _udp_ota.read ();
189192 _md5 = readStringUntil (' \n ' );
190193 _md5.trim ();
191- if (_md5.length () != 32 ) {
194+ if (_md5.length () != 32 ) { // MD5 produces 32 character hex string for firmware integrity
192195 log_e (" bad md5 length" );
193196 return ;
194197 }
195198
196199 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 ();
202206
203207 _udp_ota.beginPacket (_udp_ota.remoteIP (), _udp_ota.remotePort ());
204208 _udp_ota.printf (" AUTH %s" , _nonce.c_str ());
@@ -222,20 +226,37 @@ void ArduinoOTAClass::_onRx() {
222226 _udp_ota.read ();
223227 String cnonce = readStringUntil (' ' );
224228 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
226230 log_e (" auth param fail" );
227231 _state = OTA_IDLE;
228232 return ;
229233 }
230234
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)) {
239260 _udp_ota.beginPacket (_udp_ota.remoteIP (), _udp_ota.remotePort ());
240261 _udp_ota.print (" OK" );
241262 _udp_ota.endPacket ();
@@ -266,7 +287,8 @@ void ArduinoOTAClass::_runUpdate() {
266287 _state = OTA_IDLE;
267288 return ;
268289 }
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
270292
271293 if (_start_callback) {
272294 _start_callback ();
0 commit comments