SOFTWARE
Oracle Guide
Encrypt and decrypt with SQL functions in Oracle. For many rows, the STRCRYPT_BULK package groups them into chunks and handles each chunk in a single external call.
System requirements
- Oracle Database
- Tested on 19c and 26ai. Published files require Oracle Linux 8 or later (glibc 2.28 or later). Single instance with a dedicated extproc agent.
- Database settings
- Character set AL32UTF8, MAX_STRING_SIZE=STANDARD
- Database server OS
- Linux x86_64 with glibc 2.28 or later (for example, Oracle Linux 8 or later). The published files do not load on Oracle Linux 7, which ships glibc 2.17.
- Build tools
- gcc, make, curl, tar, sha256sum (coreutils 8.25 or later), and oci.h under ORACLE_HOME
- Privileges
- The oracle OS account with sudo on the database server, and SYS (AS SYSDBA) in the database
RAC, the multithreaded extproc agent (agtctl), and MAX_STRING_SIZE=EXTENDED have not been tested.
Where to run each step
| Step | Where | Account |
|---|---|---|
| 1. Pre-checks through 4. extproc.ora settings | Shell on the database server | oracle OS account (login shell with ORACLE_HOME set, using sudo) |
| 5. Install the SQL objects | sqlplus from a shell on the database server | SYS AS SYSDBA, connected to the PDB that holds your application tables |
| 6. Restrict execute privileges | Same sqlplus as step 5 | SYS AS SYSDBA, same PDB |
| 7. Verify the installation | sqlplus or any SQL client | The application user that will encrypt and decrypt, same PDB |
Oracle loads the C wrapper liboracle_strcrypt.so as an external procedure (extproc), and the wrapper calls libstrcrypt.so.
extproc runs as oracle on the database server, so the libraries, key file, and settings all live on the database server. Nothing needs to be installed on the application server.
1. Pre-checks
echo "$ORACLE_HOME"
ldd --version | head -1
ls "$ORACLE_HOME/rdbms/public/oci.h"
command -v gcc make curl sha256sum tar sudo
lsnrctl status | grep -i '^Service'
Run this from a shell opened with sudo su - oracle. The last command lists the services; note the PDB service name (for example, orclpdb1) for step 5.
command -v prints only the tools that are installed.
Common errors
| Symptom | Cause and fix |
|---|---|
| ORACLE_HOME prints an empty line | You are not in the oracle login shell. Switch with sudo su - oracle. |
| ldd (GNU libc) 2.17 or anything below 2.28 | The published files cannot be used on this server (for example, Oracle Linux 7). |
| oci.h: No such file or directory | A full database software install or the Instant Client SDK is required. |
| gcc or make is missing from the command -v output | Install the build tools before step 2. |
2. Build and install the libraries
Only if the build tools are missing
sudo dnf install -y gcc makeDownload, verify, build, and install
mkdir -p ~/strcrypt-oracle && cd ~/strcrypt-oracle
curl -fLO https://www.cubiware.co.kr/api/download/strcrypt/0.3.1/libstrcrypt.so
curl -fLO https://www.cubiware.co.kr/api/download/strcrypt/0.3.1/libstrcrypt.so.hmac
curl -fLO https://www.cubiware.co.kr/api/download/strcrypt/0.3.1/strcrypt-oracle-0.3.1.tar.gz
curl -fLO https://www.cubiware.co.kr/api/download/strcrypt/0.3.1/SHA256SUMS
sha256sum -c --ignore-missing SHA256SUMS
tar xzf strcrypt-oracle-0.3.1.tar.gz && cd strcrypt-oracle-0.3.1
make ORACLE_HOME="$ORACLE_HOME" STRCRYPT_LIB_DIR="$HOME/strcrypt-oracle" STRCRYPT_INCLUDE_DIR=.
sudo install -m 755 ../libstrcrypt.so liboracle_strcrypt.so /usr/lib/
sudo install -m 644 ../libstrcrypt.so.hmac /usr/lib/
ldd /usr/lib/liboracle_strcrypt.so | grep strcrypt
The wrapper has to be built against the Oracle headers (oci.h) on your database server, so it is provided as source. The build happens under ~/strcrypt-oracle, and only the two libraries are copied to /usr/lib.
The install succeeded if the last line prints libstrcrypt.so => /usr/lib/libstrcrypt.so.
Common errors
| Symptom | Cause and fix |
|---|---|
| make: command not found / gcc: command not found | Run the build tools install command first. |
| sha256sum: unrecognized option '--ignore-missing' | The server has an old coreutils (for example, Oracle Linux 7). This server is not supported. |
| FAILED in the sha256sum output | The file was corrupted during download. Download it again. |
| *** ORACLE_HOME is not set | Run the commands from the oracle login shell with ORACLE_HOME set. |
| *** oci.h not found under ORACLE_HOME=... | Resolve the oci.h item from the pre-checks. |
| warning: control reaches end of non-void function | Expected. The build still completes. |
| The final ldd shows not found or GLIBC_2.28 not found | The library was not found or the server glibc is too old. Check that both files are in /usr/lib and check the glibc version. |
| oracle is not in the sudoers file | Run only the lines that start with sudo from an account with root privileges. |
3. Generate the key file
sudo install -d -o oracle -m 700 /etc/strcrypt
head -c 24 /dev/urandom | base64 | head -c 32 | sudo -u oracle tee /etc/strcrypt/strcrypt.key > /dev/null
sudo chmod 600 /etc/strcrypt/strcrypt.key
sudo -u oracle wc -c /etc/strcrypt/strcrypt.key
This creates a 32-byte key in a file only the oracle account can read. The last line should print 32 /etc/strcrypt/strcrypt.key.
The key is never passed through SQL arguments or tables. If you lose the key, the ciphertext cannot be recovered, so keep a separate backup.
Common errors
| Symptom | Cause and fix |
|---|---|
| wc does not print 32 | The key file is empty or truncated. Run the first three lines again. Continuing with an empty key file causes ORA-20502 in step 5. |
| Permission denied | extproc runs as oracle, so the key file must be owned by oracle with permission 600. |
4. extproc.ora settings
mkdir -p $ORACLE_HOME/hs/admin
grep -sn '^SET EXTPROC_DLLS' $ORACLE_HOME/hs/admin/extproc.ora
cat >> $ORACLE_HOME/hs/admin/extproc.ora <<'EOF'
SET EXTPROC_DLLS=ONLY:/usr/lib/liboracle_strcrypt.so
SET LD_LIBRARY_PATH=/usr/lib
SET STRCRYPT_KEY_FILE=/etc/strcrypt/strcrypt.key
EOF
This appends three lines to $ORACLE_HOME/hs/admin/extproc.ora. The grep on the second line checks for an existing SET EXTPROC_DLLS line and prints nothing if there is none.
The extproc started by the database reads this file, so you do not need to edit listener.ora or restart anything. The settings apply to new sessions.
To use algorithms other than the Korean standards, add a SET STRCRYPT_MODE=non-approved line.
Common errors
| Symptom | Cause and fix |
|---|---|
| grep prints an existing SET EXTPROC_DLLS line | Merge it with the added line into a single line, joining paths with a colon (:). For example: SET EXTPROC_DLLS=ONLY:/existing/path.so:/usr/lib/liboracle_strcrypt.so |
| Later queries fail with ORA-28595: Extproc agent: Invalid DLL Path | The EXTPROC_DLLS line is missing or the path is different. |
| Later queries fail with ORA-20502: STRCrypt: operation failed | The STRCRYPT_KEY_FILE line is missing, the key file is empty, or the oracle account cannot read it. |
5. Install the SQL objects
cd ~/strcrypt-oracle/strcrypt-oracle-0.3.1
sqlplus sys@//localhost:1521/ORCLPDB1 as sysdba @install.sql
sqlplus sys@//localhost:1521/ORCLPDB1 as sysdba @install_bulk.sql
Replace ORCLPDB1 with the PDB service name from step 1. sqlplus asks for the SYS password.
install.sql creates six SQL functions, and install_bulk.sql creates four collection types and the STRCRYPT_BULK package, and each script runs an encryption and decryption test at the end.
Two [OK] lines from each script mean success. Both scripts can be run again.
Common errors
| Symptom | Cause and fix |
|---|---|
| ORA-12514: ... Service ORCLPDB1 is not registered, or another connection error | Replace ORCLPDB1 with a PDB service name from the lsnrctl output in the pre-checks. If the port is not 1521, change it too. |
| ORA-20502: STRCrypt: operation failed at the end of the install | The objects were created, but the final check could not read the key. Fix steps 3 and 4, then run the script again. |
| ORA-20502: ... 암호모듈이 에러 상태입니다 (the crypto module is in an error state) | libstrcrypt.so.hmac in /usr/lib does not match libstrcrypt.so. Keep both files from the same release together. |
| ORA-06520: PL/SQL: Error loading external library | Check the final ldd output from step 2. |
| ORA-00904: "STRCRYPT_ENCRYPT": invalid identifier for the application user | The objects were installed in the CDB root (CDB$ROOT) or in a different PDB. Install again in the PDB your application connects to. |
6. Restrict execute privileges
BEGIN
FOR o IN (SELECT object_name FROM dba_objects
WHERE owner = 'SYS'
AND (object_name LIKE 'STRCRYPT\_%' ESCAPE '\' OR object_name = 'ORACLE_STRCRYPT_LIB')
AND object_type IN ('FUNCTION', 'PACKAGE', 'TYPE', 'LIBRARY')) LOOP
BEGIN
EXECUTE IMMEDIATE 'REVOKE EXECUTE ON SYS.' || o.object_name || ' FROM PUBLIC';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -1927 THEN
RAISE;
END IF;
END;
IF o.object_name <> 'ORACLE_STRCRYPT_LIB' THEN
EXECUTE IMMEDIATE 'GRANT EXECUTE ON SYS.' || o.object_name || ' TO APP_USER';
END IF;
END LOOP;
END;
/
The install scripts grant execute to everyone (PUBLIC). Left as is, anyone who can connect to the database can decrypt,
so connect as SYS the same way as in step 5 and run this block. Replace APP_USER with the user that will encrypt and decrypt, and repeat that line for more users. You can run this block again.
Common errors
| Symptom | Cause and fix |
|---|---|
| ORA-01917: user or role does not exist | Replace APP_USER with the actual user name. |
| ORA-00904: "STRCRYPT_ENCRYPT": invalid identifier for users you did not grant | Expected. The PUBLIC grant has been revoked. |
7. Verify the installation
Character set and a basic round trip
SELECT value FROM nls_database_parameters WHERE parameter = 'NLS_CHARACTERSET';
SELECT STRCRYPT_DECRYPT(STRCRYPT_ENCRYPT('test')) FROM DUAL;Batch round-trip test (1,000 rows; the test table is dropped at the end)
CREATE TABLE strcrypt_selftest AS
SELECT level AS id, 'row-' || level || '-홍길동' AS val FROM dual CONNECT BY level <= 1000;
SELECT COUNT(*) AS checked, SUM(CASE WHEN d.v = t.val THEN 0 ELSE 1 END) AS mismatches
FROM TABLE(STRCRYPT_BULK.decrypt_rows(CURSOR(
SELECT e.k, e.v, CAST(NULL AS RAW(2000))
FROM TABLE(STRCRYPT_BULK.encrypt_rows(CURSOR(
SELECT TO_CHAR(id), val, CAST(NULL AS RAW(2000)) FROM strcrypt_selftest), 'aria256gcm')) e))) d
JOIN strcrypt_selftest t ON t.id = TO_NUMBER(d.k);
DROP TABLE strcrypt_selftest PURGE;
Connect to the same PDB as the application user. The first query should return AL32UTF8, the second test,
and the round-trip test CHECKED 1000 and MISMATCHES 0. The round-trip test needs the CREATE TABLE privilege.
Common errors
| Symptom | Cause and fix |
|---|---|
| The character set is not AL32UTF8 | This configuration is not supported. Non-ASCII values such as Korean text fail or are stored corrupted. |
| ORA-00904: "STRCRYPT_ENCRYPT": invalid identifier | Check that step 6 granted this user and that you are connected to the same PDB. |
| MISMATCHES is not 0 | This is more likely a data issue than an install issue. Check the character set again. |
SQL functions
CREATE TABLE users_encrypted AS
SELECT user_id, STRCRYPT_ENCRYPT(email) AS email_encrypted
FROM users;
SELECT user_id, STRCRYPT_DECRYPT(email_encrypted) AS email
FROM users_encrypted;
SELECT STRCRYPT_ENCRYPT_ALGO('alice@example.com', 'seed128gcm') FROM DUAL;Use them like any other function in SELECT, INSERT, or UPDATE. Each row makes its own extproc call, so switch to batch processing below when you handle many rows. A NULL input returns NULL.
Ciphertext differs every time, even for the same value, so it cannot be indexed. For exact-match search, store
STRCRYPT_SEARCH_INDEX(value, 'table.column') in an indexed search column and compare it with the index of the search term.
Do not use an unkeyed SHA-256 hash: guessable values such as national ID numbers can be recovered by hashing every candidate.
A search column reveals which rows share a value, so add one only where search is needed, and grant execute on the search index function only to users who need it.
Batch processing
Decrypt while selecting many rows
SELECT TO_NUMBER(k) AS id, v AS ssn
FROM TABLE(STRCRYPT_BULK.decrypt_rows(
CURSOR(SELECT TO_CHAR(id), ssn_enc, CAST(NULL AS RAW(2000))
FROM customers))); decrypt_rows and encrypt_rows read the cursor in chunks (100 rows by default) and make one extproc call per chunk.
The cursor must return three columns in this order: key, value, and AAD (RAW). Pass NULL when there is no AAD.
Encrypt an existing column in bulk
DECLARE
TYPE t_id IS TABLE OF customers.id%TYPE;
v_id t_id;
v_in STRCRYPT_VC_TAB;
v_out STRCRYPT_VC_TAB;
v_last customers.id%TYPE := -1;
BEGIN
LOOP
SELECT id, ssn BULK COLLECT INTO v_id, v_in
FROM customers WHERE id > v_last ORDER BY id FETCH FIRST 100 ROWS ONLY;
EXIT WHEN v_id.COUNT = 0;
STRCRYPT_BULK.encrypt_tab(v_in, 'aria256gcm', v_out);
FORALL i IN 1 .. v_id.COUNT
UPDATE customers SET ssn_enc = v_out(i) WHERE id = v_id(i);
v_last := v_id(v_id.COUNT);
COMMIT;
END LOOP;
END;
/
Collect a chunk in primary key order, call encrypt_tab once, apply the results by primary key, and commit after each chunk.
Committing with a cursor still open can raise ORA-01555, so each chunk is queried again. If a run fails partway, the committed chunks stay, so start again from the last primary key.
Load decrypted results into another table
INSERT /*+ APPEND */ INTO customers_plain (id, ssn)
SELECT TO_NUMBER(k), v
FROM TABLE(STRCRYPT_BULK.decrypt_rows(
CURSOR(SELECT /*+ PARALLEL(4) */ TO_CHAR(id), ssn_enc, CAST(NULL AS RAW(2000))
FROM customers)));
COMMIT;
Inside the pipelined functions the library uses a single thread, and Oracle parallel execution handles parallelism.
For encrypt_tab and decrypt_tab, keep p_threads at its default of 1. With 1 KB records, values above 1 made bulk UPDATEs slower.
Limitations
- Results must fit in 4,000 bytes of VARCHAR2; longer results raise ORA-20503. With ARIA-256-GCM, plaintext can be up to 2,971 bytes.
- If any record in a batch call fails, the whole call fails (ORA-20502). Failed records are not silently filled with NULL.
- Values containing a NUL (
CHR(0)) are rejected bySTRCRYPT_ENCRYPTandSTRCRYPT_ENCRYPT_ALGOwith ORA-20502. - The same value encrypts to a different ciphertext each time, so you cannot search or index the ciphertext column. Keep a separate search index (
STRCRYPT_SEARCH_INDEX) column if you need lookups. Do not use an unkeyed hash column. - Plaintext passes through the memory of the extproc process on the database server.
API reference
| Function or procedure | Description |
|---|---|
STRCRYPT_ENCRYPT(input_text) / STRCRYPT_DECRYPT(encrypted_text) | Encrypt and decrypt a string. Decryption detects the algorithm automatically |
STRCRYPT_ENCRYPT_ALGO(input_text, algorithm) / STRCRYPT_DECRYPT_ALGO(encrypted_text, algorithm) | Encrypt and decrypt with a chosen algorithm. For decryption, a NULL algorithm is detected automatically |
STRCRYPT_SEARCH_INDEX(p_value, p_domain) | Search index for exact-match lookup (64 hex characters). p_domain identifies the column (for example customers.ssn). Same value as the Python, Node.js, and Java bindings |
STRCRYPT_ALGORITHMS() / STRCRYPT_HW_INFO() | Available algorithms and hardware acceleration info (JSON strings) |
STRCRYPT_BULK.encrypt_tab(p_in, p_algorithm, p_out, p_aads, p_threads) | Encrypt a collection of plaintexts (STRCRYPT_VC_TAB) in one call |
STRCRYPT_BULK.decrypt_tab(p_in, p_out, p_aads, p_threads) | Decrypt a collection of ciphertexts in one call |
STRCRYPT_BULK.encrypt_rows(p_cur, p_algorithm, p_chunk) | Pipelined function that reads a cursor in chunks and returns (key, ciphertext) rows |
STRCRYPT_BULK.decrypt_rows(p_cur, p_chunk) | Pipelined function that reads a cursor in chunks and returns (key, plaintext) rows |
Test environments
| Environment | Method |
|---|---|
| Oracle AI Database 26ai Free (Oracle Linux 8.10, glibc 2.28) | Ran the published files and the commands on this page as written |
| Oracle Database 19c Enterprise Edition (Oracle Linux 7.9, glibc 2.17) | Replaced libstrcrypt.so with a glibc 2.17 build of the same source and ran the remaining commands |
In both environments, steps 1 to 7, the SQL function and batch examples (a 250-row round trip), and rejection of calls from users without a grant were confirmed (2026-09-17).