redmanmale

if-goto

Защита паролей в конфигах с помощью Windows Data Protection API

Abstract

В этой небольшой заметке поделюсь опытом защиты паролей в конфигах приложения. Опишу подводные камни, с которыми столкнулся в процессе интеграции Windows Data Protection API (DPAPI) в код наших модулей на C# и Java.

Introduction

Наше приложение, как это в общем-то принято, всякие логины-пароли к сторонним сервисам хранит у себя в конфигах. Хранить пароли в открытом виде это не очень, но какое-то время до этого руки не доходили. А тут, значит, дошли. Пароли надо шифровать.
Единственной целевой платформой (на тот момент) была Windows, поэтому после небольшого исследования мы остановили свой выбор на DPAPI. Код работы с конфигом в целом и шифрование в частности достаточно инкапсулирован, при необходимости работу с шифрованием несложно заменить.

Methods

DPAPI достаточно примитивно и по сути имеет два метода - зашифровать и расшифровать, Protect и Unprotect, соответственно.

Пример кода на C#; библиотека на Java; если вас не устраивает лицензия (LGPL), вся работа с DPAPI умещается в пару экранов, можно написать самому.

Но расшифровка самим приложением на рантайме это полдела. Перед запуском, но после установки и задания паролей, конфиги надо зашифровать. А это самое интересное!

К сожалению, до инсталлятора руки тоже пока не дошли, и установка производится bat-скриптами (не шучу). Конфигов несколько, структура у них сложная, паролей внутри много; шифровать конфиг целиком нельзя. Писать логику парсинга сложного конфига в bat-скрипте — занятие весьма на любителя.
Сделали вот что: в существующий пайплайн bat-скриптов добавляем один новый, который вызывает Powershell-скрипт. А в него можно вставлять логику на C#, а это совсем другое дело. Profit (почти)!

Создаём отдельную сборку на C#, там пишем всю обработку конфигов, шифрование… не забываем тестировать, всё как положено.

Нюансы:

  • В Powershell-скриптах можно использовать сборки на C# до 5.0 включительно (никаких тебе inline out-переменных), поэтому код надо писать с оглядкой на это (в студии в настройках проекта можно указывать версию языка).
  • C# внутри Powershell’а можно использовать разными способами:
    • указывать сборки и использовать классы из них;
    • вставлять прямо в скрипт код на C#. При выполнении скрипта он соберётся во временную сборку и загрузится в память.

Плюс второго способ в том, что не нужно отдельно собирать сборку по работе с конфигом, делать зависимые билды на CI и прочее. Минус — дублирование C# кода (в модуле и скрипте). Смотрите сами.

Ещё момент: шифрование должно производиться под тем же пользователем, под которым потом будет работать приложение (иначе не расшифруется), а скрипты могут запускаются под другим.
Значит нужно запросить credentials нужного пользователя, и запустить скрипт шифрования от его имени.

$account = Get-Credential
if (!$account)
{
    Write-Host("Authentication failed. Invalid user name and/or password.") -ForegroundColor red
    pause
    [Environment]::Exit(1)
}

Start-Process powershell -Credential $account -Argument "whoami `r`n pause"

Results

На выходе имеем цепочку: bat-скрипт -> Powershell-скрипт для запроса логина/пароля пользователя и запуска от его имени -> Powershell-скрипт для самого шифрования, где внури лежит код на C#.

Да, немного громоздко, зато каждую часть удобно отлаживать и тестировать отдельно, и парсинг конфигов написан на нормальных языках (C#, Java). Успех!