postgresqluser-defined-functionsencryptionfeistel-cipher

Decrypt Feistel Cipher in PostgreSQL


I updated a bunch of fields in my db using this Feistel Cipher. According to the documentation, the cipher can be undone to get the original value. How can I undo the values if need be?

Here is the original cipher function:

CREATE OR REPLACE FUNCTION pseudo_encrypt(VALUE int) returns bigint AS $$
DECLARE
l1 int;
l2 int;
r1 int;
r2 int;
i int:=0;
BEGIN
 l1:= (VALUE >> 16) & 65535;
 r1:= VALUE & 65535;
 WHILE i < 3 LOOP
   l2 := r1;
   r2 := l1 # ((((1366.0 * r1 + 150889) % 714025) / 714025.0) * 32767)::int;
   l1 := l2;
   r1 := r2;
   i := i + 1;
 END LOOP;
 RETURN ((l1::bigint << 16) + r1);
END;
$$ LANGUAGE plpgsql strict immutable;

Solution

  • You could use this self-reversible variant in the first place:

    CREATE FUNCTION rev_pseudo_encrypt(VALUE bigint) returns bigint AS $$
    DECLARE
    l1 int;
    l2 int;
    r1 int;
    r2 int;
    i int:=0;
    BEGIN
     l1:= (VALUE >> 16) & 65535;
     r1:= VALUE & 65535;
     WHILE i < 3 LOOP
       l2 := r1;
       r2 := l1 # ((((1366.0 * r1 + 150889) % 714025) / 714025.0) * 32767)::int;
       l1 := l2;
       r1 := r2;
       i := i + 1;
     END LOOP;
     RETURN ((r1::bigint<<16) + l1);
    END;
    $$ LANGUAGE plpgsql strict immutable;
    

    It differs from the original version by taking bigint instead of int as input (but the input should still less than 2^32), and by the fact that the two 16 bits blocks are swapped in the 32 bits final result.

    It has the property that rev_pseudo_encrypt(rev_pseudo_encrypt(x)) = x in addition to the uniquess of results.
    Also there's the advantage that the input type is the same as the output type.

    On the other hand, to reverse the values generated by the original version, their 16 bits blocks need to be swapped before being fed to the algorithm, and the result again swapped:

    create function swap16(bigint) returns bigint as
     'select (($1&65535)<<16)+(($1)>>16)'
    language sql stable;
    
     select pseudo_encrypt(1234);
     pseudo_encrypt 
    ----------------
          223549288
    
    select swap16(pseudo_encrypt(swap16(223549288)::int));
     swap16 
    --------
       1234