Warning: include(/volume1/web/cyberhost.biz/wp-content/plugins/jaster_cahce/cache/top-cache.php): failed to open stream: No such file or directory in /volume1/web/cyberhost.biz/index.php on line 9 Call Stack: 0.0000 356272 1. {main}() /volume1/web/cyberhost.biz/index.php:0 Warning: include(): Failed opening '/volume1/web/cyberhost.biz/wp-content/plugins/jaster_cahce/cache/top-cache.php' for inclusion (include_path='.:/usr/share/pear') in /volume1/web/cyberhost.biz/index.php on line 9 Call Stack: 0.0000 356272 1. {main}() /volume1/web/cyberhost.biz/index.php:0 Расшифровка базы данных KeePass: пошаговое руководство | Хостинг за 90 р. от cyberhost.biz — платный хостинг
+7 993 930-19-90 suport@cyberhost.biz

На днях мне нужно было реализовать расшифровку базы данных KeePass. Меня поразило то, что нет ни одного документа и ни одной статьи с исчерпывающей информацией об алгоритме расшифровки файлов .kdb и .kdbx с учетом всех нюансов. Это и побудило меня написать данную статью.

На данный момент существует 2 версии KeePass:

  • KeePass 1.x (генерирует файлы .kdb);
  • KeePass 2.x (генерирует файлы .kdbx).

Структура файла с базой данных KeePass (.kdb, .kdbx) состоит из 3 частей:

  • Подпись (не зашифрована);
  • Заголовок (не зашифрован);
  • Данные (зашифрованы).

Далее я подробно расскажу о том, как дешифровать базу данных KeePass 1.x и KeePass 2.x.


Расшифровка базы данных KeePass
Последовательность действий:

  • Читаем подпись базы данных.
  • Читаем заголовок базы данных.
  • Генерируем мастер-ключ.
  • Расшифровываем базу данных.
  • Проверяем целостность данных.
  • Если файл был сжат, распаковываем его.
  • Расшифровываем пароли.
  • Пункты 5, 6 и 7 относятся только к .kdbx файлам!

    Подпись
    BaseSignature (4 байта)

    Первая подпись одинакова для .kbd и .kdbx файлов. Она говорит о том, что данный файл является базой данных KeePass:

    • 0x9AA2D903

    VersionSignature (4 байта)

    Bторая подпись указывает на версию KeePass и, следовательно, отличается для .kbd и .kdbx файлов:

    • 0xB54BFB65 — KeePass 1.x (файл .kbd).
    • 0xB54BFB66 — KeePass 2.x pre-release (файл .kdbx).
    • 0xB54BFB67 — KeePass 2.x post-release (файл .kdbx).

    FileVersion (4 байта)

    Третья подпись есть только у файлов .kdbx и содержит в себе версию файла. Для файлов .kdb данная информация содержится в заголовке базы данных.

    Таким образом, в KeePass 1.x длина подписи составляет 8 байт, а в KeePass 2.x — 12 байт.

    Заголовок

    После подписи базы данных начинается заголовок.

    Заголовок KeePass 1.x

    Заголовок .kdb файла состоит из следующий полей:

  • Flags (4 байта): данное поле говорит о том, какие виды шифрования использовались при создании файла:
    • 0x01 — SHA256;
    • 0x02 — AES256;
    • 0x04 — ARC4;
    • 0x08 — Twofish.
  • Version (4 байта): версия файла.
  • Master Seed (16 байт): используется для создания мастер-ключа.
  • Encryption IV (16 байт): используется для расшифровки данных.
  • Number of Groups (4 байта): общее количество групп в базе данных.
  • Number of Entries (4 байта): общее количество записей в базе данных.
  • Content Hash (32 байта): hash расшифрованных данных.
  • Transform Seed (32 байта): используется для создания мастер-ключа.
  • Transform Rounds (4 байта): используется для создания мастер-ключа.
  • Заголовок KeePass 2.x

    В .kdbx файлах каждое поле заголовка состоит из 3 частей:

  • ID поля (1 байт): возможные значения от 0 до 10.
  • Длина данных (2 байта).
  • Данные ([длина данных] байт)
  • Заголовок .kdbx файла состоит из следующий полей:

    • ID=0x01 Comment: данное поле может быть представлено в заголовке, но в моей базе данных его не было.
    • ID=0x02 Cipher ID: UUID, указывающий на используемый метод шифрования (например, для AES 256 UUID = [0x31, 0xC1, 0xF2, 0xE6, 0xBF, 0x71, 0x43, 0x50, 0xBE, 0x58, 0x05, 0x21, 0x6A, 0xFC, 0x5A, 0xFF]).
    • ID=0x03 Compression Flags: ID алгоритма, использующегося для сжатия базы данных:
      • 0x00: None;
      • 0x01: GZip.
    • ID=0x04 Master Seed: используется для создания мастер-ключа.
    • ID=0x05 Transform Seed: используется для создания мастер-ключа.
    • ID=0x06 Transform Rounds: используется для создания мастер-ключа.
    • ID=0x07 Encryption IV: используется для расшифровки данных.
    • ID=0x08 Protected Stream Key: используется для расшифровки паролей.
    • ID=0x09 Stream Start Bytes: первые 32 байта расшифрованной базы данных. Они используются для проверки целостности расшифрованных данных и корректности мастер-ключа. Эти 32 байта рандомно генерируются каждый раз, когда в файле сохраняются изменения.
    • ID=0x0A Inner Random Stream ID: ID алгоритма, использующегося для расшифровки паролей:
      • 0x00: None;
      • 0x01: ARC4;
      • 0x02: Salsa20.
    • ID=0x00 End of Header: последнее поле заголовка базы данных, после него начинается сама база данных.

    Генерация мастер-ключа

    Генерация мастер-ключа происходит в 2 этапа:

  • Генерация составного ключа;
  • Генерация мастер-ключа на основе составного ключа.
  • 1. Генерация составного ключа

    Для генерации составного ключа используется хэш-алгоритм SHA256. В таблицах ниже представлен псевдокод для генерации составного ключа, исходя из того, какая версия KeePass используется, и какие входные данные необходимы для расшифровки базы данных (только пароль, только файл-ключ или все вместе):

    KeePass 1.x

    Пароль
    sha256(password)
    Файл-ключ
    sha256(keyfile)
    Пароль + Файл-ключ
    sha256(concat(sha256(password), sha256(keyfile)))

    KeePass 2.x

    Пароль
    sha256(sha256(password))
    Файл-ключ
    sha256(sha256(keyfile))
    Пароль + Файл-ключ
    sha256(concat(sha256(password), sha256(keyfile)))
    Windows User Account (WUA)
    sha256(sha256(WUA))
    Пароль + Файл-ключ + (WUA)
    sha256(concat(sha256(password), sha256(keyfile), sha256(WUA)))

    Обращаю внимание на то, что если для расшифровки базы данных необходимо несколько сущностей (например, пароль и файл-ключ), то сначала нужно получить хэш от каждой сущности, а потом соединить их вместе (concat) и взять хэш от объединенной последовательности.

    2. Генерация мастер-ключа на основе составного ключа

  • Нужно зашифровать составной ключ, полученный выше, с помощью алгоритма AES-256-EBC.
    • В качестве ключа нужно использовать Transform Seed из заголовка.
    • Данное шифрование нужно произвести Transform Rounds (из заголовка) раз.
  • С помощью SHA256 получаем хэш от зашифрованного составного ключа.
  • Соединяем Master Seed из заголовка с полученным хэшем.
  • С помощью SHA256 получаем хэш от объединенной последовательности — это и есть наш мастер-ключ!
  • Псевдокод<p>void GenerateMasterKey()
    {
    //шифруем составной ключ TransformRounds раз
    for(int i = 0; i < TransformRounds; i++) {
    result = encrypt_AES_EBC(TransformSeed, composite_key);
    composite_key = result;
    }</p>
    <source>//получаем хэш от зашифрованного составного ключа
    hash = sha256(composite_key);
    //объединяем полученный хэш с полем MasterSeed из заголовка
    key = concat(MasterSeed, hash);
    //получаем хэш от объединенной выше последовательности
    master_key = sha256(key);

    }

    Расшифровка данных KeePass 1.x

    Сразу после заголовка начинается сама зашифрованная база данных. Алгоритм расшифровки следующий:

  • Весь оставшийся кусок файла расшифровываем с помощью алгоритма AES-256-CBC.
    • В качестве ключа используем сгенерированный выше мастер-ключ.
    • В качестве вектора инициализации используем Encryption IV из заголовка.
  • Последние несколько байт расшифрованной базы данных являются лишними — это несколько одинаковых байт в конце файла (padding). Чтобы устранить их влияние, нужно прочитать последний байт расшифрованной БД — это то количество «лишних» байт, которое в дальнейшем учитывать не надо.
  • С помощью SHA256 получаем хэш от расшифрованных данных (байты из предыдущего пункта не учитываем).
  • Проверяем, что полученный хэш совпадает с полем Content Hash из заголовка:
    • eсли хэш совпадает, то мы успешно расшифровали нашу базу данных! Можно сохранить расшифрованные данные как .xml файл и убедиться, что все логины с паролями расшифрованы верно,
    • eсли хэш не совпадает, это значит, что либо был предоставлен не верный пароль или файл-ключ, либо данные были повреждены.
  • Псевдокод<p>bool DecryptKeePass1x()
    {
    //определяем длину зашифрованной БД
    //(размер файла — размер подписи — размер заголовка)
    db_len = file_size — signature_size — header_size;</p>
    <source>//расшифровываем данные
    decrypted_data = decrypt_AES_256_CBC(master_key, EncryptionIV, encrypted_data);
    //узнаем количество "лишних" байт
    extra = decrypted_data[db_len — 1];
    //получаем хэш от данных (без учета extra байт!)
    content_hash = sha256(decrypted_data[:(db_len — extra)]);
    //проверяем, что полученный хэш совпадает с полем СontentHash из заголовка
    if (СontentHash == content_hash)
    return true;
    else
    return false;

    }

    Расшифровка данных KeePass 2.x

    Сразу после поля End of Header заголовка начинается сама зашифрованная база данных. Алгоритм расшифровки следующий:

  • Весь оставшийся кусок файла расшифровываем с помощью алгоритма AES-256-CBC.
    • В качестве ключа используем сгенерированный выше мастер-ключ.
    • В качестве вектора инициализации используем Encryption IV из заголовка.
  • Последние несколько байт расшифрованной базы данных являются лишними — это несколько одинаковых байт в конце файла (padding). Чтобы устранить их влияние, нужно прочитать последний байт расшифрованной БД — это то количество «лишних» байт, которое в дальнейшем учитывать не надо.
  • Проверяем, что первые 32 байта расшифрованной базы данных совпадают с полем Stream Start Bytes заголовка:
    • eсли данные совпадают, значит мы сгенерировали правильный мастер-ключ,
    • eсли данные не совпадают, это значит, что либо был предоставлен неверный пароль, файл-ключ или WUA, либо данные были повреждены.
  • Если предыдущий пункт выполнен успешно, отбрасываем первые 32 байта. Проверяем поле Compression Flags заголовка. Если было использовано GZip сжатие файла, то распаковываем данные.
  • Приступаем к проверке целостности данных. Данные разбиты на блоки, максимальный размер блока равен 1024*1024. Каждый блок данных начинается с заголовка. Структура заголовка следующая:
    • ID блока (4 байта): номер блока начиная с 0;
    • Хэш данных блока (32 байта);
    • Размер блока (4 байта).
  • Следовательно, порядок действий следующий:
    • Считываем заголовок блока.
    • Считываем данные блока.
    • С помощью SHA256 получаем хэш от данных блока.
    • Проверяем, что хэш совпадает с хэшем из заголовка.
  • Осуществляем последовательность действий из предыдущего пункта для каждого блока данных. Если данные во всех блоках сохранны, то вырезаем все заголовки блоков, и полученная последовательность и есть расшифрованная база данных.
  • ВНИМАНИЕ: даже в расшифрованном .kdbx файле пароли могут находиться в зашифрованном виде.
  • Сохраняем расшифрованные и обезглавленные данные как .xml файл.
  • Находим в нем все ноды с именем «Value», атрибутом «Protected», значением этого атрибута «True» и берем значения этих нод. Это и есть все еще зашифрованные пароли.
  • Декодируем все зашифрованные пароли с помощью алгоритма base64decode.
  • В поле Inner Random Stream ID заголовка смотрим, какой алгоритм использовался при шифровании паролей. В моем случае это был Salsa20.
  • Генерируем псевдослучайную 64 байтную последовательность с помощью алгоритма Salsa20:
    • В качестве ключа используем хэш поля Protected Stream Key заголовка, полученный с помощью SHA256.
    • В качестве вектора инициализации используем константную 8-ми байтную последовательность 0xE830094B97205D2A.
  • ВАЖНО: С помощью этой 64 байтной последовательности можно расшифровать ровно 64 символа по порядку соединенных вместе декодированных паролей. Если этого недостаточно для расшифровки всех паролей, нужно сгенерировать следующую псевдослучайную последовательность и продолжить расшифровку паролей и т.д. до конца.
  • Для получения финального пароля, необходимо сделать XOR декодированного с помощью base64decode пароля с псевдослучайной последовательностью, полученной в предыдущем пункте (более понятно последовательность действий представлена в псевдокоде ниже).
  • ОЧЕНЬ ВАЖНО: пароли должны расшифровываться по порядку! Именно в той последовательности, в которой они представлены в xml файле.
  • Находим в xml файле все ноды с именем «Value», атрибутом «Protected», значением этого атрибута «True»:
    • Заменяем значение атрибута на «False».
    • Значение ноды заменяем расшифрованным паролем.
  • И вот только теперь мы получили полностью расшифрованную базу данных KeePass 2.x! Ура!=)
  • Псевдокод<p>bool DecryptKeePass2x()
    {
    //определяем длину зашифрованной БД
    //(размер файла — размер подписи — размер заголовка)
    db_len = file_size — signature_size — header_size;</p>
    <source>//расшифровываем данные
    decrypted_data = decrypt_AES_256_CBC(master_key, EncryptionIV, encrypted_data);
    //узнаем количество "лишних" байт
    extra = decrypted_data[db_len — 1];
    db_len -= extra;
    //проверяем, что первые 32 байта расшифрованной БД
    //совпадают с полем StreamStartBytes заголовка
    if (StreamStartBytes != decrypted_data[0:32])
    return false;
    //отбрасываем эти 32 байта
    db_len -= 32;
    decrypted_data += 32;
    //проверяем поле CompressionFlag заголовка
    //если файл был сжат, распаковываем его
    if (CompressionFlag == 1)
    unzip(decrypted_data);
    //проверяем целостность данных
    while (db_len > (BlockHeaderSize))
    {
    //считываем заголовок базы данных
    block_data = decrypted_data[0:BlockHeaderSize];
    decrypted_data += BlockHeaderSize;
    db_len -= BlockHeaderSize;
    if (block_data.blockDataSize == 0) {
    break;
    }
    //получаем хэш данных блока
    hash = sha256(decrypted_data[0:block_data.blockDataSize]);
    //проверяем, что полученный хэш совпадает с хэшем из заголовка
    if(block_data.blockDataHash == hash) {
    pure_data += decrypted_data[0:block_data.blockDataSize];
    decrypted_data += block_data.blockDataSize;
    db_len -= block_data.blockDataSize;
    }
    else {
    return false;
    }
    }
    //сохраняем расшифрованные и обезглавленные данные как xml файл
    xml = pure_data.ToXml();
    //получаем хэш от поля ProtectedStreamKey заголовка
    key = sha256(ProtectedStreamKey);
    //инициализируем алгоритм Salsa20
    IV_SALSA = 0xE830094B97205D2A;
    salsa.setKey(key);
    salsa.setIv(IV_SALSA);
    stream_pointer = 0;
    key_stream[64] = salsa.generateKeyStream();
    //расшифровываем пароли
    while(true)
    {
    //находим следующую попорядку ноду с именем "Value",
    //атрибутом "Protected", значением атрибута "True"
    node = xml.FindNextElement("Value", "Protected", "True");
    if (node == NULL) {
    break;
    }
    //берем значение ноды и декодируем с помощью алгоритма base64decode
    decoded_pass = base64decode(node.value);
    //расшифровываем пароль с помощью псевдослучайной последовательности key_stream
    for (int i = 0; i < len(decoded_pass); i++) {
    decoded_pass[i] = decoded_pass[i] ^ key_stream[stream_pointer];
    stream_pointer++;
    //если 64 байтной псевдослучайной последовательности не хватило,
    //генерируем еще одну последовательность
    if (stream_pointer >= 64) {
    key_stream[64] = salsa.generateKeyStream();
    stream_pointer = 0;
    }
    }
    //заменяем значение атрибута "Protected" на "False"
    node.attribute.value = "False";
    //заменяем зашифрованный пароль дешифрованным
    node.value = decoded_pass;
    }
    return true;

    }

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

    Warning: include(/volume1/web/cyberhost.biz/wp-content/plugins/jaster_cahce/cache/bottom-cache.php): failed to open stream: No such file or directory in /volume1/web/cyberhost.biz/index.php on line 13 Call Stack: 0.0000 356272 1. {main}() /volume1/web/cyberhost.biz/index.php:0 Warning: include(): Failed opening '/volume1/web/cyberhost.biz/wp-content/plugins/jaster_cahce/cache/bottom-cache.php' for inclusion (include_path='.:/usr/share/pear') in /volume1/web/cyberhost.biz/index.php on line 13 Call Stack: 0.0000 356272 1. {main}() /volume1/web/cyberhost.biz/index.php:0