модуль OpenSSL
frozen_string_literal: false
frozen_string_literal: false
frozen_string_literal: false
frozen_string_literal: false
frozen_string_literal: false
OpenSSL предоставляет SSL, TLS и общие криптографические средства. Он оборачивает библиотеку OpenSSL.
Установка
OpenSSL поставляется в составе стандартной библиотеки Ruby.
Это означает, что расширение OpenSSL компилируется с Ruby и упаковывается при сборке. Во время компиляции Ruby необходимо связаться с библиотекой OpenSSL на вашей системе. Однако вы не можете использовать openssl, предоставленный Apple, для построения стандартной библиотеки openssl.
Если вы используете OSX, вам следует установить другой openssl и запустить «`./configure –with-openssl-dir=/path/to/another-openssl“`. Для пользователей Homebrew запустите `brew install openssl` и затем «`./configure –with-openssl-dir=`brew –prefix 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
или
key3 = OpenSSL::PKey::RSA.new File.read 'public_key.pem' key3.private? # => false
Загрузка зашифрованного ключа
OpenSSL попросит вас ввести фразу пароля при загрузке зашифрованного ключа. Если вы не сможете ввести фразу пароля, вы можете предоставить её при загрузке ключа:
key4_pem = File.read 'private.secure.pem' 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
X509 Сертификаты
Создание сертификата
В этом примере создаётся самозаверяющий сертификат с использованием ключа RSA и подписи SHA1.
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 cipher = OpenSSL::Cipher::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. Для настройки сессии SSL используется SSLContext.
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 и контекстом. SSLSocket#connect необходимо вызвать, чтобы инициировать рукопожатие SSL и начать шифрование. Для сокета клиента не требуются ключ и сертификат.
require 'socket' tcp_client = TCPSocket.new 'localhost', 5000 ssl_client = OpenSSL::SSL::SSLSocket.new client_socket, context ssl_client.connect ssl_client.puts "hello server!" puts ssl_client.gets
Проверка участника
Непроверенное соединение SSL не обеспечивает достаточной безопасности. Для повышения безопасности клиент или сервер могут проверить сертификат своего участника.
Клиент может быть изменён для проверки сертификата сервера по отношению к сертификату центра сертификации:
context.ca_file = 'ca_cert.pem' context.verify_mode = OpenSSL::SSL::VERIFY_PEER require 'socket' tcp_client = TCPSocket.new 'localhost', 5000 ssl_client = OpenSSL::SSL::SSLSocket.new client_socket, context ssl_client.connect ssl_client.puts "hello server!" puts ssl_client.gets
Если сертификат сервера недействителен или context.ca_file не задано при проверке узлов, будет поднято исключение OpenSSL::SSL::SSLError.
Константы
- OPENSSL_FIPS
- OPENSSL_LIBRARY_VERSION
- OPENSSL_VERSION
-
Версия OpenSSL, с которой был скомпилирован модуль OpenSSL для Ruby.
- OPENSSL_VERSION_NUMBER
-
Номер версии OpenSSL, с которой был скомпилирован модуль OpenSSL для Ruby (в шестнадцатеричной системе счисления).
- VERSION
-
Версия расширения OpenSSL для Ruby.
Публичные методы класса
# File ext/openssl/lib/openssl/digest.rb, line 75 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)
{
VALUE old = dOSSL;
dOSSL = val;
if (old != dOSSL) {
if (dOSSL == Qtrue) {
CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON);
fprintf(stderr, "OSSL_DEBUG: IS NOW ON!\n");
} else if (old == Qtrue) {
CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_OFF);
fprintf(stderr, "OSSL_DEBUG: IS NOW OFF!\n");
}
}
return val;
} Включает или выключает CRYPTO_MEM_CHECK. Также отображает некоторые сообщения отладки в 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 HAVE_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, очевидно, будет иметь эффект только для установок OpenSSL библиотеки, совместимых с FIPS. Попытка сделать это в противном случае приведет к ошибке.
Примеры
OpenSSL.fips_mode = true # Включение режима FIPS OpenSSL.fips_mode = false # и выключение
Приватные методы экземпляра
# File ext/openssl/lib/openssl/digest.rb, line 75 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.