Upgrading FeistelCipher
View SourceFrom v0.14.0 or v0.15.0 to v1.0.0
v0.14.0/v0.15.0 → v1.0.0 is fully backward compatible when using time_bits: 0. The cipher algorithm (HMAC-SHA256) is identical; only the API and PostgreSQL function names changed.
What changed
- PG function names:
feistel_cipher→feistel_cipher_v1,feistel_column_trigger→feistel_trigger_v1. Old functions are left untouched so they coexist during upgrade. bitsoption renamed todata_bits(default changed from 52 to 38)- New time-related options in v1:
time_bits(default:15) for time-based prefixtime_bucket(default:86400)time_offset(default:0)encrypt_time(default:false)- Use
time_bits: 0to keep the old behavior
Steps
Update dependency to
~> 1.0Run the upgrade task to generate a database migration:
mix feistel_cipher.upgrade
- Edit the generated migration to fill in your
functions_salt(find it in the migration with timestamp 19730501000000):
def up do
# Install v1 functions (coexist with old v0.x functions).
FeistelCipher.up_v1_functions(functions_prefix: "public", functions_salt: YOUR_SALT)
end
def down do
FeistelCipher.down_v1_functions(functions_prefix: "public")
endGenerate trigger migration(s):
For Ash users: Run
mix ash.codegen --name upgrade_feistel_triggers_to_v1. In the generated migration'supfunction, replacedown_for_triggerwithdown_for_legacy_triggerto drop legacy triggers. Also in thedownfunction, replaceup_for_triggerwithup_for_legacy_triggerandbits:withtime_bits: 0, data_bits:.For plain Ecto users: Create a separate migration to upgrade each trigger from v0.x to v1. For each table using Feistel cipher, drop the old trigger and recreate with v1:
# bits: N -> time_bits: 0, data_bits: N (old default for bits was 52) execute FeistelCipher.down_for_legacy_trigger("public", "posts", "seq", "id") execute FeistelCipher.up_for_v1_trigger("public", "posts", "seq", "id", time_bits: 0, data_bits: 52, functions_prefix: "public")(Optional) Drop old v0.x functions in your last migration. Which functions exist depends on the version you're upgrading from:
# v0.15.0 execute "DROP FUNCTION IF EXISTS public.feistel_cipher(bigint, int, bigint, int)" execute "DROP FUNCTION IF EXISTS public.feistel_column_trigger()" # v0.14.0 execute "DROP FUNCTION IF EXISTS public.feistel_encrypt(bigint, int, bigint, int)" execute "DROP FUNCTION IF EXISTS public.feistel_column_trigger()" # v0.13.x or earlier execute "DROP FUNCTION IF EXISTS public.feistel(bigint, int, bigint)" execute "DROP FUNCTION IF EXISTS public.handle_feistel_encryption()"Run
mix ecto.migrate
From v0.13.x or earlier to v1.0.0
v0.14.0 introduced a breaking change to the cipher algorithm: the round function was hardened from a simple hash to HMAC-SHA256. This means encryption results are different — the same input produces different output.
Impact
- Existing encrypted data remains valid as long as the old PostgreSQL functions (
feistel_cipher) stay in the database. - New rows will use
feistel_cipher_v1which produces different encrypted values than the oldfeistel_cipherfor the same input. - There are no primary key collisions because both old and new functions are bijective (one-to-one mapping) within their bit range. The old function maps input space A → output space A (a permutation), and the new function maps input space A → output space A (a different permutation). Since each is collision-free independently, mixing them does not cause collisions.
- However, the 1:1 reversibility property is lost for old rows: calling
feistel_cipher_v1(old_encrypted_id)will NOT return the originalseqvalue.
When this matters
- If your application decrypts IDs (e.g.,
id → seqlookup using the cipher function), old rows will decrypt incorrectly with the new function. - If your application only uses encrypted IDs as opaque identifiers (lookup by
iddirectly), this is not an issue.
Steps
Follow the same steps as "From v0.14.0 or v0.15.0 to v1.0.0" above. The upgrade task works the same way.
If you need to maintain decryption capability for old rows, keep the old feistel_cipher function in the database (skip Step 3) and use it for decrypting pre-upgrade data.