Skip to content

Commit 4cdd508

Browse files
committed
Merge branch 'feature/github_pull_15073' into 'master'
feat(wpa_supplicant): Add optimized PSK implementation See merge request espressif/esp-idf!36229
2 parents 2c68d2d + a8e5fd4 commit 4cdd508

File tree

5 files changed

+376
-22
lines changed

5 files changed

+376
-22
lines changed

components/wpa_supplicant/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@ if(CONFIG_ESP_WIFI_MBEDTLS_CRYPTO)
119119
"esp_supplicant/src/crypto/crypto_mbedtls-bignum.c"
120120
"esp_supplicant/src/crypto/crypto_mbedtls-rsa.c"
121121
"esp_supplicant/src/crypto/crypto_mbedtls-ec.c")
122+
if(NOT CONFIG_IDF_TARGET_ESP32)
123+
list(APPEND crypto_src "esp_supplicant/src/crypto/fastpsk.c")
124+
endif()
122125
# Add internal RC4 as RC4 has been removed from mbedtls
123126
set(crypto_src ${crypto_src} "src/crypto/rc4.c")
124127
if(NOT CONFIG_MBEDTLS_DES_C)

components/wpa_supplicant/esp_supplicant/src/crypto/crypto_mbedtls.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD
2+
* SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
@@ -38,6 +38,7 @@
3838

3939
#ifdef CONFIG_FAST_PBKDF2
4040
#include "fastpbkdf2.h"
41+
#include "fastpsk.h"
4142
#endif
4243

4344
static int digest_vector(mbedtls_md_type_t md_type, size_t num_elem,
@@ -751,9 +752,16 @@ int pbkdf2_sha1(const char *passphrase, const u8 *ssid, size_t ssid_len,
751752
int iterations, u8 *buf, size_t buflen)
752753
{
753754
#ifdef CONFIG_FAST_PBKDF2
755+
/* For ESP32: Using pbkdf2_hmac_sha1() because esp_fast_psk() utilizes hardware,
756+
* but for ESP32, the SHA1 hardware implementation is slower than the software implementation.
757+
*/
758+
#if CONFIG_IDF_TARGET_ESP32
754759
fastpbkdf2_hmac_sha1((const u8 *) passphrase, os_strlen(passphrase),
755760
ssid, ssid_len, iterations, buf, buflen);
756761
return 0;
762+
#else
763+
return esp_fast_psk(passphrase, os_strlen(passphrase), ssid, ssid_len, iterations, buf, buflen);
764+
#endif
757765
#else
758766
int ret = mbedtls_pkcs5_pbkdf2_hmac_ext(MBEDTLS_MD_SHA1, (const u8 *) passphrase,
759767
os_strlen(passphrase), ssid,
Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
/*
8+
* Specialized and optimized PBKDF2-SHA1 implementation for Wi-Fi PSK
9+
*
10+
* Initially authored by Chien Wong([email protected]).
11+
*/
12+
13+
/*
14+
* This implementation derives a Pre-Shared Key (PSK) for WPA/WPA2 using a custom PBKDF2-like approach.
15+
*
16+
* PBKDF2 (Password-Based Key Derivation Function 2) is a standard algorithm used to derive cryptographic keys
17+
* from a password and salt. It relies on iteratively applying a pseudorandom function, such as HMAC, to the input.
18+
* The derived key is designed to be computationally expensive to generate, making brute-force attacks more difficult.
19+
*
20+
* In standard PBKDF2, the process is as follows:
21+
* 1. Combine the password and salt (SSID in WPA).
22+
* 2. Compute HMAC for this combination, iteratively applying the previous HMAC output as input for the next iteration.
23+
* 3. XOR all intermediate results to produce the final derived key.
24+
*
25+
* This implementation adapts PBKDF2 for WPA/WPA2 by leveraging the SHA1 hashing algorithm and fixed parameters:
26+
* - The password is up to 63 characters long.
27+
* - The SSID (salt) is up to 32 bytes.
28+
* - The iteration count is fixed at 4096, as required by WPA.
29+
* - The output key length is 32 bytes, suitable for WPA.
30+
*
31+
* Key Differences from Standard PBKDF2:
32+
* - Instead of a general-purpose pseudorandom function, this implementation uses a fixed combination of SHA1 blocks.
33+
* - The logic for handling HMAC is explicitly implemented to optimize for this specific use case.
34+
* - Padding and block alignment are carefully managed to fit within hardware constraints (e.g., the ESP32 SHA1 hardware).
35+
*
36+
* How This Implementation Works:
37+
* 1. The `fast_psk_f` function computes one segment of the derived key. It takes as input:
38+
* - The password.
39+
* - The SSID.
40+
* - A counter value (`count`) that varies for each segment.
41+
* 2. HMAC-SHA1 is implemented explicitly using:
42+
* - An inner padding block (`ipad`) initialized with 0x36 XORed with the password.
43+
* - An outer padding block (`opad`) initialized with 0x5C XORed with the password.
44+
* 3. Intermediate hashes (`U1`, `U2`, ..., `Un`) are computed iteratively. Each `U` value depends on the previous one.
45+
* - `U1` is derived from the password, SSID, and counter.
46+
* - Subsequent `U` values are derived using SHA1 on the previous `U` value.
47+
* 4. All intermediate values are XORed together to produce the final segment of the key.
48+
* 5. The `esp_fast_psk` function combines two invocations of `fast_psk_f` to produce the complete 32-byte key.
49+
* - The first invocation computes the first 16 bytes.
50+
* - The second invocation computes the second 16 bytes.
51+
*
52+
* - The code uses the ESP SHA1 hardware accelerator for faster computation.
53+
*/
54+
55+
#include "fastpsk.h"
56+
57+
#include <string.h>
58+
#include "soc/soc_caps.h"
59+
60+
#if SOC_SHA_SUPPORT_PARALLEL_ENG
61+
#include "sha/sha_parallel_engine.h"
62+
#else
63+
#include "sha/sha_core.h"
64+
#endif
65+
#include "esp_log.h"
66+
67+
#ifndef PUT_UINT32_BE
68+
#define PUT_UINT32_BE(n, b, i) \
69+
{ \
70+
(b)[(i)] = (unsigned char)((n) >> 24); \
71+
(b)[(i) + 1] = (unsigned char)((n) >> 16); \
72+
(b)[(i) + 2] = (unsigned char)((n) >> 8); \
73+
(b)[(i) + 3] = (unsigned char)((n)); \
74+
}
75+
#endif
76+
77+
#define FAST_PSK_SHA1_BLOCKS 2
78+
#define SHA1_BLOCK_SZ 64
79+
#define SHA1_BLOCK_SZ_WORDS 16
80+
#define SHA1_OUTPUT_SZ 20
81+
#define SHA1_OUTPUT_SZ_WORDS 5
82+
#define FAST_PSK_SHA1_BLOCKS_BUF_BYTES (FAST_PSK_SHA1_BLOCKS * SHA1_BLOCK_SZ)
83+
#define FAST_PSK_SHA1_BLOCKS_BUF_WORDS (FAST_PSK_SHA1_BLOCKS * SHA1_BLOCK_SZ / 4)
84+
85+
/* Union to represent SHA1 HMAC blocks */
86+
union hmac_block {
87+
union {
88+
uint32_t words[SHA1_BLOCK_SZ / 4]; /* SHA1 block in words */
89+
uint8_t bytes[SHA1_BLOCK_SZ]; /* SHA1 block in bytes */
90+
} block[FAST_PSK_SHA1_BLOCKS];
91+
uint8_t whole_bytes[FAST_PSK_SHA1_BLOCKS_BUF_BYTES]; /* Complete block as bytes */
92+
uint32_t whole_words[FAST_PSK_SHA1_BLOCKS_BUF_WORDS]; /* Complete block as words */
93+
};
94+
_Static_assert(sizeof(union hmac_block) == 128, "Incorrect layout of hmac_block");
95+
96+
/* Structure to hold HMAC context */
97+
struct fast_psk_context {
98+
union hmac_block inner, outer; /* Inner and outer padding */
99+
uint32_t sum[SHA1_OUTPUT_SZ_WORDS]; /* Intermediate hash result */
100+
};
101+
102+
/* Acquire SHA1 hardware for exclusive use */
103+
static inline void sha1_setup(void)
104+
{
105+
#if SOC_SHA_SUPPORT_PARALLEL_ENG
106+
esp_sha_lock_engine(SHA1);
107+
#else
108+
esp_sha_acquire_hardware();
109+
#endif
110+
}
111+
112+
/* Release SHA1 hardware */
113+
static inline void sha1_teardown(void)
114+
{
115+
#if SOC_SHA_SUPPORT_PARALLEL_ENG
116+
esp_sha_unlock_engine(SHA1);
117+
#else
118+
esp_sha_release_hardware();
119+
#endif
120+
}
121+
122+
/*
123+
* Pads the given HMAC block context with the appropriate SHA1 padding.
124+
* Length is the number of bytes of actual data in the block.
125+
*/
126+
static void pad_blocks(union hmac_block *ctx, size_t len)
127+
{
128+
size_t bits = len << 3; /* Convert length to bits */
129+
uint8_t *bytes = ctx->whole_bytes;
130+
bytes[len] = 0x80; /* Append 0x80 as per SHA1 padding rules */
131+
132+
// Set all remaining bytes to 0
133+
memset(&bytes[len + 1], 0, FAST_PSK_SHA1_BLOCKS_BUF_BYTES - (len + 1));
134+
135+
/*
136+
* Simplified PUT_UINT64_BE(bits, bytes, FAST_PSK_SHA1_BLOCKS_BUF_BYTES - 8).
137+
* Since len < 128 => bits < 1024, we only need to update the two least significant
138+
* bytes, actually.
139+
*/
140+
PUT_UINT32_BE(bits, bytes, FAST_PSK_SHA1_BLOCKS_BUF_BYTES - 4);
141+
}
142+
143+
/*
144+
* Performs SHA1 hash operation on two consecutive blocks.
145+
* Input: blocks array (two blocks of 64 bytes each), output (20-byte digest).
146+
*/
147+
#if CONFIG_IDF_TARGET_ESP32
148+
static inline void write32_be(uint32_t n, uint8_t out[4])
149+
{
150+
#if defined(__GNUC__) && __GNUC__ >= 4 && __BYTE_ORDER == __LITTLE_ENDIAN
151+
*(uint32_t *)(out) = __builtin_bswap32(n);
152+
#else
153+
out[0] = (n >> 24) & 0xff;
154+
out[1] = (n >> 16) & 0xff;
155+
out[2] = (n >> 8) & 0xff;
156+
out[3] = n & 0xff;
157+
#endif
158+
}
159+
#endif /* CONFIG_IDF_TARGET_ESP32 */
160+
161+
void sha1_op(uint32_t blocks[FAST_PSK_SHA1_BLOCKS_BUF_WORDS], uint32_t output[SHA1_OUTPUT_SZ_WORDS])
162+
{
163+
/* First block */
164+
esp_sha_block(SHA1, blocks, true);
165+
/* Second block */
166+
esp_sha_block(SHA1, &blocks[SHA1_BLOCK_SZ_WORDS], false);
167+
/* Read the final digest */
168+
esp_sha_read_digest_state(SHA1, output);
169+
170+
#if CONFIG_IDF_TARGET_ESP32
171+
for (int i = 0; i < SHA1_OUTPUT_SZ_WORDS; i++) {
172+
write32_be(output[i], ((uint8_t*) output) + i * 4);
173+
}
174+
#endif /* CONFIG_IDF_TARGET_ESP32 */
175+
}
176+
177+
/*
178+
* Implements the PBKDF2-HMAC-SHA1 function for WPA key derivation.
179+
* - password: The passphrase (up to 63 bytes).
180+
* - password_len: Length of the passphrase.
181+
* - ssid: The SSID (up to 32 bytes).
182+
* - ssid_len: Length of the SSID.
183+
* - count: The iteration counter.
184+
* - digest: Output buffer for the resulting digest (20 bytes).
185+
*/
186+
void fast_psk_f(const char *password, size_t password_len, const uint8_t *ssid, size_t ssid_len, uint32_t count, uint8_t digest[SHA1_OUTPUT_SZ])
187+
{
188+
struct fast_psk_context ctx_, *ctx = &ctx_;
189+
size_t i;
190+
191+
/* Clear the context */
192+
memset(ctx, 0, sizeof(*ctx));
193+
194+
/* Initialize inner and outer padding */
195+
memset(ctx->outer.block[0].bytes, 0x5c, SHA1_BLOCK_SZ);
196+
memset(ctx->inner.block[0].bytes, 0x36, SHA1_BLOCK_SZ);
197+
198+
/* XOR the password into the padding */
199+
for (i = 0; i < password_len; ++i) {
200+
ctx->outer.block[0].bytes[i] ^= password[i];
201+
ctx->inner.block[0].bytes[i] ^= password[i];
202+
}
203+
204+
/* Prepare the first input block for HMAC (S || i) */
205+
/* Copy SSID */
206+
memcpy(ctx->inner.block[1].bytes, ssid, ssid_len);
207+
/* Append the counter */
208+
PUT_UINT32_BE(count, ctx->inner.block[1].bytes, ssid_len);
209+
/* Pad the block */
210+
pad_blocks(&ctx->inner, SHA1_BLOCK_SZ + ssid_len + 4);
211+
212+
sha1_setup();
213+
214+
uint32_t *pi, *po;
215+
pi = ctx->inner.whole_words;
216+
po = ctx->outer.whole_words;
217+
218+
// T1 = SHA1(K ^ ipad, S || i)
219+
sha1_op(pi, ctx->outer.block[1].words);
220+
221+
// U1 = SHA1(K ^ opad, T1)
222+
pad_blocks(&ctx->outer, SHA1_BLOCK_SZ + SHA1_OUTPUT_SZ);
223+
uint32_t *inner_blk1 = ctx->inner.block[1].words;
224+
uint32_t *outer_blk1 = ctx->outer.block[1].words;
225+
uint32_t *sum = ctx->sum;
226+
227+
sha1_op(po, inner_blk1);
228+
/* Copy result to the sum */
229+
memcpy(sum, inner_blk1, SHA1_OUTPUT_SZ);
230+
pad_blocks(&ctx->inner, SHA1_BLOCK_SZ + SHA1_OUTPUT_SZ);
231+
232+
/* Iterate for remaining 4096 - 1 times */
233+
for (i = 1; i < 4096; ++i) {
234+
/* Compute Tn and Un */
235+
// Tn = SHA1(K ^ ipad, Un-1)
236+
sha1_op(pi, outer_blk1);
237+
// Un = SHA1(K ^ opad, Tn)
238+
sha1_op(po, inner_blk1);
239+
240+
/* XOR the results to accumulate into F */
241+
// F = U1 ^ U2 ^ ... Un
242+
for (size_t j = 0; j < SHA1_OUTPUT_SZ_WORDS; ++j) {
243+
sum[j] ^= inner_blk1[j];
244+
}
245+
}
246+
247+
sha1_teardown();
248+
249+
/* Copy the final result to the output digest */
250+
memcpy(digest, sum, SHA1_OUTPUT_SZ);
251+
252+
/* Clear sensitive data */
253+
memset(ctx, 0, sizeof(*ctx));
254+
}
255+
256+
int esp_fast_psk(const char *password, size_t password_len, const uint8_t *ssid, size_t ssid_len, size_t iterations, uint8_t *output, size_t output_len)
257+
{
258+
if (!(ssid_len <= 32 && password_len <= 63 && iterations == 4096 && output_len == 32)) {
259+
return -1; /* Invalid input parameters */
260+
}
261+
262+
/* Compute the first 16 bytes of the PSK */
263+
fast_psk_f(password, password_len, ssid, ssid_len, 2, output);
264+
265+
/* Replicate the first 16 bytes to form the second half temporarily */
266+
memcpy(output + SHA1_OUTPUT_SZ, output, 32 - SHA1_OUTPUT_SZ);
267+
268+
/* Compute the second 16 bytes of the PSK */
269+
fast_psk_f(password, password_len, ssid, ssid_len, 1, output);
270+
271+
return 0; /* Success */
272+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#pragma once
8+
9+
#include <stddef.h>
10+
#include <stdint.h>
11+
12+
#ifdef __cplusplus
13+
extern "C" {
14+
#endif
15+
16+
/**
17+
* @brief Calculate PSK
18+
*
19+
* @param password Password
20+
* @param password_len Length of password, it must be <= 63
21+
* @param ssid SSID
22+
* @param ssid_len Length of SSID, it must be <= 32
23+
* @param iterations Iterations of the PBKDF2-SHA1, this is a dummy param and it must be 4096
24+
* @param output Buffer for calculated PSK, it must be at least 32 bytes
25+
* @param output_len Length of output to return, this is a dummy param and it must be 32
26+
* @return 0 on success, non-zero on failure
27+
*/
28+
int esp_fast_psk(const char *password, size_t password_len, const uint8_t *ssid, size_t ssid_len, size_t iterations, uint8_t *output, size_t output_len);
29+
30+
#ifdef __cplusplus
31+
}
32+
#endif

0 commit comments

Comments
 (0)