[Ruby] Test de référence de chiffrement de clé commune

introduction

Lorsqu'il s'agit d'informations sensibles, il est courant de crypter du texte brut, de l'enregistrer dans une base de données et de le décrypter lors de sa récupération.

Dans mon travail, j'ai examiné les méthodes de chiffrement et de déchiffrement avec Ruby, et j'ai eu l'occasion de faire un test de référence avec chaque méthode, j'ai donc résumé le contenu à ce moment-là.

Deux méthodes sont envisagées: la bibliothèque standard Ruby OpenSSL (propre implémentation) et AWS Key Management Service (KMS).

Votre propre implémentation est susceptible d'être coûteuse en calcul, et KMS est susceptible d'être coûteux en réseau, donc l'accent sera mis sur la façon dont cela en résultera.

Méthode de cryptage

Chiffrement de clé commune

Une méthode dans laquelle l'expéditeur et le destinataire partagent une clé secrètement et utilisent une clé commune pour le cryptage et le décryptage. Si les mêmes données sont toujours remplacées par le même chiffre, le texte brut est déduit de la fréquence, définissez donc le vecteur d'initialisation (ou sel) de sorte que les mêmes données puissent être remplacées par un chiffre différent. Cette fois, j'ai utilisé le vecteur d'initialisation.

Cela ressemble à ceci lorsque vous utilisez la bibliothèque Ruby standard ʻOpenSSL :: Cipher`.

def encrypt(plaintext, key, iv)
  enc = OpenSSL::Cipher.new('AES-256-CBC')
  enc.encrypt
  enc.key = key
  enc.iv = iv
  enc.update(plaintext) + enc.final
end

def decrypt(encrypted_data, key, iv)
  dec = OpenSSL::Cipher.new('AES-256-CBC')
  dec.decrypt
  dec.key = key
  dec.iv = iv
  decrypted_data = dec.update(encrypted_data) + dec.final

  #Les données déchiffrées sont ASCII-Puisqu'il s'agit de 8BIT, corriger l'encodage de force
  decrypted_data.force_encoding("UTF-8")
end

plaintext = "Chaîne de caractères à chiffrer"

key = "Clé commune"
iv = "Vecteur d'initialisation"

#Cryptage des données
encrypted_data = encrypt(plaintext, key, iv)

#Décryptage des données
decrypt(encrypted_data, key, iv)

Chiffrement à clé publique

Une méthode dans laquelle le cryptage est effectué avec une clé publique et le décryptage est effectué avec une clé privée.

Cela ressemble à ceci lorsque vous utilisez la bibliothèque Ruby standard ʻOpenSSL :: Cipher`.

def encrypt(plaintext, public_key)
  Base64.encode64(
    public_key.public_encrypt(
      data, 
      OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING
    )
  )
end

def decrypt(encrypted_data, private_key)
  decrypted_data = private_key.private_decrypt(
    Base64.decode64(encrypted_data), 
    OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING
  )

  #Les données déchiffrées sont ASCII-Puisqu'il s'agit de 8BIT, corriger l'encodage de force
  decrypted_data.force_encoding("UTF-8")
end

plaintext = "Chaîne de caractères à chiffrer"

public_key = OpenSSL::PKey::RSA.new(File.read(public_key_file))
private_key = OpenSSL::PKey::RSA.new(File.read(private_key_file))

#Cryptage des données
encrypted_data = encrypt(plaintext, public_key)

#Décryptage des données
decrypt(encrypted_data, private_key)

Vue d'ensemble du benchmark

Cette fois, comme le texte brut à chiffrer est un texte long de plusieurs centaines de caractères, le chiffrement à clé publique ne peut pas être utilisé (il peut être utilisé avec un peu d'ingéniosité, mais ce n'est pas recommandé), nous avons donc décidé d'utiliser un chiffrement à clé commune.

Utilisez la bibliothèque Benchmark pour l'analyse comparative https://docs.ruby-lang.org/ja/latest/class/Benchmark.html

require 'benchmark'

result = Benchmark.realtime do
  #Le processus à mesurer est décrit ici.
end

puts "#{result}s"

Comparaison

Conditions du banc

Script de référence de chiffrement

Bibliothèque standard Ruby OpenSSL

require 'openssl'
require 'base64'
require 'benchmark'

def encrypt(plaintext, key, iv)
  enc = OpenSSL::Cipher.new('AES-256-CBC')
  enc.encrypt
  enc.key = key
  enc.iv = iv
  enc.update(comment) + enc.final
end

data = <<-EOS
Longue phrase ...
EOS

key = "Clé commune"
iv = "Vecteur d'initialisation"

result = Benchmark.realtime do
  1000.times do
    encrypt(plaintext, key, iv)
  end
end

KMS

require 'aws-sdk-s3'
require 'base64'
require 'benchmark'

class KMSClient
  REGION = 'ap-northeast-1'
  ALIAS_NAME = 'Nom d'alias KMS'

  def initialize
    @client = Aws::KMS::Client.new(
      region: REGION,
      #Si vous avez défini un point de terminaison VPC, spécifiez-le à la place de la région
      # endpoint: 'https://vpce-xxxxx.kms.ap-northeast-1.vpce.amazonaws.com',
      access_key_id: '',
      secret_access_key: '',
    )
    @alias = @client.list_aliases.aliases.find { |a| a.alias_name == ALIAS_NAME }
  end

  def encrypt(plaintext)
    ciphertext = @client.encrypt(
      key_id: @alias.target_key_id,
      plaintext: plaintext
    )

    Base64.encode64(ciphertext.ciphertext_blob)
  end
end

plaintext = <<-EOS
Longue phrase ...
EOS

client = KMSClient.new

result = Benchmark.realtime do
  1000.times do
    client.encrypt(plaintext)
  end
end

puts "#{result}s"

Script de référence de décryptage

Bibliothèque standard Ruby OpenSSL

require 'openssl'
require 'base64'
require 'benchmark'

def decrypt(encrypted_data, key, iv)
  dec = OpenSSL::Cipher.new('AES-256-CBC')
  dec.decrypt
  dec.key = key
  dec.iv = iv
  decrypted_data = dec.update(encrypted_data) + dec.final
  decrypted_data.force_encoding("UTF-8")
end

plaintext = <<-EOS
Longue phrase ...
EOS

key = "Clé commune"
iv = "Vecteur d'initialisation"

encrypted_data = encrypt(plaintext, key, iv)

result = Benchmark.realtime do
  1000.times do
    decrypt(encrypted_data, key, iv)
  end
end

puts "#{result}s"

KMS

require 'aws-sdk-s3'
require 'base64'
require 'benchmark'

class KMSClient
  REGION = 'ap-northeast-1'
  ALIAS_NAME = 'Nom d'alias KMS'

  def initialize
    @client = Aws::KMS::Client.new(
      region: REGION,
      #Si vous avez défini un point de terminaison VPC, spécifiez-le à la place de la région
      # endpoint: 'https://vpce-xxxxx.kms.ap-northeast-1.vpce.amazonaws.com',
      access_key_id: '',
      secret_access_key: '',
    )
    @alias = @client.list_aliases.aliases.find { |a| a.alias_name == ALIAS_NAME }
    p @alias
  end

  def encrypt(plaintext)
    ciphertext = @client.encrypt(
      key_id: @alias.target_key_id,
      plaintext: plaintext
    )

    Base64.encode64(ciphertext.ciphertext_blob)
  end

  def decrypt(ciphertext_blob)
    @client.decrypt(ciphertext_blob: Base64.decode64(ciphertext_blob)).plaintext
  end
end

plaintext = <<-EOS
Longue phrase ...
EOS

client = KMSClient.new

encrypted_data = client.encrypt(plaintext)

result = Benchmark.realtime do
  1000.times do
    client.decrypt(encrypted_data)
  end
end

puts "#{result}s"

Résultats de référence

chiffrement

Méthode Le nombre de secondes
Bibliothèque standard Ruby OpenSSL 0.006588994991034269
KMS 8.035557514987886
KMS (point de terminaison VPC) 7.766658762935549

Décryptage

Méthode Le nombre de secondes
Bibliothèque standard Ruby OpenSSL 0.0037274740170687437
KMS 8.964495759923011
KMS (point de terminaison VPC) 7.9086791928857565

Résumé

Après tout, KMS semble lent en raison du coût important du réseau. Je pense que c'est le résultat de l'accès réseau à AWS chaque fois que la méthode de cryptage / décryptage est appelée. Définir un point de terminaison VPC et lui permettre de se connecter au sein du VPC l'améliorerait un peu, mais il ne semble toujours pas battre sa propre implémentation. Cependant, si vous allongez la clé utilisée pour le chiffrement pour améliorer la sécurité, le coût du calcul augmentera même si vous l'implémentez vous-même, il semble donc que vous deviez faire attention à ce point.

Recommended Posts

[Ruby] Test de référence de chiffrement de clé commune