модуль OpenSSL
OpenSSL предоставляет SSL, TLS и криптографические средства общего назначения. Он оборачивает библиотеку OpenSSL.
Примеры
Все примеры предполагают, что вы загрузили OpenSSL с помощью:
require 'openssl'
Эти примеры построены друг на друге. Например, ключ, созданный в следующем примере, используется во всех последующих примерах.
Ключи
Создание ключа
Этот пример создаёт пару ключей RSA с размером 2048 бит и сохраняет их в текущей директории.
key = OpenSSL::PKey::RSA.new 2048 open 'private_key.pem', 'w' do |io| io.write key.to_pem end open 'public_key.pem', 'w' do |io| io.write key.public_key.to_pem end
Экспорт ключа
Ключи, сохранённые на диске без шифрования, небезопасны, так как любой, у кого есть доступ к ключу, может его использовать, если он не зашифрован. Для безопасного экспорта ключа можно экспортировать его с парольной фразой.
cipher = OpenSSL::Cipher.new 'AES-128-CBC' pass_phrase = 'my secure pass phrase goes here' key_secure = key.export cipher, pass_phrase open 'private.secure.pem', 'w' do |io| io.write key_secure end
OpenSSL::Cipher.ciphers возвращает список доступных шифров.
Загрузка ключа
Ключ также можно загрузить из файла.
key2 = OpenSSL::PKey::RSA.new File.read 'private_key.pem' key2.public? # => true key2.private? # => true
или
key3 = OpenSSL::PKey::RSA.new File.read 'public_key.pem' key3.public? # => true key3.private? # => false
Загрузка зашифрованного ключа
OpenSSL запросит у вас парольную фразу при загрузке зашифрованного ключа. Если у вас нет возможности ввести парольную фразу, вы можете указать её при загрузке ключа:
key4_pem = File.read 'private.secure.pem' pass_phrase = 'my secure pass phrase goes here' key4 = OpenSSL::PKey::RSA.new key4_pem, pass_phrase
Шифрование RSA
RSA обеспечивает шифрование и дешифрование с использованием открытых и закрытых ключей. Вы можете использовать различные методы заполнения в зависимости от предполагаемого использования зашифрованных данных.
Шифрование и дешифрование
Асимметричное шифрование с открытым/закрытым ключом медленное и уязвимо к атакам в случаях, когда оно используется без заполнения или непосредственно для шифрования больших блоков данных. Типичные случаи использования шифрования RSA включают «обёртку» симметричного ключа с открытым ключом получателя, который затем «распакует» этот симметричный ключ с помощью своего закрытого ключа. Тем не менее, в практике не следует использовать этот упрощённый пример схемы передачи ключей, следует всегда отдавать предпочтение стандартизированным протоколам.
wrapped_key = key.public_encrypt key
Симметричный ключ, зашифрованный открытым ключом, может быть расшифрован только соответствующим закрытым ключом получателя.
original_key = key.private_decrypt wrapped_key
По умолчанию используется заполнение PKCS#1, но также возможно использовать и другие формы заполнения, см. PKey::RSA для получения дополнительной информации.
Подписи
Использование «private_encrypt» для шифрования данных с помощью закрытого ключа эквивалентно применению цифровой подписи к данным. Проверяющая сторона может проверить подпись, сравнив результат расшифрования подписи с помощью «public_decrypt» с исходными данными. Однако OpenSSL::PKey уже имеет методы «sign» и «verify», которые обрабатывают цифровые подписи стандартизированным способом - в практике не следует использовать «private_encrypt» и «public_decrypt».
Для подписи документа сначала вычисляется криптографически безопасный хэш документа, который затем подписывается с использованием закрытого ключа.
digest = OpenSSL::Digest::SHA256.new signature = key.sign digest, document
Для проверки подписи вычисляется хэш документа, и подпись расшифровывается с использованием открытого ключа. Затем результат сравнивается с только что вычисленным хэшем. Если они совпадают, подпись действительна.
digest = OpenSSL::Digest::SHA256.new if key.verify digest, signature, document puts 'Valid' else puts 'Invalid' end
PBKDF2 Шифрование на основе паролей
Если это поддерживается используемой версией OpenSSL, для шифрования на основе пароля следует использовать возможности PKCS5. Если это не поддерживается или если этого требуют устаревшие приложения, также поддерживаются более старые, менее безопасные методы, определённые в RFC 2898 (см. ниже).
PKCS5 поддерживает PBKDF2, как это было определено в PKCS#5 v2.0. Он по-прежнему использует пароль, соль и, дополнительно, количество итераций, которые замедлят процесс вывода ключа. Чем медленнее этот процесс, тем больше усилий требуется для взлома полученного ключа.
Шифрование
Стратегия заключается в том, чтобы сначала создать Cipher для шифрования, а затем сгенерировать случайный IV и ключ, полученный из пароля с использованием PBKDF2. PKCS #5 v2.0 рекомендует использовать как минимум 8 байт для соли, количество итераций в значительной степени зависит от используемого оборудования.
cipher = OpenSSL::Cipher.new 'AES-128-CBC' cipher.encrypt iv = cipher.random_iv pwd = 'some hopefully not to easily guessable password' salt = OpenSSL::Random.random_bytes 16 iter = 20000 key_len = cipher.key_len digest = OpenSSL::Digest::SHA256.new key = OpenSSL::PKCS5.pbkdf2_hmac(pwd, salt, iter, key_len, digest) cipher.key = key Now encrypt the data: encrypted = cipher.update document encrypted << cipher.final
Расшифровка
Используйте те же шаги, что и раньше, чтобы вывести симметричный ключ AES, но на этот раз настройте Cipher для расшифровки.
cipher = OpenSSL::Cipher.new 'AES-128-CBC' cipher.decrypt cipher.iv = iv # the one generated with #random_iv pwd = 'some hopefully not to easily guessable password' salt = ... # the one generated above iter = 20000 key_len = cipher.key_len digest = OpenSSL::Digest::SHA256.new key = OpenSSL::PKCS5.pbkdf2_hmac(pwd, salt, iter, key_len, digest) cipher.key = key Now decrypt the data: decrypted = cipher.update encrypted decrypted << cipher.final
PKCS #5 Шифрование на основе паролей
PKCS #5 - это стандарт шифрования на основе пароля, описанный в RFC2898. Он позволяет использовать короткий пароль или пароль для создания безопасного ключа шифрования. Если возможно, следует использовать PBKDF2, как описано выше, если обстоятельства позволяют это.
PKCS #5 использует Cipher, парольную фразу и соль для генерации ключа шифрования.
pass_phrase = 'my secure pass phrase goes here' salt = '8 octets'
Шифрование
Сначала настройте шифратор для шифрования
encryptor = OpenSSL::Cipher.new 'AES-128-CBC' encryptor.encrypt encryptor.pkcs5_keyivgen pass_phrase, salt
Затем передайте данные, которые вы хотите зашифровать
encrypted = encryptor.update 'top secret document' encrypted << encryptor.final
Расшифровка
Используйте новый экземпляр Cipher, настроенный для расшифровки
decryptor = OpenSSL::Cipher.new 'AES-128-CBC' decryptor.decrypt decryptor.pkcs5_keyivgen pass_phrase, salt
Затем передайте данные, которые вы хотите расшифровать
plain = decryptor.update encrypted plain << decryptor.final
Сертификаты
Создание сертификата
Этот пример создаёт самозаверяющий сертификат с использованием ключа RSA и подписи SHA1.
key = OpenSSL::PKey::RSA.new 2048 name = OpenSSL::X509::Name.parse 'CN=nobody/DC=example' cert = OpenSSL::X509::Certificate.new cert.version = 2 cert.serial = 0 cert.not_before = Time.now cert.not_after = Time.now + 3600 cert.public_key = key.public_key cert.subject = name
Расширения сертификата
Вы можете добавить расширения к сертификату с помощью OpenSSL::SSL::ExtensionFactory, чтобы указать назначение сертификата.
extension_factory = OpenSSL::X509::ExtensionFactory.new nil, cert
cert.add_extension extension_factory.create_extension('basicConstraints', 'CA:FALSE', true)
cert.add_extension extension_factory.create_extension(
'keyUsage', 'keyEncipherment,dataEncipherment,digitalSignature')
cert.add_extension extension_factory.create_extension('subjectKeyIdentifier', 'hash')
Список поддерживаемых расширений (и в некоторых случаях их возможных значений) можно получить из файла «objects.h» в исходном коде OpenSSL.
Подписание сертификата
Для подписи сертификата установите издателя и используйте OpenSSL::X509::Certificate#sign с алгоритмом дайджеста. Это создаёт самозаверяющий сертификат, потому что мы используем одно и то же имя и ключ для подписи сертификата, что и для создания сертификата.
cert.issuer = name cert.sign key, OpenSSL::Digest::SHA1.new open 'certificate.pem', 'w' do |io| io.write cert.to_pem end
Загрузка сертификата
Как и ключ, сертификат также можно загрузить из файла.
cert2 = OpenSSL::X509::Certificate.new File.read 'certificate.pem'
Проверка сертификата
Certificate#verify вернёт true, если сертификат был подписан заданным открытым ключом.
raise 'certificate can not be verified' unless cert2.verify key
Центр сертификации
Центр сертификации (ЦС) - это доверенная третья сторона, которая позволяет вам проверить владение неизвестными сертификатами. ЦС выдает подписи ключей, которые указывают, что он доверяет пользователю этого ключа. Пользователь, столкнувшийся с ключом, может проверить подпись с помощью открытого ключа ЦС.
Ключ ЦС
Ключи ЦС ценны, поэтому мы шифруем и сохраняем их на диске, убеждаясь, что они недоступны для других пользователей.
ca_key = OpenSSL::PKey::RSA.new 2048 pass_phrase = 'my secure pass phrase goes here' cipher = OpenSSL::Cipher.new 'AES-128-CBC' open 'ca_key.pem', 'w', 0400 do |io| io.write ca_key.export(cipher, pass_phrase) end
Сертификат ЦС
Сертификат ЦС создаётся так же, как и сертификат выше, но с другими расширениями.
ca_name = OpenSSL::X509::Name.parse 'CN=ca/DC=example'
ca_cert = OpenSSL::X509::Certificate.new
ca_cert.serial = 0
ca_cert.version = 2
ca_cert.not_before = Time.now
ca_cert.not_after = Time.now + 86400
ca_cert.public_key = ca_key.public_key
ca_cert.subject = ca_name
ca_cert.issuer = ca_name
extension_factory = OpenSSL::X509::ExtensionFactory.new
extension_factory.subject_certificate = ca_cert
extension_factory.issuer_certificate = ca_cert
ca_cert.add_extension extension_factory.create_extension('subjectKeyIdentifier', 'hash')
Это расширение указывает, что ключ ЦС может использоваться как ключ ЦС.
ca_cert.add_extension extension_factory.create_extension('basicConstraints', 'CA:TRUE', true)
Это расширение указывает, что ключ ЦС может использоваться для проверки подписей как на сертификатах, так и на отзывах сертификатов.
ca_cert.add_extension extension_factory.create_extension(
'keyUsage', 'cRLSign,keyCertSign', true)
Корневые сертификаты ЦС самозаверяющие.
ca_cert.sign ca_key, OpenSSL::Digest::SHA1.new
Сертификат ЦС сохраняется на диске, чтобы его можно было распространить всем пользователям ключей, которые этот ЦС будет подписывать.
open 'ca_cert.pem', 'w' do |io| io.write ca_cert.to_pem end
Запрос на подписание сертификата
ЦС подписывает ключи через запрос на подписание сертификата (CSR). CSR содержит информацию, необходимую для идентификации ключа.
csr = OpenSSL::X509::Request.new csr.version = 0 csr.subject = name csr.public_key = key.public_key csr.sign key, OpenSSL::Digest::SHA1.new
CSR сохраняется на диске и отправляется в ЦС для подписи.
open 'csr.pem', 'w' do |io| io.write csr.to_pem end
Создание сертификата из CSR
Получив CSR, ЦС проверит его перед подписанием. Минимальная проверка заключалась бы в проверке подписи CSR.
csr = OpenSSL::X509::Request.new File.read 'csr.pem' raise 'CSR can not be verified' unless csr.verify csr.public_key
После проверки создаётся сертификат, отмечается для различных применений, подписывается ключом ЦС и возвращается запросившему.
csr_cert = OpenSSL::X509::Certificate.new
csr_cert.serial = 0
csr_cert.version = 2
csr_cert.not_before = Time.now
csr_cert.not_after = Time.now + 600
csr_cert.subject = csr.subject
csr_cert.public_key = csr.public_key
csr_cert.issuer = ca_cert.subject
extension_factory = OpenSSL::X509::ExtensionFactory.new
extension_factory.subject_certificate = csr_cert
extension_factory.issuer_certificate = ca_cert
csr_cert.add_extension extension_factory.create_extension('basicConstraints', 'CA:FALSE')
csr_cert.add_extension extension_factory.create_extension(
'keyUsage', 'keyEncipherment,dataEncipherment,digitalSignature')
csr_cert.add_extension extension_factory.create_extension('subjectKeyIdentifier', 'hash')
csr_cert.sign ca_key, OpenSSL::Digest::SHA1.new
open 'csr_cert.pem', 'w' do |io|
io.write csr_cert.to_pem
end
SSL и TLS-соединения
Используя созданный ключ и сертификат, мы можем создать SSL или TLS-соединение. SSLContext используется для настройки SSL-сессии.
context = OpenSSL::SSL::SSLContext.new
SSL сервер
Для безопасной коммуникации с клиентами SSL-серверу необходим сертификат и закрытый ключ:
context.cert = cert context.key = key
Затем создайте SSLServer с сокетом сервера TCP и контекстом. Используйте SSLServer как обычный TCP-сервер.
require 'socket'
tcp_server = TCPServer.new 5000
ssl_server = OpenSSL::SSL::SSLServer.new tcp_server, context
loop do
ssl_connection = ssl_server.accept
data = connection.gets
response = "I got #{data.dump}"
puts response
connection.puts "I got #{data.dump}"
connection.close
end
SSL клиент
Клиент SSL создаётся с сокетом TCP и контекстом. Для инициализации SSL-рукопожатия и начала шифрования необходимо вызвать SSLSocket#connect. Ключ и сертификат не требуются для сокета клиента.
Обратите внимание, что SSLSocket#close по умолчанию не закрывает лежащий в основе сокет. Установите SSLSocket#sync_close в true, если вы хотите это сделать.
require 'socket' tcp_socket = TCPSocket.new 'localhost', 5000 ssl_client = OpenSSL::SSL::SSLSocket.new tcp_socket, context ssl_client.sync_close = true ssl_client.connect ssl_client.puts "hello server!" puts ssl_client.gets ssl_client.close # shutdown the TLS connection and close tcp_socket
Проверка подлинности узла
Непроверенное SSL-соединение не обеспечивает достаточной безопасности. Для повышения безопасности клиент или сервер могут проверять сертификат своего узла.
Клиент может быть изменён для проверки сертификата сервера по отношению к сертификату центра сертификации:
context.ca_file = 'ca_cert.pem' context.verify_mode = OpenSSL::SSL::VERIFY_PEER require 'socket' tcp_socket = TCPSocket.new 'localhost', 5000 ssl_client = OpenSSL::SSL::SSLSocket.new tcp_socket, context ssl_client.connect ssl_client.puts "hello server!" puts ssl_client.gets
Если сертификат сервера недействителен или context.ca_file не установлен при проверке узлов, будет поднята ошибка OpenSSL::SSL::SSLError.
Константы
- OPENSSL_FIPS
-
Логическое значение, указывающее, включена ли FIPS-поддержка в OpenSSL или нет.
- OPENSSL_LIBRARY_VERSION
-
Версия OpenSSL, с которой работает расширение OpenSSL для Ruby.
- OPENSSL_VERSION
-
Версия OpenSSL, с которой было построено расширение OpenSSL для Ruby.
- OPENSSL_VERSION_NUMBER
-
Номер версии OpenSSL, с которой было построено расширение OpenSSL для Ruby (в шестнадцатеричной системе счисления).
- VERSION
-
Версия расширения OpenSSL для Ruby.
Публичные методы класса
# File ext/openssl/lib/openssl/digest.rb, line 71 def Digest(name) OpenSSL::Digest.const_get(name) end
Возвращает подкласс Digest, определяемый по name.
require 'openssl'
OpenSSL::Digest("MD5")
# => OpenSSL::Digest::MD5
Digest("Foo")
# => NameError: wrong constant name Foo
static VALUE
ossl_debug_get(VALUE self)
{
return dOSSL;
} static VALUE
ossl_debug_set(VALUE self, VALUE val)
{
dOSSL = RTEST(val) ? Qtrue : Qfalse;
return val;
} Включает или отключает режим отладки. В режиме отладки все ошибки, добавленные в очередь ошибок OpenSSL, будут выведены в stderr.
VALUE
ossl_get_errors(void)
{
VALUE ary;
long e;
ary = rb_ary_new();
while ((e = ERR_get_error()) != 0){
rb_ary_push(ary, rb_str_new2(ERR_error_string(e, NULL)));
}
return ary;
} Просмотр оставшихся ошибок в очереди.
Возможные ошибки здесь, скорее всего, связаны с ошибкой в реализации OpenSSL в Ruby.
static VALUE
ossl_fips_mode_set(VALUE self, VALUE enabled)
{
#ifdef OPENSSL_FIPS
if (RTEST(enabled)) {
int mode = FIPS_mode();
if(!mode && !FIPS_mode_set(1)) /* turning on twice leads to an error */
ossl_raise(eOSSLError, "Turning on FIPS mode failed");
} else {
if(!FIPS_mode_set(0)) /* turning off twice is OK */
ossl_raise(eOSSLError, "Turning off FIPS mode failed");
}
return enabled;
#else
if (RTEST(enabled))
ossl_raise(eOSSLError, "This version of OpenSSL does not support FIPS mode");
return enabled;
#endif
} Включает или выключает режим FIPS. Включение режима FIPS будет иметь эффект только для FIPS-совместимых установок библиотеки OpenSSL. В противном случае это приведет к ошибке.
Примеры
OpenSSL.fips_mode = true # turn FIPS mode on OpenSSL.fips_mode = false # and off again
static VALUE
mem_check_start(VALUE self)
{
CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON);
return Qnil;
} Вызывает CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON). Начинает отслеживание выделения памяти. См. также ::print_mem_leaks.
Доступно только при компиляции с совместимым OpenSSL и опцией конфигурации –enable-debug.
static VALUE
print_mem_leaks(VALUE self)
{
#if OPENSSL_VERSION_NUMBER >= 0x10100000
int ret;
#endif
BN_CTX_free(ossl_bn_ctx);
ossl_bn_ctx = NULL;
#if OPENSSL_VERSION_NUMBER >= 0x10100000
ret = CRYPTO_mem_leaks_fp(stderr);
if (ret < 0)
ossl_raise(eOSSLError, "CRYPTO_mem_leaks_fp");
return ret ? Qfalse : Qtrue;
#else
CRYPTO_mem_leaks_fp(stderr);
return Qnil;
#endif
} Для отладки библиотеки Ruby/OpenSSL. Вызывает CRYPTO_mem_leaks_fp(stderr). Выводит обнаруженные утечки памяти в стандартный поток ошибок. Это очищает глобальное состояние, поэтому после вызова этого метода нельзя использовать другие методы библиотеки.
Возвращает true, если утечки обнаружены, и false в противном случае.
Доступно только при компиляции с совместимым OpenSSL и опцией конфигурации –enable-debug.
Пример
OpenSSL.mem_check_start
NOT_GCED = OpenSSL::PKey::RSA.new(256)
END {
GC.start
OpenSSL.print_mem_leaks # will print the leakage
}
Приватные методы экземпляра
# File ext/openssl/lib/openssl/digest.rb, line 71 def Digest(name) OpenSSL::Digest.const_get(name) end
Возвращает подкласс Digest, определяемый по name.
require 'openssl'
OpenSSL::Digest("MD5")
# => OpenSSL::Digest::MD5
Digest("Foo")
# => NameError: wrong constant name Foo
Ruby Core © 1993–2017 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.