diff --git a/src/Makefile b/src/Makefile
index 86b626e99f7df0678da0f39e21fc883b07fbc1b5..9ed290e4348026a9c2d7efd18115911226a6f97d 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -18,6 +18,7 @@ endif
 
 OBJS = \
 	utils/debug.o \
+	utils/libcrypto.o \
 	utils/liblist.o \
 	utils/utils.o \
 	utils/1905_ubus.o \
@@ -38,6 +39,7 @@ AGENT_OBJS = \
 	agent_tlv.o \
 	agent_cmdu.o \
 	assoc_ctrl.o \
+	autoconfig.o \
 	backhaul.o \
 	backhaul_blacklist.o \
 	config.o \
diff --git a/src/agent.c b/src/agent.c
index 7568bf8da17f64d74d6ae9f147713a8262160308..5c93d7b785dbc57ea6ef9ee70ebcce2501830ed7 100644
--- a/src/agent.c
+++ b/src/agent.c
@@ -48,6 +48,7 @@
 #include "agent_ubus.h"
 #include "agent_ubus_dbg.h"
 #include "assoc_ctrl.h"
+#include "autoconfig.h"
 #include "backhaul.h"
 #include "backhaul_blacklist.h"
 #include "config.h"
@@ -5774,6 +5775,7 @@ struct wifi_radio_element *agent_add_radio(struct agent *a, char *name)
 	timer_init(&re->bk.steer_timeout, bsta_steer_cb);
 	timer_init(&re->unassoc_sta_meas_timer,
 				agent_unassoc_sta_meas_timer_cb);
+
 	return re;
 }
 
@@ -5784,6 +5786,7 @@ static int init_wifi_radios(struct agent *a)
 	int ret = 0;
 
 	list_for_each_entry(rcfg, &a->cfg.radiolist, list) {
+		uint8_t zero_block[SHA256_LENGTH] = {0};
 		wifi_object_t r_wobj = WIFI_OBJECT_INVALID;
 		struct wifi_radio_element *re;
 		char r_objname[32] = {0};
@@ -5803,6 +5806,11 @@ static int init_wifi_radios(struct agent *a)
 		dbg("%s: getting radio:%s status\n", __func__, re->name);
 		ret = ubus_call_object(a, r_wobj, "status", parse_radio, re);
 
+		if (!ret && !memcmp(zero_block, re->autconfig.sha256, SHA256_LENGTH)) {
+			if (autoconfig_radio_hash_from_file(re->macaddr, re->autconfig.sha256))
+				dbg("%s: Autoconfig SHA256 for radio %s is unset\n", __func__, re->name);
+		}
+
 		/* Get fresh opclass preferences after scan */
 		wifi_radio_update_opclass_preferences(a, re, 1);
 		agent_set_post_scan_action_pref(a, re, true);
@@ -8108,6 +8116,11 @@ void run_agent(void *opts)
 
 	agent_get_system_info(w);
 
+#if (EASYMESH_VERSION >= 6)
+	autoconfig_ap_mld_hash_from_file(w->ap_mld_cfg_sha256);
+	autoconfig_bsta_mld_hash_from_file(w->bsta_mld_cfg_sha256);
+#endif
+
 	/* w->cfg.enabled */
 	agent_publish_object(w, MAPAGENT_OBJECT);
 	agent_publish_dbg_object(w, MAPAGENT_DBG_OBJECT);
diff --git a/src/agent.h b/src/agent.h
index 3c70685d4cb00c28edf477c6fe7fa14549e01af2..c66ac62b84d7b111ac9d1ca190c78db4e80e36fb 100644
--- a/src/agent.h
+++ b/src/agent.h
@@ -26,6 +26,8 @@
 
 #include "utils/allmac.h"
 #include "utils/debug.h"
+#include "utils/libcrypto.h"
+#include "autoconfig.h"
 #include "config.h"
 #include "nl.h"
 #include "wifi.h"
@@ -606,18 +608,6 @@ struct wsc_ext {
 	struct wsc_vendor_ie ven_ies[VEN_IES_MAX];
 };
 
-struct wsc_data {
-	uint8_t *m1_frame;
-	uint16_t m1_size;
-	struct wsc_key *key;
-};
-
-#define HEARTBEAT_AUTOCFG_INTERVAL 70
-enum autocfg_state {
-	AUTOCFG_HEARTBEAT,
-	AUTOCFG_ACTIVE
-};
-
 #define SCAN_MAX_CHANNEL 16
 struct wifi_scan_request_opclass {
 	uint8_t classid;
@@ -686,7 +676,6 @@ struct wifi7_radio_capabilities {
 };
 #endif
 
-#define SHA256_LENGTH 32
 struct wifi_radio_element {
 	char name[16];
 	uint8_t macaddr[6];
@@ -754,7 +743,6 @@ struct wifi_radio_element {
 	uint16_t mid;
 	uint16_t wsc_mid;
 	uint16_t renew_mid; /* debug purposes */
-	uint8_t autconfig_wsc_sha256[SHA256_LENGTH];
 
 	/* radio scan state */
 	enum wifi_scan_state scan_state;
diff --git a/src/agent_map.c b/src/agent_map.c
index 8d0ebc228242bf3ac768ba5453aefdc0dc09cffd..4886ca4bf62f6d9d6113eb81ff83fc42c3b20923 100644
--- a/src/agent_map.c
+++ b/src/agent_map.c
@@ -48,7 +48,7 @@
 #include "agent_tlv.h"
 #include "agent_ubus.h"
 #include "assoc_ctrl.h"
-#include "utils/allmac.h"
+#include "autoconfig.h"
 #include "backhaul.h"
 #include "backhaul_blacklist.h"
 #include "config.h"
@@ -62,9 +62,13 @@
 #include "extension.h"
 #include "timer_impl.h"
 #include "unasta.h"
-#include "utils/1905_ubus.h"
+
+#include "utils/allmac.h"
 #include "utils/debug.h"
+#include "utils/1905_ubus.h"
+#include "utils/libcrypto.h"
 #include "utils/utils.h"
+
 #include "wifi.h"
 #include "wifi_opclass.h"
 #include "wifi_scanresults.h"
@@ -2041,146 +2045,6 @@ bool agent_is_ts_enabled(struct agent *a)
 	return true;
 }
 
-static bool has_ap_autoconfig_wsc_changed(struct agent *a,
-					  struct tlv *tlvs[][TLV_MAXNUM],
-					  size_t tlvs_size, uint8_t *sha256_out)
-{
-	enum {
-		AP_RADIO_ID = 0,
-		WSC = 1,
-		DEFAULT_8021Q_SETTINGS = 2,
-		TRAFFIC_SEPARATION_POLICY = 3,
-#if (EASYMESH_VERSION >= 6)
-		AP_MLD_CONFIG = 4,
-		BACKHAUL_STA_MLD_CONFIG = 5,
-		MAX_TLV_TYPES = 6
-#else
-		MAX_TLV_TYPES = 4
-#endif
-	};
-
-	uint8_t new_sha256[SHA256_LENGTH];
-	const struct wifi_radio_element *radio;
-	uint8_t bssid[6];
-	EVP_MD_CTX *ctx;
-	int i;
-	bool ret = true;
-
-	if (tlvs_size != MAX_TLV_TYPES) {
-		err("%s: Unsupported version of CMDU.\n", __func__);
-		return true;
-	}
-
-	memcpy(bssid, tlvs[AP_RADIO_ID][0]->data, 6);
-	radio = agent_get_radio(a, bssid);
-	if (!radio) {
-		err("%s: Unknown radio.\n", __func__);
-		return true;
-	}
-
-	/* Calculate SHA-256 hash from all TLVs data */
-	ctx = EVP_MD_CTX_new();
-	if (!ctx)
-		return true;
-
-	if (!EVP_DigestInit_ex(ctx, EVP_sha256(), NULL))
-		goto error_cleanup;
-
-	if (!EVP_DigestUpdate(ctx, tlvs[AP_RADIO_ID][0]->data, tlv_length(tlvs[AP_RADIO_ID][0])))
-		goto error_cleanup;
-
-	/* Use decrypted data of WSC TLVs */
-	i = 0;
-	while (i < TLV_MAXNUM && tlvs[WSC][i]) {
-		struct wps_credential wps_out = { 0 };
-		uint8_t *ext_out = NULL;
-		uint16_t ext_len = 0;
-		int ret = 0;
-		uint16_t m2_len = tlv_length(tlvs[WSC][i]);
-		/* It's workaround as wsc_process_m2 modifies m2 buffer */
-		uint8_t *m2_tmp = malloc(m2_len);
-
-		if (!m2_tmp)
-			goto error_cleanup;
-
-		memcpy(m2_tmp, tlvs[WSC][i]->data, m2_len);
-
-		ret = wsc_process_m2(radio->autconfig.m1_frame,
-				     radio->autconfig.m1_size,
-				     radio->autconfig.key, m2_tmp, m2_len,
-				     &wps_out, &ext_out, &ext_len);
-
-		free(m2_tmp);
-
-		if (ret) {
-			ret = false;
-			goto error_cleanup;
-		}
-
-		if (!EVP_DigestUpdate(ctx, &wps_out, sizeof(wps_out))) {
-			free(ext_out);
-			goto error_cleanup;
-		}
-
-		if (ext_out && !EVP_DigestUpdate(ctx, ext_out, ext_len)) {
-			free(ext_out);
-			goto error_cleanup;
-		}
-
-		free(ext_out);
-
-		++i;
-	}
-
-	if (tlvs[DEFAULT_8021Q_SETTINGS][0] &&
-	    !EVP_DigestUpdate(ctx, tlvs[DEFAULT_8021Q_SETTINGS][0]->data,
-			      tlv_length(tlvs[DEFAULT_8021Q_SETTINGS][0]))) {
-		goto error_cleanup;
-	}
-
-	if (tlvs[TRAFFIC_SEPARATION_POLICY][0] &&
-	    !EVP_DigestUpdate(ctx, tlvs[TRAFFIC_SEPARATION_POLICY][0]->data,
-			      tlv_length(tlvs[TRAFFIC_SEPARATION_POLICY][0]))) {
-		goto error_cleanup;
-	}
-
-#if (EASYMESH_VERSION >= 6)
-	if (tlvs[AP_MLD_CONFIG][0] &&
-	    !EVP_DigestUpdate(ctx, tlvs[AP_MLD_CONFIG][0]->data,
-			      tlv_length(tlvs[AP_MLD_CONFIG][0]))) {
-		goto error_cleanup;
-	}
-
-	if (tlvs[BACKHAUL_STA_MLD_CONFIG][0] &&
-	    !EVP_DigestUpdate(ctx, tlvs[BACKHAUL_STA_MLD_CONFIG][0]->data,
-			      tlv_length(tlvs[BACKHAUL_STA_MLD_CONFIG][0]))) {
-		goto error_cleanup;
-	}
-#endif
-
-	if (!EVP_DigestFinal_ex(ctx, new_sha256, NULL))
-		goto error_cleanup;
-
-	EVP_MD_CTX_free(ctx);
-
-	dbg("%s: current SHA256: %02x%02x%02x%02x%02x%02x...\n", __func__,
-	    radio->autconfig_wsc_sha256[0], radio->autconfig_wsc_sha256[1],
-	    radio->autconfig_wsc_sha256[2], radio->autconfig_wsc_sha256[3],
-	    radio->autconfig_wsc_sha256[4], radio->autconfig_wsc_sha256[5]);
-
-	dbg("%s: new SHA256:     %02x%02x%02x%02x%02x%02x...\n", __func__,
-	    new_sha256[0], new_sha256[1], new_sha256[2], new_sha256[3],
-	    new_sha256[4], new_sha256[5]);
-
-	memcpy(sha256_out, new_sha256, SHA256_LENGTH);
-
-	return memcmp(radio->autconfig_wsc_sha256, new_sha256, SHA256_LENGTH);
-
-error_cleanup:
-	EVP_MD_CTX_free(ctx);
-	return ret;
-}
-
 #define RELOAD_TIMEOUT 5
 #if (EASYMESH_VERSION >= 6)
 static void mlo_update_id_in_configs(char *ifname, uint8_t mld_id)
@@ -2519,37 +2383,6 @@ static int mlo_process_bsta_mld_config(struct agent *a, uint8_t *tlv_data)
 }
 #endif
 
-#if (EASYMESH_VERSION >= 6)
-static bool update_tlv_hash(struct agent *a, struct tlv *tv,
-			    uint8_t *sha256_out)
-{
-	bool ret = false;
-	uint8_t new_sha256[SHA256_LENGTH] = {0};
-	EVP_MD_CTX *ctx;
-
-	ctx = EVP_MD_CTX_new();
-	if (!ctx)
-		return true;
-
-	if (!EVP_DigestInit_ex(ctx, EVP_sha256(), NULL))
-		goto error_cleanup;
-
-	if (!EVP_DigestUpdate(ctx, tv->data, tlv_length(tv)))
-		goto error_cleanup;
-
-	if (!EVP_DigestFinal_ex(ctx, new_sha256, NULL))
-		goto error_cleanup;
-
-	if (memcmp(sha256_out, new_sha256, SHA256_LENGTH)) {
-		memcpy(sha256_out, new_sha256, SHA256_LENGTH);
-		ret = true;
-	}
-
-error_cleanup:
-	EVP_MD_CTX_free(ctx);
-	return ret;
-}
-#endif
 int handle_ap_autoconfig_wsc(void *agent, struct cmdu_buff *rx_cmdu,
 			     struct node *n)
 {
@@ -2563,6 +2396,7 @@ int handle_ap_autoconfig_wsc(void *agent, struct cmdu_buff *rx_cmdu,
 #ifdef CHECK_PARTIAL_WIFI_RELOAD
 	bool full_reconf_required = false;
 #endif /* CHECK_PARTIAL_WIFI_RELOAD */
+	int diff = DIFF_NONE;
 
 	trace("%s: --->\n", __func__);
 
@@ -2593,8 +2427,11 @@ int handle_ap_autoconfig_wsc(void *agent, struct cmdu_buff *rx_cmdu,
 
 	dbg("|%s:%d| found radio = %s\n", __func__, __LINE__, radio->name);
 
-	if (!has_ap_autoconfig_wsc_changed(a, tv,
-				AP_AUTOCONFIGURATION_WSC_M2_NUM_OF_TLV_TYPES, new_sha256)) {
+	diff = autoconfig_wsc_m2_diff(radio, tv,
+				AP_AUTOCONFIGURATION_WSC_M2_NUM_OF_TLV_TYPES,
+				new_sha256);
+
+	if (diff == DIFF_NONE || diff == DIFF_ERR) {
 		info("%s: The same config received, skip radio configuration for: " MACFMT "\n",
 		     __func__, MAC2STR(radio->macaddr));
 
@@ -2650,7 +2487,7 @@ int handle_ap_autoconfig_wsc(void *agent, struct cmdu_buff *rx_cmdu,
 			 */
 			agent_autoconfig_event(a, radio->name, "teardown",
 					"M2 process failure");
-			memset(radio->autconfig_wsc_sha256, 0, SHA256_LENGTH);
+			autoconfig_clean_wsc_hash(&radio->autconfig);
 			return -1;
 		}
 
@@ -2668,7 +2505,7 @@ int handle_ap_autoconfig_wsc(void *agent, struct cmdu_buff *rx_cmdu,
 					"IOPSYS extension process error");
 			if (ext)
 				free(ext);
-			memset(radio->autconfig_wsc_sha256, 0, SHA256_LENGTH);
+			autoconfig_clean_wsc_hash(&radio->autconfig);
 			return -1;
 		}
 
@@ -2797,10 +2634,11 @@ int handle_ap_autoconfig_wsc(void *agent, struct cmdu_buff *rx_cmdu,
 	if (tv[AP_AUTOCONFIGURATION_WSC_M2_AP_MLD_CONFIG_IDX][0]) {
 		mlo_process_ap_mld_config(a,
 				tv[AP_AUTOCONFIGURATION_WSC_M2_AP_MLD_CONFIG_IDX][0]->data);
-		update_tlv_hash(a, tv[AP_AUTOCONFIGURATION_WSC_M2_AP_MLD_CONFIG_IDX][0],
-				a->ap_mld_cfg_sha256);
 		a->reconfig_reason |= AGENT_RECONFIG_REASON_MLD_ID;
 		timer_set(&a->reload_scheduler, RELOAD_TIMEOUT * 1000);
+
+		if (diff & DIFF_AP_MLD)
+			autoconfig_ap_mld_hash_to_file(a->ap_mld_cfg_sha256);
 	}
 
 	if (tv[AP_AUTOCONFIGURATION_WSC_M2_BACKHAUL_STA_MLD_CONFIG_IDX][0]) {
@@ -2810,15 +2648,18 @@ int handle_ap_autoconfig_wsc(void *agent, struct cmdu_buff *rx_cmdu,
 				tv[AP_AUTOCONFIGURATION_WSC_M2_BACKHAUL_STA_MLD_CONFIG_IDX][0]->data);
 		if (ret)
 			wifi_teardown_map_bsta_mld(a, radio->band);
-		update_tlv_hash(a, tv[AP_AUTOCONFIGURATION_WSC_M2_BACKHAUL_STA_MLD_CONFIG_IDX][0],
-				a->bsta_mld_cfg_sha256);
 		a->reconfig_reason |= AGENT_RECONFIG_REASON_MLD_ID;
 		timer_set(&a->reload_scheduler, RELOAD_TIMEOUT * 1000);
+
+		if (diff & DIFF_BSTA_MLD)
+			autoconfig_bsta_mld_hash_to_file(a->bsta_mld_cfg_sha256);
 	} else {
 		dbg("%s: Missing BSTA MLD config TLV, teardown\n",
 		    __func__);
 		wifi_teardown_map_bsta_mld(a, radio->band);
 	}
+
+
 #endif
 
 	dbg("|%s:%d| radio (%s) was configured! Apply heartbeat for this radio\n",
@@ -2827,7 +2668,9 @@ int handle_ap_autoconfig_wsc(void *agent, struct cmdu_buff *rx_cmdu,
 	agent_autoconfig_event(a, radio->name, "success", "completed");
 
 	/* Save successfully processed autoconfig wsc SHA256 */
-	memcpy(radio->autconfig_wsc_sha256, new_sha256, SHA256_LENGTH);
+	autoconfig_update_wsc_hash(&radio->autconfig, new_sha256);
+	autoconfig_radio_hash_to_file(radio->macaddr, new_sha256);
+
 	dbg("%s: Applied autoconfig SHA256: %02x%02x%02x%02x%02x%02x...\n", __func__,
 	    new_sha256[0], new_sha256[1], new_sha256[2], new_sha256[3], new_sha256[4], new_sha256[5]);
 
@@ -6706,7 +6549,8 @@ int handle_ap_mld_config_request(void *agent, struct cmdu_buff *cmdu, struct nod
 	/* Send the 1905 ack message to Controller */
 	send_1905_acknowledge(a, cmdu->origin, cmdu_get_mid(cmdu), NULL, 0);
 
-	if (update_tlv_hash(a, tv[AP_MLD_CONFIG_REQUEST_AP_MLD_CONFIG_IDX][0],
+	if (update_sha256_hash(tv[AP_MLD_CONFIG_REQUEST_AP_MLD_CONFIG_IDX][0]->data,
+				tlv_length(tv[AP_MLD_CONFIG_REQUEST_AP_MLD_CONFIG_IDX][0]),
 				a->ap_mld_cfg_sha256)) {
 		/* Proces ReConfiguration */
 		mlo_process_ap_mld_config(a, tv[AP_MLD_CONFIG_REQUEST_AP_MLD_CONFIG_IDX][0]->data);
@@ -6772,7 +6616,8 @@ int handle_bsta_mld_config_request(void *agent, struct cmdu_buff *cmdu, struct n
 	send_1905_acknowledge(a, cmdu->origin, cmdu_get_mid(cmdu), NULL, 0);
 
 	/* Process TLV only if changed */
-	if (update_tlv_hash(a, tv[BSTA_MLD_CONFIG_REQUEST_BSTA_MLD_CONFIG_IDX][0],
+	if (update_sha256_hash(tv[BSTA_MLD_CONFIG_REQUEST_BSTA_MLD_CONFIG_IDX][0]->data,
+				tlv_length(tv[BSTA_MLD_CONFIG_REQUEST_BSTA_MLD_CONFIG_IDX][0]),
 				a->bsta_mld_cfg_sha256)) {
 		int ret = 0;
 
diff --git a/src/autoconfig.c b/src/autoconfig.c
new file mode 100644
index 0000000000000000000000000000000000000000..38a422913a2c2f059a9821f9b98e8e563b76cda3
--- /dev/null
+++ b/src/autoconfig.c
@@ -0,0 +1,572 @@
+/*
+ * autoconfig.c - implements autoconfig logic and util functions
+ *
+ * Copyright (C) 2025 IOPSYS Software Solutions AB. All rights reserved.
+ *
+ */
+#include <json-c/json.h>
+#include <libubox/blobmsg_json.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <1905_tlvs.h>
+#include <i1905_wsc.h>
+#include <cmdu.h>
+#include <openssl/evp.h>
+
+#include "agent.h"
+#include "autoconfig.h"
+
+#include "utils/debug.h"
+
+void autoconfig_clean_wsc_hash(struct wsc_data *data)
+{
+	memset(data->sha256, 0, SHA256_LENGTH);
+}
+
+void autoconfig_update_wsc_hash(struct wsc_data *data, uint8_t *new_sha256)
+{
+	memcpy(&data->sha256, new_sha256, SHA256_LENGTH);
+}
+
+int autoconfig_wsc_m2_diff(struct wifi_radio_element *re,
+			  struct tlv *tlvs[][TLV_MAXNUM],
+			  size_t tlvs_size,
+			  uint8_t *wsc_m2_sha256)
+{
+	enum {
+		AP_RADIO_ID = 0,
+		WSC = 1,
+		DEFAULT_8021Q_SETTINGS = 2,
+		TRAFFIC_SEPARATION_POLICY = 3,
+#if (EASYMESH_VERSION >= 6)
+		AP_MLD_CONFIG = 4,
+		BACKHAUL_STA_MLD_CONFIG = 5,
+		MAX_TLV_TYPES = 6
+#else
+		MAX_TLV_TYPES = 4
+#endif
+	};
+
+	uint8_t new_sha256[SHA256_LENGTH];
+	uint8_t bssid[6];
+	EVP_MD_CTX *ctx;
+	int i;
+	int diff = DIFF_NONE;
+	struct wsc_data *data = &re->autconfig;
+
+	if (tlvs_size != MAX_TLV_TYPES) {
+		agnt_dbg(LOG_APCFG, "%s: Unsupported version of CMDU.\n", __func__);
+		return DIFF_ERR;
+	}
+
+	memcpy(bssid, tlvs[AP_RADIO_ID][0]->data, 6);
+
+	/* Calculate SHA-256 hash from all TLVs data */
+	ctx = EVP_MD_CTX_new();
+	if (!ctx)
+		return DIFF_ERR;
+
+	if (!EVP_DigestInit_ex(ctx, EVP_sha256(), NULL))
+		goto error_cleanup;
+
+	if (!EVP_DigestUpdate(ctx, tlvs[AP_RADIO_ID][0]->data, tlv_length(tlvs[AP_RADIO_ID][0])))
+		goto error_cleanup;
+
+	/* Use decrypted data of WSC TLVs */
+	i = 0;
+	while (i < TLV_MAXNUM && tlvs[WSC][i]) {
+		struct wps_credential wps_out = { 0 };
+		uint8_t *ext_out = NULL;
+		uint16_t ext_len = 0;
+		uint16_t m2_len = tlv_length(tlvs[WSC][i]);
+		/* It's workaround as wsc_process_m2 modifies m2 buffer */
+		uint8_t *m2_tmp = malloc(m2_len);
+		int ret;
+
+		if (!m2_tmp)
+			goto error_cleanup;
+
+		memcpy(m2_tmp, tlvs[WSC][i]->data, m2_len);
+
+		ret = wsc_process_m2(data->m1_frame,
+				     data->m1_size,
+				     data->key, m2_tmp, m2_len,
+				     &wps_out, &ext_out, &ext_len);
+
+		free(m2_tmp);
+
+		if (ret)
+			goto error_cleanup;
+
+		if (!EVP_DigestUpdate(ctx, &wps_out, sizeof(wps_out))) {
+			free(ext_out);
+			goto error_cleanup;
+		}
+
+		if (ext_out && !EVP_DigestUpdate(ctx, ext_out, ext_len)) {
+			free(ext_out);
+			goto error_cleanup;
+		}
+
+		free(ext_out);
+
+		++i;
+	}
+
+	if (tlvs[DEFAULT_8021Q_SETTINGS][0] &&
+	    !EVP_DigestUpdate(ctx, tlvs[DEFAULT_8021Q_SETTINGS][0]->data,
+			      tlv_length(tlvs[DEFAULT_8021Q_SETTINGS][0]))) {
+		goto error_cleanup;
+	}
+
+	if (tlvs[TRAFFIC_SEPARATION_POLICY][0] &&
+	    !EVP_DigestUpdate(ctx, tlvs[TRAFFIC_SEPARATION_POLICY][0]->data,
+			      tlv_length(tlvs[TRAFFIC_SEPARATION_POLICY][0]))) {
+		goto error_cleanup;
+	}
+
+	if (!EVP_DigestFinal_ex(ctx, new_sha256, NULL))
+		goto error_cleanup;
+
+	EVP_MD_CTX_free(ctx);
+
+	agnt_dbg(LOG_APCFG, "%s: current WSC M2 SHA256: %02x%02x%02x%02x%02x%02x...\n", __func__,
+		 data->sha256[0], data->sha256[1],
+		 data->sha256[2], data->sha256[3],
+		 data->sha256[4], data->sha256[5]);
+
+	agnt_dbg(LOG_APCFG, "%s: new WSC M2 SHA256:     %02x%02x%02x%02x%02x%02x...\n", __func__,
+		 new_sha256[0], new_sha256[1],
+		 new_sha256[2], new_sha256[3],
+		 new_sha256[4], new_sha256[5]);
+
+	memcpy(wsc_m2_sha256, new_sha256, SHA256_LENGTH);
+	if (memcmp(data->sha256, new_sha256, SHA256_LENGTH))
+		diff |= DIFF_WSC_M2;
+
+#if (EASYMESH_VERSION >= 6)
+	if (tlvs[AP_MLD_CONFIG][0]) {
+		bool ap_mld_updated = false;
+
+		ap_mld_updated = update_sha256_hash(tlvs[AP_MLD_CONFIG][0]->data,
+				tlv_length(tlvs[AP_MLD_CONFIG][0]),
+				re->agent->ap_mld_cfg_sha256);
+
+		if (ap_mld_updated) {
+			agnt_dbg(LOG_APCFG, "%s: AP MLD SHA256 has changed\n", __func__);
+			diff |= DIFF_AP_MLD;
+		} else
+			agnt_dbg(LOG_APCFG, "%s: no change of AP MLD SHA256\n", __func__);
+	}
+
+	if (tlvs[BACKHAUL_STA_MLD_CONFIG][0]) {
+		bool bsta_mld_updated = false;
+
+		bsta_mld_updated = update_sha256_hash(tlvs[BACKHAUL_STA_MLD_CONFIG][0]->data,
+				tlv_length(tlvs[BACKHAUL_STA_MLD_CONFIG][0]),
+				re->agent->bsta_mld_cfg_sha256);
+		if (bsta_mld_updated) {
+			agnt_dbg(LOG_APCFG, "%s: BSTA MLD SHA256 has changed\n", __func__);
+			diff |= DIFF_BSTA_MLD;
+		} else
+			agnt_dbg(LOG_APCFG, "%s: no change of BSTA MLD SHA256\n", __func__);
+	}
+#endif
+	return diff;
+
+error_cleanup:
+	EVP_MD_CTX_free(ctx);
+
+	return DIFF_ERR;
+}
+
+static const char *json_get_string(struct json_object *object, const char *key)
+{
+	json_bool ret;
+	struct json_object *value;
+
+	if (!object || !json_object_is_type(object, json_type_object))
+		return NULL;
+
+	ret = json_object_object_get_ex(object, key, &value);
+	if (!ret || !value || !json_object_is_type(value, json_type_string))
+		return NULL;
+
+	return json_object_get_string(value);
+}
+
+#if (EASYMESH_VERSION >= 6)
+enum autocfg_mld {
+	AP_MLD,
+	BSTA_MLD,
+};
+
+static int autoconfig_mld_hash_to_file(uint8_t *sha256, int type)
+{
+	struct json_object *wsc_json;
+	struct json_object *sha256_obj;
+	char type_str[16] = {};
+	char *sha256_str;
+	json_bool found_in_file;
+
+	dbg("%s: file:%s\n", __func__, AUTOCFG_FILE);
+
+	strncpy(type_str,
+		((type == AP_MLD) ? "ap_mld_sha256" : "bsta_mld_sha256"),
+		sizeof(type_str) - 1);
+
+	/* Get the json object from the file */
+	wsc_json = json_object_from_file(AUTOCFG_FILE);
+	if (!wsc_json) {
+		agnt_dbg(LOG_APCFG, "%s: failed to read json:%s, error:%s. "\
+			 "Try to generate new\n", __func__,
+			 AUTOCFG_FILE, json_util_get_last_err());
+
+		wsc_json = json_object_new_object();
+		if (!wsc_json) {
+			agnt_warn(LOG_APCFG, "%s: failed to create json obj, error:%s. ",
+				  __func__, json_util_get_last_err());
+			return -1;
+		}
+	}
+
+	found_in_file = json_object_object_get_ex(wsc_json, type_str, &sha256_obj);
+	if (found_in_file) {
+		/* Remove old SHA first */
+		json_object_object_del(wsc_json, type_str);
+	}
+
+	/* Create sha256 string */
+	sha256_str = calloc(SHA256_LENGTH * 2 + 1, sizeof(char));
+	if (!sha256_str)
+		goto out;
+	btostr(sha256, SHA256_LENGTH, sha256_str);
+
+	sha256_obj = json_object_new_string(sha256_str);
+	if (WARN_ON(!sha256_obj)) {
+		agnt_warn(LOG_APCFG,
+				"%s: failed to add %s, error:%s. ",
+			  __func__, type_str, json_util_get_last_err());
+		goto free;
+	}
+
+	json_object_object_add(wsc_json, type_str, sha256_obj);
+
+	/* Update sha256 file */
+	json_object_to_file(AUTOCFG_FILE, wsc_json);
+
+	agnt_dbg(LOG_APCFG, "%s: Wrote %s SHA256: %02x%02x%02x%02x%02x%02x... to file %s\n",
+		 __func__, ((type == AP_MLD) ? "AP MLD" : "BSTA MLD"),
+		 sha256[0], sha256[1],
+		 sha256[2], sha256[3],
+		 sha256[4], sha256[5],
+		 AUTOCFG_FILE);
+
+free:
+	free(sha256_str);
+out:
+	json_object_put(wsc_json);
+
+	return 0;
+}
+
+static int get_mld_hash_from_file(uint8_t *sha256_out, int type, const struct blobmsg_policy *attr)
+{
+	struct blob_attr *tb[1];
+	struct blob_buf wsc_m2_file = {0};
+	char *sha256_str;
+	int blen;
+	int ret = 0;
+
+	blob_buf_init(&wsc_m2_file, 0);
+
+	if (!blobmsg_add_json_from_file(&wsc_m2_file, AUTOCFG_FILE)) {
+		agnt_dbg(LOG_APCFG, "Failed to parse %s\n", AUTOCFG_FILE);
+		ret = -1;
+		goto free;
+	}
+
+	blobmsg_parse(attr, 1, tb, blob_data(wsc_m2_file.head), blob_len(wsc_m2_file.head));
+
+	if (!tb[0]) {
+		ret = -1;
+		goto free;
+	}
+
+	sha256_str = blobmsg_get_string(tb[0]);
+	if (!sha256_str) {
+		agnt_err(LOG_APCFG, "%s: No valid %s SHA256\n", __func__,
+			 ((type == AP_MLD) ? "AP MLD" : "BSTA MLD"));
+		ret = -1;
+		goto free;
+	}
+
+	blen = strlen(sha256_str) / 2;
+	if (blen > SHA256_LENGTH) {
+		agnt_err(LOG_APCFG, "%s: %s SHA256 too long\n", __func__,
+			 ((type == AP_MLD) ? "AP MLD" : "BSTA MLD"));
+		ret = -1;
+		goto free;
+	}
+
+	strtob(sha256_str, blen, sha256_out);
+
+	dbg("%s: Read %s SHA256: %02x%02x%02x%02x%02x%02x...\n", __func__,
+	    ((type == AP_MLD) ? "AP MLD" : "BSTA MLD"),
+	    sha256_out[0], sha256_out[1],
+	    sha256_out[2], sha256_out[3],
+	    sha256_out[4], sha256_out[5]);
+
+free:
+	blob_buf_free(&wsc_m2_file);
+
+	return ret;
+}
+
+/* keep the AP MLD md5sum in non-persistent storage */
+int autoconfig_ap_mld_hash_to_file(uint8_t *sha256)
+{
+	return autoconfig_mld_hash_to_file(sha256, AP_MLD);
+}
+
+int autoconfig_ap_mld_hash_from_file(uint8_t *sha256_out)
+{
+	static const struct blobmsg_policy attr_ap[] = {
+		[0] = { .name = "ap_mld_sha256", .type = BLOBMSG_TYPE_STRING },
+	};
+
+	return get_mld_hash_from_file(sha256_out, AP_MLD, attr_ap);
+}
+
+/* keep the BSTA MLD md5sum in non-persistent storage */
+int autoconfig_bsta_mld_hash_to_file(uint8_t *sha256)
+{
+	return autoconfig_mld_hash_to_file(sha256, BSTA_MLD);
+}
+
+int autoconfig_bsta_mld_hash_from_file(uint8_t *sha256_out)
+{
+	static const struct blobmsg_policy attr_bsta[] = {
+		[0] = { .name = "bsta_mld_sha256", .type = BLOBMSG_TYPE_STRING },
+	};
+
+	return get_mld_hash_from_file(sha256_out, BSTA_MLD, attr_bsta);
+}
+#endif /* EASYMESH_VERSION >= 6 */
+
+/* keep the WSC M2 md5sum in non-persistent storage */
+int autoconfig_radio_hash_to_file(uint8_t *macaddr, uint8_t *sha256)
+{
+	struct json_object *wsc_json;
+	struct json_object *radios, *radio_obj = NULL;
+	struct json_object *sha256_obj;
+	char *sha256_str;
+	json_bool ret;
+	int len, i;
+
+	dbg("%s: file:%s\n", __func__, AUTOCFG_FILE);
+
+	/* Get the json object from the file */
+	wsc_json = json_object_from_file(AUTOCFG_FILE);
+	if (!wsc_json) {
+		agnt_dbg(LOG_APCFG, "%s: failed to read json:%s, error:%s. "\
+			 "Try to generate new\n", __func__,
+			 AUTOCFG_FILE, json_util_get_last_err());
+
+		wsc_json = json_object_new_object();
+		if (!wsc_json) {
+			agnt_warn(LOG_APCFG, "%s: failed to create json obj, error:%s. ",
+				  __func__, json_util_get_last_err());
+			return -1;
+		}
+	}
+
+	/* Get the radios array */
+	ret = json_object_object_get_ex(wsc_json, "radios", &radios);
+	if (!ret) {
+		/* Create radios array if not found */
+		radios = json_object_new_array();
+		if (!radios) {
+			agnt_warn(LOG_APCFG, "%s: failed to add radio array, error:%s. ",
+				  __func__, json_util_get_last_err());
+			goto out_radio;
+		}
+		json_object_object_add(wsc_json, "radios", radios);
+	} else if (!json_object_is_type(radios, json_type_array)) {
+		agnt_warn(LOG_APCFG, "%s: file: %s has wrong format\n",
+			  __func__, AUTOCFG_FILE);
+		goto out_radio;
+	}
+
+	/* Check if radio already exists in radio array */
+	len = json_object_array_length(radios);
+	for (i = 0; i < len; i++) {
+		struct json_object *t;
+		uint8_t radio_mac[6];
+		const char *p;
+
+		t = json_object_array_get_idx(radios, i);
+		if (!t) {
+			agnt_dbg(LOG_APCFG, "%s: couldn't get entry:%d from radio array", __func__, i);
+			continue;
+		}
+
+		p = json_get_string(t, "macaddr");
+		if (!p) {
+			agnt_dbg(LOG_APCFG, "%s: couldn't get macaddr from entry:%d", __func__, i);
+			continue;
+		}
+
+		if (!hwaddr_aton(p, radio_mac)) {
+			agnt_dbg(LOG_APCFG, "%s: couldn't convert macaddr from entry:%d", __func__, i);
+			continue;
+		}
+
+		if (!memcmp(macaddr, radio_mac, 6)) {
+			/* radio entry already present in file */
+			radio_obj = t;
+			break;
+		}
+	}
+
+	/* Create new radio object if not found */
+	if (!radio_obj) {
+		char radiostr[18] = {0};
+		struct json_object *val;
+
+		radio_obj = json_object_new_object();
+		if (WARN_ON(!radio_obj))
+			goto out_radio;
+
+		/* radio mac */
+		hwaddr_ntoa(macaddr, radiostr);
+		val = json_object_new_string(radiostr);
+		if (!val) {
+			json_object_put(radio_obj);
+			goto out_radio;
+		}
+		json_object_object_add(radio_obj, "macaddr", val);
+
+		/* Add radio object to radios array */
+		json_object_array_add(radios, radio_obj);
+	}
+
+	/* Get the sha256 string */
+	ret = json_object_object_get_ex(radio_obj, "m2_sha256", &sha256_obj);
+	if (ret) {
+		/* Remove old SHA first */
+		json_object_object_del(radio_obj, "m2_sha256");
+	}
+
+	/* Create sha256 string */
+	sha256_str = calloc(SHA256_LENGTH * 2 + 1, sizeof(char));
+	if (!sha256_str)
+		goto out_radio;
+
+	btostr(sha256, SHA256_LENGTH, sha256_str);
+
+	sha256_obj = json_object_new_string(sha256_str);
+	if (!sha256_obj) {
+		json_object_put(radio_obj);
+		goto free;
+	}
+	json_object_object_add(radio_obj, "m2_sha256", sha256_obj);
+
+	/* Update sha256 file */
+	json_object_to_file(AUTOCFG_FILE, wsc_json);
+
+	agnt_dbg(LOG_APCFG, "%s: Wrote autoconfig SHA256: %02x%02x%02x%02x%02x%02x... to file %s\n",
+		 __func__,
+		 sha256[0], sha256[1],
+		 sha256[2], sha256[3],
+		 sha256[4], sha256[5],
+		 AUTOCFG_FILE);
+
+free:
+	free(sha256_str);
+out_radio:
+	json_object_put(wsc_json);
+
+	return 0;
+}
+
+int autoconfig_radio_hash_from_file(uint8_t *macaddr, uint8_t *sha256_out)
+{
+	struct blob_buf radios = { 0 };
+	struct blob_attr *b;
+	static const struct blobmsg_policy attr[] = {
+		[0] = { .name = "radios", .type = BLOBMSG_TYPE_ARRAY },
+	};
+	struct blob_attr *tb[ARRAY_SIZE(attr)];
+	int rem;
+	int ret = 0;
+
+	blob_buf_init(&radios, 0);
+
+	if (!blobmsg_add_json_from_file(&radios, AUTOCFG_FILE)) {
+		agnt_dbg(LOG_APCFG, "Failed to parse %s\n", AUTOCFG_FILE);
+		ret = -1;
+		goto out;
+	}
+
+	blobmsg_parse(attr, ARRAY_SIZE(attr), tb, blob_data(radios.head), blob_len(radios.head));
+
+	if (!tb[0]) {
+		ret = -1;
+		goto out;
+	}
+
+	blobmsg_for_each_attr(b, tb[0], rem) {
+		static const struct blobmsg_policy radio_attr[2] = {
+			[0] = { .name = "macaddr", .type = BLOBMSG_TYPE_STRING },
+			[1] = { .name = "m2_sha256", .type = BLOBMSG_TYPE_STRING },
+		};
+		struct blob_attr *tb1[ARRAY_SIZE(radio_attr)];
+		char radio_str[18] = {0};
+		uint8_t radio_mac[6] = {0};
+		char *sh256_str;
+		int blen;
+
+		blobmsg_parse(radio_attr, ARRAY_SIZE(radio_attr), tb1, blobmsg_data(b), blob_len(b));
+		if (!tb1[0] || !tb1[1])
+			continue;
+
+		strncpy(radio_str, blobmsg_data(tb1[0]), sizeof(radio_str) - 1);
+		if (!hwaddr_aton(radio_str, radio_mac)) {
+			agnt_dbg(LOG_APCFG, "Failed to convert macaddr %s\n", radio_str);
+			continue;
+		}
+
+		if (memcmp(macaddr, radio_mac, 6))
+			continue;
+
+		sh256_str = blobmsg_get_string(tb1[1]);
+		if (!sh256_str) {
+			agnt_err(LOG_APCFG, "%s: No valid sha256\n", __func__);
+			ret = -1;
+			break;
+		}
+
+		blen = strlen(sh256_str) / 2;
+		if (blen > SHA256_LENGTH) {
+			agnt_err(LOG_APCFG, "%s: SHA256 string too long\n", __func__);
+			ret = -1;
+			break;
+		}
+
+		strtob(sh256_str, blen, sha256_out);
+	}
+
+	dbg("%s: Read autoconfig SHA256 for radio " MACFMT ": %02x%02x%02x%02x%02x%02x...\n",
+		__func__, MAC2STR(macaddr),
+		sha256_out[0], sha256_out[1],
+		sha256_out[2], sha256_out[3],
+		sha256_out[4], sha256_out[5]);
+
+out:
+	blob_buf_free(&radios);
+
+	return ret;
+}
diff --git a/src/autoconfig.h b/src/autoconfig.h
new file mode 100644
index 0000000000000000000000000000000000000000..93f45dd367d380220f1c0adf7124909f26a5bea0
--- /dev/null
+++ b/src/autoconfig.h
@@ -0,0 +1,52 @@
+#ifndef AUTOCONFIG_H
+#define AUTOCONFIG_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "utils/libcrypto.h"
+
+#define HEARTBEAT_AUTOCFG_INTERVAL 70
+
+#define AUTOCFG_FILE "/etc/multiap/wsc_m2.json"
+
+struct wifi_radio_element;
+
+enum autocfg_state {
+	AUTOCFG_HEARTBEAT,
+	AUTOCFG_ACTIVE
+};
+
+enum autocfg_diff {
+	DIFF_NONE     = 0,
+	DIFF_WSC_M2   = 1 << 0,
+#if (EASYMESH_VERSION >= 6)
+	DIFF_AP_MLD   = 1 << 1,
+	DIFF_BSTA_MLD = 1 << 2,
+#endif
+	DIFF_ERR      = 255,
+};
+
+struct wsc_data {
+	uint8_t *m1_frame;
+	uint16_t m1_size;
+	struct wsc_key *key;
+	uint8_t sha256[SHA256_LENGTH];
+};
+
+void autoconfig_clean_wsc_hash(struct wsc_data *wsc);
+void autoconfig_update_wsc_hash(struct wsc_data *data, uint8_t *new_sha256);
+int autoconfig_wsc_m2_diff(struct wifi_radio_element *re,
+			  struct tlv *tlvs[][TLV_MAXNUM],
+			  size_t tlvs_size,
+			  uint8_t *wsc_m2_sha256);
+
+int autoconfig_radio_hash_to_file(uint8_t *macaddr, uint8_t *sha256);
+int autoconfig_radio_hash_from_file(uint8_t *macaddr, uint8_t *sha256_out);
+#if (EASYMESH_VERSION >= 6)
+int autoconfig_ap_mld_hash_to_file(uint8_t *sha256);
+int autoconfig_ap_mld_hash_from_file(uint8_t *sha256_out);
+int autoconfig_bsta_mld_hash_to_file(uint8_t *sha256);
+int autoconfig_bsta_mld_hash_from_file(uint8_t *sha256_out);
+#endif
+#endif
diff --git a/src/utils/libcrypto.c b/src/utils/libcrypto.c
new file mode 100644
index 0000000000000000000000000000000000000000..3d5c1605e364aa0af975a41aa507805ec35853d3
--- /dev/null
+++ b/src/utils/libcrypto.c
@@ -0,0 +1,42 @@
+/*
+ * liblcrytpo.c - implements crytpographic function wrappers
+ *
+ * Copyright (C) 2025 iopsys Software Solutions AB. All rights reserved.
+ *
+ */
+
+#include <string.h>
+#include <openssl/evp.h>
+
+#include "libcrypto.h"
+
+bool update_sha256_hash(void *data, int length, uint8_t *sha256_out)
+{
+	bool ret = false;
+	uint8_t new_sha256[SHA256_LENGTH] = {0};
+	EVP_MD_CTX *ctx;
+
+	ctx = EVP_MD_CTX_new();
+	if (!ctx)
+		return true;
+
+	if (!EVP_DigestInit_ex(ctx, EVP_sha256(), NULL))
+		goto error_cleanup;
+
+	if (!EVP_DigestUpdate(ctx, data, length))
+		goto error_cleanup;
+
+	if (!EVP_DigestFinal_ex(ctx, new_sha256, NULL))
+		goto error_cleanup;
+
+	if (memcmp(sha256_out, new_sha256, SHA256_LENGTH)) {
+		memcpy(sha256_out, new_sha256, SHA256_LENGTH);
+		ret = true;
+	}
+
+error_cleanup:
+	EVP_MD_CTX_free(ctx);
+	return ret;
+}
+
+
diff --git a/src/utils/libcrypto.h b/src/utils/libcrypto.h
new file mode 100644
index 0000000000000000000000000000000000000000..cfabe32d492f27c25163aedf32e7cb8b59dff87f
--- /dev/null
+++ b/src/utils/libcrypto.h
@@ -0,0 +1,16 @@
+/*
+ * libcrypto.h - crypto utility functions header file
+ *
+ * Copyright (C) 2025 iopsys Software Solutions AB. All rights reserved.
+ *
+ */
+
+#ifndef LIBCRYPTO_H
+#define LIBCRYPTO_H
+
+#include <stdbool.h>
+
+#define SHA256_LENGTH 32
+
+bool update_sha256_hash(void *data, int length, uint8_t *sha256_out);
+#endif /* LIBCRYPTO_H */