pgsodium is a Postgres extension that provides an alternative to pgcrypto with encryption functions that wrap the libsodium encryption library. It offers features like multi-layered role-based access to symmetric functions, integration with key management systems, end-to-end encryption with key exchange, and streaming encryption with forward secrecy. It aims to address limitations of pgcrypto like lack of key derivation, public key signing, and missing cryptography functions.
2. pgsodium is an alternative to pgcrypto
● A Postgres extension containing encryption functions.
● pgsodium contains no encryption algorithms.
● Wraps the libsodium encryption library 1 to 1.
● libsodium provides a high-level hard-to-misuse crypto API.
● Based on NaCL by Daniel J. Bernstein.
● Crypto random numbers and bytea, shared secret, public key, signing,
authenticated encryption, AEAD, streaming encryption, key exchange, key
derivation, key management service, and more!
3. Key Features of pgsodium
● Uses Multi-layered role-based access to its symmetric functions.
● Symmetric Keyless mode internally using keys derived by id from hidden key
unavailable to SQL. Least privileged role can never use raw bytea keys.
● Can work with KMS cloud providers. Google, Amazon and Zymbit examples
provided.
● Payload compatible with dozens of languages that bind libsodium.
● End-to-End encryption between clients with key exchange, even over untrusted
postgres server.
● Streaming encryption with ratcheting-based forward secrecy.
4. Layered Access Control
pgsodium_keymaker: all access/make symmetric keys
pgsodium_keyholder: use/derive symmetric keys
pgsodium_keyiduser: key id only.
● Principle of Least Privilege: revoke, revoke, revoke.
5. Why not pgcrypto?
"I'd strongly advise against having any new infrastrure (sic) depend on
pgcrypto. Its code quality imo is well below our standards and contains
serious red flags like very outdated copies of cryptography algorithm
implementations. I think we should consider deprecating and removing it,
not expanding its use." - Andres Freund (pgsql-hackers Feb 2020)
This is an unsolicited public opinion about pgcrypto. This is not an endorsement
of pgsodium. Andres Freund has nothing to do with pgsodium.
6. Why not pgcrypto?
● pgcrypto is raw bytea key only, no key derivation by id and no hidden root
key.
● No built-in integration with KMS.
● No role-based key access control.
● pgcrypto is text centric. All pgsodium input/output is bytea.
● No public key signing? This is why I created pgsodium.
● Other missing bits: key derivation, key exchange, AEAD, streaming
encryption, etc.
7. Why not pgsodium?
● Only 4 years old.
● Does not come built into postgres.
● Not nearly as many users/deployments/eyeballs.
● Not available on any “cloud” managed postgres services.
● Not b/w compatible with pgcrypto.
● Low “bus factor”.
8. Shared secret with crypto_secretbox()
-- These examples use psql client-side variables with gset
crypto_secretbox_keygen() boxkey gset
crypto_secretbox_noncegen() secretboxnonce gset
crypto_secretbox('secret message', :'secretboxnonce', :'boxkey') secretbox gset
crypto_secretbox_open(:'secretbox', :'secretboxnonce', :'boxkey')
-- “Keyless” mode shown below, restricted to pgsodium_keyiduser
crypto_secretbox(‘secret message', :'secretboxnonce', 42) secretbox gset
crypto_secretbox_open(:'secretbox', :'secretboxnonce', 42)
9. Public encryption with crypto_box()
SELECT crypto_box_noncegen() boxnonce gset
SELECT public, secret FROM crypto_box_new_keypair() gset bob_
SELECT public, secret FROM crypto_box_new_keypair() gset alice_
-- Assume bob and alice exchange public keys
crypto_box('hi bob', :'boxnonce', :'bob_public', :'alice_secret') box gset
crypto_box_open(:'box', :'boxnonce', :'alice_public', :'bob_secret')
-- Anonymous “sealed” boxes, only Bob can open
crypto_box_seal('bob is your uncle', :'bob_public') sealed gset
crypto_box_seal_open(:'sealed', :'bob_public', :'bob_secret')
10. Public signing with crypto_sign()
SELECT public, secret FROM crypto_sign_new_keypair() gset sign_
crypto_sign('this is authentic', :'sign_secret') signed gset
crypto_sign_open(:'signed', :'sign_public')
11. Key Exchange with crypto_kx_*()
SELECT public, secret FROM crypto_kx_new_keypair() gset bob_
SELECT public, secret FROM crypto_kx_new_keypair() gset alice_
-- Bob and Alice exchange public keys
SELECT tx, rx FROM crypto_kx_client_session_keys(
:'bob_public', :'bob_secret',
:'alice_public') gset session_bob_ -- Bob’s tx/rx session keys
SELECT tx, rx FROM crypto_kx_server_session_keys(
:'alice_public', :'alice_secret',
:'bob_public') gset session_alice_ -- Alice’s tx/rx session keys
12. Encrypted columns by key id
CREATE SCHEMA pgsodium;
CREATE EXTENSION pgsodium WITH SCHEMA pgsodium;
CREATE ROLE auser;
GRANT pgsodium_keyiduser TO auser;
GRANT USAGE ON SCHEMA pgsodium TO auser;
13. Encrypted columns by key id
CREATE TABLE test (
id bigserial primary key,
key_id bigint not null default 1,
nonce bytea not null,
data bytea
);
CREATE OR REPLACE VIEW test_view AS
SELECT id, convert_from(
pgsodium.crypto_secretbox_open(
data,
nonce,
key_id),
'utf8') AS data FROM test;
14. Encrypted columns by key id
CREATE OR REPLACE FUNCTION test_encrypt() RETURNS trigger
language plpgsql AS
$$
DECLARE
new_nonce bytea = pgsodium.crypto_secretbox_noncegen();
test_id bigint;
BEGIN
INSERT INTO test (nonce) VALUES (new_nonce) RETURNING ID INTO test_id;
UPDATE test SET
data = pgsodium.crypto_secretbox(
convert_to(new.data, 'utf8'),
new_nonce,
key_id)
WHERE id = test_id;
RETURN new;
END;
$$;
15. Encrypted columns by key id
CREATE TRIGGER test_encrypt_trigger
INSTEAD OF INSERT ON test_view
FOR EACH ROW
EXECUTE FUNCTION test_encrypt();
16. Encrypted columns by key id
CREATE OR REPLACE FUNCTION rotate_key(test_id bigint, new_key bigint)
RETURNS void LANGUAGE plpgsql AS $$
DECLARE
new_nonce bytea;
BEGIN
new_nonce = pgsodium.crypto_secretbox_noncegen();
UPDATE test SET nonce = new_nonce, key_id = new_key,
data = pgsodium.crypto_secretbox(
pgsodium.crypto_secretbox_open(test.data, test.nonce, test.key_id),
new_nonce,
new_key)
WHERE test.id = test_id;
RETURN;
END;
$$;
17. Thank you!
● Check it out at https://github.com/michelp/pgsodium
● Just released 1.3.0!
● Jupyter Python notebook examples:
○ https://github.com/michelp/pgsodium/blob/master/example/PgSodiumAnonymizer.ipynb
○ https://github.com/michelp/pgsodium/blob/master/example/Box.ipynb
● Please contribute! Pull requests accepted.
● Contact me pelletier.michel@gmail.com with any questions.