Skip to content

Latest commit

 

History

History

openssl

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

Шифрование с использованием OpenSSL/LibreSSL

Основы шифрования в Linux

Криптография в Linux, как и во многих других UNIX-подобных системах реализована с помощью пакета openssl или совместимого с ним форка libressl.

Пакет предоставляет:

  • команду openssl для выполнения операций в командной строке
  • библиотеку libcrypto с реализацией алгоритмов шифрования
  • библиотеку libssl с реализацией взаимодействия по протоколам SSL и TLS.

Вычисление хеш-значений

Команды:

  • openssl md5
  • openssl sha256
  • openssl sha512

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

Симметричное шифрование

Команда:

openssl enc -ШИФР -in ИМЯФАЙЛА -out ВЫХФАЙЛ

Выполяет шифрование симметричным ключем, то есть некоторым "паролем", который является одинаковым как для шифрования, так и для обратной операции дешифрования.

Полный список поддерживаемых шифров отображается командой openssl enc -ciphers. Наиболее часто используемые:

  • des - достаточно старый алгоритм с использованием 56-битного ключа;
  • aes256 или aes-256-cbc - более надежный и достаточно быстрый;
  • base64 - без шифрования (ключ не требуется); удобный способ конвертировать бинарные файлы в текстовое представление и обратно.

Опция -d означает обратное преобразование, то есть дешифрование. Опция -base64 подразумевает, что зашифрованные данные дополнительно преобразуются в кодировку Base64, например, для передачи данных в виде текста.

После запуска команды будет запрошен пароль и его подтверждение. В случае, когда нужно автоматизировать запуск команды, используется опция -pass, после которой передается, каким именно образом задается пароль:

  • pass:ПАРОЛЬ - пароль задается обычным текстом в виде аргумента командной строки; жутко небезопасно;
  • env:ПЕРЕМЕННАЯ - пароль задается определенной переменной окружения; немного лучше, но можно выяснить через /proc/.../environ;
  • file:ИМЯФАЙЛА - пароль берется из файла;
  • fd:ЧИСЛО - пароль берется из файлового дескриптора с указанным номером; используется при запуске через fork+exec.

Поскольку алгоритмы симметричного шифрования подразумевают использование ключа фиксированного размера, текстовый пароль произвольной длины предварительно преобразуется с помощью хеш-функции. По умолчанию используется SHA-256, но это можно задавать с помощью опции -md АЛГОРИТМ.

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

Шифрование с использованием пары ключей

Стандартным алгоритмом для шифрования с использованием пары ключей считается RSA.

Генерация ключей осуществляется командой:

openssl genrsa -out ФАЙЛ РАЗРЯДНОСТЬ

Если имя выходного файла не указано, то ключ в текстовом формате будет сохранен на стандартный поток вывода. Обычно ключи RSA хранят в файлах с суффиксом имени .pem.

Разрядность определяет стойкость ключа, по умолчанию - 2048 бит.

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

openssl genrsa -aes256 -passout ОПЦИИ_ПАРОЛЯ

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

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

openssl rsa -in ПРИВАТНЫЙ_КЛЮЧ -out ПУБЛИЧНЫЙ_КЛЮЧ -pubout

Если при создании пары ключей использовалось шифрование, то необходимо ввести пароль, либо задать его через -passin.

Шифрование с использованием открытого ключа:

openssl rsautl -encrypt -pubin -inkey ПУБЛИЧНЫЙ_КЛЮЧ -in ФАЙЛ -out ВЫХОД

Обратная операция с использованием закрытого ключа:

openssl rsautl -decrypt -inkey ПРИВАТНЫЙ_КЛЮЧ -in ФАЙЛ -out ВЫХОД

Ограничением алгоритма RSA является то, что размер шифруемых данных не может превышать размер ключа. С этим можно бороться следующими способами:

  1. Делить исходные данные на блоки размером по 2 или 4 Кбайт и шифровать их по-отдельности
  2. Генерировать случаным образом одноразовый сеансовый ключ, который будет использован в паре с алгоритмом симметричного шифрования, но сам будет зашифрован с помощью RSA.
# Генерируем случайный ключ длиной 30 байт и сохраняем
# его текстовое Base64 представление в переменной $KEY
KEY=`openssl rand -base64 30`


# Шифруем симметричный ключ с помощью открытого ключа RSA
echo $KEY | openssl rsautl -encrypt -pubin \
                           -inkey public.pem \
                           -out symm_key_encrypted.bin

# Шифруем данные из большого файла README.md симметричным
# ключем из переменной $KEY, которая предварительно
# экспортируется, чтобы быть доступной дочернему процессу
export KEY
openssl enc -aes256 -in README.md \
                    -out README_encrypted.bin \
                    -pass env:KEY

# Забываем сеансовый ключ - он больше не нужен
unset KEY

Далее можно смело передавать по незащищенному каналу файлы README_encrypted.bin, который содержит данные, и symm_key_encrypted.bin с зашифрованным симметричным ключем.

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

# Расшифровываем сеансовый симметричный ключ с помощью
# приватного ключа RSA и сохраняем в переменной $KEY
KEY=`openssl rsautl -decrypt \
                    -inkey private.pem \
                    -in symm_key_encrypted.bin`

# Выполняем декодирование файла с данными, используя
# полученный сеансовый ключ
  export KEY
  openssl enc -d -aes256 -pass env:KEY \
              -in README_encrypted.bin

# Забываем расшифрованный сеансовый ключ
unset KEY

API библиотеки libcrypto

В качестве онлайн-документации по API OpenSSL удобнее использовать документацию из проекта LibreSSL.

Использование с CMake

Если сторонний фреймворк состоит из нескольких библиотек, то команда find_package позволяет указать, какие именно необходимо использовать, указав их перечень после COMPONENTS:

find_package(OpenSSL COMPONENTS Crypto REQUIRED)

В случае успешного нахождения libcrypto из OpenSSL, будут определены переменные ${OPENSSL_INCLUDE_DIR} и ${OPENSSL_CRYPTO_LIBRARY}.

Workflow

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

  1. Инициализация - функции, заканчивающиеся на Init
  2. Добавление очередной порции данных с помощью одной из функций, имя которой заканчивается на Update. Этот процесс можно повторять итеративно по мере поступления данных
  3. Финализация - функции, оканчивающиеся на Final; на этом этапе появляется итоговый результат преобразования.

Функции libcrypto

Функции, имена которых начинаются с SHA или MD5 предназначены для вычисления хеш-значений. Они используют простой workflow из трех стадий.

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

Пример:

// Создание контекста
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();

// Генерация ключа и начального вектора из
// пароля произвольной длины и 8-байтной соли
EVP_BytesToKey(
  EVP_aes_256_cbc(),    // алгоритм шифрования
  EVP_sha256(),         // алгоритм хеширования пароля
  salt,                 // соль
  password, strlen(password), // пароль
  1,                    // количество итераций хеширования
  key,          // результат: ключ нужной длины
  iv            // результат: начальный вектор нужной длины
);

// Начальная стадия: инициализация
EVP_DecryptInit(
  ctx,                  // контекст для хранения состояния  
  EVP_aes_256_cbc(),    // алгоритм шифрования
  key,                  // ключ нужного размера
  iv                    // начальное значение нужного размера
);