Хранение секретов в Windows-приложениях - избегаем открытых настроек с помощью DPAPI

· · Разработка Windows, Безопасность, DPAPI, C# / .NET, Win32

В предыдущей статье «Чек-лист минимальной безопасности при разработке Windows-приложений» мы обозначили минимальную планку: «не размещать секреты в исходном коде или в открытых настройках» и «в Win32 / .NET использовать DPAPI / ProtectedData».

В этот раз мы копнём чуть глубже одну из этих рекомендаций: «использовать DPAPI, чтобы хотя бы было лучше, чем открытый текст».

Речь пойдёт о таких Windows-приложениях:

  • десктопные приложения на WPF / WinForms / WinUI
  • Windows-клиенты на C# / .NET
  • приложения, которым хочется сохранять учётные данные подключения или API-токены в локальном файле конфигурации

Здесь разбирается реалистичный дизайн для того, чтобы «секрет, который приходится хранить локально, не оставался хотя бы в открытом виде в appsettings.json». Речь не о «абсолютной защите, побеждающей любого злоумышленника». Если перегнуть в эту сторону, разговор о безопасности быстро превращается в страшилку.

1. Сначала — вывод

На практике проще всего рассуждать в таком порядке.

  1. В принципе не давать клиенту долгоживущий секрет
    • предпочитать аутентификацию Windows, интегрированную аутентификацию, интерактивный вход пользователя, хранение секретов на стороне сервера
  2. Если локальное хранение всё же необходимо, не хранить в открытом виде
    • на Windows в первую очередь рассматривать DPAPI / ProtectedData
  3. Для обычного десктопного приложения базовый выбор — DataProtectionScope.CurrentUser
    • у LocalMachine область применения довольно узкая
  4. DPAPI не защищает вплоть до ситуации «устройство полностью скомпрометировано»
    • код, выполняющийся с теми же правами пользователя, по сути способен расшифровать то же, что и сам этот пользователь

И самый важный тезис этой статьи вот в чём:

«Секретный ключ всё равно нужно где-то хранить, так разве открытый текст и DPAPI не одно и то же с точки зрения безопасности?»

Это наполовину верно, а вывод — неверен.

  • Собственное шифрование AES + ключ в том же приложении или той же конфигурации — это действительно близко к открытому тексту
  • Но DPAPI перекладывает управление ключом на ОС и привязывает того, кто может расшифровать данные, к «данному пользователю Windows» или «данному компьютеру»
  • В результате устойчивость к таким инцидентам, как утечка одного лишь конфигурационного файла, вынос его на другой ПК, ошибочная отправка, утечка резервной копии или попадание в репозиторий, кардинально меняется

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

Утверждать, что положить ключ под коврик у входа — то же самое, что выдать ключ в администраторской после проверки личности, — было бы немного грубым упрощением.

2. Почему открытые настройки опасны

Причина, по которой хранение в открытом виде опасно, гораздо приземлённее теории криптографии. На практике утечки происходят примерно по таким путям.

  • файл конфигурации целиком попадает в Git
  • файл конфигурации целиком оказывается в ZIP-архиве для расследования сбоя
  • файл конфигурации прикладывают к обращению в поддержку
  • третьи лица могут прочитать его через резервные копии или общие папки
  • строка подключения или токен целиком попадают в лог
  • уволенный сотрудник или другой пользователь читает файл на том же устройстве

Главная проблема открытого текста в том, что «секрет заканчивается в тот момент, когда его смогли прочитать».

  • открыли файл — и всё кончено
  • скопировали — и всё кончено
  • приложили к письму — и всё кончено
  • осталось в репозитории — и придётся разбираться с этим почти вечно

Атакующему даже не нужно быть искусным. Одной лишь возможности «открыть в текстовом редакторе» уже достаточно, чтобы защита была весьма слабой.

3. Ответ на «раз секретный ключ всё равно где-то хранится, разве это не одно и то же?»

Этот вопрос вполне обоснован. И если ответить на него небрежно, статья про безопасность тут же становится расплывчатой.

Ответ такой: в смысле «ключ где-то нужен» — да; в смысле «поэтому это одно и то же» — нет.

3.1. Что одинаково, а что различается

Действительно, шифрованию в конечном счёте нужен какой-то root of trust. Секрет не появляется бесплатно из ниоткуда во вселенной — тут мир суров.

Однако различие в безопасности определяется тремя моментами.

  • держит ли приложение ключ напрямую
  • к какой стороне привязан ключ
  • можно ли расшифровать данные, если украден только файл

Если свести эти различия в таблицу, получится так.

Способ Прочитали файл конфигурации Файл вынесли на другой ПК Прочитал другой пользователь на том же ПК Код, работающий с теми же правами пользователя
Открытый текст Утекает сразу же Утекает как есть Утекает как есть Естественно, может прочитать
Собственное шифрование + ключ в той же конфигурации / бинарнике Утекает почти всегда Утекает почти всегда Утекает почти всегда Естественно, может расшифровать
DPAPI + CurrentUser Файл сам по себе сразу не прочитать Обычно трудно расшифровать Обычно трудно расшифровать Может расшифровать
DPAPI + LocalMachine Файл сам по себе сразу не прочитать Вне этого ПК обычно трудно расшифровать На том же ПК может расшифровать широкий круг процессов Может расшифровать

Важно, что DPAPI разделяет «умение прочитать файл» и «умение воспользоваться секретом».

В открытом тексте это одно и то же. Если файл можно прочитать, значит, можно прочитать и секрет.

А в DPAPI, как минимум с CurrentUser, расшифровка должна происходить:

  • от имени того самого пользователя Windows
  • в том самом контексте Windows
  • через защитный механизм ОС

На месте реального инцидента эта разница оказывается весьма значительной.

3.2. «Но если это тот же пользователь, разве нельзя расшифровать?» — совершенно верно

Об этом стоит сказать без утайки.

Код, выполняемый с теми же правами пользователя, по сути способен расшифровать всё, что может расшифровать сам этот пользователь.

То есть DPAPI не рассчитан в первую очередь на такие ситуации:

  • устройство уже скомпрометировано вредоносным ПО
  • атакующий способен выполнять код от имени этого пользователя
  • устройство полностью захвачено на уровне администратора

В такой ситуации, раз само приложение способно расшифровать данные, то же самое сможет и код атакующего. Тут аргумент «но ведь мы зашифровали» звучит не очень убедительно.

DPAPI действует прежде всего против «утечки файлов, ошибочного размещения, офлайн-выноса и доступа со стороны другого пользователя».

Перепутав это, можно попасть в обе ловушки:

  • недооценить то, что действительно защищается, и отказаться от использования
  • переоценить то, что защитить нельзя, и почувствовать ложное спокойствие

Обе одинаково незаметно опасны.

3.3. В чём же тогда реальная польза

Если сформулировать пользу DPAPI одной фразой: «можно отделить сам секрет от читаемости файла конфигурации».

Например, в таких инцидентах разница между открытым текстом и DPAPI проявляется:

  • пользователь случайно отправил файл конфигурации в поддержку
  • файл конфигурации попал в ZIP для расследования
  • из резервной копии утёк только файл конфигурации
  • файл скопировали в общую папку
  • разработчик видит только зашифрованные данные и не может прочитать содержимое

Это вполне реальное преимущество. Не нужно представлять атакующего суперменом из кино — можно просто сократить радиус повседневных инцидентов.

4. Почему DPAPI — золотая середина

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

4.1. Управление ключами можно переложить на ОС

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

С DPAPI проблему «как создать ключ шифрования и где его хранить» можно вывести за пределы реализации приложения.

В этом смысле ближе к сути видеть в DPAPI не «API для выбора алгоритма шифрования», а «API, делегирующий управление ключами операционной системе».

4.2. Сторону, способную расшифровать данные, можно привязать к пользователю Windows или к компьютеру

Для обычного десктопного приложения во многих случаях достаточно выбрать CurrentUser. Расшифровка возможна при условии, что:

  • этот пользователь выполнил вход в систему
  • обработка выполняется в контексте этого пользователя

Благодаря этому вы получаете свойство: скопировать только зашифрованные данные на другой ПК и сразу воспользоваться ими не получится.

4.3. Легко получить и обнаружение подделки

При собственном шифровании часто бывает так: «зашифровали через AES — и всё», а про обнаружение подделки забывают.

DPAPI обеспечивает и защиту целостности зашифрованных данных, поэтому обнаружение попыток произвольно изменить зашифрованные данные легко переложить на механизм самой ОС — это практическое преимущество.

4.4. Легко использовать из C# / .NET

В C# можно напрямую использовать System.Security.Cryptography.ProtectedData. Отсутствие необходимости добавлять сторонние библиотеки — большое подспорье для приложений, ориентированных исключительно на Windows.

5. Что DPAPI защищает, а что нет

Здесь безопаснее чётко разграничить.

5.1. Что становится проще защитить

DPAPI эффективен как минимум в таких сценариях:

  • утечка открытого текста файла конфигурации
  • вынос файла на другой ПК
  • доступ со стороны другого пользователя на том же ПК (при условии CurrentUser)
  • утечка через резервные копии или вложения
  • состояние «случайно смогли прочитать» на этапе разработки и сопровождения

5.2. Что не защищается или защищается слабо

С другой стороны, в следующих ситуациях не стоит слишком полагаться на DPAPI.

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

Последний пункт — «долгоживущий секрет, общий для всех клиентов» — особенно важен.

Например, такие решения:

  • встраивать одинаковый API-ключ для всех клиентов
  • иметь одинаковый общий пароль на всех устройствах
  • распространять фиксированный ключ расшифровки, полностью замкнутый на клиенте

легко распространяются на всех сразу, как только их извлекли хотя бы с одной машины.

DPAPI эффективен, чтобы «сделать место хранения лучше, чем открытый текст», но он не оправдывает хранение на клиенте того секрета, которого там вообще не должно быть.

6. Выбор между CurrentUser и LocalMachine

Это довольно важный момент. Небрежный выбор сильно меняет смысл.

6.1. Базовый вариант — CurrentUser

Для обычного десктопного приложения Windows базовым вариантом стоит рассматривать CurrentUser.

Подходящие примеры:

  • пользовательские десктопные приложения на WPF / WinForms / WinUI
  • приложения с настройками и учётными данными для каждого пользователя
  • приложения, хранящие настройки под %LocalAppData% или %AppData%

В этом случае данные легко трактовать как «секрет конкретного пользователя Windows».

6.2. У LocalMachine область применения довольно узкая

LocalMachine выглядит удобным, но для обычного десктопного приложения он слишком широк.

Подходит он, например, в таких случаях:

  • служба Windows на доверенной машине с единственным назначением
  • секрет, используемый только конкретным процессом на этой машине
  • случай, когда данные должны использоваться на одном устройстве вне зависимости от того, какой пользователь вошёл в систему

Но предостережения здесь серьёзные.

  • доступен для расшифровки широкому кругу процессов, работающих на этом ПК
  • легко становится опасным на общих терминалах, RDS, узлах-плацдармах, в средах с несколькими пользователями
  • выбор по принципу «удобно, потому что подходит всем» почти всегда приводит к проблемам позже

6.3. Если сомневаетесь, рассуждайте так

  • обычное UI-приложение -> CurrentUser
  • особый случай, когда действительно нужна защита на уровне машины -> LocalMachine
  • нужна расшифровка любым пользователем, но на устройстве есть и другие пользователи -> обычно лучше пересмотреть сам дизайн

6.4. Со службами и impersonation осторожности требуется больше

Если задействованы службы Windows или impersonation, смысл CurrentUser немного усложняется.

  • от имени какой учётной записи выполняется процесс
  • загружен ли профиль этой учётной записи
  • в каком контексте происходит момент расшифровки

Если здесь возникнет рассинхронизация, легко получить ситуацию «зашифровать удалось, а расшифровать — нет». Для служб «просто взять CurrentUser» иногда недостаточно.

7. Минимальные рекомендации по реализации

Если цель Windows-приложения — просто «отказаться от открытого текста в файле конфигурации», дизайн не обязан быть сложным. Однако есть несколько моментов, которые не стоит упускать.

7.1. Защищаем только секреты

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

Например, разделите так. Следующее часто можно оставить в открытом виде:

  • URL сервера
  • имя пользователя
  • имя базы данных
  • флаги функциональности

А вот следующее — объект защиты:

  • пароли
  • API-токены
  • refresh-токены
  • учётные данные общей папки

При таком разделении вы получаете:

  • удобное редактирование конфигурации
  • удобную проверку различий
  • ясность в том, что именно является секретом
  • простоту эксплуатации в целом

7.2. Базовое место хранения — per-user

Для обычного десктопного приложения базовым местом хранения должна быть директория per-user.

  • %LocalAppData%\Vendor\App\settings.json
  • %AppData%\Vendor\App\settings.json

Как минимум, не стоит небрежно размещать файл под папкой установки или в легко доступном для совместного использования месте.

Даже при защите через DPAPI, если ACL места хранения выстроены небрежно, получаем: «зашифрованные данные читаются», «структура конфигурации видна», «эксплуатационные ошибки случаются». Защита эффективнее не в один слой, а в несколько.

7.3. optionalEntropy — не универсальный второй ключ

В ProtectedData можно передать optionalEntropy. Это удобно, но это не «волшебный второй ключ», который сделает всё безопасным, если зашить его в бинарник.

  • размещённый в том же файле, он не становится секретом
  • зашитый как фиксированное значение в бинарнике, он тоже не тянет на надёжный секрет
  • тем не менее он полезен для идентификации назначения и предотвращения неправильного использования

На практике достаточно передавать в качестве фиксированной последовательности байт:

  • имя приложения
  • название назначения
  • идентификатор версии

и использовать это, чтобы «случайно не принять зашифрованные данные другого назначения».

7.4. Это не значит, что зашифрованные данные можно класть в Git

Это тоже незаметно важный момент.

Зашифрованные данные DPAPI намного лучше открытого текста, но это не значит, что можно целиком класть файл конфигурации в репозиторий.

Причина проста:

  • зашифрованные данные хранятся долго
  • когда-нибудь то же устройство или тот же контекст может воспроизвестись
  • в файле есть и информация, помимо самого секрета
  • складывается культура «раз защищено, можно обращаться небрежно»

«Лучше, чем открытый текст» и «безопасно, где бы ни лежало» — совершенно разные вещи.

7.5. Не выводить в лог

Неожиданно частый паттерн — всё сводится на нет из-за вывода расшифрованного значения в лог.

  • при сбое подключения выводить строку подключения целиком
  • при API 401 оставлять заголовок Authorization в логах
  • подмешивать секрет в сообщение исключения

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

8. Минимальный пример реализации на C# / .NET

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

using System;
using System.Security.Cryptography;
using System.Text;

public static class DpapiSecretProtector
{
    // Для идентификации назначения. Не второй секретный ключ.
    private static readonly byte[] Entropy =
        Encoding.UTF8.GetBytes("ComComponent:DesktopApp:SettingsSecret:v1");

    public static string ProtectToBase64(string plaintext)
    {
        ArgumentNullException.ThrowIfNull(plaintext);

        byte[] plainBytes = Encoding.UTF8.GetBytes(plaintext);
        byte[] protectedBytes = Array.Empty<byte>();

        try
        {
            protectedBytes = ProtectedData.Protect(
                plainBytes,
                optionalEntropy: Entropy,
                scope: DataProtectionScope.CurrentUser);

            return Convert.ToBase64String(protectedBytes);
        }
        finally
        {
            Array.Clear(plainBytes, 0, plainBytes.Length);

            if (protectedBytes.Length > 0)
            {
                Array.Clear(protectedBytes, 0, protectedBytes.Length);
            }
        }
    }

    public static string UnprotectFromBase64(string protectedBase64)
    {
        ArgumentNullException.ThrowIfNull(protectedBase64);

        byte[] protectedBytes = Convert.FromBase64String(protectedBase64);
        byte[] plainBytes = Array.Empty<byte>();

        try
        {
            plainBytes = ProtectedData.Unprotect(
                protectedBytes,
                optionalEntropy: Entropy,
                scope: DataProtectionScope.CurrentUser);

            return Encoding.UTF8.GetString(plainBytes);
        }
        finally
        {
            Array.Clear(protectedBytes, 0, protectedBytes.Length);

            if (plainBytes.Length > 0)
            {
                Array.Clear(plainBytes, 0, plainBytes.Length);
            }
        }
    }
}

Использование простое.

string protectedPassword = DpapiSecretProtector.ProtectToBase64(password);

// Сохранение, например, в JSON
// settings.DbPasswordProtected = protectedPassword;

string password = DpapiSecretProtector.UnprotectFromBase64(settings.DbPasswordProtected);

Файл конфигурации может выглядеть, например, так.

{
  "ApiBaseUrl": "https://api.example.com/",
  "UserName": "app-user",
  "PasswordProtected": "AQAAANCMnd8BFdERjHoAwE..."
}

Преимущества такой формы:

  • URL и имя пользователя можно редактировать как обычно
  • защищён только пароль
  • структура конфигурации легко читается
  • меньше риск инцидентов, чем при хранении в открытом виде

9. Дизайн, который всё равно остаётся опасным

Даже при использовании DPAPI следующие решения остаются опасными.

9.1. Долго носить с собой расшифрованное значение

Расшифрованное значение желательно не:

  • выводить в лог
  • выводить на экран
  • включать в исключение
  • оставлять надолго в долгоживущем объекте

«Зашифровано при хранении» и «безопасно во время использования» — разные вопросы.

9.2. Давать одинаковый секрет для всех установок

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

Потому что если приложение способно расшифровать данные хотя бы на одной машине, этот секрет можно извлечь.

Секреты такого рода лучше двигать в сторону:

  • хранения на стороне сервера
  • клиента, у которого есть только токен
  • учётных данных для каждого пользователя
  • токенов с ограниченным сроком действия

9.3. Выбирать LocalMachine, потому что «удобно»

Это действительно распространённая ситуация. Возникает соблазн выбрать LocalMachine по причинам вроде:

  • читается даже после смены пользователя
  • читается и из службы
  • удобно, если просто работает

Но для обычного десктопного приложения это расширяет возможность расшифровки на другие процессы этого же ПК, что сильно меняет смысл.

9.4. Успокаиваться, добавив собственное шифрование

Вместо DPAPI применять решения вроде:

  • зашивать ключ AES в исходный код
  • размещать ключ AES в другом поле файла конфигурации
  • трактовать «слегка обфусцированную строку» как ключ

— как правило, малоэффективно.

Между «не открытый текст» и «безопасно» лежит довольно большая пропасть.

10. Случаи, когда DPAPI недостаточно

DPAPI удобен, но не всесилен. В следующих случаях стоит рассмотреть другие варианты.

10.1. Хотите запускать не только на Windows

DPAPI / ProtectedData рассчитаны на Windows. Для кроссплатформенного приложения на этом строить нельзя.

10.2. Нужно работать с одним и тем же секретом на нескольких машинах или у нескольких пользователей

Требование расшифровывать одни и те же зашифрованные данные на нескольких ПК или использовать их совместно несколькими пользователями выходит за пределы сильной стороны DPAPI — привязки «к этому устройству, к этому пользователю».

В таком случае стоит рассмотреть другой дизайн, соответствующий требованиям:

  • управление секретами на стороне сервера
  • инфраструктура учётных данных
  • аутентификация Windows / интегрированная аутентификация
  • хранилище учётных данных для приложения

10.3. Объект хранения — сами учётные данные пользователя

Для packaged desktop app / WinUI, когда объект хранения явно представляет собой пару:

  • имя пользователя
  • пароль

вариантом становится и Credential Locker. Однако центральная тема этой статьи — именно практическая линия DPAPI для «отказа от открытого текста в файле конфигурации Windows-клиента».

11. Рекомендуемый порядок приоритетов на практике

Напоследок: если сомневаетесь на практике, проще всего рассуждать в таком порядке.

Приоритет 1: вообще не хранить

  • аутентификация Windows
  • интегрированная аутентификация
  • интерактивный вход
  • хранение секрета на стороне сервера
  • короткоживущие токены

Приоритет 2: двигаться в сторону секретов для каждого пользователя

  • per-user вместо общего секрета
  • обновляемые токены вместо долгоживущих фиксированных учётных данных
  • избегать ключа, общего для всех клиентов

Приоритет 3: если нужно локальное хранение — DPAPI

  • как правило, CurrentUser
  • место хранения — per-user
  • защищать только секретные поля
  • не выводить в лог

Приоритет 4: LocalMachine — исключение

  • действительно ли необходима привязка на уровне машины
  • не заходят ли на это устройство другие пользователи
  • оправдан ли такой выбор с точки зрения дизайна службы

12. Итог

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

И на вопрос:

«Ключ всё равно где-то хранится, так разве это не одно и то же?»

практичнее всего ответить так.

  • при собственном шифровании с ключом в том же месте — это действительно почти одно и то же
  • DPAPI — не одно и то же
    • управление ключами можно переложить на ОС
    • сторону, способную расшифровать данные, можно привязать к пользователю Windows / компьютеру
    • утечка одного лишь файла перестаёт автоматически означать утечку секрета
  • однако это не решает
    • код, работающий с теми же правами пользователя
    • полностью скомпрометированное устройство
    • долгоживущий общий секрет, которого вообще не должно быть на клиенте

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

В реальной практике работы с Windows-клиентами эта разница весьма значительна. Начать с того, чтобы не упустить именно этот момент, — самый реалистичный путь.

13. Справочные материалы

  • Предыдущая статья: https://comcomponent.com/ru/blog/2026/03/14/001-windows-app-security-minimum-checklist/
  • Microsoft Learn: CryptProtectData https://learn.microsoft.com/en-us/windows/win32/api/dpapi/nf-dpapi-cryptprotectdata
  • Microsoft Learn: ProtectedData https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.protecteddata?view=windowsdesktop-10.0
  • Microsoft Learn: DataProtectionScope https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.dataprotectionscope?view=windowsdesktop-10.0
  • Microsoft Learn: How to: Use Data Protection https://learn.microsoft.com/en-us/dotnet/standard/security/how-to-use-data-protection
  • Microsoft Learn: Credential locker for Windows apps https://learn.microsoft.com/en-us/windows/apps/develop/security/credential-locker

Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.

Если ваше Windows-приложение приняли за вирус — как реагировать на ложные срабатывания Microsoft Defender и жить с влиянием на производительность

Разбираем правильный порядок действий, если Microsoft Defender ложно определяет ваше Windows-приложение как вредоносное: как устроена сов...

Эти страницы показывают тему статьи в более широком контексте услуг и решений.

Статья напрямую связана со следующими услугами.

Технические консультации и ревью дизайна

Если хочется начать с пересмотра открытых настроек в существующем приложении или разобраться, когда использовать DPAPI, а когда Credential Locker, эта тема хорошо подходит для технической консультации и ревью архитектуры.

Частые вопросы

Вопросы, которые часто возникают при консультациях по теме статьи.

Что такое DPAPI?
DPAPI (Data Protection API) — это механизм защиты данных, предоставляемый Windows: он делегирует управление ключами шифрования операционной системе и привязывает сторону, способную расшифровать данные, к конкретному пользователю Windows или к конкретному компьютеру. Из C# / .NET он доступен через класс System.Security.Cryptography.ProtectedData без необходимости добавлять сторонние библиотеки. Ближе к сути смотреть на него не как на API для выбора алгоритма шифрования, а как на API, делегирующий управление ключами операционной системе. Это реалистичный вариант, чтобы не хранить пароли и API-токены, сохраняемые в конфигурационных файлах, в открытом виде.
Ключ всё равно где-то хранится, так разве открытый текст и DPAPI не одно и то же?
Нет, это не одно и то же. Если использовать собственное шифрование AES и хранить ключ в том же приложении или файле конфигурации, это действительно близко к открытому тексту. Но DPAPI перекладывает управление ключами на ОС и привязывает сторону, способную расшифровать данные, к пользователю Windows или к компьютеру. В результате устойчивость к таким инцидентам, как утечка одного лишь конфигурационного файла, вынос его на другой ПК, ошибочная отправка, утечка резервной копии или попадание в репозиторий, кардинально меняется. Принципиальное отличие DPAPI от открытого текста в том, что он позволяет разделить «умение прочитать файл» и «умение воспользоваться секретом».
От чего DPAPI не защищает?
Код, выполняемый с теми же правами пользователя, по сути способен расшифровать всё, что может расшифровать сам этот пользователь. Поэтому DPAPI не защищает от ситуаций, когда устройство уже скомпрометировано вредоносным ПО, захвачено с правами администратора, а также не защищает открытый текст в памяти после расшифровки. Кроме того, долгоживущий секрет, одинаковый для всех клиентов, легко распространяется на всех сразу, как только его извлекли хотя бы с одной машины, — хранение через DPAPI это принципиально не решает. DPAPI эффективен главным образом против утечки файлов, их неправильного размещения, офлайн-выноса и доступа со стороны другого пользователя.
Что использовать — CurrentUser или LocalMachine у DataProtectionScope?
Для обычного десктопного приложения Windows базовый выбор — CurrentUser: данные можно трактовать как секрет конкретного пользователя, и даже если скопировать только зашифрованные данные на другой ПК, воспользоваться ими напрямую будет сложно. LocalMachine доступен для расшифровки широкому кругу процессов, работающих на этом ПК, поэтому легко становится опасным на общих терминалах и в средах с несколькими пользователями — область применения довольно узкая, например службы Windows на доверенной машине с единственным назначением. Если выбрать LocalMachine просто потому, что «удобно — им может пользоваться кто угодно», это почти всегда приводит к проблемам позже.

Об авторе

Страница с профилем автора статьи.

Го Комура

Представитель KomuraSoft LLC

Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.

Публичные ссылки

Вернуться в блог