Data encryption
The pgcrypto module provides cryptographic functions for Greengage DB, allowing you to store selected table columns in encrypted form.
Column-level encryption adds an extra layer of protection for sensitive data: values cannot be read without the correct encryption key and remain unreadable even if accessed directly from disk.
The examples in this topic are for demonstration purposes only. They use hardcoded passwords, encryption keys, and other sensitive information for clarity.
Do not use hardcoded secrets in production. In production environments, always follow secure practices:
-
Store secrets in dedicated secret management systems (for example, HashiCorp Vault).
-
Use environment variables or secured configuration files with restricted access.
-
Implement proper key rotation and strong password policies.
-
Ensure private keys are never committed to version control or embedded in application code.
Following these practices helps protect sensitive data and prevent accidental exposure.
To follow the examples in this topic, connect to the Greengage DB master host using psql as described in Connect to Greengage DB via psql.
Create a new database and connect to it:
CREATE DATABASE marketplace;
\c marketplace
Overview
Supported algorithms
The table below lists the cryptographic algorithms and features supported by pgcrypto with built-in providers and with OpenSSL.
| Functionality | Built-in | With OpenSSL |
|---|---|---|
MD5 |
yes |
yes |
SHA-1 |
yes |
yes |
SHA-224/256/384/512 |
yes |
yes |
Other digest algorithms |
no |
yes (see the note below) |
Blowfish |
yes |
yes |
AES |
yes |
yes |
DES/3DES/CAST5 |
no |
yes |
Raw encryption |
yes |
yes |
PGP symmetric encryption |
yes |
yes |
PGP public-key encryption |
yes |
yes |
Any digest algorithm supported by OpenSSL is automatically available in pgcrypto.
Security limitations
All pgcrypto functions execute on the database server.
Data and passwords are transmitted in plaintext between client applications and the server unless the connection is encrypted.
For this reason, use SSL connections or connect locally.
Alternatively, perform encryption and decryption directly within the client application.
Data protected with pgcrypto may be vulnerable to side-channel attacks.
This does not imply that the ciphertext itself is weak.
The risk arises from the execution of cryptographic operations, such as decryption, where runtime characteristics may vary depending on the input data.
For example, the time required for a pgcrypto decryption function to complete varies among ciphertexts of a given size.
pgcrypto functions follow common SQL behavior: they return NULL if any argument is NULL.
Careless use of these functions may therefore introduce security risks.
Performance considerations
Encryption introduces additional processing overhead and can affect database performance. For this reason, avoid encrypting all data unnecessarily. It is generally recommended to encrypt only small amounts of highly sensitive data that is accessed frequently, such as credit card numbers or social security numbers.
Large data values should be encrypted only when they are accessed infrequently, where the security benefits outweigh the performance cost.
Register pgcrypto
Connect to the target database and register the pgcrypto extension:
CREATE EXTENSION pgcrypto;
General hashing functions
General hashing functions are used to verify data integrity and authenticate messages. They allow you to generate a fixed-size representation of data that cannot be reversed to reveal the original value. Common use cases include detecting data modifications and creating hash-based message authentication codes (HMAC).
The digest() function generates one-way hashes for arbitrary data.
When Greengage DB is compiled with the --with-openssl option, a wider range of hashing algorithms becomes available.
The hmac() function uses a secret key with a hash to protect data integrity and authenticity. It allows recipients to verify that a message has not been modified and originates from a trusted sender.
The following example generates a SHA-256 hash of a string using digest():
SELECT digest('Top secret', 'sha256');
The result looks like this:
digest -------------------------------------------------------------------- \xad8b354a65533f41288aaabf615826869c70b42815c387899130462ef6068b92 (1 row)
In this example, a SHA-384 hash of the same string is generated:
SELECT digest('Top secret', 'sha384');
The result:
digest ---------------------------------------------------------------------------------------------------- \x477187c44fd737c2599da5550b3ebe9e9b0a519f8ed7de1718167b0ca129dd2696385f3fb834dd0b259f383681b0a77f (1 row)
This command generates an HMAC using a secret key:
SELECT hmac('Top secret', 'secret_key', 'sha256');
The binary output of digest() can be converted to hexadecimal format using encode():
SELECT encode(digest('Top secret', 'sha256'), 'hex') AS hash;
The result looks as follows:
hash ------------------------------------------------------------------ ad8b354a65533f41288aaabf615826869c70b42815c387899130462ef6068b92 (1 row)
Example: verify data integrity
This example demonstrates how to use digest() to detect whether sensitive data has been altered.
A SHA-256 hash of each personal_id is stored and later compared to the current value to verify integrity.
Create a table to store customer data along with a hash of their personal IDs:
CREATE TABLE customers
(
customer_id INTEGER,
name TEXT,
personal_id TEXT,
personal_id_hash BYTEA
)
WITH (appendoptimized = true)
DISTRIBUTED BY (customer_id);
Insert sample data, computing the hash for each personal_id:
INSERT INTO customers (customer_id, name, personal_id, personal_id_hash)
VALUES (1, 'Andrew Fuller', 'ID-001-AF', digest('ID-001-AF', 'sha256')),
(2, 'Michael Suyama', 'ID-002-MS', digest('ID-002-MS', 'sha256')),
(3, 'Laura Callahan', 'ID-003-LC', digest('ID-003-LC', 'sha256'));
Verify whether the stored data matches the hash:
SELECT name,
personal_id,
personal_id_hash = digest(personal_id, 'sha256') AS is_intact
FROM customers;
All rows should be intact initially:
name | personal_id | is_intact ----------------+-------------+----------- Laura Callahan | ID-003-LC | t Andrew Fuller | ID-001-AF | t Michael Suyama | ID-002-MS | t (3 rows)
Simulate data corruption by updating a personal ID:
UPDATE customers
SET personal_id = 'CORRUPTED'
WHERE customer_id = 2;
Verify data integrity again to detect the change:
SELECT name,
personal_id,
personal_id_hash = digest(personal_id, 'sha256') AS is_intact
FROM customers;
The result:
name | personal_id | is_intact ----------------+-------------+----------- Laura Callahan | ID-003-LC | t Andrew Fuller | ID-001-AF | t Michael Suyama | CORRUPTED | f (3 rows)
Password hashing functions
Password hashing functions are used to securely store user passwords in a database. Instead of storing passwords in plain text, a hash of the password is saved, making it computationally infeasible to recover the original password even if the database is compromised.
The crypt() function is specifically designed for password hashing.
It includes the salt parameter to ensure that each password generates a unique hash, even if two users have the same password.
The gen_salt() function is used to generate this salt.
This command generates a hashed password using crypt() with a generated salt:
SELECT crypt('top_secret_123', gen_salt('bf'));
The result shows the hashed password:
crypt -------------------------------------------------------------- $2a$06$uJlom0lne.dUlBhX7Z8oSOsoto5eMcft9xWMVhZeHaoSoEgScBERG (1 row)
Example: verify password
This example demonstrates how to store hashed passwords and verify them using crypt() and gen_salt().
The password hashes are stored in the database, and authentication is performed by comparing the stored hash with the result of crypt() applied to the input password.
Create a table to store users and their hashed passwords:
CREATE TABLE users
(
user_id SERIAL,
username TEXT,
password_hash TEXT
)
DISTRIBUTED BY (user_id);
Insert sample users, hashing their passwords with Blowfish (bf) and a generated salt:
INSERT INTO users (username, password_hash)
VALUES ('alice', crypt('alice_secret', gen_salt('bf'))),
('bob', crypt('bob_secret', gen_salt('bf'))),
('carol', crypt('carol_secret', gen_salt('bf')));
Check authentication for a correct password:
SELECT username,
(password_hash = crypt('alice_secret', password_hash)) AS is_authenticated
FROM users
WHERE username = 'alice';
Result — password matches:
username | is_authenticated ----------+------------------ alice | t (1 row)
Check authentication for an incorrect password:
SELECT username,
(password_hash = crypt('wrong_password', password_hash)) AS is_authenticated
FROM users
WHERE username = 'alice';
Result — password does not match:
username | is_authenticated ----------+------------------ alice | f (1 row)
PGP symmetric encryption
PGP symmetric encryption allows data to be securely encrypted using a shared password. The password is processed using a String-to-Key (S2K) algorithm to produce a binary key, which is then used to encrypt data.
In this example, pgp_sym_encrypt() is used to encrypt a message using a password:
SELECT pgp_sym_encrypt(
'some data to encrypt',
'top_secret_key'
) AS encrypted_message;
You can also encrypt a message with additional options, such as a specific cipher, compression, or Unicode handling:
SELECT pgp_sym_encrypt(
'some data to encrypt',
'top_secret_key',
'cipher-algo=aes256, compress-algo=1, unicode-mode=1'
) AS encrypted_message_with_options;
Example: encrypt columns with symmetric keys
This example demonstrates how to encrypt sensitive data in a table using pgp_sym_encrypt() and decrypt it using pgp_sym_decrypt().
Create a table to store customer data and the encrypted personal IDs:
CREATE TABLE customers
(
customer_id INTEGER,
name TEXT,
personal_id BYTEA
)
WITH (appendoptimized = true)
DISTRIBUTED BY (customer_id);
Set a secret key to be used for encryption and decryption:
\set secret_key 'top_secret_123'
Insert customer data, encrypting the personal_id column with the secret key:
INSERT INTO customers (customer_id, name, personal_id)
VALUES (1, 'Andrew Fuller', pgp_sym_encrypt('ID-001-AF', :'secret_key')),
(2, 'Michael Suyama', pgp_sym_encrypt('ID-002-MS', :'secret_key')),
(3, 'Laura Callahan', pgp_sym_encrypt('ID-003-LC', :'secret_key'));
Select the raw encrypted data to show how it appears in the table:
SELECT name,
personal_id
FROM customers;
The result:
name | personal_id ----------------+---------------------------------------------------------------------------------------------------------------------------------------------------------- Laura Callahan | \xc30d040703028df998cc672875417fd23a0140342d5d9d0ff2b009502c9afff26256f48b2af213230a7173533f8cd91181794b9c70c065c2877064cbc6e51a0958ac21463881e619ad986d Andrew Fuller | \xc30d04070302cc4a79bf1b48614972d23a017261253469237edb8598d1c8df0e7c2895b268ea6eace7f38dd159740616cafa9d15981e6c22751c9ac3196c19deb1eb3c8cd6d1ba7ef7b076 Michael Suyama | \xc30d04070302e97127260b161f4062d23a01c412858005b76d946c82f9663c9a1b2b6cdadb72a9876ca01fce4f0987c9a5d0039b493e8c21dc8218ff2a33f3231a5cbdf9cf3176eeb0ff82 (3 rows)
Decrypt the personal_id column to view the original data:
SELECT name,
pgp_sym_decrypt(personal_id, :'secret_key') AS personal_id
FROM customers;
The result:
name | personal_id ----------------+------------- Michael Suyama | ID-002-MS Andrew Fuller | ID-001-AF Laura Callahan | ID-003-LC (3 rows)
PGP public-key encryption
PGP public-key encryption allows data to be securely encrypted using a recipient’s public key so that only the holder of the corresponding private key can decrypt it.
You can encrypt a message using a recipient’s public key with pgp_pub_encrypt():
SELECT pgp_pub_encrypt(
'Top secret',
dearmor('-----BEGIN PGP PUBLIC KEY BLOCK----- <public_key> -----END PGP PUBLIC KEY BLOCK-----')
);
To decrypt the message, provide the corresponding private key to pgp_pub_decrypt():
SELECT pgp_pub_decrypt(
data::bytea,
dearmor('-----BEGIN PGP PRIVATE KEY BLOCK----- <private_key> -----END PGP PRIVATE KEY BLOCK-----')
);
The armor() function is useful for importing and exporting keys or messages in a text-based format.
Example: encrypt columns with public-key cryptography
This example demonstrates how to store data encrypted with a recipient’s public key and decrypt it using the corresponding private key.
The data is stored in the database as a BYTEA column and can only be decrypted by the holder of the private key.
Create a table to store customer data and the encrypted personal IDs:
CREATE TABLE customers
(
customer_id INTEGER,
name TEXT,
personal_id BYTEA
)
WITH (appendoptimized = true)
DISTRIBUTED BY (customer_id);
Load the public and private PGP keys from files into psql variables.
In this example, the key files are stored in the examples/data_encryption/certs directory:
\set public_key `cat examples/data_encryption/certs/public.key`
\set private_key `cat examples/data_encryption/certs/secret.key`
For instructions on how to generate PGP keys, see Generate PGP key pairs with GnuPG.
Insert customer data, encrypting the personal_id column with the public key:
INSERT INTO customers (customer_id, name, personal_id)
VALUES (1, 'Andrew Fuller', pgp_pub_encrypt('ID-001-AF', dearmor(:'public_key'))),
(2, 'Michael Suyama', pgp_pub_encrypt('ID-002-MS', dearmor(:'public_key'))),
(3, 'Laura Callahan', pgp_pub_encrypt('ID-003-LC', dearmor(:'public_key')));
Decrypt the personal_id column using the corresponding private key to view the original data:
SELECT name,
pgp_pub_decrypt(personal_id, dearmor(:'private_key'), 'top_secret_123') AS personal_id
FROM customers;
The result:
name | personal_id ----------------+------------- Andrew Fuller | ID-001-AF Michael Suyama | ID-002-MS Laura Callahan | ID-003-LC (3 rows)
Generate PGP key pairs with GnuPG
You can use the GnuPG tool to generate PGP keys.
Generate a new PGP key pair:
$ gpg --full-generate-key
At the prompt, select the key type, key size, expiration period, and user identity.
You will also be asked to define a secure passphrase.
This passphrase is later used as the psw argument of the pgp_pub_decrypt() function.
List available secret keys:
$ gpg --list-secret-keys
Example output:
sec dsa2048 2026-02-16 [SC]
446049EA9E873DB8A526437CAD2C1899311684E7
uid [ultimate] Greengage DB User <example@greengagedb.org>
ssb elg2048 2026-02-16 [E]
Export the public key:
$ gpg -a --export <KEYID> > public.key
In the example above, KEYID is 446049EA9E873DB8A526437CAD2C1899311684E7.
Export the private key:
$ gpg -a --export-secret-keys <KEYID> > secret.key
Reference
This section does not include the raw encryption functions (encrypt(), decrypt(), encrypt_iv(), decrypt_iv()), which operate directly on ciphers but lack integrity checks, automatic padding, and text handling.
PGP‑based encryption is preferred over these raw functions.
For details on the raw encryption functions, see the pgcrypto section in the PostgreSQL documentation.
pgcrypto functions
- digest(data text, type text) returns bytea
- digest(data bytea, type text) returns bytea
-
Computes a binary hash value for the given input data. The
typeparameter specifies the hash algorithm to use (see Supported algorithms). Common algorithms includemd5,sha1,sha224,sha256,sha384, andsha512. Ifpgcryptowas built with OpenSSL support, additional algorithms are available.See also: General hashing functions.
- hmac(data text, key text, type text) returns bytea
- hmac(data bytea, key bytea, type text) returns bytea
-
Computes a keyed hash (HMAC) of the given data using the specified secret key. The
typeparameter specifies the hash algorithm to use, the same as for digest().Unlike
digest(), the hash value can only be recomputed by someone who knows the key. This protects against tampering where both the data and its hash could otherwise be modified together.If the key is longer than the hash algorithm’s block size, it is first hashed, and the result is used as the effective key.
See also: General hashing functions.
- crypt(password text, salt text) returns text
-
Computes a
crypt(3)-style hash of the given password. When storing a new password, use gen_salt() to generate a new random salt value. To verify a password, pass the stored hash as thesaltparameter and compare the result with the stored value.The table below lists the algorithms supported by the
crypt()function.Algorithm Max password length Adaptive Salt bits Output length Description bf
72
yes
128
60
Blowfish-based, variant 2a
md5
unlimited
no
48
34
MD5-based crypt
xdes
8
yes
24
20
Extended DES
des
8
no
12
13
Original UNIX crypt
See also: Password hashing functions.
- gen_salt(type text [, iter_count integer ]) returns text
-
Generates a new random salt string for use with crypt(). The generated salt also encodes the hashing algorithm to be used by
crypt().The
typeparameter specifies the hashing algorithm. Supported values are:des,xdes,md5, andbf.The optional
iter_countparameter specifies the iteration count for thexdesandbfalgorithms. Higher values increase the time required to compute the hash and therefore increase resistance to brute-force attacks. Allowed values foriter_countare shown in the table below.Algorithm Default Min Max xdes
725
1
16777215
bf
6
4
31
See also: Password hashing functions.
- pgp_sym_encrypt(data text, psw text [, options text ]) returns bytea
- pgp_sym_encrypt_bytea(data bytea, psw text [, options text ]) returns bytea
-
Encrypts data using a symmetric PGP key specified by
psw. The optionaloptionsparameter can include one or more settings, as described in Options for PGP functions.See also: PGP symmetric encryption.
- pgp_sym_decrypt(msg bytea, psw text [, options text ]) returns text
- pgp_sym_decrypt_bytea(msg bytea, psw text [, options text ]) returns bytea
-
Decrypts a PGP message that was encrypted with a symmetric key.
pgp_sym_decrypt()cannot be used to decrypt arbitraryBYTEAdata, as it may produce invalid character output. For binary data or when the original encoding is unknown,pgp_sym_decrypt_bytea()can be used safely.The optional
optionsparameter can include settings as described in Options for PGP functions.See also: PGP symmetric encryption.
- pgp_pub_encrypt(data text, key bytea [, options text ]) returns bytea
- pgp_pub_encrypt_bytea(data bytea, key bytea [, options text ]) returns bytea
-
Encrypts data using a public PGP key. Passing a secret (private) key to this function will result in an error.
The optional
optionsparameter can include settings as described in Options for PGP functions.See also: PGP public-key encryption.
- pgp_pub_decrypt(msg bytea, key bytea [, psw text [, options text ]]) returns text
- pgp_pub_decrypt_bytea(msg bytea, key bytea [, psw text [, options text ]]) returns bytea
-
Decrypts a message that was encrypted with a public PGP key. The
keymust be the secret (private) key corresponding to the public key used for encryption. If the private key is password-protected, provide the password inpsw; if no password is needed but options are required, pass an empty string forpsw.pgp_pub_decrypt()cannot be used to decrypt arbitraryBYTEAdata, as it may produce invalid character output. For binary data or when the original encoding is unknown,pgp_pub_decrypt_bytea()can be used safely.The optional
optionsparameter can include settings as described in Options for PGP functions.See also: PGP public-key encryption.
- pgp_key_id(bytea) returns text
-
Extracts the key ID from a PGP public or private key. If an encrypted message is provided, it returns the key ID that was used to encrypt the data.
- armor(data bytea) returns text
- dearmor(data text) returns bytea
-
Converts binary data to and from PGP ASCII-armor format. This format is essentially Base64-encoded data with a CRC checksum and additional formatting for portability and readability.
See also: PGP public-key encryption.
- gen_random_bytes(count integer) returns bytea
-
Generates
countcryptographically strong random bytes. At most 1024 bytes can be generated in a single call to avoid exhausting the system’s randomness pool.
- gen_random_uuid() returns uuid
-
Generates a version 4 (random) UUID.
Options for PGP functions
- cipher-algo
-
Specifies which cipher algorithm to use.
Values:
bf,aes128,aes192,aes256(OpenSSL-only:3des,cast5).
Default:aes128.
Applies to:pgp_sym_encrypt(),pgp_pub_encrypt().
- compress-algo
-
Specifies which compression algorithm to use. Only available if Greengage DB was built with zlib (
--with-zlib).Values:
-
0— no compression. -
1— ZIP compression. -
2— ZLIB compression (ZIP plus metadata and block CRCs).
Default:
0.
Applies to:pgp_sym_encrypt(),pgp_pub_encrypt(). -
- compress-level
-
Controls the compression level. Higher levels yield smaller output but are slower.
0disables compression.Values:
0,1—9.
Default:6.
Applies to:pgp_sym_encrypt(),pgp_pub_encrypt().
- convert-crlf
-
Whether to convert line feeds (
\n) to carriage-return line feeds (\r\n) when encrypting, and back when decrypting. This ensures RFC 4880-compliant text storage.Values:
0,1.
Default:0.
Applies to:pgp_sym_encrypt(),pgp_pub_encrypt(),pgp_sym_decrypt(),pgp_pub_decrypt().
- disable-mdc
-
Disables SHA-1 integrity protection. Use only for compatibility with legacy PGP software that predates SHA-1 protection (RFC 4880).
Values:
0,1.
Default:0.
Applies to:pgp_sym_encrypt(),pgp_pub_encrypt().
- sess-key
-
Specifies whether to use a separate session key. Public-key encryption always uses a separate session key. This option applies only to symmetric-key encryption, which by default uses the S2K key directly.
Values:
0,1.
Default:0.
Applies to:pgp_sym_encrypt().
- s2k-mode
-
Specifies the String-to-Key (S2K) algorithm.
Values:
-
0— without salt (insecure). -
1— with salt and fixed iteration count. -
3— with salt and variable iteration count.
Default:
3.
Applies to:pgp_sym_encrypt(). -
- s2k-digest-algo
-
Specifies the digest algorithm used in S2K calculation.
Values:
md5,sha1.
Default:sha1.
Applies to:pgp_sym_encrypt().
- s2k-cipher-algo
-
Specifies the cipher used to encrypt the separate session key.
Values:
bf,aes,aes128,aes192,aes256.
Default: uses the cipher-algo value.
Applies to:pgp_sym_encrypt().
- unicode-mode
-
Controls whether textual data is converted from the database encoding to UTF-8 and back. If the database is already UTF-8, no conversion occurs, but the message is tagged as UTF-8. Without this option, no tag is applied.
Values:
0,1.
Default:0.
Applies to:pgp_sym_encrypt(),pgp_pub_encrypt().