GithubHelp home page GithubHelp logo

michelp / pgsodium Goto Github PK

View Code? Open in Web Editor NEW
535.0 12.0 32.0 462 KB

Modern cryptography for PostgreSQL using libsodium.

License: Other

Makefile 0.19% C 41.56% PLpgSQL 35.37% Dockerfile 1.19% Shell 2.41% Jupyter Notebook 10.96% Perl 8.27% Batchfile 0.05%
postgresql cryptography library encryption decryption libsodium sql

pgsodium's Introduction

Tests

pgsodium

pgsodium is an encryption library extension for PostgreSQL using the libsodium library for high level cryptographic algorithms.

pgsodium can be used a straight interface to libsodium, but it can also use a powerful feature called Server Key Management where pgsodium loads an external secret key into memory that is never accessible to SQL. This inaccessible root key can then be used to derive sub-keys and keypairs by key id. This id (type bigint) can then be stored instead of the derived key.

Another advanced feature of pgsodium is Transparent Column Encryption which can automatically encrypt and decrypt one or more columns of data in a table.

Table of Contents

Installation

pgsodium requires libsodium >= 1.0.18. In addition to the libsodium library and it's development headers, you may also need the PostgreSQL header files typically in the '-dev' packages to build the extension.

After installing the dependencies, clone the repo and run sudo make install. You can also install pgsodium through the pgxn extension network with pgxn install pgsodium.

pgTAP tests can be run with sudo -u postgres pg_prove test.sql or they can be run in a self-contained Docker image. Run ./test.sh if you have docker installed to run all tests.

As of version 3.0.0 pgsodium requires PostgreSQL 14+. Use pgsodium 2.0.* for earlier versions of Postgres. Once you have the extension correctly compiled you can install it into your database using the SQL:

CREATE EXTENSION pgsodium;

Note that pgsodium is very careful about the risk of search_path hacking and must go into a database schema named pgsodium. The above command will automatically create that schema. You are encouraged to always reference pgsodium functions by their fully qualified names, or by making sure that the pgsodium schema is first in your search_path.

Usage

Without using the optional Server Managed Keys feature pgsodium is a simple and straightforward interface to the libsodium API.

pgsodium arguments and return values for content and keys are of type bytea. If you wish to use text or varchar values for general content, you must make sure they are encoded correctly. The encode() and decode() and convert_to()/convert_from() binary string functions can convert from text to bytea. Simple ascii text strings without escape or unicode characters will be cast by the database implicitly, and this is how it is done in the tests to save time, but you should really be explicitly converting your text content if you wish to use pgsodium without conversion errors.

Most of the libsodium API is available as SQL functions. Keys that are generated in pairs are returned as a record type, for example:

postgres=# SELECT * FROM crypto_box_new_keypair();
                               public                               |                               secret
--------------------------------------------------------------------+--------------------------------------------------------------------
 \xa55f5d40b814ae4a5c7e170cd6dc0493305e3872290741d3be24a1b2f508ab31 | \x4a0d2036e4829b2da172fea575a568a74a9740e86a7fc4195fe34c6dcac99976
(1 row)

pgsodium is careful to use memory cleanup callbacks to zero out all allocated memory used when freed. In general it is a bad idea to store secrets in the database itself, although this can be done carefully it has a higher risk. To help with this problem, pgsodium has an optional Server Key Management function that can load a hidden server key at boot that other keys are derived from.

Server Key Management

If you add pgsodium to your shared_preload_libraries configuration and place a special script in your postgres shared extension directory, the server can preload a libsodium key on server start. This root secret key cannot be accessed from SQL. The only way to use the server secret key is to derive other keys from it using derive_key() or use the key_id variants of the API that take key ids and contexts instead of raw bytea keys.

Server managed keys are completely optional, pgsodium can still be used without putting it in shared_preload_libraries, but you will need to provide your own key management. Skip ahead to the API usage section if you choose not to use server managed keys.

See the file getkey_scripts/pgsodium_getkey_urandom.sh for an example script that returns a libsodium key using the linux /dev/urandom CSPRNG.

pgsodium also comes with example scripts for:

Next place pgsodium in your shared_preload_libraries. For docker containers, you can append this after the run:

docker run -d ... -c 'shared_preload_libraries=pgsodium'

When the server starts, it will load the secret key into memory, but this key is never accessible to SQL. It's possible that a sufficiently clever malicious superuser can access the key by invoking external programs, causing core dumps, looking in swap space, or other attack paths beyond the scope of pgsodium. Databases that work with encryption and keys should be extra cautious and use as many process hardening mitigations as possible.

It is up to you to edit the get key script to get or generate the key however you want. pgsodium can be used to generate a new random key with select encode(randombytes_buf(32), 'hex'). Other common patterns including prompting for the key on boot, fetching it from an ssh server or managed cloud secret system, or using a command line tool to get it from a hardware security module.

You can specify the location of the get key script with a database configuration variable in either postgresql.conf or using ALTER SYSTEM:

pgsodium.getkey_script = 'path_to_script'

Server Key Derivation

New keys are derived from the primary server secret key by id and an optional context using the libsodium Key Derivation Functions. Key id are just bigint integers. If you know the key id, key length (default 32 bytes) and the context (default 'pgsodium'), you can deterministicly generate a derived key.

Derived keys can be used to encrypt data or as a seed for deterministicly generating keypairs using crypto_sign_seed_keypair() or crypto_box_seed_keypair(). It is wise not to store these secrets but only store or infer the key id, length and context. If an attacker steals your database image, they cannot generate the key even if they know the key id, length and context because they will not have the server secret key.

The key id, key length and context can be secret or not, if you store them then possibly logged in database users can generate the key if they have permission to call the derive_key() function. Keeping the key id and/or length context secret to a client avoid this possibility and make sure to set your database security model correctly so that only the minimum permission possible is given to users that interact with the encryption API.

Key rotation is up to you, whatever strategy you want to go from one key to the next. A simple strategy is incrementing the key id and re-encrypting from N to N+1. Newer keys will have increasing ids, you can always tell the order in which keys are superceded.

A derivation context is an 8 byte bytea. The same key id in different contexts generate different keys. The default context is the ascii encoded bytes pgsodium. You are free to use any 8 byte context to scope your keys, but remember it must be a valid 8 byte bytea which automatically cast correctly for simple ascii string. For encoding other characters, see the encode() and decode() and convert_to()/convert_from() binary string functions. The derivable keyspace is huge given one bigint keyspace per context and 2^64 contexts.

To derive a key:

# select derive_key(1);
                          derive_key
--------------------------------------------------------------------
 \x84fa0487750d27386ad6235fc0c4bf3a9aa2c3ccb0e32b405b66e69d5021247b

# select derive_key(1, 64);
                                                          derive_key
------------------------------------------------------------------------------------------------------------------------------------
 \xc58cbe0522ac4875707722251e53c0f0cfd8e8b76b133f399e2c64c9999f01cb1216d2ccfe9448ed8c225c8ba5db9b093ff5c1beb2d1fd612a38f40e362073fb

# select derive_key(1, 32, '__auth__');
                          derive_key
--------------------------------------------------------------------
 \xa9aadb2331324f399fb58576c69f51727901c651c970f3ef6cff47066ea92e95

The default keysize is 32 and the default context is 'pgsodium'.

Derived keys can be used either directly in crypto_secretbox_* functions for "symmetric" encryption or as seeds for generating other keypairs using for example crypto_box_seed_new_keypair() and crypto_sign_seed_new_keypair().

# select * from crypto_box_seed_new_keypair(derive_key(1));
                               public                               |                               secret
--------------------------------------------------------------------+--------------------------------------------------------------------
 \x01d0e0ec4b1fa9cc8dede88e0b43083f7e9cd33be4f91f0b25aa54d70f562278 | \x066ec431741a9d39f38c909de4a143ed39b09834ca37b6dd2ba3d015206f14ca

Key Management API

pgsodium provides an API and internal table and view for simple key id and context managment. This table provides a number of useful columns including experation capability. Keys generated with this API must be used for the Transparent Column Encryption features.

Managed Keys have UUIDs for indentifiers, these UUIDs are used to lookup keys in the table. Note that the key management is based on the same Server Key Management that uses the internal hidden root key, so both the Key Management API and Transparent Column Encryption require it.

To create a new key, call the pgsodium.create_key() function:

# select * from pgsodium.create_key();
-[ RECORD 1 ]-------------------------------------
id          | 74d97ba2-f9e3-4a64-a032-8427cd6bd686
status      | valid
created     | 2022-08-04 05:06:53.878502
expires     |
key_type    | aead-det
key_id      | 4
key_context | \x7067736f6469756d
comment     | This is an optional comment
user_data   |

pgsodium.create_key() takes the following arguments, all of them are optional:

  • key_type pgsodium.key_type = 'aead-det': The type of key to create.If you do not specify a raw_key argument, a new derived key_id of the correct type will be automatically generated in key_context argument context. Possible values are:
    • aead-det
    • aead-ietf
    • hmacsha512
    • hmacsha256
    • auth
    • shorthash
    • generichash
    • kdf
    • generichash
    • kdf
    • secretbox
    • secretstream
  • name text = null: The optional unique name of the key.
  • raw_key bytea = null: A raw key to store encrypted, if not specified, the raw key is derived from key_id and key_context.
  • raw_key_nonce bytea = null: The nonce used to encrypt the raw key with, if not specified a new random nonce will be generated.
  • key_context bytea = 'pgsodium': The libsodium context to use for derivation if key_id is not null.
  • parent_key uuid = null: The parent key use to encrypt the raw key. If not specified, a new unnamed key is created.
  • expires timestamptz = null: The expiration time checked by the pgsodium.valid_key view.
  • associated_data text = '': Extra user data you can associate with the encrypted raw key. This data is appended to the key UUID, and mixed into the encryption signature and can be authenticated with it.

Keys of the type aead-det can be used for Transparent Column Encryption. The view pgsodium.valid_keys filters the key table for only keys that are valid and not expired.

Security Roles

The pgsodium API has two nested layers of security roles:

  • pgsodium_keyiduser Is the less privileged role that can only access keys by their UUID. This is the role you would typically give to a user facing application.

  • pgsodium_keymaker is the more privileged role and can work with raw bytea And managed server keys. You would not typically give this role to a user facing application.

Note that public key apis like crypto_box and crypto_sign do not have "key id" variants, because they work with a combination of four keys, two keypairs for each of two parties.

As the point of public key encryption is for each party to keep their secrets and for that secret to not be centrally derivable. You can certainly call something like SELECT * FROM crypto_box_seed_new_keypair(derive_key(1)) and make deterministic keypairs, but then if an attacker steals your root key they can derive all keypair secrets, so this approach is not recommended.

Transparent Column Encryption

pgsodium provides a useful pattern where a trigger is used to encrypt a column of data in a table which is then decrypted using a view. This is called Transparent Column Encryption and can be enabled with pgsodium using the SECURITY LABEL ... PostgreSQL command.

If an attacker acquires a dump of the table or database, they will not be able to derive the keys used to encrypt the data since they will not have the root server managed key, which is never revealed to SQL See the example file for more details.

In order to use TCE you must use keys created from the Key Management Table API. This API returns key ids that are UUIDs for use with the internal encryption functions used by the TCE functionality. Creating a key to use is the first step:

# select * from pgsodium.create_key();
-[ RECORD 1 ]-------------------------------------
id                | dfc44293-fa78-4a1a-9ef9-7e600e63e101
status            | valid
created           | 2022-08-03 18:50:53.355099
expires           |
key_type          | aead-det
key_id            | 5
key_context       | \x7067736f6469756d
comment           |
associated_data   |

This key is now stored in the pgsodium.key table, and can be accessed via the pgsodium.valid_key view:

# select EXISTS (select 1 from pgsodium.valid_key where id = 'dfc44293-fa78-4a1a-9ef9-7e600e63e101');
-[ RECORD 1 ]
exists | t

Now this key id can be used for simple TCE as shown in the next section.

One Key Id for the Entire Column

For the simplest case, a column can be encrypted with one key id which must be of the type aead-det (as created above):

CREATE TABLE private.users (
	id bigserial primary key,
	secret text);

SECURITY LABEL FOR pgsodium	ON COLUMN private.users.secret
  IS 'ENCRYPT WITH KEY ID dfc44293-fa78-4a1a-9ef9-7e600e63e101';

The advantage of this approach is it is very simple, the user creates one key and labels a column with it. The cryptographic algorithm for this approach uses a nonceless encryption algorithm called crypto_aead_det_xchacha20(). If you wish to use a nonce value, see below.

One Key ID per Row

Using one key for an entire column means that whoever can decrypt one row can decrypt them all from a database dump. Also changing (rotating) the key means rewriting the whole table.

A more fine grained approach is to store one key id per row:

CREATE TABLE private.users (
	id bigserial primary key,
	secret text,
	key_id uuid not null,
  nonce bytea
);

SECURITY LABEL FOR pgsodium
	ON COLUMN private.users.secret
  IS 'ENCRYPT WITH KEY COLUMN key_id;

This approach ensures that “cracking” the key for one row does not help decrypt any others. It also acts as a natural partition that can work in conjunction with RLS to share distinct keys between owners.

One Key ID per Row with Nonce Support

The default cryptographic algorithm for the above approach uses a nonceless encryption algorithm called crypto_aead_det_xchacha20(). This scheme has the advantage that it does not require nonce values, the disadvantage is that duplicate plaintexts will produce duplicate ciphertexts, but this information can not be used to attack the key it can only reveal the duplication.

However duplication is still information, and if you want more security, slightly better performance, or you require duplicate plaintexts to have different ciphertexts, a unique nonce can be provided that mixes in some additional non-secret data that deduplicates ciphertexts for duplicate plaintext:

CREATE TABLE private.users (
	id bigserial primary key,
	secret text,
	key_id uuid not null,
    nonce bytea
);

SECURITY LABEL FOR pgsodium
	ON COLUMN private.users.secret
  IS 'ENCRYPT WITH KEY COLUMN key_id NONCE nonce';

One Key ID per Row with Nonce Support and Associated Data

The aead-det algorithm can mix user provided data into the authentication signature for the encrypted secret. This "authenticates" the plaintext and ensures that it has not been altered (or the decryption will fail). This is useful for associated useful metadata with your secrets:

CREATE TABLE private.users (
	id bigserial primary key,
	secret text,
	key_id uuid not null,
    nonce bytea,
    associated_data text
);

SECURITY LABEL FOR pgsodium
	ON COLUMN private.users.secret
  IS 'ENCRYPT WITH KEY COLUMN key_id NONCE nonce ASSOCIATED (id, associated_data)';

You can specify multiple columns as shown above with both the id and associated data column. Columns used for associated data must be deterministicly castable to text.

TCE and ON CONFLICT UPDATE Clauses "UPSERT" Pattern

UPSERT is not a command in PostgreSQL, it is one pattern among many possible when using the INSERT ... ON CONFLICT DO ... clause in Postgres to either insert a value, or do some other action, which is commonly to update the alreadt extant row that the command was attempting to INSERT. This pattern usually looks like:

INSERT INTO my_table (my_columns...) VALUES (new_values...)
  ON CONFLICT (some_unique_key_like_id) DO UPDATE
  SET my_data = EXCLUDED.my_data;

The statement tries to insert a row, and if there is a unique constraint violation, it will instead update the row with the value of the row that was about to be inserted.

Unfortunately, the value of the row that was about to be inserted is already encrypted, so this pattern does not work, instead to do an "UPSERT" you must combine the unencrypted data from the view with the encryted data in the table, so that the unencrypted value is "reencrypted" correctly and not "double encrypted".

The function below shows how this query can be formatted as a stored procedure, if you are using PostgREST, this is the "RPC" function to use to do the intended upsert behavior:

CREATE OR REPLACE FUNCTION upsert_test(p_id bigint, p_name text DEFAULT NULL, p_secret text DEFAULT NULL)
    RETURNS test LANGUAGE sql AS
    $$
    INSERT INTO test (id, name, secret) VALUES (p_id, p_name, p_secret)
    ON CONFLICT (id) DO UPDATE
        SET name   = coalesce(p_name, (SELECT name FROM test WHERE id = p_id)),
            secret = coalesce(p_secret, (SELECT decrypted_secret FROM decrypted_test WHERE id = p_id))
    RETURNING *
    $$;

If you do not need the stored procedure you can modify the inner query to suite your specific needs as a literal SQL query.

Postgres 15 and "Security Invoker" Views

Postgres 15 added a new propery to views called security_invoker which changes the behavior of views that access an underlying table with row security labels. Before pg15, all views ran as the owner of the view itself, making interaction with RLS clumsy, starting with pg15, views that are marked as security_invoker will "run" with the privileges of the invoking user, not the owner of the view, this makes working with RLS policies simpler.

Any TCE security label can be appended with the string SECURITY INVOKER' which will cause the automatically generated view to be marked security_invoker=true`. Note that you are still responsible for understanding the grants/revokes to your view and table, which can vary depending on the specific needs of your application's security model.

Inspecting Security Labels

The system catalog pg_seclabel can be hard to decipher, requiring joins to figure out which labels apply to which columns. The pgsodium.seclabel view simplifies this task by resolving the table and column names for you:

postgres=> select * from pgsodium.seclabel ;
-[ RECORD 1 ]---------------------------------------------------------------------------------------------------------
nspname | tce-example
relname | test2
attname | secret2
label   | ENCRYPT WITH KEY COLUMN secret2_key_id ASSOCIATED (id, associated2) NONCE nonce2
-[ RECORD 2 ]---------------------------------------------------------------------------------------------------------
nspname | tce-example
relname | test2
attname | secret
label   | ENCRYPT WITH KEY ID f8db208c-8201-466a-98cd-b0d91f5326ca ASSOCIATED (associated) NONCE nonce
-[ RECORD 3 ]---------------------------------------------------------------------------------------------------------
nspname | tce-example
relname | test
attname | secret
label   | ENCRYPT WITH KEY ID 2a5500f3-9378-4134-89db-5fd870a5ce7a
-[ RECORD 4 ]---------------------------------------------------------------------------------------------------------
nspname | pgsodium
relname | key
attname | raw_key
label   | ENCRYPT WITH KEY COLUMN parent_key ASSOCIATED (id, associated_data) NONCE raw_key_nonce
-[ RECORD 5 ]---------------------------------------------------------------------------------------------------------
nspname | tce-example
relname | bob-testt
attname | secret2-test
label   | ENCRYPT WITH KEY COLUMN secret2_key_id-test ASSOCIATED (associated2-test) NONCE nonce2-test SECURITY INVOKER

Disabling View and Trigger generation

If you wish to disable the EVENT TRIGGER that fires and generates the trigger and view, you can turn if off with a configuration setting:

SET pgsodium.enable_event_trigger = 'off';

This parameter can be set in the postgresql.conf file, or passed to the server on startup with -C. Disabling trigger generation can be useful for doing migrations when you don't want trigger generation to get in the way of copying DDL from one system to another. See the postgres docs for Setting Parameters.

Simple public key encryption with crypto_box()

Here's an example usage from the test.sql that uses command-line psql client commands (which begin with a backslash) to create keypairs and encrypt a message from Alice to Bob.

-- Generate public and secret keypairs for bob and alice
-- \gset [prefix] is a psql command that will create local
-- script variables

SELECT public, secret FROM crypto_box_new_keypair() \gset bob_
SELECT public, secret FROM crypto_box_new_keypair() \gset alice_

-- Create a boxnonce

SELECT crypto_box_noncegen() boxnonce \gset

-- Alice encrypts the box for bob using her secret key, the nonce and his public key

SELECT crypto_box('bob is your uncle', :'boxnonce', :'bob_public', :'alice_secret') box \gset

-- Bob decrypts the box using his secret key, the nonce, and Alice's public key

SELECT crypto_box_open(:'box', :'boxnonce', :'alice_public', :'bob_secret');

Note in the above example, no secrets are stored in the db, but they are interpolated into the sql by the psql client that is sent to the server, so it's possible they can show up in the database logs. You can avoid this by using derived keys.

Avoid secret logging

If you choose to work with your own keys and not restrict yourself to the pgsodium_keyiduser role, a useful approach is to keep keys in an external storage and disables logging while injecting the keys into local variables with SET LOCAL. If the images of database are hacked or stolen, the keys will not be available to the attacker.

To disable logging of the key injections, SET LOCAL is also used to disable log_statements and then re-enable normal logging afterwards. as shown below. Setting log_statement requires superuser privileges:

-- SET LOCAL must be done in a transaction block
BEGIN;

-- Generate a boxnonce, and public and secret keypairs for bob and alice
-- This creates secrets that are sent back to the client but not stored
-- or logged.  Make sure you're using an encrypted database connection!

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_

-- Turn off logging and inject secrets
-- into session with set local, then resume logging.

SET LOCAL log_statement = 'none';
SET LOCAL app.bob_secret = :'bob_secret';
SET LOCAL app.alice_secret = :'alice_secret';
RESET log_statement;

-- Now call the `current_setting()` function to get the secrets, these are not
-- stored in the db but only in session memory, when the session is closed they are no longer
-- accessible.

-- Alice encrypts the box for bob using her secret key and his public key

SELECT crypto_box('bob is your uncle', :'boxnonce', :'bob_public',
                  current_setting('app.alice_secret')::bytea) box \gset

-- Bob decrypts the box using his secret key and Alice's public key.

SELECT crypto_box_open(:'box', :'boxnonce', :'alice_public',
                          current_setting('app.bob_secret')::bytea);

COMMIT;

For additional paranoia you can use a function to check that the connection being used is secure or a unix domain socket.

CREATE FUNCTION is_ssl_or_domain_socket() RETURNS bool
LANGUAGE plpgsql AS $$
DECLARE
    addr text;
    ssl text;
BEGIN
    SELECT inet_client_addr() INTO addr;
    SELECT current_setting('ssl', true) INTO ssl;
    IF NOT FOUND OR ((ssl IS NULL OR ssl != 'on')
        AND (addr IS NOT NULL OR length(addr) != 0))
    THEN
        RETURN false;
    END IF;
    RETURN true;
END;
$$;

This doesn't guarantee the secret won't leak out in some way of course, but it can useful if you never store secrets and send them only through secure channels back to the client, for example using the psql client \gset command shown above, or by only storing a derived key id and context.

API Reference

The reference below is adapted from and uses some of the same language found at the libsodium C API Documentation. Refer to those documents for details on algorithms and other libsodium specific details.

The libsodium documentation is Copyright (c) 2014-2018, Frank Denis [email protected] and released under The ISC License.

Generating Random Data

Functions:

    randombytes_random() -> integer

    randombytes_uniform(upper_bound integer) -> integer

    randombytes_buf(size integer) -> bytea

The library provides a set of functions to generate unpredictable data, suitable for creating secret keys.

# select randombytes_random();
 randombytes_random
--------------------
         1229887405
(1 row)

The randombytes_random() function returns an unpredictable value between 0 and 0xffffffff (included).

# select randombytes_uniform(42);
 randombytes_uniform
---------------------
                  23
(1 row)

The randombytes_uniform() function returns an unpredictable value between 0 and upper_bound (excluded). Unlike randombytes_random() % upper_bound, it guarantees a uniform distribution of the possible output values even when upper_bound is not a power of 2. Note that an upper_bound < 2 leaves only a single element to be chosen, namely 0.

# select randombytes_buf(42);
                                    randombytes_buf
----------------------------------------------------------------------------------------
 \x27cec8d2c3de16317074b57acba2109e43b5623e1fb7cae12e8806daa21a72f058430f22ec993986fcb2
(1 row)

The randombytes_buf() function returns a bytea with an unpredictable sequence of bytes.

# select randombytes_new_seed() bufseed \gset
# select randombytes_buf_deterministic(42, :'bufseed');
                             randombytes_buf_deterministic
----------------------------------------------------------------------------------------
 \xa183e8d4acd68119ab2cacd9e46317ec3a00a6a8820b00339072f7c24554d496086209d7911c3744b110
(1 row)

The randombytes_buf_deterministic() returns a size bytea containing bytes indistinguishable from random bytes without knowing the seed. For a given seed, this function will always output the same sequence. size can be up to 2^38 (256 GB).

C API Documentation

Secret key cryptography

C API Documentation

Authenticated encryption

Functions:

    crypto_secretbox_keygen() -> bytea

    crypto_secretbox_noncegen() -> bytea

    crypto_secretbox(message bytea, nonce bytea, key bytea) -> bytea

    crypto_secretbox_open(ciphertext bytea, nonce bytea, key bytea) -> bytea

crypto_secretbox_keygen() generates a random secret key which can be used to encrypt and decrypt messages.

crypto_secretbox_noncegen() generates a random nonce which will be used when encrypting messages. For security, each nonce must be used only once, though it is not a secret. The purpose of the nonce is to add randomness to the message so that the same message encrypted multiple times with the same key will produce different ciphertexts.

crypto_secretbox() encrypts a message using a previously generated nonce and secret key or key id. The encrypted message can be decrypted using crypto_secretbox_open() Note that in order to decrypt the message, the original nonce will be needed.

crypto_secretbox_open() decrypts a message encrypted by crypto_secretbox().

C API Documentation

Authentication

Functions:

    crypto_auth_keygen() -> bytea

    crypto_auth(message bytea, key bytea) -> bytea

    crypto_auth_verify(mac bytea, message bytea, key bytea) -> boolean

crypto_auth_keygen() generates a message-signing key for use by crypto_auth().

crypto_auth() generates an authentication tag (mac) for a combination of message and secret key. This does not encrypt the message; it simply provides a means to prove that the message has not been tampered with. To verify a message tagged in this way, use crypto_auth_verify(). This function is deterministic: for a given message and key, the generated mac will always be the same.

Note that this requires access to the secret key, which is not something that should normally be shared. If many users need to verify message it is usually better to use Public Key Signatures rather than sharing secret keys.

crypto_auth_verify() verifies that the given mac (authentication tag) matches the supplied message and key. This tells us that the original message has not been tampered with.

C API Documentation

Public key cryptography

C API Documentation

Authenticated encryption

Functions:

    crypto_box_new_keypair() -> crypto_box_keypair

    crypto_box_noncegen() -> bytea

    crypto_box(message bytea, nonce bytea,
               public bytea, secret bytea) -> bytea

    crypto_box_open(ciphertext bytea, nonce bytea,
                    public bytea, secret bytea) -> bytea

crypto_box_new_keypair() returns a new, randomly generated, pair of keys for public key encryption. The public key can be shared with anyone. The secret key must never be shared.

crypto_box_noncegen() generates a random nonce which will be used when encrypting messages. For security, each nonce must be used only once, though it is not a secret. The purpose of the nonce is to add randomness to the message so that the same message encrypted multiple times with the same key will produce different ciphertexts.

crypto_box() encrypts a message using a nonce, the intended recipient's public key and the sender's secret key. The resulting ciphertext can only be decrypted by the intended recipient using their secret key. The nonce must be sent along with the ciphertext.

crypto_box_open() decrypts a ciphertext encrypted using crypto_box(). It takes the ciphertext, nonce, the sender's public key and the recipient's secret key as parameters, and returns the original message. Note that the recipient should ensure that the public key belongs to the sender.

C API Documentation

Public key signatures

Functions:

    crypto_sign_new_keypair() -> crypto_sign_keypair

  combined mode functions:

    crypto_sign(message bytea, key bytea) -> bytea

    crypto_sign_open(signed_message bytea, key bytea) -> bytea

  detached mode functions:

    crypto_sign_detached(message bytea, key bytea) -> bytea

    crypto_sign_verify_detached(sig bytea, message bytea, key bytea) -> boolean

  multi-part message functions:

    crypto_sign_init() -> bytea

    crypto_sign_update(state bytea, message bytea) -> bytea

    crypto_sign_final_create(state bytea, key bytea) -> bytea

    crypto_sign_final_verify(state bytea, signature bytea, key bytea) -> boolean

Aggregates:

    crypto_sign_update_agg(message bytea) -> bytea

    crypto_sign_update_agg(state, bytea message bytea) -> bytea

These functions are used to authenticate that messages have have come from a specific originator (the holder of the secret key for which you have the public key), and have not been tampered with.

crypto_sign_new_keypair() returns a new, randomly generated, pair of keys for public key signatures. The public key can be shared with anyone. The secret key must never be shared.

crypto_sign() and crypto_sign_verify() operate in combined mode. In this mode the message that is being signed is combined with its signature as a single unit.

crypto_sign() creates a signature, using the signer's secret key, which it prepends to the message. The result can be authenticated using crypto_sign_open().

crypto_sign_open() takes a signed message created by crypto_sign(), checks its validity using the sender's public key and returns the original message if it is valid, otherwise raises a data exception.

crypto_sign_detached() and crypto_sign_verify_detached() operate in detached mode. In this mode the message is kept independent from its signature. This can be useful when wishing to sign objects that have already been stored, or where multiple signatures are desired for an object.

crypto_sign_detached() generates a signature for message using the signer's secret key. The result is a signature which exists independently of the message, which can be verified using crypto_sign_verify_detached().

crypto_sign_verify_detached() is used to verify a signature generated by crypto_sign_detached(). It takes the generated signature, the original message, and the signer's public key and returns true if the signature matches the message and key, and false otherwise.

crypto_sign_init(), crypto_sign_update(), crypto_sign_final_create(), crypto_sign_final_verify(), and the aggregates crypto_sign_update_agg() handle signatures for multi-part messages. To create or verify a signature for a multi-part message crypto_sign_init() is used to start the process, and then each message-part is passed to crypto_sign_update() or crypto_sign_update_agg(). Finally the signature is generated using crypto_sign_final_update() or verified using crypto_sign_final_verify().

crypto_sign_init() creates an initial state value which will be passed to crypto_sign_update() or crypto_sign_update_agg().

crypto_sign_update() or crypto_sign_update_agg() will be used to update the state for each part of the multi-part message. crypto_sign_update() takes as a parameter the state returned from crypto_sign_init() or the preceding call to crypto_sign_update() or crypto_sign_update_agg(). crypto_sign_update_agg() has two variants: one takes a previous state value, allowing multiple aggregates to be processed sequentially, and one takes no state parameter, initialising the state itself. Note that the order in which the parts of a multi-part message are processed is critical. They must be processed in the same order for signing and verifying.

crypto_sign_final_update() takes the state returned from the last call to crypto_sign_update() or crypto_sign_update_agg() and the signer's secret key and produces the final signature. This can be checked using crypto_sign_final_verify().

crypto_sign_final_verify() is used to verify a multi-part message signature created by crypto_sign_final_update(). It must be preceded by the same set of calls to crypto_sign_update() or crypto_sign_update_agg() (with the same message-parts, in the same order) that were used to create the signature. It takes the state returned from the last such call, along with the signature and the signer's public key and returns true if the messages, key and signature all match.

To sign or verify multi-part messages in SQL, CTE (Common Table Expression) queries are particularly effective. For example to sign a message consisting of a timestamp and several message_parts:

with init as
  (
    select crypto_sign_init() as state
  ),
timestamp_part as
  (
    select crypto_sign_update(i.state, m.timestamp::bytea) as state
      from init i
     cross join messages m
     where m.message_id = 42
  ),
remaining_parts as
  (
    select crypto_sign_update(t.state, p.message_part::bytea) as state
      from timestamp_part t
     cross join (
       select message_part
         from message_parts
        where message_id = 42
        order by message_part_num) p
  )
select crypto_sign_final_create(r.state, k.secret_key) as sig
  from remaining_parts r
 cross join keys k
 where k.key_name = 'xyzzy';

Note that storing secret keys in a table, as is done in the example above, is a bad practice unless you have effective row-level security in place.

C API Documentation

Sealed boxes

Sealed boxes are designed to anonymously send messages to a recipient given its public key. Only the recipient can decrypt these messages, using its private key. While the recipient can verify the integrity of the message, it cannot verify the identity of the sender.

SELECT public, secret FROM crypto_box_new_keypair() \gset bob_

SELECT crypto_box_seal('bob is your uncle', :'bob_public') sealed \gset

The sealed psql variable is now the encrypted sealed box. To unseal it, bob needs his public and secret key:

SELECT is(crypto_box_seal_open(:'sealed', :'bob_public', :'bob_secret'),
          'bob is your uncle', 'crypto_box_seal/open');

C API Documentation

Hashing

This API computes a fixed-length fingerprint for an arbitrary long message. Sample use cases:

  • File integrity checking
  • Creating unique identifiers to index arbitrary long data

The crypto_generichash and crypto_shorthash functions can be used to generate hashes. crypto_generichash takes an optional hash key argument which can be NULL. In this case, a message will always have the same fingerprint, similar to the MD5 or SHA-1 functions for which crypto_generichash() is a faster and more secure alternative.

But a key can also be specified. A message will always have the same fingerprint for a given key, but different keys used to hash the same message are very likely to produce distinct fingerprints. In particular, the key can be used to make sure that different applications generate different fingerprints even if they process the same data.

SELECT is(crypto_generichash('bob is your uncle'),
          '\x6c80c5f772572423c3910a9561710313e4b6e74abc0d65f577a8ac1583673657',
          'crypto_generichash');

SELECT is(crypto_generichash('bob is your uncle', NULL),
          '\x6c80c5f772572423c3910a9561710313e4b6e74abc0d65f577a8ac1583673657',
          'crypto_generichash NULL key');

SELECT is(crypto_generichash('bob is your uncle', 'super sekret key'),
          '\xe8e9e180d918ea9afe0bf44d1945ec356b2b6845e9a4c31acc6c02d826036e41',
          'crypto_generichash with key');

Many applications and programming language implementations were recently found to be vulnerable to denial-of-service attacks when a hash function with weak security guarantees, such as Murmurhash 3, was used to construct a hash table .

In order to address this, Sodium provides the crypto_shorthash() function, which outputs short but unpredictable (without knowing the secret key) values suitable for picking a list in a hash table for a given key. This function is optimized for short inputs. The output of this function is only 64 bits. Therefore, it should not be considered collision-resistant.

Use cases:

  • Hash tables Probabilistic
  • data structures such as Bloom filters
  • Integrity checking in interactive protocols

Example:

SELECT is(crypto_shorthash('bob is your uncle', 'super sekret key'),
          '\xe080614efb824a15',
          'crypto_shorthash');

C API Documentation

Password hashing

SELECT lives_ok($$SELECT crypto_pwhash_saltgen()$$, 'crypto_pwhash_saltgen');

SELECT is(crypto_pwhash('Correct Horse Battery Staple', '\xccfe2b51d426f88f6f8f18c24635616b'),
        '\x77d029a9b3035c88f186ed0f69f58386ad0bd5252851b4e89f0d7057b5081342',
        'crypto_pwhash');

SELECT ok(crypto_pwhash_str_verify(crypto_pwhash_str('Correct Horse Battery Staple'),
          'Correct Horse Battery Staple'),
          'crypto_pwhash_str_verify');

C API Documentation

Key Derivation

Multiple secret subkeys can be derived from a single primary key. Given the primary key and a key identifier, a subkey can be deterministically computed. However, given a subkey, an attacker cannot compute the primary key nor any other subkeys.

SELECT crypto_kdf_keygen() kdfkey \gset
SELECT length(crypto_kdf_derive_from_key(64, 1, '__auth__', :'kdfkey')) kdfsubkeylen \gset
SELECT is(:kdfsubkeylen, 64, 'kdf byte derived subkey');

SELECT length(crypto_kdf_derive_from_key(32, 1, '__auth__', :'kdfkey')) kdfsubkeylen \gset
SELECT is(:kdfsubkeylen, 32, 'kdf 32 byte derived subkey');

SELECT is(crypto_kdf_derive_from_key(32, 2, '__auth__', :'kdfkey'),
    crypto_kdf_derive_from_key(32, 2, '__auth__', :'kdfkey'), 'kdf subkeys are deterministic.');

C API Documentation

Key Exchange

Using the key exchange API, two parties can securely compute a set of shared keys using their peer's public key and their own secret key.

SELECT crypto_kx_new_seed() kxseed \gset

SELECT public, secret FROM crypto_kx_seed_new_keypair(:'kxseed') \gset seed_bob_
SELECT public, secret FROM crypto_kx_seed_new_keypair(:'kxseed') \gset seed_alice_

SELECT tx, rx FROM crypto_kx_client_session_keys(
    :'seed_bob_public', :'seed_bob_secret',
    :'seed_alice_public') \gset session_bob_

SELECT tx, rx FROM crypto_kx_server_session_keys(
    :'seed_alice_public', :'seed_alice_secret',
    :'seed_bob_public') \gset session_alice_

SELECT crypto_secretbox('hello alice', :'secretboxnonce', :'session_bob_tx') bob_to_alice \gset

SELECT is(crypto_secretbox_open(:'bob_to_alice', :'secretboxnonce', :'session_alice_rx'),
          'hello alice', 'secretbox_open session key');

SELECT crypto_secretbox('hello bob', :'secretboxnonce', :'session_alice_tx') alice_to_bob \gset

SELECT is(crypto_secretbox_open(:'alice_to_bob', :'secretboxnonce', :'session_bob_rx'),
          'hello bob', 'secretbox_open session key');

C API Documentation

HMAC512/256

[https://en.wikipedia.org/wiki/HMAC]

In cryptography, an HMAC (sometimes expanded as either keyed-hash message authentication code or hash-based message authentication code) is a specific type of message authentication code (MAC) involving a cryptographic hash function and a secret cryptographic key. As with any MAC, it may be used to simultaneously verify both the data integrity and authenticity of a message.

select crypto_auth_hmacsha512_keygen() hmac512key \gset
select crypto_auth_hmacsha512('food', :'hmac512key') hmac512 \gset

select is(crypto_auth_hmacsha512_verify(:'hmac512', 'food', :'hmac512key'), true, 'hmac512 verified');
select is(crypto_auth_hmacsha512_verify(:'hmac512', 'fo0d', :'hmac512key'), false, 'hmac512 not verified');

C API Documentation

Advanced Stream API (XChaCha20)

The stream API is for advanced users only and only provide low level encryption without authentication.

C API Documentation

XChaCha20-SIV

Deterministic/nonce-reuse resistant authenticated encryption scheme using XChaCha20.

C API Documentation

SignCryption

Traditional authenticated encryption with a shared key allows two or more parties to decrypt a ciphertext and verify that it was created by a member of the group knowing that secret key.

However, it doesn't allow verification of who in a group originally created a message.

In order to do so, authenticated encryption has to be combined with signatures.

The Toorani-Beheshti signcryption scheme achieves this using a single key pair per device, with forward security and public verifiability.

C API Documentation

pgsodium's People

Contributors

0xflotus avatar aliyoge avatar andrewwasielewski avatar autarch avatar burmecia avatar bvallelunga avatar ioguix avatar jkatz avatar marcmunro avatar michelp avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pgsodium's Issues

How to set security_invoker automatically?

Currently every time we use:

SECURITY LABEL FOR pgsodium ON COLUMN ...

it generates/replaces a decrypted view. This view does not have security_invoker = on by default.

We can use a separate command to enable it:

ALTER VIEW public.decrypted_foo SET (security_invoker = on);

However if we were to enable encryption on another column from the same table, this would remove security_invoker since the view is replaced.

I'm not really sure what the right solution is here, but i did find it a bit surprising and clearly since security_invoker is such a critical configuration property of a view to enable RLS, having it removed is a big security issue.

Should security_invoker be enabled by default on supported Postgres versions? Should it remain on the newly generated view if it existed on the previous version? Should we be able to specify it when calling SECURITY LABEL?

Thanks for your consideration!

jsonb type support

Hello! Thank you for the great extension.

Are there any plans to enable TCE on jsonb data? It would be even better if it could be supported!

Currently, we got this error:

ECURITY LABEL FOR pgsodium ON COLUMN temp_table.content IS 'ENCRYPT WITH KEY ID c9c52d1c-372d-4187-bcd6-dad2a06c3992';
ERROR:  syntax error at or near ","
LINE 12: ,
         ^
QUERY:
    DROP VIEW IF EXISTS pg_temp_13.decrypted_temp_table;
    CREATE VIEW pg_temp_13.decrypted_temp_table AS SELECT
                id,
        content,
,
        created_at
    FROM temp_table;
    ALTER VIEW pg_temp_13.decrypted_temp_table OWNER TO postgres;

CONTEXT:  PL/pgSQL function pgsodium.create_mask_view(oid,integer,boolean) line 39 at EXECUTE
SQL statement "SELECT pgsodium.create_mask_view(objoid, objsubid, debug)
    FROM pg_catalog.pg_seclabel sl
    WHERE sl.objoid = target
      AND sl.label ILIKE 'ENCRYPT%'
      AND sl.provider = 'pgsodium'"
PL/pgSQL function pgsodium.update_mask(oid,boolean) line 4 at PERFORM
SQL statement "SELECT pgsodium.update_mask(objoid, debug)
    FROM pg_catalog.pg_seclabel sl
    JOIN pg_catalog.pg_class cl ON (cl.oid = sl.objoid)
    WHERE label ilike 'ENCRYPT%'
       AND cl.relowner = session_user::regrole::oid
       AND provider = 'pgsodium'
           AND objoid::regclass != 'pgsodium.key'::regclass"
PL/pgSQL function pgsodium.update_masks(boolean) line 3 at PERFORM
SQL statement "SELECT pgsodium.update_masks()"
PL/pgSQL function pgsodium.trg_mask_update() line 9 at PERFORM
CREATE TABLE temp_table (
    id varchar(128) NOT NULL,
    content jsonb NOT NULL,
    created_at timestamp with time zone NOT NULL,
    PRIMARY KEY (id)
);

We use this on supabase (hosted):

> SELECT VERSION();
PostgreSQL 15.1 (Ubuntu 15.1-1.pgdg20.04+1) on aarch64-unknown-linux-gnu, compiled by gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0, 64-bit

Where my PGXN Release?

Details here. Draft META.json for you (check the license; "ISC" is not known to PGXN):

{
   "name": "pgsodium",
   "abstract": "Postgres extension for libsodium functions",
   "description": "pgsodium is a PostgreSQL extension that exposes modern libsodium based cryptographic functions to SQL.",
   "version": "1.0.0",
   "maintainer": [
      "Michel Pelletier <[email protected]>"
   ],
   "license": "postgresql",
   "provides": {
      "pgsodium": {
         "abstract": "Postgres extension for libsodium functions",
         "file": "src/pgsodium.h",
         "docfile": "README.md",
         "version": "1.0.0"
      }
   },
   "prereqs": {
      "runtime": {
         "requires": {
            "PostgreSQL": "10.0.0"
         }
      }
   },
   "resources": {
      "bugtracker": {
         "web": "https://github.com/michelp/pgsodium/issues/"
      },
      "repository": {
         "url": "git://github.com/michelp/pgsodium.git",
         "web": "https://github.com/michelp/pgsodium/",
         "type": "git"
      }
   },
   "generated_by": "David E. Wheeler",
   "meta-spec": {
      "version": "1.0.0",
      "url": "https://pgxn.org/meta/spec.txt"
   },
   "tags": [
      "sodium",
      "crypto",
      "cryptography",
      "encryption",
      "random",
      "asymmetric encryption",
      "public key"
   ]
}

ERROR: pgsodium_derive_helper: pgsodium_derive: no server secret key defined

Hi
when i call select pgsodium.derive_key(1); getting an error ERROR: pgsodium_derive_helper: pgsodium_derive: no server secret key defined.
I have added shared_preload_libraries = 'pgsodium'
and pgsodium.server_secret_key = '696fe962615267e8d65adad896ad12d996921fbbaf3fe8e56573bec1c941ca90' in postgresql.conf file
How can i solve the issue?
pgsodium.version-3.1.8

Avoid secret logging

Hello

Re https://github.com/michelp/pgsodium#avoid-secret-logging

Setting log_statement to 'none' isn't enough to avoid secret logging.

If, say, log_min_duration_statement is set to 0, the secret will be logged anyway.

I don't know if there's a simple way to avoid completely secret logging (besides log_min_duration_statement, logging could be handled by an ad hoc extension).

Comments on functions?

Hi,

Since there are so many functions with this extension, it would be really helpful if there were "comment on function" so that tools can display it.

Thanks in advance.

Doesn't work well with on conflict Json

I have this code

 token_value := jsonb_build_object(
    'token', token,
    'workspace_name', workspace_name, 
    'user', user_email
  );
  
  INSERT INTO services_connected (workspace_id, service_name, data)
  VALUES (workspace_id_value, 'google', token_value)
  ON CONFLICT (workspace_id, service_name) DO UPDATE SET data = EXCLUDED.data;

this works fine but ON CONFLICT, the value stored is incorrect.

bug in pgsodium.valid_key view

Hello,

First of all, thank you for releasing this very nice project!

I'd like to report a small error in the definition of the view pgsodium.valid_key:

ELSE key.expires < now()

should be replaced by

ELSE key.expires > now()

(unless I missed something)

Best regards,

Wrong memory space allocated

Hi,

I have a memory warning when using PostgreSQL 15.1 compiled with --enabled-cassert (enables MEMORY_CONTEXT_CHECKING) and current HEAD of pgsodium. See:

nacl=# SELECT convert_from(
    pgsodium.crypto_aead_det_decrypt(
        decode('TA3aB8kpo4tZFbonlD6UPS3WeOMD6QxiAMDfWZ0bu+nkMtZQ', 'base64'), 
        '', 
        'acf73c9c-a1f3-473b-ae3a-9daff68f05fa'::uuid, 
        NULL::bytea), 
    'utf8'::name
);
WARNING:  problem in alloc set ExprContext: detected write past chunk end in block 0x20b29c0, chunk 0x20b2a10
WARNING:  problem in alloc set ExprContext: detected write past chunk end in block 0x20b29c0, chunk 0x20b2a10
 convert_from 
--------------
 blah
(1 row)

I might be wrong, but I suspect some missing header size or something related when allocating space in pgsodium_crypto_aead_det_encrypt_by_id and other equivalent funcs. Which means more data are written to in the allocated space than asked when the data AND its header are written there.

By the way, note that if I give NULL as additional data, it just crash:

nacl=# SELECT convert_from(
    pgsodium.crypto_aead_det_decrypt(
        decode('TA3aB8kpo4tZFbonlD6UPS3WeOMD6QxiAMDfWZ0bu+nkMtZQ', 'base64'), 
        NULL, 
        'acf73c9c-a1f3-473b-ae3a-9daff68f05fa'::uuid, 
        NULL::bytea), 
    'utf8'::name
);
server closed the connection unexpectedly
	This probably means the server terminated abnormally
	before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.
The connection to the server was lost. Attempting reset: Failed.

The backtrace:

(gdb) bt
#0  0x0000000000b35976 in pg_detoast_datum (datum=0x0) at fmgr.c:1710
#1  0x00007fd48dd8d0df in pgsodium_crypto_aead_det_decrypt_by_id (fcinfo=0x2090440) at src/aead.c:304
[...]

The source code being:

pgsodium_crypto_aead_det_decrypt_by_id (PG_FUNCTION_ARGS)
{
	bytea      *ciphertext = PG_GETARG_BYTEA_P (0);
	bytea      *additional = PG_GETARG_BYTEA_P (1); // <- 304

Regards,

Updated value in secret column does not get encrypted

Hi everyone,

following my setup in #73, I have a secret value stored in secret_column, which is encrypted at insert.
Now, when updating the value, I expect it to be encrypted again, but it won't. Edit: It is stored as clear text.

Is this behavior expected, or is this, again, an issue I have with supabase?

Kind regards
Tim

edit: I added the database setup scripts in my comment below

`get_key_by_name` should be marked as stable

The definition of the function get_key_by_name is:

CREATE OR REPLACE FUNCTION pgsodium.get_key_by_name(text)
 RETURNS pgsodium.valid_key
 LANGUAGE sql
 STABLE SECURITY DEFINER
 SET search_path TO ''
AS $function$
    SELECT * from pgsodium.valid_key WHERE name = $1;
$function$
;

By default it is marked as volatile (ref) and thus prohibits the index scan when used as the comparison value (ref).


For example, the plan for query

select * from citizen
where id = pgsodium.crypto_shorthash('plain', (pgsodium.get_key_by_name('default-siphash-key')).id);

is

Seq Scan on citizen  (cost=0.00..7630.92 rows=1 width=112)
  Filter: (id = pgsodium.crypto_shorthash('\x706c61696e'::bytea, (pgsodium.get_key_by_name('default-siphash-key'::text)).id))

The plan for an alternative query

select * from citizen
where id = pgsodium.crypto_shorthash('plain', (select id from pgsodium.valid_key where name = 'default-siphash-key'))

is

Index Scan using citizen_pkey on citizen  (cost=2.91..5.12 rows=1 width=112)
  Index Cond: (id = pgsodium.crypto_shorthash('\x706c61696e'::bytea, $0))
  InitPlan 1 (returns $0)
    ->  Index Scan using pgsodium_key_unique_name on key  (cost=0.14..2.37 rows=1 width=16)
          Index Cond: (name = 'default-siphash-key'::text)
          Filter: ((status = ANY ('{valid,default}'::pgsodium.key_status[])) AND CASE WHEN (expires IS NULL) THEN true ELSE (expires > now()) END)

By marking get_key_by_name as stable (alter function pgsodium.get_key_by_name stable), the plan for the first query is now

Index Scan using citizen_pkey on citizen  (cost=0.79..3.00 rows=1 width=112)
  Index Cond: (id = pgsodium.crypto_shorthash('\x706c61696e'::bytea, (pgsodium.get_key_by_name('default-siphash-key'::text)).id))

Generally, the performance improvement is usually significant for index-scan vs. seq-scan. Please consider to mark get_key_by_name and get_key_by_id as stable.

Regards.

Dockerfile needs updation. Image isnt getting generated for postgresql 16.3

docker build .

[+] Building 196.0s (13/30)
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 2.32kB 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 108B 0.0s
=> [internal] load metadata for docker.io/library/ubuntu:latest 0.7s
=> [ 1/26] FROM docker.io/library/ubuntu:latest@sha256:3f85b7caad41a95462cf5b787d8a04604c8262cdcdf9a472b8c52ef83375fe15 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 4.91kB 0.0s
=> CACHED [ 2/26] RUN apt-get update && apt-get install -y make cmake git curl build-essential m4 sudo gdbserver gdb libreadline-dev bison flex zlib1g- 0.0s
=> CACHED [ 3/26] RUN groupadd -r postgres && useradd --no-log-init -r -m -s /bin/bash -g postgres -G sudo postgres 0.0s
=> CACHED [ 4/26] RUN /bin/rm -Rf "/home/postgres/data" && mkdir "/home/postgres/data" 0.0s
=> CACHED [ 5/26] WORKDIR /home/postgres 0.0s
=> [ 6/26] RUN git clone --branch REL_16_3 https://github.com/postgres/postgres.git --depth=1 && cd postgres && ./configure --prefix=/usr/ --enabl 60.6s
=> [ 7/26] RUN chown postgres:postgres /home/postgres 0.3s
=> [ 8/26] RUN curl -s -L https://github.com/theory/pgtap/archive/v1.2.0.tar.gz | tar zxvf - && cd pgtap-1.2.0 && make && make install 1.6s
=> ERROR [ 9/26] RUN curl -s -L https://download.libsodium.org/libsodium/releases/libsodium-1.0.18.tar.gz | tar zxvf - && cd libsodium-1.0.18 && ./configure 132.7s

[ 9/26] RUN curl -s -L https://download.libsodium.org/libsodium/releases/libsodium-1.0.18.tar.gz | tar zxvf - && cd libsodium-1.0.18 && ./configure && make check && make -j 4 install:
#0 132.7
#0 132.7 gzip: stdin: unexpected end of file
#0 132.7 tar: Child returned status 1
#0 132.7 tar: Error is not recoverable: exiting now


Dockerfile:29

27 |
28 | RUN curl -s -L https://github.com/theory/pgtap/archive/v1.2.0.tar.gz | tar zxvf - && cd pgtap-1.2.0 && make && make install
29 | >>> RUN curl -s -L https://download.libsodium.org/libsodium/releases/libsodium-1.0.18.tar.gz | tar zxvf - && cd libsodium-1.0.18 && ./configure && make check && make -j 4 install
30 | RUN cpan App::cpanminus && cpan TAP::Parser::SourceHandler::pgTAP && cpan App::prove
31 |

ERROR: failed to solve: process "/bin/sh -c curl -s -L https://download.libsodium.org/libsodium/releases/libsodium-1.0.18.tar.gz | tar zxvf - && cd libsodium-1.0.18 && ./configure && make check && make -j 4 install" did not complete successfully: exit code: 2

How to manage pgsodium_root.key in primary-secondary cluster

I have a primary-secondary cluster with automatic failover and load balancing. All nodes should have the same root key in pgsodium_root.key otherwise it will create conflicts on the secondary nodes, right?

If yes, should I replace/mount pgsodium_root.key after installation on the secondary nodes and everything will work correctly? There won't be any problems with the generated keys from the root key as those will be replicated on the secondary nodes?

Postgres crash running tests on latest master

...
ok 32 - secretbox_open session key
ok 33 - secretbox_open session key
ok 34 - sha256
ok 35 - sha512
ok 36 - hmac512 verified
ok 37 - hmac512 not verified
ok 38 - hmac256 verified
ok 39 - hmac256 not verified
psql:test/test.sql:275: server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
psql:test/test.sql:275: fatal: connection to server was lost
marc:pgsodium$

Command: psql -d test -f test/test.sql
(database is newly created for the tests)

Postgres version:
marc:pgsodium$ psql
psql (12.3 (Debian 12.3-1.pgdg90+1))

__
Marc

Error .. security label

While attempting to apply a security label to a table column

SECURITY LABEL FOR pgsodium ON COLUMN test.mka_sysadmin_crypt.paswd
 IS 'ENCRYPT WITH KEY ID 751013a0-21ee-4743-a5eb-692ca7f6e26d';

I encountered the following error:

[42601] ERROR: syntax error at or near "," Where: PL/pgSQL function pgsodium.create_mask_view(oid,integer,boolean) line 41 at EXECUTE SQL statement "SELECT pgsodium.create_mask_view(objoid, objsubid, debug) FROM pg_catalog.pg_seclabel sl WHERE sl.objoid = tar ...

Upon investigation, I traced the issue to the function decrypted_columns(relid oid) returns text and specifically to the line SET search_path="". It appears that assigning an empty string to search_path is not permissible.

And I am also unable to recompile this function due to the following error:

 [2BP01] ERROR: cannot drop function pgsodium.decrypted_columns(oid) because extension pgsodium requires it.

Postgresql version: PostgreSQL 16.2 (Debian 16.2-1.pgdg110+2) on x86_64-pc-linux-gnu, compiled by gcc (Debian 10.2.1-6) 10.2.1 20210110, 64-bit

pgsodium version: 3.1.9

Better NULL input checking?

Reported by @ioguix in #43

By the way, note that if I give NULL as additional data, it just crash:

pgsodium.crypto_aead_det_decrypt was incorrectly not labeled STRICT so it crashed on NULL input, I can fix that, or I'm thinking it makes more sense for all functions (in most cases) to throw errors on NULL input instead of returning NULL, which means removing STRICT and doing NULL checking as shown in this branch:

https://github.com/michelp/pgsodium/compare/fix/better-null-checking?expand=1

this is just one example of many that would need to be added. Thoughts?

How to dump and restore ?

Hello,

I have encrypt some data with with a per row key id

Making a dump of the db is working fine. The data are encrypted in the dump, that's what I want.

But when I try to restore the dump the encryption triggers are triggered again. And my restored data is now encrypted twice.

Is there a pgsodium approach to dump and restore db?

I also tried to disable-triggers during restore whitout success.

TCE not using specified key to encrypt on update

When I use pgsodium with a unique key_id for each row, encryption works well when I add a new record, and I can see the decrypted value in the associated view:

create table if not exists "schema"."secret" (
    "id" uuid DEFAULT uuid_generate_v4() NOT NULL PRIMARY KEY,
    "createdAt" timestamp with time zone DEFAULT "now"() NOT NULL,
    "updatedAt" timestamp with time zone DEFAULT "now"() NOT NULL,
    "lastUsed" timestamp with time zone,
    "name" text DEFAULT ''::text not null,
    "value" text DEFAULT ''::text not null,
    "key_id" uuid NOT NULL references pgsodium.key(id) default (pgsodium.create_key()).id,
    "nonce" bytea DEFAULT pgsodium.crypto_aead_det_noncegen(),
    "userId" uuid DEFAULT "auth"."uid"() references "auth"."users"("id")
);

SECURITY LABEL FOR pgsodium 
    ON COLUMN "schema"."secret"."value" 
    IS 'ENCRYPT WITH KEY COLUMN key_id ASSOCIATED (userId) NONCE nonce';

However, once I update a row in the 'secret' table, a NEW key is created in the pgsodium schema to encrypt the value, which is not subsequently updated in the key_id column. So after updating a record, I can no longer access its decrypted value from the associated view.

Is this a bug?

Error installing the pgsodium version 3.0.4

Error installing the pgsodium version 3.0.4 on PostgreSQL 14

psql (14.5 (Debian 14.5-2.pgdg110+2))
Type "help" for help.

postgres=# CREATE EXTENSION pgsodium;
ERROR:  relation "pgsodium.key" does not exist
LINE 1: CREATE EXTENSION pgsodium;

postgres=# select name, default_version as version, comment from pg_available_extensions where name like '%pgsodium%' order by 1;
   name   | version |                  comment                   
----------+---------+--------------------------------------------
 pgsodium | 3.0.4   | Postgres extension for libsodium functions
(1 row)

no problem installing the extension version 3.0.2

postgres=# select * from pg_available_extension_versions where name = 'pgsodium' order by version desc;
   name   | version | installed | superuser | trusted | relocatable | schema | requires |                  comment                   
----------+---------+-----------+-----------+---------+-------------+--------+----------+--------------------------------------------
 pgsodium | 3.0.4   | f         | t         | f       | f           |        |          | Postgres extension for libsodium functions
 pgsodium | 3.0.3   | f         | t         | f       | f           |        |          | Postgres extension for libsodium functions
 pgsodium | 3.0.2   | f         | t         | f       | f           |        |          | Postgres extension for libsodium functions
 pgsodium | 3.0.0   | f         | t         | f       | f           |        |          | Postgres extension for libsodium functions
 pgsodium | 2.0.2   | f         | t         | f       | f           |        |          | Postgres extension for libsodium functions
 pgsodium | 2.0.1   | f         | t         | f       | f           |        |          | Postgres extension for libsodium functions
 pgsodium | 2.0.0   | f         | t         | f       | f           |        |          | Postgres extension for libsodium functions
 pgsodium | 1.2.0   | f         | t         | f       | f           |        |          | Postgres extension for libsodium functions
 pgsodium | 1.1.1   | f         | t         | f       | f           |        |          | Postgres extension for libsodium functions
 pgsodium | 1.1.0   | f         | t         | f       | f           |        |          | Postgres extension for libsodium functions
 pgsodium | 1.0.0   | f         | t         | f       | f           |        |          | Postgres extension for libsodium functions
(11 rows)

postgres=# CREATE EXTENSION pgsodium WITH VERSION '3.0.3'; 
ERROR:  relation "pgsodium.key" does not exist
LINE 1: CREATE EXTENSION pgsodium WITH VERSION '3.0.3';
                     ^
postgres=# CREATE EXTENSION pgsodium WITH VERSION '3.0.2'; 
CREATE EXTENSION
postgres=# \dx
                               List of installed extensions
   Name   | Version |   Schema   |                       Description                       
----------+---------+------------+---------------------------------------------------------
 pgsodium | 3.0.2   | public     | Pgsodium is a modern cryptography library for Postgres.
 plpgsql  | 1.0     | pg_catalog | PL/pgSQL procedural language
(2 rows)

Error creating security label using Postgres 14/15 and pgsodium 3.1.5

Steps to reproduce:

  1. SELECT format('ENCRYPT WITH KEY ID %s', (pgsodium.create_key('aead-det')).id) AS seclabel \gset
  2. SECURITY LABEL FOR pgsodium ON COLUMN public.owner.did IS :'seclabel';

Internal Postgres error logs:

2023-02-06 05:10:52.790 UTC [104] ERROR:  syntax error at or near "FROM" at character 217
2023-02-06 05:10:52.790 UTC [104] QUERY:  
           DROP VIEW IF EXISTS public.decrypted_owner;
           CREATE VIEW public.decrypted_owner AS SELECT 
                       id,       
               created,       
               updated,       
               deleted,       
               did,

           FROM public.owner;
           ALTER VIEW public.decrypted_owner OWNER TO postgres;
           
2023-02-06 05:10:52.790 UTC [104] CONTEXT:  PL/pgSQL function pgsodium.create_mask_view(oid,integer,boolean) line 39 at EXECUTE
       SQL statement "SELECT pgsodium.create_mask_view(objoid, objsubid, debug)
           FROM pg_catalog.pg_seclabel sl
           WHERE sl.objoid = target
             AND sl.label ILIKE 'ENCRYPT%'
             AND sl.provider = 'pgsodium'"
       PL/pgSQL function pgsodium.update_mask(oid,boolean) line 4 at PERFORM
       SQL statement "SELECT pgsodium.update_mask(objoid, debug)
           FROM pg_catalog.pg_seclabel sl
           JOIN pg_catalog.pg_class cl ON (cl.oid = sl.objoid)
           WHERE label ilike 'ENCRYPT%'
              AND cl.relowner = session_user::regrole::oid
              AND provider = 'pgsodium'
                  AND objoid::regclass != 'pgsodium.key'::regclass"
       PL/pgSQL function pgsodium.update_masks(boolean) line 3 at PERFORM
       SQL statement "SELECT pgsodium.update_masks()"
       PL/pgSQL function pgsodium.trg_mask_update() line 9 at PERFORM
2023-02-06 05:10:52.790 UTC [104] STATEMENT:  SECURITY LABEL FOR pgsodium ON COLUMN public.owner.did IS 'ENCRYPT WITH KEY ID d78a3c04-d8aa-4c23-a8b6-85b60867cffb'

For some reason an additional comma , is being added to the last column in the CREATE VIEW statement, not sure why this is?

I've tested this pull request using branch refactor_tce and it works fine.

Failing pgtap Tests

The log output for the github action tests indicate several failures, although this not reflected in the final job status (passing). The failed tests for the latest run on main (https://github.com/michelp/pgsodium/actions/runs/4045434666):

postgresql 13

not ok 9 - There should be the correct schemas
# Failed test 9: "There should be the correct schemas"
#     Missing schemas:
#         pgsodium
#         pgsodium_masks

postgresql 14

not ok 9 - There should be the correct schemas
# Failed test 9: "There should be the correct schemas"
#     Missing schemas:
#         pgsodium
#         pgsodium_masks
not ok 104 - Role pg_read_all_data should be granted no privileges on table pgsodium.key
# Failed test 104: "Role pg_read_all_data should be granted no privileges on table pgsodium.key"
#     Extra privileges:
#         SELECT
not ok 105 - Role pg_write_all_data should be granted no privileges on table pgsodium.key
# Failed test 105: "Role pg_write_all_data should be granted no privileges on table pgsodium.key"
#     Extra privileges:
#         DELETE
#         INSERT
#         UPDATE
not ok 179 - Role pg_read_all_data should be granted no privileges on table pgsodium.decrypted_key
# Failed test 179: "Role pg_read_all_data should be granted no privileges on table pgsodium.decrypted_key"
#     Extra privileges:
#         SELECT
not ok 180 - Role pg_write_all_data should be granted no privileges on table pgsodium.decrypted_key
# Failed test 180: "Role pg_write_all_data should be granted no privileges on table pgsodium.decrypted_key"
#     Extra privileges:
#         DELETE
#         INSERT
#         UPDATE
not ok 224 - Role pg_read_all_data should be granted no privileges on table pgsodium.mask_columns
# Failed test 224: "Role pg_read_all_data should be granted no privileges on table pgsodium.mask_columns"
#     Extra privileges:
#         SELECT
not ok 225 - Role pg_write_all_data should be granted no privileges on table pgsodium.mask_columns
# Failed test 225: "Role pg_write_all_data should be granted no privileges on table pgsodium.mask_columns"
#     Extra privileges:
#         DELETE
#         INSERT
#         UPDATE
not ok 293 - Role pg_read_all_data should be granted no privileges on table pgsodium.masking_rule
# Failed test 293: "Role pg_read_all_data should be granted no privileges on table pgsodium.masking_rule"
#     Extra privileges:
#         SELECT
not ok 294 - Role pg_write_all_data should be granted no privileges on table pgsodium.masking_rule
# Failed test 294: "Role pg_write_all_data should be granted no privileges on table pgsodium.masking_rule"
#     Extra privileges:
#         DELETE
#         INSERT
#         UPDATE
not ok 347 - Role pg_read_all_data should be granted no privileges on table pgsodium.valid_key
# Failed test 347: "Role pg_read_all_data should be granted no privileges on table pgsodium.valid_key"
#     Extra privileges:
#         SELECT
not ok 348 - Role pg_write_all_data should be granted no privileges on table pgsodium.valid_key
# Failed test 348: "Role pg_write_all_data should be granted no privileges on table pgsodium.valid_key"
#     Extra privileges:
#         DELETE
#         INSERT
#         UPDATE
not ok 362 - Role pg_read_all_data should be granted no privileges on sequence pgsodium.key_key_id_seq
# Failed test 362: "Role pg_read_all_data should be granted no privileges on sequence pgsodium.key_key_id_seq"
#     Extra privileges:
#         SELECT
not ok 363 - Role pg_write_all_data should be granted no privileges on sequence pgsodium.key_key_id_seq
# Failed test 363: "Role pg_write_all_data should be granted no privileges on sequence pgsodium.key_key_id_seq"
#     Extra privileges:
#         UPDATE

postgresql 15

not ok 9 - There should be the correct schemas
# Failed test 9: "There should be the correct schemas"
#     Missing schemas:
#         pgsodium
#         pgsodium_masks
not ok 12 - Schema public should be owned by postgres
# Failed test 12: "Schema public should be owned by postgres"
#         have: pg_database_owner
#         want: postgres
not ok 104 - Role pg_read_all_data should be granted no privileges on table pgsodium.key
# Failed test 104: "Role pg_read_all_data should be granted no privileges on table pgsodium.key"
#     Extra privileges:
#         SELECT
not ok 105 - Role pg_write_all_data should be granted no privileges on table pgsodium.key
# Failed test 105: "Role pg_write_all_data should be granted no privileges on table pgsodium.key"
#     Extra privileges:
#         DELETE
#         INSERT
#         UPDATE
not ok 180 - Role pg_read_all_data should be granted no privileges on table pgsodium.decrypted_key
# Failed test 180: "Role pg_read_all_data should be granted no privileges on table pgsodium.decrypted_key"
#     Extra privileges:
#         SELECT
not ok 181 - Role pg_write_all_data should be granted no privileges on table pgsodium.decrypted_key
# Failed test 181: "Role pg_write_all_data should be granted no privileges on table pgsodium.decrypted_key"
#     Extra privileges:
#         DELETE
#         INSERT
#         UPDATE
not ok 226 - Role pg_read_all_data should be granted no privileges on table pgsodium.mask_columns
# Failed test 226: "Role pg_read_all_data should be granted no privileges on table pgsodium.mask_columns"
#     Extra privileges:
#         SELECT
not ok 227 - Role pg_write_all_data should be granted no privileges on table pgsodium.mask_columns
# Failed test 227: "Role pg_write_all_data should be granted no privileges on table pgsodium.mask_columns"
#     Extra privileges:
#         DELETE
#         INSERT
#         UPDATE
not ok 296 - Role pg_read_all_data should be granted no privileges on table pgsodium.masking_rule
# Failed test 296: "Role pg_read_all_data should be granted no privileges on table pgsodium.masking_rule"
#     Extra privileges:
#         SELECT
not ok 297 - Role pg_write_all_data should be granted no privileges on table pgsodium.masking_rule
# Failed test 297: "Role pg_write_all_data should be granted no privileges on table pgsodium.masking_rule"
#     Extra privileges:
#         DELETE
#         INSERT
#         UPDATE
not ok 351 - Role pg_read_all_data should be granted no privileges on table pgsodium.valid_key
# Failed test 351: "Role pg_read_all_data should be granted no privileges on table pgsodium.valid_key"
#     Extra privileges:
#         SELECT
not ok 352 - Role pg_write_all_data should be granted no privileges on table pgsodium.valid_key
# Failed test 352: "Role pg_write_all_data should be granted no privileges on table pgsodium.valid_key"
#     Extra privileges:
#         DELETE
#         INSERT
#         UPDATE
not ok 367 - Role pg_read_all_data should be granted no privileges on sequence pgsodium.key_key_id_seq
# Failed test 367: "Role pg_read_all_data should be granted no privileges on sequence pgsodium.key_key_id_seq"
#     Extra privileges:
#         SELECT
not ok 368 - Role pg_write_all_data should be granted no privileges on sequence pgsodium.key_key_id_seq
# Failed test 368: "Role pg_write_all_data should be granted no privileges on sequence pgsodium.key_key_id_seq"
#     Extra privileges:
#         UPDATE

Incoherency between fields `pgsodium.key.status` and `pgsodium.key.expires`

Hi,

It seems the pgsodium.key.status field can be incoherent with the real status of the key when it expires:

=# select * from pgsodium.create_key(expires => 'yesterday');
 id | name | status | key_type | key_id | key_context | created | expires | associated_data 
----+------+--------+----------+--------+-------------+---------+---------+-----------------
    |      |        |          |        |             |         |         | 
(1 row)

=# select * from pgsodium.key \gx
-[ RECORD 1 ]---+-------------------------------------
id              | 77421bb0-2489-4b5e-8a05-9a7e49bcb778
status          | valid                          -- <=== created as valid
created         | 2023-02-08 20:56:00.987969+01
expires         | 2023-02-07 00:00:00+01         -- <=== is expired !
key_type        | aead-det
key_id          | 1
[...]

Moreover, actual result of pgsodium.create_key() might seem confusing as it returns an empty line from view pgsodium.valid_key whereas the key is actually created.

Transparent Column Decryption

Hello,

It's not clear to me how to decrypt a column using a view that was encrypted with a security label. Is there any documentation around this? Thanks!

Homomorphic encryption

Are the plans to add homomorphic encryption to this extension, so it's possible to sum up data without decrypting data first?

docker pre-built image

i would like to suggest having a docker image that is pre-built with pgsodium. it would be easier to play with.

Transparent Column Encryption with Empty string

Hello,

I created a simple table for TCE testing.
I got everything created automatically (function, trigger, decrypted view).
However, when I insert empty string in the column to be encrypted, the decryption fails.

CREATE TABLE users (
id bigserial primary key,
secret text,
key_id uuid not null default 'e3496f2a-787f-45a0-9717-f648496179d1',
nonce bytea default pgsodium.crypto_aead_det_noncegen()
);

SECURITY LABEL FOR pgsodium
ON COLUMN users.secret
IS 'ENCRYPT WITH KEY COLUMN key_id NONCE nonce';

insert into users( secret ) values ( '12345' );
select * from decrypted_users;
insert into users( secret ) values ( '' );
select * from decrypted_users;

SQL Error [22000]: ERROR: pgsodium_crypto_aead_det_decrypt_by_id: invalid message
Where: PL/pgSQL function pgsodium.crypto_aead_det_decrypt(bytea,bytea,uuid,bytea) line 12 at RETURN

Regards,

installation rpm pgsodium

Hi ,
I try to install pgsodium with rpm on Redhat 8.X (pgsodium_14-3.1.5-1.rhel8.x86_64) .
=> postgresql.conf,
shared_preload_libraries='pgsodium'
When postgres restart , we have this error : "The getkey script "/usr/pgsql-14/share/extension/pgsodium_getkey" does not exists" .
ls -l /usr/pgsql-14/share/extension/pgso*
-rw-r--r--. 1 root root 6275 Jan 4 23:14 /usr/pgsql-14/share/extension/pgsodium--1.0.0--1.1.0.sql
-rw-r--r--. 1 root root 4606 Jan 4 23:14 /usr/pgsql-14/share/extension/pgsodium--1.0.0.sql
-rw-r--r--. 1 root root 59 Jan 4 23:14 /usr/pgsql-14/share/extension/pgsodium--1.1.0--1.1.1.sql
-rw-r--r--. 1 root root 6731 Jan 4 23:14 /usr/pgsql-14/share/extension/pgsodium--1.1.1--1.2.0.sql
-rw-r--r--. 1 root root 7076 Jan 4 23:14 /usr/pgsql-14/share/extension/pgsodium--1.2.0--2.0.0.sql
-rw-r--r--. 1 root root 0 Jan 4 23:14 /usr/pgsql-14/share/extension/pgsodium--2.0.0--2.0.1.sql
-rw-r--r--. 1 root root 0 Jan 4 23:14 /usr/pgsql-14/share/extension/pgsodium--2.0.1--2.0.2.sql
-rw-r--r--. 1 root root 16256 Jan 4 23:14 /usr/pgsql-14/share/extension/pgsodium--2.0.2--3.0.0.sql
-rw-r--r--. 1 root root 271 Jan 4 23:14 /usr/pgsql-14/share/extension/pgsodium--3.0.0--3.0.2.sql
-rw-r--r--. 1 root root 150 Jan 4 23:14 /usr/pgsql-14/share/extension/pgsodium--3.0.2--3.0.3.sql
-rw-r--r--. 1 root root 2452 Jan 4 23:14 /usr/pgsql-14/share/extension/pgsodium--3.0.3--3.0.4.sql
-rw-r--r--. 1 root root 28466 Jan 4 23:14 /usr/pgsql-14/share/extension/pgsodium--3.0.4--3.0.5.sql
-rw-r--r--. 1 root root 2698 Jan 4 23:14 /usr/pgsql-14/share/extension/pgsodium--3.0.5--3.0.6.sql
-rw-r--r--. 1 root root 5245 Jan 4 23:14 /usr/pgsql-14/share/extension/pgsodium--3.0.6--3.0.7.sql
-rw-r--r--. 1 root root 17568 Jan 4 23:14 /usr/pgsql-14/share/extension/pgsodium--3.0.7--3.1.0.sql
-rw-r--r--. 1 root root 4542 Jan 4 23:14 /usr/pgsql-14/share/extension/pgsodium--3.1.0--3.1.1.sql
-rw-r--r--. 1 root root 223 Jan 4 23:14 /usr/pgsql-14/share/extension/pgsodium--3.1.1--3.1.2.sql
-rw-r--r--. 1 root root 364 Jan 4 23:14 /usr/pgsql-14/share/extension/pgsodium--3.1.2--3.1.3.sql
-rw-r--r--. 1 root root 96 Jan 4 23:14 /usr/pgsql-14/share/extension/pgsodium--3.1.3--3.1.4.sql
-rw-r--r--. 1 root root 2655 Jan 4 23:14 /usr/pgsql-14/share/extension/pgsodium--3.1.4--3.1.5.sql
-rw-r--r--. 1 root root 140 Jan 4 23:14 /usr/pgsql-14/share/extension/pgsodium.control

ls /usr/pgsql-14/share/extension/pgsodium_getkey
ls: cannot access '/usr/pgsql-14/share/extension/pgsodium_getkey': No such file or directory

Thanks
Sylvie Halat

only pgsodium 1.0.0 is available for installation on PostgreSQL 9.6

Error installing the extension on PostgreSQL 9.6

available version for installation

postgres=# select name, default_version as version, comment from pg_available_extensions where name like '%pgsodium%' order by 1
postgres-# ;
   name   | version |                  comment                   
----------+---------+--------------------------------------------
 pgsodium | 2.0.2   | Postgres extension for libsodium functions
(1 row)

create extension

postgres=# CREATE EXTENSION pgsodium;
ERROR:  could not stat file "/usr/share/postgresql/9.6/extension/pgsodium--2.0.2.sql": No such file or directory

lists the specific extension versions that are available for installation

postgres=# select * from pg_available_extension_versions where name = 'pgsodium' order by version desc;
   name   | version | installed | superuser | relocatable | schema | requires |                  comment                   
----------+---------+-----------+-----------+-------------+--------+----------+--------------------------------------------
 pgsodium | 1.0.0   | f         | t         | t           |        |          | Postgres extension for libsodium functions
(1 row)

we can only install version 1.0.0

postgres=# CREATE EXTENSION pgsodium WITH VERSION '1.0.0';
CREATE EXTENSION
postgres=# \dx
                         List of installed extensions
   Name   | Version |   Schema   |                Description                 
----------+---------+------------+--------------------------------------------
 pgsodium | 1.0.0   | public     | Postgres extension for libsodium functions
 plpgsql  | 1.0     | pg_catalog | PL/pgSQL procedural language
(2 rows)

without the possibility of updating

error:

postgres=# ALTER EXTENSION pgsodium UPDATE;
ERROR:  syntax error at or near "FROM"
LINE 2:    REVOKE ALL ON FUNCTION derive_key FROM PUBLIC;
                                             ^
QUERY:  
			REVOKE ALL ON FUNCTION derive_key FROM PUBLIC;
			GRANT EXECUTE ON FUNCTION derive_key TO pgsodium_keymaker;
		
CONTEXT:  PL/pgSQL function inline_code_block line 29 at EXECUTE

list of extension files

root@6d7ebcea4a6f:/# ls -l /usr/share/postgresql/9.6/extension/ | grep pgsodium
-rw-r--r-- 1 root root    6300 Oct 17 19:18 pgsodium--1.0.0--1.1.0.sql
-rw-r--r-- 1 root root    4606 Oct 17 19:18 pgsodium--1.0.0.sql
-rw-r--r-- 1 root root      59 Oct 17 19:18 pgsodium--1.1.0--1.1.1.sql
-rw-r--r-- 1 root root    6731 Oct 17 19:18 pgsodium--1.1.1--1.2.0.sql
-rw-r--r-- 1 root root    7076 Oct 17 19:18 pgsodium--1.2.0--2.0.0.sql
-rw-r--r-- 1 root root       0 Oct 17 19:18 pgsodium--2.0.0--2.0.1.sql
-rw-r--r-- 1 root root       0 Oct 17 19:18 pgsodium--2.0.1--2.0.2.sql
-rw-r--r-- 1 root root     135 Oct 17 19:18 pgsodium.control

TCE : Update with using Old Value

Hello,

Looks like table gets corrupted when I'm trying to update an encrypted column using this column.
CREATE TABLE users (
id bigserial primary key,
secret text,
key_id uuid not null default 'e3496f2a-787f-45a0-9717-f648496179d1',
nonce bytea default pgsodium.crypto_aead_det_noncegen()
);

SECURITY LABEL FOR pgsodium
ON COLUMN users.secret
IS 'ENCRYPT WITH KEY COLUMN key_id NONCE nonce';

insert into users( secret ) values ( '12345' );

For now, everything is right
Then
update users set secret = secret || 'test' where id = 1;

At this stage, my secret has been corrupted and is not accessible anymore...
Do I miss anything ?

Thanks

Help section needed - Backup/Restore of database

Hi,
could someone add a help section explaining how backup and restore of data is supposed to work.
Imagine a situation that we want to transfer a database from one server to another, is there even a way to do that. As I understand it, both servers would have to have the same master key right? But what if those servers must have different master keys.

Or is there some way to define masterkey while restoring data to new server?

Thank you

Port pgsodium to Windows

I have ported pgsodium to Windows. It need some minor changes in order to build. It needs plenty of clean-up in order to be in such a state that it can be upstreamed.

Do these changes have a chance of being accepted? Or would you prefer to keep Windows-support out?

setting security label in two different tables fails

Hi everyone,

I have two similar tables, within both I want to encrypt the content of one column (in this example its the column is called secret_column in both tables).

CREATE TABLE table_name1 (
   id_1 uuid PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(),
   key_id_1 uuid NOT NULL REFERENCES pgsodium.key(id) DEFAULT (pgsodium.create_key()).id,
   nonce_1 bytea NOT NULL DEFAULT pgsodium.crypto_aead_det_noncegen(),
   secret_column_1 text NOT NULL DEFAULT 'undefined'::text
);

CREATE TABLE table_name2 (
   id_2 uuid PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(),
   key_id_2 uuid NOT NULL REFERENCES pgsodium.key(id) DEFAULT (pgsodium.create_key()).id,
   nonce_2 bytea NOT NULL DEFAULT pgsodium.crypto_aead_det_noncegen(),
   secret_column_2 text NOT NULL DEFAULT 'undefined'::text
);
SECURITY LABEL FOR pgsodium
  ON COLUMN table_name1.secret_column_1
  IS 'ENCRYPT WITH KEY COLUMN key_id_1 NONCE nonce_1 ASSOCIATED id_1';

SECURITY LABEL FOR pgsodium
  ON COLUMN table_name2.secret_column_2
  IS 'ENCRYPT WITH KEY COLUMN key_id_2 NONCE nonce_2 ASSOCIATED id_2';

I am able to set the security labels for each table separately, but If I want to add both, it fails, giving me the error:

Failed to run sql query: must be owner of relation table_name1

I am new to this topic and looked up every source I can find online, but none could solve my problem.
I really appreciate your help!

Thank you in advance
Tim :-)

Does this not support custom domains?

I was getting some errors I didn't understand and finally changed my column type to a native TEXT and it worked. The original setup was something akin to this:

CREATE DOMAIN EMAIL AS TEXT 
  CONSTRAINT "must be a valid email address" 
    CHECK (VALUE ~ '^[a-zA-Z0-9.!#$%&''*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$');

CREATE TABLE test (
  id XUID PRIMARY KEY NOT NULL
  ,email EMAIL NOT NULL
  ,sodium_key_id UUID NOT NULL REFERENCES pgsodium.key(id) DEFAULT (pgsodium.create_key()).id
  ,sodium_nonce BYTEA NOT NULL DEFAULT pgsodium.crypto_aead_det_noncegen()
);

SECURITY LABEL FOR pgsodium ON COLUMN test.email IS 'ENCRYPT WITH KEY COLUMN sodium_key_id';

adding the security label yields this error:

Query 1 ERROR at Line 1: : ERROR:  syntax error at or near ","
LINE 6: ,       
        ^
QUERY:  
    DROP VIEW IF EXISTS public.decrypted_test;
    CREATE VIEW public.decrypted_test  AS SELECT 
                id,       
        email,
,       
        sodium_key_id,       
        sodium_nonce
    FROM public.test;
    ALTER VIEW public.decrypted_test OWNER TO insurance;
    
CONTEXT:  PL/pgSQL function pgsodium.create_mask_view(oid,integer,boolean) line 38 at EXECUTE
SQL statement "SELECT pgsodium.create_mask_view(objoid, objsubid, debug)
    FROM pg_catalog.pg_seclabel sl
    WHERE sl.objoid = target
      AND sl.label ILIKE 'ENCRYPT%'
      AND sl.provider = 'pgsodium'"
PL/pgSQL function pgsodium.update_mask(oid,boolean) line 4 at PERFORM
SQL statement "SELECT pgsodium.update_mask(r.objid)"
PL/pgSQL function pgsodium.trg_mask_update() line 27 at PERFORM

When I switch the type from EMAIL to TEXT, it works fine.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.