Spec-Zone.ru › Ruby 2.2

модуль 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

или

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, с которой работает расширение OpenSSL для Ruby.

OPENSSL_VERSION

Версия OpenSSL, с которой было построено расширение OpenSSL для Ruby.

OPENSSL_VERSION_NUMBER

Номера версии OpenSSL, с которой было построено расширение OpenSSL для Ruby (в шестнадцатеричной системе).

VERSION

Версия расширения OpenSSL для Ruby.

Публичные методы класса

Digest(name) Показать исходный код
# File ext/openssl/lib/openssl/digest.rb, line 81
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
debug → true | false Показать исходный код
static VALUE
ossl_debug_get(VALUE self)
{
    return dOSSL;
}
debug = boolean → boolean Показать исходный код
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.

errors → [String...] Показать исходный код
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.

fips_mode = boolean → boolean Показать исходный код
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 # и выключение снова

Приватные методы экземпляра

Digest(name) Показать исходный код
# File ext/openssl/lib/openssl/digest.rb, line 81
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.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API