ESP32: кэш последних генераций секрета

This commit is contained in:
AidarKC 2026-06-23 12:06:24 +04:00
parent cf2b54464e
commit 365b22d778
3 changed files with 177 additions and 3 deletions

View File

@ -0,0 +1,25 @@
## Кратко
Добавлен локальный кэш последних 5 успешных генераций мастер-секрета на ESP32.
## Что сделано
- Генерация секретов теперь сначала проверяет кэш по `SHA-256(login + 0x00 + password)`.
- При совпадении ESP32 сразу подставляет готовый секрет и не запускает долгую генерацию.
- Успешные результаты складываются в NVS как последние 5 записей.
## Что проверять
- Сгенерировать секрет для `login + password`.
- Повторить ту же пару ещё раз.
- Убедиться, что вторая попытка не запускает долгую генерацию и сразу показывает готовый секрет.
- Проверить, что после 6 различных успешных генераций самая старая запись вытесняется.
## Ожидаемый результат
- Повторная генерация той же пары `login + password` берётся из кэша.
- Кэш хранит только последние 5 успешных результатов.
## Статус
`pending`

View File

@ -37,6 +37,10 @@ static bool gError = false;
static char gMessage[96] = {}; static char gMessage[96] = {};
static uint8_t gSecret[32] = {}; static uint8_t gSecret[32] = {};
static char gSecretB58[64] = {}; static char gSecretB58[64] = {};
static Preferences gCachePrefs;
static bool gCachePrefsReady = false;
static char gCacheLogin[32] = {};
static uint8_t gCacheLookupHash[32] = {};
static uint32_t gDoneBlocks = 0; static uint32_t gDoneBlocks = 0;
static uint32_t gStartMs = 0; static uint32_t gStartMs = 0;
static uint32_t gCurPass = 0; static uint32_t gCurPass = 0;
@ -80,6 +84,136 @@ static void setMessage(const char *message) {
snprintf(gMessage, sizeof(gMessage), "%s", message ? message : ""); snprintf(gMessage, sizeof(gMessage), "%s", message ? message : "");
} }
static void sha256calc(const uint8_t *in, size_t len, uint8_t *out32);
static const char *CACHE_NS = "secret_cache";
static const char *CACHE_VERSION_KEY = "ver";
static const char *CACHE_HEAD_KEY = "head";
static const char *CACHE_COUNT_KEY = "count";
static const uint8_t CACHE_VERSION = 1;
static const uint8_t CACHE_MAX_ENTRIES = 5;
struct SecretCacheEntry {
char login[32];
uint8_t lookupHash[32];
uint8_t secret[32];
};
static void computeLookupHash(const char *normalizedLogin, const char *password, uint8_t out32[32]) {
std::vector<uint8_t> material;
const char *login = normalizedLogin ? normalizedLogin : "";
const char *pwd = password ? password : "";
size_t loginLen = strlen(login);
size_t pwdLen = strlen(pwd);
material.reserve(loginLen + 1 + pwdLen);
material.insert(material.end(), login, login + loginLen);
material.push_back(0);
material.insert(material.end(), pwd, pwd + pwdLen);
sha256calc(material.data(), material.size(), out32);
}
static bool cacheOpen(String &error) {
error = "";
if (gCachePrefsReady) return true;
if (!gCachePrefs.begin(CACHE_NS, false)) {
error = "Cache NVS open failed";
return false;
}
if (!gCachePrefs.isKey(CACHE_VERSION_KEY)) {
gCachePrefs.putUChar(CACHE_VERSION_KEY, CACHE_VERSION);
gCachePrefs.putUChar(CACHE_HEAD_KEY, 0);
gCachePrefs.putUChar(CACHE_COUNT_KEY, 0);
}
gCachePrefsReady = true;
return true;
}
static void cacheClose() {
if (gCachePrefsReady) {
gCachePrefs.end();
gCachePrefsReady = false;
}
}
static String cacheSlotKey(uint8_t slot) {
return String("s") + String(slot);
}
static bool loadCacheEntry(uint8_t slot, SecretCacheEntry &entry) {
memset(&entry, 0, sizeof(entry));
String key = cacheSlotKey(slot);
if (!gCachePrefs.isKey(key.c_str())) {
return false;
}
size_t len = gCachePrefs.getBytesLength(key.c_str());
if (len != sizeof(entry)) {
return false;
}
return gCachePrefs.getBytes(key.c_str(), &entry, sizeof(entry)) == sizeof(entry);
}
static void storeCacheEntry(uint8_t slot, const SecretCacheEntry &entry) {
String key = cacheSlotKey(slot);
gCachePrefs.putBytes(key.c_str(), &entry, sizeof(entry));
}
static bool findCachedSecret(const char *normalizedLogin, const char *password, uint8_t secretOut[32]) {
String error;
if (!cacheOpen(error)) {
return false;
}
uint8_t lookupHash[32];
computeLookupHash(normalizedLogin, password, lookupHash);
uint8_t head = gCachePrefs.getUChar(CACHE_HEAD_KEY, 0);
uint8_t count = gCachePrefs.getUChar(CACHE_COUNT_KEY, 0);
for (uint8_t i = 0; i < count && i < CACHE_MAX_ENTRIES; ++i) {
uint8_t slot = (uint8_t)((head + CACHE_MAX_ENTRIES - 1 - i) % CACHE_MAX_ENTRIES);
SecretCacheEntry entry;
if (!loadCacheEntry(slot, entry)) {
continue;
}
if (memcmp(entry.lookupHash, lookupHash, 32) == 0) {
memcpy(secretOut, entry.secret, 32);
snprintf(gCacheLogin, sizeof(gCacheLogin), "%s", entry.login);
memcpy(gCacheLookupHash, lookupHash, 32);
return true;
}
}
return false;
}
static void rememberSecret(const char *normalizedLogin, const uint8_t secret32[32]) {
String error;
if (!cacheOpen(error)) {
return;
}
SecretCacheEntry entry = {};
snprintf(entry.login, sizeof(entry.login), "%s", normalizedLogin ? normalizedLogin : "");
memcpy(entry.lookupHash, gCacheLookupHash, 32);
memcpy(entry.secret, secret32, 32);
uint8_t head = gCachePrefs.getUChar(CACHE_HEAD_KEY, 0);
uint8_t count = gCachePrefs.getUChar(CACHE_COUNT_KEY, 0);
storeCacheEntry(head, entry);
head = (uint8_t)((head + 1) % CACHE_MAX_ENTRIES);
if (count < CACHE_MAX_ENTRIES) {
count++;
}
gCachePrefs.putUChar(CACHE_HEAD_KEY, head);
gCachePrefs.putUChar(CACHE_COUNT_KEY, count);
gCachePrefs.putUChar(CACHE_VERSION_KEY, CACHE_VERSION);
}
static void finishSecretFromBytes(const uint8_t secret32[32], const char *message) {
memcpy(gSecret, secret32, 32);
shineSecretBase58Encode(gSecret, 32, gSecretB58, sizeof(gSecretB58));
gDone = true;
gRunning = false;
gError = false;
gInitDone = false;
gDoneBlocks = TOTAL_FILLS;
setMessage(message ? message : "Secret generated");
}
static void b2_compress(B2State *S, const uint8_t *blk) { static void b2_compress(B2State *S, const uint8_t *blk) {
uint64_t m[16], v[16]; uint64_t m[16], v[16];
for (int i = 0; i < 16; i++) m[i] = ((const uint64_t *)blk)[i]; for (int i = 0; i < 16; i++) m[i] = ((const uint64_t *)blk)[i];
@ -449,7 +583,6 @@ bool shineSecretInitSd(String &error) {
bool shineSecretStart(const char *normalizedLogin, const char *password, String &error) { bool shineSecretStart(const char *normalizedLogin, const char *password, String &error) {
error = ""; error = "";
if (!shineSecretInitSd(error)) return false;
if (!normalizedLogin || !*normalizedLogin) { if (!normalizedLogin || !*normalizedLogin) {
error = "login not set"; error = "login not set";
return false; return false;
@ -463,6 +596,20 @@ bool shineSecretStart(const char *normalizedLogin, const char *password, String
return false; return false;
} }
uint8_t cachedSecret[32] = {};
if (findCachedSecret(loginBuf, password ? password : "", cachedSecret)) {
if (gSdFile) gSdFile.close();
snprintf(gCacheLogin, sizeof(gCacheLogin), "%s", loginBuf);
computeLookupHash(loginBuf, password ? password : "", gCacheLookupHash);
finishSecretFromBytes(cachedSecret, "Secret loaded from cache");
return true;
}
snprintf(gCacheLogin, sizeof(gCacheLogin), "%s", loginBuf);
computeLookupHash(loginBuf, password ? password : "", gCacheLookupHash);
if (!shineSecretInitSd(error)) return false;
if (gSdFile) gSdFile.close(); if (gSdFile) gSdFile.close();
SD_MMC.remove(SD_MEM_FILE); SD_MMC.remove(SD_MEM_FILE);
gSdFile = SD_MMC.open(SD_MEM_FILE, "w+"); gSdFile = SD_MMC.open(SD_MEM_FILE, "w+");
@ -482,6 +629,7 @@ bool shineSecretStart(const char *normalizedLogin, const char *password, String
if (!password || !password[0]) { if (!password || !password[0]) {
deriveLegacyMasterSecret(password ? password : "", gSecret); deriveLegacyMasterSecret(password ? password : "", gSecret);
shineSecretBase58Encode(gSecret, 32, gSecretB58, sizeof(gSecretB58)); shineSecretBase58Encode(gSecret, 32, gSecretB58, sizeof(gSecretB58));
rememberSecret(loginBuf, gSecret);
gDone = true; gDone = true;
gRunning = false; gRunning = false;
setMessage("Secret generated"); setMessage("Secret generated");
@ -536,6 +684,7 @@ bool shineSecretStep(uint32_t blocksPerTick) {
gSdFile.read(gBufOut, A2_BLKSZ); gSdFile.read(gBufOut, A2_BLKSZ);
b2long(gBufOut, A2_BLKSZ, gSecret, A2_DKLEN); b2long(gBufOut, A2_BLKSZ, gSecret, A2_DKLEN);
shineSecretBase58Encode(gSecret, 32, gSecretB58, sizeof(gSecretB58)); shineSecretBase58Encode(gSecret, 32, gSecretB58, sizeof(gSecretB58));
rememberSecret(gCacheLogin, gSecret);
gDone = true; gDone = true;
gRunning = false; gRunning = false;
setMessage("Secret generated"); setMessage("Secret generated");

View File

@ -1,2 +1,2 @@
client.version=1.2.237 client.version=1.2.238
server.version=1.2.223 server.version=1.2.224