В системах на основе паролей основной вычислительной нагрузкой при шифровании и расшифровании данных становится этап деривации ключа. В Stanford JavaScript Crypto Library (SJCL) этот процесс обычно реализуется через PBKDF2, который многократно применяет хеш-функцию к паролю и соли для получения криптографически стойкого ключа.
Повторная генерация одного и того же ключа при каждом обращении к зашифрованным данным приводит к избыточным вычислениям. Особенно это заметно в браузерных приложениях, где дешифрование может выполняться часто: при открытии защищённых разделов, синхронизации данных или работе с локальным хранилищем. В таких сценариях применяется кэширование производных ключей.
SJCL предоставляет реализацию PBKDF2 через
sjcl.misc.pbkdf2:
var key = sjcl.misc.pbkdf2(password, salt, iterations, keyLength);
Параметры функции:
password — исходный пароль пользователяsalt — криптографическая сольiterations — количество итераций (усложнение
перебора)keyLength — требуемая длина ключаКаждый вызов функции выполняет полный цикл вычислений, что при
больших значениях iterations становится дорогостоящим.
Кэширование основано на предположении, что один и тот же пароль и соль часто используются повторно в пределах одной сессии. При этом повторная деривация ключа не изменяет результат, что делает её кандидатом для мемоизации.
Основные цели кэширования:
Простейшая схема кэширования строится на отображении параметров деривации в готовый ключ:
var keyCache = {};
function getDerivedKey(password, salt, iterations, keyLength) {
var cacheKey = password + ":" + sjcl.codec.hex.fromBits(salt) + ":" + iterations + ":" + keyLength;
if (keyCache[cacheKey]) {
return keyCache[cacheKey];
}
var key = sjcl.misc.pbkdf2(password, salt, iterations, keyLength);
keyCache[cacheKey] = key;
return key;
}
Такой подход эффективен в рамках одной сессии приложения, однако требует внимания к управлению памятью и очистке кэша.
В библиотеке предусмотрен специализированный механизм кэширования
через sjcl.misc.cachedPbkdf2:
var cachedPBKDF2 = sjcl.misc.cachedPbkdf2(password);
var key = cachedPBKDF2(salt, iterations, keyLength);
Особенность этого подхода заключается в том, что кэш привязан к конкретному паролю. Внутри объекта сохраняются уже вычисленные результаты для различных параметров соли и итераций.
Внутренняя структура кэша оптимизирована под повторное использование результатов при одинаковом пароле, что особенно полезно в приложениях с долгоживущими сессиями.
Механизм cachedPbkdf2 использует стратегию частичного
мемоизированного замыкания:
Упрощённая логика:
sjcl.misc.cachedPbkdf2 = function(password) {
var cache = {};
return function(salt, iterations, keyLength) {
var id = sjcl.codec.hex.fromBits(salt) + ":" + iterations + ":" + keyLength;
if (cache[id]) {
return cache[id];
}
var derived = sjcl.misc.pbkdf2(password, salt, iterations, keyLength);
cache[id] = derived;
return derived;
};
};
Эффективность кэширования напрямую зависит от стабильности входных параметров.
Таким образом, максимальная эффективность достигается в системах, где:
При использовании кэширования в браузере возникает необходимость контроля объёма памяти. Без очистки кэша возможно накопление значительного количества производных ключей.
Практические подходы:
Пример ограничения размера:
var keyCache = {};
var cacheOrder = [];
var MAX_CACHE_SIZE = 50;
function setCache(key, value) {
if (cacheOrder.length >= MAX_CACHE_SIZE) {
var oldest = cacheOrder.shift();
delete keyCache[oldest];
}
cacheOrder.push(key);
keyCache[key] = value;
}
Кэширование производных ключей требует осторожности с точки зрения безопасности:
Рекомендации:
В приложениях, где один пароль используется для множества зашифрованных объектов с разными солями, кэширование становится особенно эффективным. В таком случае выгодно разделять уровни кэша:
Иерархическая структура позволяет снизить количество повторных вычислений при массовой обработке данных.
На практике встречаются несколько распространённых проблем:
Каждая из этих ошибок снижает как безопасность, так и предсказуемость поведения системы.
Кэширование производных ключей наиболее эффективно в следующих типах приложений:
В таких случаях повторное использование ключей является нормой, а не исключением, что делает кэширование естественным элементом архитектуры.