Криптография в 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 является то, что размер шифруемых данных не может превышать размер ключа. С этим можно бороться следующими способами:
- Делить исходные данные на блоки размером по 2 или 4 Кбайт и шифровать их по-отдельности
- Генерировать случаным образом одноразовый сеансовый ключ, который будет использован в паре с алгоритмом симметричного шифрования, но сам будет зашифрован с помощью 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 OpenSSL удобнее использовать документацию из проекта LibreSSL.
Если сторонний фреймворк состоит из нескольких библиотек, то команда find_package
позволяет указать, какие именно необходимо использовать, указав их перечень после COMPONENTS
:
find_package(OpenSSL COMPONENTS Crypto REQUIRED)
В случае успешного нахождения libcrypto
из OpenSSL, будут определены переменные ${OPENSSL_INCLUDE_DIR}
и ${OPENSSL_CRYPTO_LIBRARY}
.
Преобразования данных криптографическими функциями подразумевает три стадии:
- Инициализация - функции, заканчивающиеся на
Init
- Добавление очередной порции данных с помощью одной из функций, имя которой заканчивается на
Update
. Этот процесс можно повторять итеративно по мере поступления данных - Финализация - функции, оканчивающиеся на
Final
; на этом этапе появляется итоговый результат преобразования.
Функции, имена которых начинаются с 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 // начальное значение нужного размера
);