Как стать автором
Обновить

Свободный менеджер паролей LessPass работает на чистой функции

Время на прочтение 5 мин
Количество просмотров 27K


Хранение уникальных паролей для всех сайтов и приложений — целое искусство. Невозможно запомнить сотню длинных паролей с высокой энтропией. Может быть, в мире есть десяток высокоразвитых аутистов, способных на такое, не больше. Остальным людям приходится использовать технические трюки. Самый распространённый вариант — записать все пароли в один файл, который защищён мастер-паролем. По такому принципу работает большинство парольных менеджеров.

У этого способа много преимуществ, но есть два главных недостатка: 1) трудно синхронизировать пароли между устройствами; 2) нужно всегда иметь в распоряжении сам файл с паролями. То есть потерял файл с паролями — и до свидания.

Новый парольный менеджер LessPass с открытым исходным кодом лишён этих недостатков, потому что работает на чистой детерминированной функции. У него есть другие недостатки, конечно. Об этом ниже. Сначала о достоинствах.

Концепция


Концепция LessPass заключается в том, что регенерация одноразовых паролей происходит на лету при помощи чистой функции, которая выдаёт детерминированные значения каждый раз при расчёте каждого уникального пароля.

На вход функции подаются четыре аргумента:

  • мастер-пароль
  • логин на сайте
  • адрес сайта
  • опции генерации пароля

В любой момент на любом компьютере вы можете повторно запустить функцию — и вычислить свой уникальный пароль для конкретного сайта.

Как это выглядит.



Из такой концепции сразу понятно главное достоинство подобного подхода — у вас вообще нет файла с паролями, как в других парольных менеджерах. Чего нет — то невозможно потерять. Теперь вместо извлечения уникального пароля из зашифрованного хранилища мы каждый раз заново генерируем этот пароль. Благодаря детерминированности функции и неизменности входных параметров всегда генерируются одни и те же уникальные пароли для всех сайтов.

Разработчик программы Гийом Винсент (Guillaume Vincent) постарался реализовать безопасность, насколько он её понимает. Какая безопасность нужна для детерминированной функции? В первую очередь — чтобы по результату её работы нельзя было определить все её аргументы, тем более что некоторые аргументы уже известны злоумышленнику, как и сама функция.

Проще говоря, если злоумышленник знает два или три из четырёх аргументов, а также результат функции, он не должен узнать четвёртый аргумент — мастер-пароль.

Для защиты мастер-пароля от брутфорса вычисление уникальных паролей LessPass прогоняет 8192 итерации функции PBKDF2 с хэш-функцией SHA-256.



Сгенерированный первой функцией хэш обрабатывается для соответствия шаблону, установленному третьим и четвёртым параметрами (набор используемых символов, длина пароля).

Теперь, чтобы «вспомнить» уникальный пароль для конкретного сайта — просто запускаем программу LessPass. Это крошечная программа состоит всего из 109 строчек.

Код программы
import crypto from 'crypto';

module.exports = {
    encryptLogin: _encryptLogin,
    renderPassword: _renderPassword,
    createFingerprint: createFingerprint,
    _deriveEncryptedLogin,
    _getPasswordTemplate,
    _prettyPrint,
    _string2charCodes,
    _getCharType,
    _getPasswordChar,
    _createHmac
};

function _encryptLogin(login, masterPassword, {iterations = 8192, keylen = 32}={}) {
    return new Promise((resolve, reject) => {
        if (!login || !masterPassword) {
            reject('login and master password parameters could not be empty');
        }
        crypto.pbkdf2(masterPassword, login, iterations, keylen, 'sha256', (error, key) => {
            if (error) {
                reject('error in pbkdf2');
            } else {
                resolve(key.toString('hex'));
            }
        });
    })
}

function _renderPassword(encryptedLogin, site, passwordOptions) {
    return _deriveEncryptedLogin(encryptedLogin, site, passwordOptions).then(function (derivedEncryptedLogin) {
        const template = passwordOptions.template || _getPasswordTemplate(passwordOptions);
        return _prettyPrint(derivedEncryptedLogin, template);
    });
}

function _createHmac(encryptedLogin, salt) {
    return new Promise(resolve => {
        resolve(crypto.createHmac('sha256', encryptedLogin).update(salt).digest('hex'));
    });
}

function _deriveEncryptedLogin(encryptedLogin, site, passwordOptions = {length: 12, counter: 1}) {
    const salt = site + passwordOptions.counter.toString();
    return _createHmac(encryptedLogin, salt).then(derivedHash => {
        return derivedHash.substring(0, passwordOptions.length);
    });
}

function _getPasswordTemplate(passwordTypes) {
    const templates = {
        lowercase: 'vc',
        uppercase: 'VC',
        numbers: 'n',
        symbols: 's',
    };
    let template = '';
    for (let templateKey in templates) {
        if (passwordTypes.hasOwnProperty(templateKey) && passwordTypes[templateKey]) {
            template += templates[templateKey]
        }
    }
    return template;
}

function _prettyPrint(hash, template) {
    let password = '';

    _string2charCodes(hash).forEach((charCode, index) => {
        const charType = _getCharType(template, index);
        password += _getPasswordChar(charType, charCode);
    });
    return password;
}

function _string2charCodes(text) {
    const charCodes = [];
    for (let i = 0; i < text.length; i++) {
        charCodes.push(text.charCodeAt(i));
    }
    return charCodes;
}

function _getCharType(template, index) {
    return template[index % template.length];
}

function _getPasswordChar(charType, index) {
    const passwordsChars = {
        V: 'AEIOUY',
        C: 'BCDFGHJKLMNPQRSTVWXZ',
        v: 'aeiouy',
        c: 'bcdfghjklmnpqrstvwxz',
        A: 'AEIOUYBCDFGHJKLMNPQRSTVWXZ',
        a: 'AEIOUYaeiouyBCDFGHJKLMNPQRSTVWXZbcdfghjklmnpqrstvwxz',
        n: '0123456789',
        s: '@&%?,=[]_:-+*$#!\'^~;()/.',
        x: 'AEIOUYaeiouyBCDFGHJKLMNPQRSTVWXZbcdfghjklmnpqrstvwxz0123456789@&%?,=[]_:-+*$#!\'^~;()/.'
    };
    const passwordChar = passwordsChars[charType];
    return passwordChar[index % passwordChar.length];
}

function createFingerprint(str) {
    return new Promise(resolve => {
        resolve(crypto.createHmac('sha256', str).digest('hex'))
    });
}

Генерация паролей работает в виде скрипта на сайте LessPass, или на клиентском компьютере. Выпущены соответствующие расширения для Chrome и для Firefox. Скрипт генератора можно запустить и в локальном облаке. Например, с помощью локального облака Cozy.

Таким образом, при использовании такого парольного менеджера невозможно потерять файл с паролями, потому что такого файла его не существует. Никаких проблем ввести уникальный пароль с любого устройства. Нужно только заново сгенерировать его.

Недостатки LessPass очевидны и вытекают из его достоинств. Самое главное, что при компрометации мастер-пароля все десятки или сотни производных уникальных паролей для всех сайтов сразу можно считать потерянными.

Второй недостаток — вы не можете нормально и удобно поменять пароль для конкретного сайта. Для этого придётся изменить один из параметров генерации паролей (Counter). Впоследствии придётся помнить, какой конкретно параметр использовался для каждого сайта. Где единица, где двойка, где тройка, и так далее. Кроме того, нужно помнить уникальные требования каждого сайта к паролям. Например, какие-то банковские приложения могут ограничить пароль только цифрами и длиной в шесть цифр. Для решения этой проблемы в LessPass реализованы профили для сайтов. В профиле хранитеся вся информация, необходимая для генерации пароля, кроме мастер-пароля.


Профили в LessPass

Третий недостаток — для брутфорса пароля злоумышленнику не требуется физический доступ к компьютеру пользователя. Ему не нужен ваш файл с зашифрованными паролями. Чисто теоретически, это упрощает его действия. Пользователь вообще не заметит, что на него идёт атака.

Ситуация ухудшается тем, что в данной конкретной реализации LessPass использует всего 8192 итерации PBKDF2-SHA256. По мнению специалистов, этого крайне недостаточно для надёжной защиты от брутфорса на GPU. Ещё несколько лет назад рекомендовалось минимум 100 000 итераций, а вычислительная мощность брутфоса примерно удваивается каждый год. Так что будем считать, что нынешняя реализация LessPass — просто демонстрационная версия, доказательство работоспособности концепции.

LessPass — не единственный детерминированный менеджер паролей, работающий на чистой функции. Есть и другие аналогичные программы, в том числе Master Password App и Forgiva. У всех этих программ те же недостатки, о которых упоминалось выше. К тому же, в них нельзя импортировать свои старые пароли, так что при миграции на такую программу придётся заново генерировать все пароли.

Несмотря на все недостатки, генератор детерминированных паролей на чистой функции — интересная концепция. Возможно, кому-то именно такой парольный менеджер покажется наиболее удобным.
Теги:
Хабы:
+30
Комментарии 38
Комментарии Комментарии 38

Публикации

Истории

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн