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

    Итак :


0. Принципы и понятия

    - KeyDB
      Ключ шифрования БД. Симметричный, используется для зашифрования и
расшифрования. Генерируется автоматически и не хранится в открытом виде нигде

    - KeyAuth (KeyAuthOpen\KeyAuthClosed)
      Пара ключей авторизации. Несимметричная. Используется для шифрования
KeyDB
и для авторизации приложений. Генерируется автоматически или по запросу (спец.
утилитой).  Открытая часть доступна движку, закрытая - приложению пользователя
и\или криптоплагину.

    - KeySession (KeySessionOpen\KeySessionClosed)
      Пара ключей используемых при авторизации приложения. Несимметричная.
Генерируется автоматически в процессе авторизации приложения для безопасной
передачи других ключей

    - криптоплагин
      подключаемая библиотека, реализующая спец. интерфейс

    - PluginID
      уникальный идентификатор криптоплагина. Служит для различения
криптоплагинов разных производителей в случае их одновременного использования
одним движком. Может (должен?) отличаться для разных версий одного
криптоплагина. Определяется производителем

    - MethodID
      название метода шифрования. Служит для выбора метода шифрования БД (и
длины ключа ?), если установленные криптоплагины поддерживают более одного
метода. Определяется производителем, хорошим тоном считается использование
общеупотребительных названий (DES, AES, BlowFish etc)

    - KeyName
      имя, имеющее смысл только для криптоплагина. С этим именем ассоциируется
место хранения криптоплагином ключа авторизации. Задаётся пользователем явно
при
зашифровывании БД, либо генерируется движком автоматически




1. Создание зашифрованной или шифрование ранее не зашифрованной БД

<CREATE | ALTER> DATABASE ...
    ENCRYPTED BY <[PluginID.]MethodID | DEFAULT> [USING KEY <KeyName>]
|
    NOT ENCRYPTED

    При задании PluginID и MethodID среди установленных плагинов ищется пара с
заданными пар-рами. При задании только MethodID выбирается первый подходящий
плагин, знающий данный метод шифрования. При задании DEFAULT криптоплагин и
метод определяется из конфига (или используется встроенный, если есть).

    Необязательный KeyName задаёт имя ключа авторизации (KeyAuth) во внешнем
хранилище. Хранилище полностью определяется криптоплагином. Заданное имя
позволяет использовать общий ключ авторизации для нескольких БД. Если имя не
задаётся, оно генерируется движком (на основе имени файла БД или случайным
образом ?)

NOT ENCRYPTED - расшифровывает БД

Если БД уже зашифрована, то доступна толька опция NOT ENCRYPTED

Команда доступна SYSDBA и OWNER'у

а) выбираем криптоплагин

б) криптоплагин генерирует ключ шифрования БД (KeyDB). Пара ключей авторизации
    (KeyAuthOpen \ KeyAuthClosed) ищется по заданному (сгенерированному
движком)
    KeyName. Если такой пары ключей не найдено, она генерируется
криптоплагином
    и сохраняется во внешнем хранилище под именем KeyName

в) шифруем KeyDB с помощью открытой части ключа авторизации
    KeyDB_enc = Encrypt(KeyDB, KeyAuthOpen)

г) сохраняем KeyDB_enc на header_page (или спец. странице keys_page)

д) сохраняем на header_page PluginID, MethodID, KeyName и KeyAuthOpen как
    признак того, что БД зашифрована

е) header_page (и keys_page) не шифруются

ж) заголовки страниц (Ods::pag) не шифруются, добавляется флаг страницы,
    указывающий на то, что она зашифрованна. Флаг одинаков для всех типов
    страниц

з) в случае ALTER DATABASE БД зашифровывается в фоновом режиме (или онлайн - в
    течение времени выполнения ALTER DATABASE ? нужен ли эксклюзивный коннект
?)




2. Работа с зашифрованной БД

CONNECT DATABASE ...
    [AUTH[ORIZED] BY <auth_string>]

    При первом открытии файла БД по header_page определяем зашифрована ли БД
и,
если да, то ищем PluginID.MethodID среди установленных плагинов

    Есть три способа передать криптоплагину ключ авторизации :

а) криптоплагин ищет ключ с заданным именем (KeyName) во внешнем хранилище

б) ключ передаётся клиентским приложением через DPB. Если приложение не знает,
    какой именно ключ нужен для данной БД, то это не сработает

в) криптоплагин криптоплагин инициирует коллбек к движку, а тот - к
клиентскому
    приложению для получения ключа авторизации от него. Один из параметров
    коллбека - KeyName

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

    Порядок авторизации приложения, в том случае когда это требуется (коллбек
во
время вызова isc_attach_database):

а) движок: криптоплагин генерит пару сессионных ключей
    (KeySessionOpen\KeySessionClosed)

б) движок: посылает приложению KeyName, KeySessionOpen и KeyDB_enc

в) приложение: расшифровывает ключ шифрования БД закрытой частью ключа
    авторизации и зашифровывает его открытой частью сессионного ключа :
    KeyDB = Decrypt(KeyDB_enc, KeyAuthClosed)
    KeyDB_enc2 = Encrypt(KeyDB, KeySessionOpen)
    ключ авторизации должен соответствовать имени KeyName

г) приложение: отсылает движку назад KeyDB_enc2

д) движок: расшифровывает ключ шифрования БД закрытой частью сессионного ключа
    KeyDB = Decrypt(KeyDB_enc2, KeySessionClosed)


    Если есть требование о том, чтобы приложение не знало ключ шифрования БД,
то
можно поступить несколько иначе :

а) движок: криптоплагин генерит пару сессионных ключей
    (KeySessionOpen\KeySessionClosed)

б) движок: посылает приложению KeyName и KeySessionOpen

в) приложение: находит KeyAuthClosed соответствующий KeyName,
    KeyAuthClosed_enc = Encrypt(KeyAuthClosed, KeySessionOpen)

г) приложение: отсылает движку назад KeyAuthClosed_enc

д) движок:
    KeyAuthClosed = Decrypt(KeyAuthClosed_enc, KeySessionClosed)
    KeyDB = Decrypt(KeyDB_enc, KeyAuthClosed)


    После получения KeyDB с помощью ключа авторизации, полученного от
приложения
или из другого источника, проверяем правильность этого KeyDB :

    зашифровываем полученный KeyDB с помощью открытой части ключа авторизации
    KeyDB_enc = Encrypt(KeyDB, KeyAuthOpen)

    и сравниваем с KeyDB_enc, хранящемся на header_page (keys_page)


    Основная цель такого алгоритма: никто не должен иметь доступ к
расшифрованным ключам - ни приложение, ни движок, ни канал передачи.

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



3. Создание зашифрованного бекапа (gbak\nbackup)

    Похоже на создание зашифрованной БД :

а) gbak использует или те же PluginID.MethodID и KeyName, что и БД, либо
    они задаются новым ключём командной строки. Криптоплагин должен быть
    доступен gbak'у

б) криптоплагин генерирует новый ключ шифрования бекапа (KeyBK) и пару ключей
    авторизации (KeyAuthOpen \ KeyAuthClosed), если KeyName был задан
    пользователем.
    Самому gbak'у для создания бекапа нужен только открытый ключ авторизации
    (KeyAuthOpen), который есть на header_page, поэтому это возможно

в) сохраняем во внешнем хранилище ключ авторизации (KeyAuthClosed), если это
    был заново сгенерённый ключ

г) шифруем KeyBK с помощью открытой части ключа авторизации
    KeyBK_enc = Encrypt(KeyBK, KeyAuthOpen)

д) сохраняем KeyBK_enc и KeyAuthOpen в открытой части заголовка бекапа

е) сохраняем в открытой части заголовка бекапа PluginID, MethodID и KeyName

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



4. Восстановление зашифрованного бекапа.

   Ключ шифрования бекапа определяется так же, как и при работе с
зашифрованной БД (см п2)




5. Плагин-интерфейс

    Основной принцип, используемый при создании данного АПИ - ключ шифрования
БД
не должен быть доступен движку (firebird). Так же, по возможности, не должны
быть доступны остальные используемые ключи. Кроме того, движок не должен
заниматься заданием методов шифрования, длины ключей или других параметров
алгоритма шифрования - все эти нюансы предлагается кодировать в имени метода
шифрования. Это кодирование, конечно, определяется производителем
криптоплагина
и документируется им же.

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

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

    Предполагается создание своего экземпляра контейнера для каждого
аттачмента,
т.к. содержимое контейнеров до момента успешной авторизации клиентского
приложения не одинаково. После успешной авторизации пользователя контейнер
ключей уничтожается, либо, если это был первый коннект, ассоциируется с
Database
и используется для шифрования страниц БД. Соответственно симметричный алгоритм
шифрования дожен быть потокобезопасным

    Криптоплагин имеет единственную экспортируемую ф-цию (getCryptoEngine),
возвращающую единственный экземпляр мета-класса (CryptoEngine),
представляющего
собой собственно криптоплагин.

typedef KEY int;

// cryptoplugin's entrypoint
CryptoEngine* getCryptoEngine();

class CryptoEngine
{
public:

  // returns unique string - indentifier of this cryptoplugin
  const char* getEngineID() = 0;

  // creates container implemented given methodID
  // or returns 0 if methodID not supported
  // container_name used by container to identify 'main' key when save\load it
  // into\from external location
  CryptoContainer* createContainer(const char* methodID, const char*
        container_name) = 0;

  // destructor
  void release() = 0;
}

// implements two encription methods - one symmetric and
// one asymmetric
class CryptoContainer
{
public:
  enum KeyKind { kkSymmetric, kkASymmetric };
  enum ExportMode { emOpenKey, emClosedKey };

  // destructor
  void release() = 0;

  // generate key and fill in key descriptor
  ISC_STATUS generateKey(KEY* key, KeyKind kind) = 0;

  // encrypt open or closed part of key_exp by key_enc
  // if buffer is NULL calculate length of exported key
  ISC_STATUS exportKey(KEY key_exp, KEY key_enc, ExportMode mode,
        char* buffer, int* len) = 0;

  // take buffer, decrypt it by key_dec and return its
  // descriptor as key_imp
  ISC_STATUS importKey(KEY *key_imp, KEY key_dec, ExportMode mode,
        char* buffer, int len) = 0;

  // next two methods worked with 'external' key representation - it can be
  // file, registry key, record in some database etc.
  // file name (registry key name etc) must be evaluated from 'container_name'
  // parameter passed into container constructor
  // external representation must have whole info to reconstruct the key and
  // encryption method i.e. it must contain both open and closed parts of
  // assymetric key, pluginID and methodID to match key against container
  // during key loading
  // only one key can exists under given name in external location

  // save key into external location known to this container
  ISC_STATUS saveKey(KEY key_exp) = 0;

  // load key from external location known to this container
  ISC_STATUS loadKey(KEY* key_imp) = 0;

  // erase internal key content and free allocated resources
  ISC_STATUS eraseKey(KEY key) = 0;

  // returns block size used by encryption method
  int getBlockSize(KEY key) = 0;

  ISC_STATUS encrypt(KEY key, char* in_buffer, int len, char* out_buffer) = 0;
  ISC_STATUS decrypt(KEY key, char* in_buffer, int len, char* out_buffer) = 0;
}

    Все методы возвращают ISC_STATUS, т.е. числовой код ошибки. Можно
создать новое FACILITY для этих кодов ошибок. В принципе, можно использовать
другой подход - тот, который мы использовали в ExternalEngine : в каждый метод
передаётся спец. объект, через который можно передавать коды ошибок и
соответствующие строки с ошибками.

--
Хорсун Влад


Ответить