module OpenSSL

OpenSSL bietet SSL, TLS und allgemeine Kryptographie. Es umschließt die OpenSSL-Bibliothek.

Beispiele

Alle Beispiele setzen voraus, dass Sie OpenSSL mit

require 'openssl'

geladen haben. Diese Beispiele bauen aufeinander auf. Zum Beispiel wird der im nächsten Schritt erstellte Schlüssel in allen folgenden Beispielen verwendet.

Schlüssel

Schlüssel erstellen

Dieses Beispiel erstellt ein RSA-Schlüsselpaar mit 2048 Bit und schreibt es in das aktuelle Verzeichnis.

key = OpenSSL::PKey::RSA.new 2048

File.write 'private_key.pem', key.private_to_pem
File.write 'public_key.pem', key.public_to_pem

Schlüssel exportieren

Auf der Festplatte ohne Verschlüsselung gespeicherte Schlüssel sind nicht sicher, da jeder, der den Schlüssel in die Hände bekommt, ihn verwenden kann, es sei denn, er ist verschlüsselt. Um einen Schlüssel sicher zu exportieren, können Sie ihn mit einem Passwort exportieren.

cipher = OpenSSL::Cipher.new 'aes-256-cbc'
password = 'my secure password goes here'

key_secure = key.private_to_pem cipher, password

File.write 'private.secure.pem', key_secure

OpenSSL::Cipher.ciphers gibt eine Liste der verfügbaren Verschlüsselungsalgorithmen zurück.

Schlüssel laden

Ein Schlüssel kann auch aus einer Datei geladen werden.

key2 = OpenSSL::PKey.read File.read 'private_key.pem'
key2.public? # => true
key2.private? # => true

oder

key3 = OpenSSL::PKey.read File.read 'public_key.pem'
key3.public? # => true
key3.private? # => false

Verschlüsselten Schlüssel laden

OpenSSL wird Sie beim Laden eines verschlüsselten Schlüssels nach Ihrem Passwort fragen. Wenn Sie das Passwort nicht eingeben können, können Sie es beim Laden des Schlüssels angeben.

key4_pem = File.read 'private.secure.pem'
password = 'my secure password goes here'
key4 = OpenSSL::PKey.read key4_pem, password

RSA-Verschlüsselung

RSA bietet Verschlüsselung und Entschlüsselung mit öffentlichen und privaten Schlüsseln. Sie können verschiedene Padding-Methoden verwenden, abhängig vom beabsichtigten Verwendungszweck der verschlüsselten Daten.

Verschlüsselung & Entschlüsselung

Asymmetrische Public/Private-Key-Verschlüsselung ist langsam und anfällig für Angriffe, wenn sie ohne Padding oder direkt zum Verschlüsseln größerer Datenmengen verwendet wird. Typische Anwendungsfälle für RSA-Verschlüsselung beinhalten das „Verpacken“ eines symmetrischen Schlüssels mit dem öffentlichen Schlüssel des Empfängers, der diesen symmetrischen Schlüssel dann wieder mit seinem privaten Schlüssel „entpacken“ würde. Das Folgende illustriert ein vereinfachtes Beispiel eines solchen Schlüsselaustauschschemas. Es sollte jedoch nicht in der Praxis verwendet werden; standardisierte Protokolle sollten immer bevorzugt werden.

wrapped_key = key.public_encrypt key

Ein mit dem öffentlichen Schlüssel verschlüsselter symmetrischer Schlüssel kann nur mit dem entsprechenden privaten Schlüssel des Empfängers entschlüsselt werden.

original_key = key.private_decrypt wrapped_key

Standardmäßig wird PKCS#1-Padding verwendet, aber es ist auch möglich, andere Padding-Formen zu verwenden. Weitere Details finden Sie unter PKey::RSA.

Signaturen

Die Verwendung von „private_encrypt“ zum Verschlüsseln von Daten mit dem privaten Schlüssel ist gleichbedeutend mit dem Anwenden einer digitalen Signatur auf die Daten. Eine verifizierende Partei kann die Signatur validieren, indem sie das Ergebnis der Entschlüsselung der Signatur mit „public_decrypt“ mit den Originaldaten vergleicht. OpenSSL::PKey verfügt jedoch bereits über die Methoden „sign“ und „verify“, die digitale Signaturen auf standardisierte Weise handhaben – „private_encrypt“ und „public_decrypt“ sollten in der Praxis nicht verwendet werden.

Um ein Dokument zu signieren, wird zuerst ein kryptografisch sicherer Hash des Dokuments berechnet, der dann mit dem privaten Schlüssel signiert wird.

signature = key.sign 'SHA256', document

Um die Signatur zu validieren, wird erneut ein Hash des Dokuments berechnet und die Signatur mit dem öffentlichen Schlüssel entschlüsselt. Das Ergebnis wird dann mit dem gerade berechneten Hash verglichen. Wenn sie gleich sind, war die Signatur gültig.

if key.verify 'SHA256', signature, document
  puts 'Valid'
else
  puts 'Invalid'
end

PBKDF2-Passwortbasierte Verschlüsselung

Wenn von der zugrunde liegenden OpenSSL-Version unterstützt, sollte die passwortbasierte Verschlüsselung die Funktionen von PKCS5 nutzen. Wenn dies nicht unterstützt wird oder wenn es von älteren Anwendungen benötigt wird, werden auch die älteren, weniger sicheren Methoden, die in RFC 2898 spezifiziert sind, unterstützt (siehe unten).

PKCS5 unterstützt PBKDF2, wie es in PKCS#5 v2.0 spezifiziert wurde. Es verwendet immer noch ein Passwort, ein Salt und zusätzlich eine Anzahl von Iterationen, die den Schlüsselableitungsprozess verlangsamen. Je langsamer dies ist, desto mehr Aufwand ist erforderlich, um den resultierenden Schlüssel per Brute-Force zu knacken.

Verschlüsselung

Die Strategie besteht darin, zuerst einen Cipher für die Verschlüsselung zu instanziieren und dann einen zufälligen IV sowie einen aus dem Passwort mittels PBKDF2 abgeleiteten Schlüssel zu generieren. PKCS #5 v2.0 empfiehlt mindestens 8 Bytes für das Salt. Die Anzahl der Iterationen hängt stark von der verwendeten Hardware ab.

cipher = OpenSSL::Cipher.new 'aes-256-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.new('SHA256')

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

Entschlüsselung

Verwenden Sie die gleichen Schritte wie zuvor, um den symmetrischen AES-Schlüssel abzuleiten, und konfigurieren Sie diesmal den Cipher für die Entschlüsselung.

cipher = OpenSSL::Cipher.new 'aes-256-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.new('SHA256')

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

X509-Zertifikate

Zertifikat erstellen

Dieses Beispiel erstellt ein selbstsigniertes Zertifikat mit einem RSA-Schlüssel und einer SHA1-Signatur.

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

Zertifikat-Erweiterungen

Sie können dem Zertifikat mit OpenSSL::SSL::ExtensionFactory Erweiterungen hinzufügen, um den Zweck des Zertifikats anzugeben.

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')

Die Liste der unterstützten Erweiterungen (und in einigen Fällen ihrer möglichen Werte) kann aus der Datei „objects.h“ im Quellcode von OpenSSL abgeleitet werden.

Zertifikat signieren

Um ein Zertifikat zu signieren, legen Sie den Aussteller fest und verwenden Sie OpenSSL::X509::Certificate#sign mit einem Digest-Algorithmus. Dies erstellt ein selbstsigniertes Zertifikat, da wir denselben Namen und Schlüssel zum Signieren des Zertifikats verwenden, der für die Erstellung des Zertifikats verwendet wurde.

cert.issuer = name
cert.sign key, OpenSSL::Digest.new('SHA1')

open 'certificate.pem', 'w' do |io| io.write cert.to_pem end

Zertifikat laden

Ähnlich wie ein Schlüssel kann auch ein Zertifikat aus einer Datei geladen werden.

cert2 = OpenSSL::X509::Certificate.new File.read 'certificate.pem'

Zertifikat verifizieren

Certificate#verify gibt true zurück, wenn ein Zertifikat mit dem gegebenen öffentlichen Schlüssel signiert wurde.

raise 'certificate can not be verified' unless cert2.verify key

Zertifizierungsstelle

Eine Zertifizierungsstelle (CA) ist eine vertrauenswürdige dritte Partei, die es Ihnen ermöglicht, den Besitz unbekannter Zertifikate zu überprüfen. Die CA stellt Schlüssel assinatura aus, die anzeigen, dass sie dem Benutzer dieses Schlüssels vertraut. Ein Benutzer, der auf den Schlüssel stößt, kann die Signatur mit dem öffentlichen Schlüssel der CA überprüfen.

CA-Schlüssel

CA-Schlüssel sind wertvoll, daher verschlüsseln und speichern wir sie auf der Festplatte und stellen sicher, dass sie für andere Benutzer nicht lesbar sind.

ca_key = OpenSSL::PKey::RSA.new 2048
password = 'my secure password goes here'

cipher = 'aes-256-cbc'

open 'ca_key.pem', 'w', 0400 do |io|
  io.write ca_key.private_to_pem(cipher, password)
end

CA-Zertifikat

Ein CA-Zertifikat wird auf die gleiche Weise erstellt wie das obige Zertifikat, jedoch mit anderen Erweiterungen.

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')

Diese Erweiterung gibt an, dass der Schlüssel der CA als CA verwendet werden kann.

ca_cert.add_extension \
  extension_factory.create_extension('basicConstraints', 'CA:TRUE', true)

Diese Erweiterung gibt an, dass der Schlüssel der CA zur Verifizierung von Signaturen auf Zertifikaten und Zertifikatswiderrufen verwendet werden kann.

ca_cert.add_extension \
  extension_factory.create_extension(
    'keyUsage', 'cRLSign,keyCertSign', true)

Stamm-CA-Zertifikate sind selbstsigniert.

ca_cert.sign ca_key, OpenSSL::Digest.new('SHA1')

Das CA-Zertifikat wird auf der Festplatte gespeichert, damit es an alle Benutzer der Schlüssel verteilt werden kann, die diese CA signieren wird.

open 'ca_cert.pem', 'w' do |io|
  io.write ca_cert.to_pem
end

Zertifikatssignierungsanforderung

Die CA signiert Schlüssel über eine Zertifikatssignierungsanforderung (CSR). Die CSR enthält die notwendigen Informationen zur Identifizierung des Schlüssels.

csr = OpenSSL::X509::Request.new
csr.version = 0
csr.subject = name
csr.public_key = key.public_key
csr.sign key, OpenSSL::Digest.new('SHA1')

Eine CSR wird auf der Festplatte gespeichert und zur Signatur an die CA gesendet.

open 'csr.pem', 'w' do |io|
  io.write csr.to_pem
end

Zertifikat aus CSR erstellen

Nach Erhalt einer CSR verifiziert die CA diese, bevor sie sie signiert. Eine minimale Verifizierung wäre die Überprüfung der Signatur der CSR.

csr = OpenSSL::X509::Request.new File.read 'csr.pem'

raise 'CSR can not be verified' unless csr.verify csr.public_key

Nach der Verifizierung wird ein Zertifikat erstellt, für verschiedene Verwendungszwecke gekennzeichnet, mit dem CA-Schlüssel signiert und an den Anforderer zurückgegeben.

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.new('SHA1')

open 'csr_cert.pem', 'w' do |io|
  io.write csr_cert.to_pem
end

SSL- und TLS-Verbindungen

Mit unserem erstellten Schlüssel und Zertifikat können wir eine SSL- oder TLS-Verbindung herstellen. Ein OpenSSL::SSL::SSLContext wird verwendet, um eine SSL-Sitzung einzurichten.

context = OpenSSL::SSL::SSLContext.new

SSL-Server

Ein SSL-Server benötigt das Zertifikat und den privaten Schlüssel, um sicher mit seinen Clients zu kommunizieren.

context.cert = cert
context.key = key

Erstellen Sie dann einen OpenSSL::SSL::SSLServer mit einem TCP-Server-Socket und dem Kontext. Verwenden Sie den SSLServer wie einen gewöhnlichen TCP-Server.

require 'socket'

tcp_server = TCPServer.new 5000
ssl_server = OpenSSL::SSL::SSLServer.new tcp_server, context

loop do
  ssl_connection = ssl_server.accept

  data = ssl_connection.gets

  response = "I got #{data.dump}"
  puts response

  ssl_connection.puts "I got #{data.dump}"
  ssl_connection.close
end

SSL-Client

Ein SSL-Client wird mit einem TCP-Socket und dem Kontext erstellt. OpenSSL::SSL::SSLSocket#connect muss aufgerufen werden, um den SSL-Handshake zu initiieren und die Verschlüsselung zu starten. Für den Client-Socket sind kein Schlüssel und kein Zertifikat erforderlich.

Beachten Sie, dass OpenSSL::SSL::SSLSocket#close den zugrunde liegenden Socket standardmäßig nicht schließt. Setzen Sie OpenSSL::SSL::SSLSocket#sync_close auf true, wenn Sie dies wünschen.

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

Peer-Verifizierung

Eine unverifizierte SSL-Verbindung bietet nicht viel Sicherheit. Für erhöhte Sicherheit können der Client oder der Server das Zertifikat seines Peers verifizieren.

Der Client kann modifiziert werden, um das Zertifikat des Servers gegen das Zertifikat der Zertifizierungsstelle zu verifizieren.

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

Wenn das Serverzertifikat ungültig ist oder context.ca_file beim Verifizieren von Peers nicht gesetzt ist, wird ein OpenSSL::SSL::SSLError ausgelöst.