35+ Years Experience Netherlands Based ⚡ Fast Response Times Ruby on Rails Experts AI-Powered Development Fixed Pricing Available Senior Architects Dutch & English 35+ Years Experience Netherlands Based ⚡ Fast Response Times Ruby on Rails Experts AI-Powered Development Fixed Pricing Available Senior Architects Dutch & English
ActiveRecord Encryption in Rails 7+: Versleutel Gevoelige Data Zonder Je ORM te Verlaten

ActiveRecord Encryption in Rails 7+: Versleutel Gevoelige Data Zonder Je ORM te Verlaten

TTB Software
rails, security
Een praktische handleiding voor het versleutelen van databasekolommen met ActiveRecord Encryption in Rails 7 en 8. Behandelt setup, key rotation, encrypted queries en productie-valkuilen.

ActiveRecord Encryption is beschikbaar sinds Rails 7.0 en laat je specifieke databasekolommen versleutelen op applicatieniveau. Geen extra gems. Geen aparte encryptieservice. Je declareert welke attributen versleuteld worden, en Rails regelt de rest transparant.

Hier lees je hoe je het opzet, wat er onder de motorkap gebeurt, en welke productie-valkuilen de officiële guides overslaan.

Waarom Versleuteling op Applicatieniveau

Database-level encryptie (zoals PostgreSQL’s pgcrypto of AWS RDS encryption at rest) beschermt tegen iemand die de fysieke schijf of snapshot steelt. Het doet niets als een aanvaller database-credentials bemachtigt of SQL injection uitvoert — die leest gewoon plaintext, net als je app.

Applicatie-level encryptie betekent dat de data als ciphertext in de database staat. Zelfs met volledige SELECT * toegang ziet een aanvaller onleesbare tekens. Je Rails-app decodeert bij het lezen en versleutelt bij het schrijven, met sleutels die volledig buiten de database leven.

Dit is belangrijk voor PII-velden: e-mailadressen, telefoonnummers, BSN-nummers, API-tokens voor integraties. Het soort data waar toezichthouders en meldplicht-datalekken om geven.

Sleutels Instellen

Rails heeft drie sleutels nodig voor het encryptieschema. Genereer ze:

bin/rails db:encryption:init

Dit geeft zoiets als:

active_record_encryption:
  primary_key: EGY8WhulUOXixybod7ZWwMIL68R9o5kC
  deterministic_key: aPA5XyALhf75NNnMzaspW7akTfZp0lPY
  key_derivation_salt: xEY0dt6TZcAMg52K7O84wYzkjvbA62Hz

Zet deze in config/credentials.yml.enc:

bin/rails credentials:edit

Plak het blok onder active_record_encryption. Rails pikt ze automatisch op.

Voor productie kun je ook omgevingsvariabelen gebruiken. In config/application.rb:

config.active_record.encryption.primary_key = ENV["AR_ENCRYPTION_PRIMARY_KEY"]
config.active_record.encryption.deterministic_key = ENV["AR_ENCRYPTION_DETERMINISTIC_KEY"]
config.active_record.encryption.key_derivation_salt = ENV["AR_ENCRYPTION_KEY_DERIVATION_SALT"]

Ik gebruik credentials voor single-server setups en environment variables bij deployment over meerdere nodes waar het synchroniseren van credential-bestanden rommelig wordt.

Je Eerste Kolom Versleutelen

Stel je hebt een users tabel met een email kolom die je wilt versleutelen. In je model:

class User < ApplicationRecord
  encrypts :email, deterministic: true
  encrypts :phone_number
end

Twee modi:

  • Deterministisch (deterministic: true): Dezelfde plaintext levert altijd dezelfde ciphertext op. Hiermee kun je de kolom doorzoeken met WHERE clausules. Gebruik dit voor velden waarop je moet zoeken — e-mailadressen, gebruikersnamen, rekeningnummers.
  • Niet-deterministisch (de standaard): Elke versleuteling produceert andere ciphertext. Veiliger (voorkomt frequentie-analyse aanvallen) maar je kunt deze kolommen niet doorzoeken. Gebruik dit voor velden die je alleen leest na het laden van het record — telefoonnummers, notities, API-sleutels.

Dat is alles voor het model. Geen migratie nodig als de kolom al bestaat als string of text type. Rails slaat de ciphertext op in de bestaande kolom.

Hoe de Ciphertext Eruitziet

Vóór versleuteling:

roger@example.com

Erna:

{"p":"nKQ8alYY2sCjBbx0","h":{"iv":"sNq5MRzjEUS4VfMR","at":"ntwU48aTKCYMxFCz8RPA3Q=="}}

Het is een JSON-envelope met de versleutelde payload, initialisatievector en authenticatietag. De kolom moet groot genoeg zijn — een string kolom (standaard 255 tekens) volstaat voor de meeste waarden, maar zeer lange plaintexts hebben wellicht text nodig.

Encrypted Data Doorzoeken

Deterministische encryptie maakt queries mogelijk die er volkomen normaal uitzien:

User.find_by(email: "roger@example.com")
# Rails versleutelt "roger@example.com" met de deterministische sleutel
# en voert uit: SELECT * FROM users WHERE email = '{"p":"nKQ8a..."}'

Dit werkt voor find_by, where, uniqueness-validaties en find_or_create_by. Vanuit je applicatiecode verandert er niets.

Wat niet werkt:

  • LIKE queries: User.where("email LIKE ?", "%example%") matcht niet. De ciphertext heeft geen relatie met het plaintext-patroon.
  • Database-level sortering: ORDER BY email sorteert op ciphertext, wat betekenisloos is.
  • Database-level constraints: Een UNIQUE index op de kolom werkt nog wel met deterministische encryptie (zelfde plaintext = zelfde ciphertext), maar CHECK constraints op plaintext-waarden niet.

Bestaande Data Migreren

Als je encryptie toevoegt aan een kolom die al plaintext-data bevat, moet je de bestaande rijen opnieuw versleutelen:

class EncryptExistingEmails < ActiveRecord::Migration[7.1]
  def up
    User.find_each do |user|
      user.encrypt
    end
  end

  def down
    User.find_each do |user|
      user.decrypt
    end
  end
end

De encrypt methode leest elk attribuut, versleutelt het en slaat het op. Op een tabel met 100K rijen duurde dit ongeveer 45 seconden in mijn tests op PostgreSQL 16 met een standaard db.t3.medium RDS instance. Voor grotere tabellen, batch het met in_batches:

User.in_batches(of: 1000) do |batch|
  batch.each(&:encrypt)
end

Draai dit in een maintenance window of achter een feature flag als je geen downtime kunt veroorloven. De kolom accepteert zowel plaintext als ciphertext tijdens de transitie — Rails detecteert wat het leest en handelt beide af.

Key Rotation

Sleutels raken gecompromitteerd. Compliance-frameworks vereisen periodieke rotatie. ActiveRecord Encryption handelt dit af met een sleutellijst:

# config/credentials.yml.enc
active_record_encryption:
  primary_key:
    - nieuwe_primary_key
    - oude_primary_key
  deterministic_key:
    - nieuwe_deterministic_key
    - oude_deterministic_key
  key_derivation_salt: xEY0dt6TZcAMg52K7O84wYzkjvbA62Hz

Rails probeert decryptie met elke sleutel op volgorde. De eerste sleutel in de lijst wordt gebruikt voor nieuwe versleutelingen. Oude sleutels ontsleutelen bestaande data.

Na het toevoegen van de nieuwe sleutel, alles opnieuw versleutelen:

User.find_each(&:encrypt)

Dit leest met elke matchende sleutel en schrijft met de nieuwe primaire sleutel. Zodra alle rijen opnieuw versleuteld zijn, kun je de oude sleutel uit de lijst verwijderen.

Productie-valkuilen

Kolomgrootte

De JSON ciphertext-envelope voegt overhead toe. Een e-mail van 50 tekens wordt ongeveer 150 tekens versleuteld. Als je kolom varchar(100) is, loop je tegen truncatie aan. Controleer je kolomgroottes voordat je encryptie inschakelt.

Database Dumps en Imports

Als je je productiedatabase dumpt en importeert in staging, heeft de staging-omgeving dezelfde encryptiesleutels nodig om de data te lezen. Dit klinkt vanzelfsprekend, maar ik heb teams urenlang zien worstelen met onleesbare data na een verse import.

Console-toegang

rails console in productie ontsleutelt transparant. Iedereen met console-toegang kan versleutelde data lezen. Beperk productie console-toegang dienovereenkomstig.

Performance

Versleuteling en ontsleuteling voegen CPU-overhead toe per attribuuttoegang. In benchmarks op Ruby 3.3 met Rails 7.2:

  • Eén attribuut versleutelen: ~0.02ms
  • Eén attribuut ontsleutelen: ~0.015ms
  • 1.000 records laden met 2 versleutelde attributen: voegt ~30ms totaal toe

Voor typische webrequests die een handvol records laden is dit onzichtbaar. Voor batchjobs die miljoenen rijen verwerken, telt het op.

Wanneer Niet Gebruiken

  • Bestandsversleuteling: Gebruik Active Storage met een custom service of client-side encryptie.
  • Wachtwoord-hashing: Blijf has_secure_password met bcrypt gebruiken. Encryptie is omkeerbaar; wachtwoordopslag hoort dat niet te zijn.
  • Kolommen in complexe SQL: Als je LIKE, BETWEEN, joins of aggregaatfuncties nodig hebt, breekt encryptie die queries.

FAQ

Kan ik kolommen op bestaande tabellen versleutelen zonder downtime?

Ja. encrypts :column_name toevoegen aan het model is backward-compatible. Rails leest zowel plaintext als ciphertext uit de kolom. Je kunt dan geleidelijk rijen opnieuw versleutelen in achtergrondtaken terwijl de app normaal verkeer afhandelt.

Werkt ActiveRecord Encryption met PostgreSQL, MySQL en SQLite?

Het werkt met alle drie. De versleuteling gebeurt in Ruby vóórdat de waarde de database-adapter bereikt, dus de database-engine ziet nooit plaintext. Ik heb het specifiek in productie getest met PostgreSQL 15 en 16, maar de Rails test-suite dekt alle drie de adapters.

Welk encryptie-algoritme gebruikt Rails?

AES-256-GCM standaard, wat zowel vertrouwelijkheid als authenticatie (manipulatiedetectie) biedt. De key derivation gebruikt PBKDF2 met de salt die je configureert. AES-256-GCM is de standaardaanbeveling en wat NIST en de meeste compliance-frameworks verwachten.

Beïnvloeden versleutelde kolommen database-indexering?

Bij deterministische encryptie kun je de kolom indexeren en werkt de index correct omdat identieke plaintexts identieke ciphertexts opleveren. Bij niet-deterministische encryptie is een index technisch mogelijk maar nutteloos. Voeg alleen indexes toe aan deterministisch versleutelde kolommen.

T

About the Author

Roger Heykoop is een senior Ruby on Rails ontwikkelaar met 19+ jaar Rails ervaring en 35+ jaar ervaring in softwareontwikkeling. Hij is gespecialiseerd in Rails modernisering, performance optimalisatie, en AI-ondersteunde ontwikkeling.

Get in Touch

Share this article

Need Expert Rails Development?

Let's discuss how we can help you build or modernize your Rails application with 19+ years of expertise

Schedule a Free Consultation