SOFTWARE
Oracle 사용법
Oracle에서 SQL 함수로 암복호화합니다. 여러 행은 STRCRYPT_BULK 패키지를 써서 청크마다 외부 호출 한 번으로 처리합니다.
시스템 요구 사항
- Oracle Database
- 19c, 26ai에서 시험했습니다. 공개 배포 파일은 Oracle Linux 8 이상(glibc 2.28 이상)에서만 로드됩니다. 단일 인스턴스, 전용 extproc 에이전트 구성입니다.
- DB 설정
- 문자집합 AL32UTF8, MAX_STRING_SIZE=STANDARD
- DB 서버 OS
- Linux x86_64, glibc 2.28 이상 (예: Oracle Linux 8 이상). 배포 파일은 glibc 2.17인 Oracle Linux 7에서 로드되지 않습니다.
- 빌드 도구
- gcc, make, curl, tar, sha256sum(coreutils 8.25 이상), ORACLE_HOME 아래 oci.h
- 권한
- DB 서버의 oracle OS 계정과 sudo, DB의 SYS(AS SYSDBA)
RAC, 멀티스레드 extproc 에이전트(agtctl), MAX_STRING_SIZE=EXTENDED 구성은 시험하지 않았습니다.
실행 위치
| 단계 | 실행 위치 | 계정 |
|---|---|---|
| 1. 사전 점검 ~ 4. extproc.ora 설정 | DB 서버 셸 | oracle OS 계정 (ORACLE_HOME이 잡힌 로그인 셸, sudo 사용) |
| 5. SQL 객체 설치 | DB 서버 셸에서 sqlplus | SYS AS SYSDBA, 애플리케이션 테이블이 있는 PDB에 접속 |
| 6. 실행 권한 정리 | 5단계와 같은 sqlplus | SYS AS SYSDBA, 같은 PDB |
| 7. 설치 확인 | sqlplus 등 SQL 클라이언트 | 암복호화를 쓸 애플리케이션 사용자, 같은 PDB |
Oracle은 C 래퍼 liboracle_strcrypt.so를 외부 프로시저(extproc)로 불러오고, 래퍼가 libstrcrypt.so를 호출합니다.
extproc는 DB 서버에서 oracle 계정으로 실행되므로 라이브러리, 키 파일, 설정은 모두 DB 서버에 둡니다. 애플리케이션 서버에는 따로 설치할 것이 없습니다.
1. 사전 점검
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' sudo su - oracle로 전환한 셸에서 실행합니다. 마지막 줄은 서비스 목록이며, 5단계에서 쓸 PDB 서비스명(예: orclpdb1)을 여기서 확인합니다.
command -v는 설치된 도구만 출력합니다.
자주 발생하는 오류
| 증상 | 원인과 조치 |
|---|---|
| ORACLE_HOME이 빈 줄로 나옴 | oracle 계정의 로그인 셸이 아닙니다. sudo su - oracle로 전환합니다. |
| ldd (GNU libc) 2.17 등 2.28 미만 | 배포 파일을 쓸 수 없는 서버입니다(Oracle Linux 7 등). |
| oci.h: No such file or directory | DB 소프트웨어 전체 설치나 Instant Client SDK가 필요합니다. |
| command -v 결과에 gcc, make가 없음 | 2단계 앞에서 빌드 도구를 설치합니다. |
2. 라이브러리 빌드와 설치
빌드 도구가 없을 때만
sudo dnf install -y gcc make다운로드·검증·빌드·설치
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
래퍼는 DB 서버의 Oracle 헤더(oci.h)로 빌드해야 하므로 소스로 제공합니다. 빌드는 ~/strcrypt-oracle 아래에서 하고, 두 라이브러리만 /usr/lib에 복사합니다.
마지막 줄이 libstrcrypt.so => /usr/lib/libstrcrypt.so를 출력하면 성공입니다.
자주 발생하는 오류
| 증상 | 원인과 조치 |
|---|---|
| make: command not found / gcc: command not found | 빌드 도구 설치 명령을 먼저 실행합니다. |
| sha256sum: unrecognized option '--ignore-missing' | coreutils가 오래된 서버입니다(Oracle Linux 7 등). 지원 대상이 아닙니다. |
| sha256sum 결과에 FAILED | 내려받는 중에 파일이 손상됐습니다. 그 파일을 다시 내려받습니다. |
| *** ORACLE_HOME is not set | ORACLE_HOME이 잡힌 oracle 계정 로그인 셸에서 실행합니다. |
| *** oci.h not found under ORACLE_HOME=... | 사전 점검의 oci.h 항목을 해결합니다. |
| warning: control reaches end of non-void function | 정상입니다. 빌드는 계속 진행됩니다. |
| 마지막 ldd 결과에 not found 또는 GLIBC_2.28 not found | 라이브러리를 찾지 못했거나 서버 glibc가 낮습니다. /usr/lib에 두 파일이 있는지, glibc 버전을 확인합니다. |
| oracle is not in the sudoers file | sudo가 붙은 줄만 root 권한이 있는 계정으로 실행합니다. |
3. 키 파일 생성
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
32바이트 키를 oracle 계정만 읽을 수 있는 파일로 만듭니다. 마지막 줄이 32 /etc/strcrypt/strcrypt.key를 출력해야 합니다.
키는 SQL 인자나 테이블로 전달하지 않습니다. 키를 잃으면 암호문을 복구할 수 없으니 키를 따로 백업해 두세요.
자주 발생하는 오류
| 증상 | 원인과 조치 |
|---|---|
| wc 결과가 32가 아님 | 키 파일이 비었거나 잘렸습니다. 세 번째 줄까지 다시 실행합니다. 빈 키 파일로 진행하면 5단계에서 ORA-20502가 납니다. |
| Permission denied | extproc는 oracle 계정으로 실행되므로 키 파일 소유자는 oracle, 권한은 600이어야 합니다. |
4. extproc.ora 설정
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 $ORACLE_HOME/hs/admin/extproc.ora 끝에 세 줄을 추가합니다. 두 번째 줄의 grep은 기존 SET EXTPROC_DLLS 줄이 있는지 확인하며, 없으면 아무것도 출력하지 않습니다.
데이터베이스가 직접 띄우는 extproc가 이 파일을 읽으므로 listener.ora는 고치지 않아도 되고 재시작도 필요 없습니다. 새로 접속한 세션부터 적용됩니다.
국내 표준 외 알고리즘을 쓰려면 SET STRCRYPT_MODE=non-approved 줄을 추가합니다.
자주 발생하는 오류
| 증상 | 원인과 조치 |
|---|---|
| grep이 기존 SET EXTPROC_DLLS 줄을 출력함 | 추가한 줄과 합쳐 한 줄로 만듭니다. 경로는 콜론(:)으로 잇습니다. 예: SET EXTPROC_DLLS=ONLY:/기존/경로.so:/usr/lib/liboracle_strcrypt.so |
| 이후 조회에서 ORA-28595: Extproc agent: Invalid DLL Path | EXTPROC_DLLS 줄이 없거나 경로가 다릅니다. |
| 이후 조회에서 ORA-20502: STRCrypt: operation failed | STRCRYPT_KEY_FILE 줄이 없거나, 키 파일이 비었거나, oracle 계정이 읽을 수 없습니다. |
5. SQL 객체 설치
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 ORCLPDB1을 1단계에서 확인한 PDB 서비스명으로 바꿉니다. 실행하면 SYS 비밀번호를 묻습니다.
install.sql은 SQL 함수 6개를, install_bulk.sql은 컬렉션 타입 4개와 STRCRYPT_BULK 패키지를 만들고 마지막에 암복호화 시험을 실행합니다.
출력에 [OK]가 두 줄씩 나오면 성공입니다. 두 스크립트는 다시 실행할 수 있습니다.
자주 발생하는 오류
| 증상 | 원인과 조치 |
|---|---|
| ORA-12514: ... Service ORCLPDB1 is not registered 등 접속 실패 | ORCLPDB1을 사전 점검의 lsnrctl 결과에 있는 PDB 서비스명으로 바꿉니다. 포트가 1521이 아니면 함께 고칩니다. |
| 설치 끝에 ORA-20502: STRCrypt: operation failed | 객체는 만들어졌고 마지막 검증에서 키를 읽지 못했습니다. 3, 4단계를 고친 뒤 스크립트를 다시 실행합니다. |
| ORA-20502: ... 암호모듈이 에러 상태입니다 | /usr/lib의 libstrcrypt.so.hmac이 libstrcrypt.so와 짝이 맞지 않습니다. 같은 배포본에서 받은 두 파일을 함께 둡니다. |
| ORA-06520: PL/SQL: Error loading external library | 2단계 마지막 ldd 결과를 확인합니다. |
| 애플리케이션 사용자에게 ORA-00904: "STRCRYPT_ENCRYPT": invalid identifier | CDB 서비스명(CDB$ROOT)에 설치했거나 다른 PDB에 설치했습니다. 애플리케이션이 접속하는 PDB에 다시 설치합니다. |
6. 실행 권한 정리
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;
/
설치 스크립트는 모든 사용자(PUBLIC)에게 실행 권한을 줍니다. 그대로 두면 DB에 접속하는 누구나 복호화할 수 있으므로,
5단계와 같은 방식으로 SYS로 접속해 이 블록을 실행합니다. APP_USER를 암복호화를 쓸 사용자 이름으로 바꾸고, 사용자가 여럿이면 그 줄을 늘립니다. 이 블록은 다시 실행할 수 있습니다.
자주 발생하는 오류
| 증상 | 원인과 조치 |
|---|---|
| ORA-01917: user or role does not exist | APP_USER를 실제 사용자 이름으로 바꿉니다. |
| 허용하지 않은 사용자의 ORA-00904: "STRCRYPT_ENCRYPT": invalid identifier | 정상입니다. PUBLIC 권한이 회수됐습니다. |
7. 설치 확인
문자집합과 기본 암복호화
SELECT value FROM nls_database_parameters WHERE parameter = 'NLS_CHARACTERSET';
SELECT STRCRYPT_DECRYPT(STRCRYPT_ENCRYPT('test')) FROM DUAL;배치 처리 왕복 시험 (1,000행, 시험 테이블은 끝에서 삭제)
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;
애플리케이션 사용자로 같은 PDB에 접속해 실행합니다. 첫 조회는 AL32UTF8, 두 번째는 test,
왕복 시험은 CHECKED 1000, MISMATCHES 0이 나와야 합니다. 왕복 시험에는 CREATE TABLE 권한이 필요합니다.
자주 발생하는 오류
| 증상 | 원인과 조치 |
|---|---|
| 문자집합이 AL32UTF8이 아님 | 지원하지 않는 구성입니다. 한글 등 비 ASCII 값이 실패하거나 깨져 저장됩니다. |
| ORA-00904: "STRCRYPT_ENCRYPT": invalid identifier | 6단계의 권한 부여 대상이 이 사용자인지, 같은 PDB인지 확인합니다. |
| MISMATCHES가 0이 아님 | 설치 문제가 아니라 데이터 문제일 가능성이 큽니다. 문자집합을 다시 확인합니다. |
SQL 함수
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;SELECT, INSERT, UPDATE 어디서나 일반 함수처럼 씁니다. 행마다 extproc를 한 번씩 호출하므로 조회 행이 많으면 아래 배치 처리를 씁니다. 입력이 NULL이면 결과도 NULL입니다.
암호문은 같은 값이어도 매번 달라서 인덱스로 찾을 수 없습니다. 같은 값 검색이 필요하면 STRCRYPT_SEARCH_INDEX(값, '테이블.열')
결과를 검색용 컬럼에 저장하고 인덱스를 겁니다. 조회할 때도 같은 함수로 검색어의 인덱스를 만들어 비교합니다. 키 없는 SHA-256 해시는 쓰지 않습니다.
주민번호처럼 추측할 수 있는 값은 후보를 모두 해시해 보면 원래 값이 나옵니다. 검색용 컬럼은 같은 값을 가진 행이 어느 것인지 드러내므로 필요한 컬럼에만 두고,
검색 인덱스 함수 실행 권한은 복호화 권한처럼 필요한 사용자에게만 줍니다.
배치 처리
여러 행 조회하며 복호화
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와 encrypt_rows는 커서를 청크(기본 100행)로 읽어 청크마다 extproc를 한 번 호출합니다.
커서는 키, 값, AAD(RAW) 3열을 이 순서로 내야 하며, AAD가 없으면 NULL을 넘깁니다.
기존 컬럼 일괄 암호화
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;
/
기본 키 순서로 청크를 모아 encrypt_tab을 한 번 호출하고, 기본 키 조건으로 반영한 뒤 청크마다 커밋합니다.
커서를 연 채 커밋하면 ORA-01555가 날 수 있어 청크마다 새로 조회합니다. 중간에 실패하면 이미 커밋한 청크는 남으므로 마지막 기본 키부터 다시 실행합니다.
복호화 결과를 다른 테이블로 적재
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;
파이프라인 함수 안에서는 라이브러리 스레드를 1개로 두고, 병렬은 Oracle 병렬 실행이 맡습니다.
encrypt_tab과 decrypt_tab의 p_threads는 기본값 1을 권장합니다. 1KB 급 레코드의 대량 UPDATE에서는 1보다 큰 값이 오히려 느렸습니다.
제한 사항
- 결과는 VARCHAR2 4,000바이트 안에 들어가야 하며, 넘으면 ORA-20503이 납니다. 평문 상한은 ARIA-256-GCM 기준 2,971바이트입니다.
- 배치 호출에서 한 건이라도 실패하면 그 호출 전체가 실패합니다(ORA-20502). 실패한 건만 NULL로 채우지 않습니다.
- 값에 NUL(
CHR(0))이 들어 있으면STRCRYPT_ENCRYPT·STRCRYPT_ENCRYPT_ALGO는 ORA-20502로 거부합니다. - 같은 값도 암호화할 때마다 암호문이 달라지므로 암호문 컬럼으로는 검색하거나 인덱스를 만들 수 없습니다. 검색이 필요하면 검색 인덱스(
STRCRYPT_SEARCH_INDEX) 컬럼을 따로 둡니다. 키 없는 해시 컬럼은 쓰지 않습니다. - 평문은 DB 서버의 extproc 프로세스 메모리를 거칩니다.
API 레퍼런스
| 함수·프로시저 | 설명 |
|---|---|
STRCRYPT_ENCRYPT(input_text) / STRCRYPT_DECRYPT(encrypted_text) | 문자열 암복호화. 복호화는 알고리즘을 자동으로 인식 |
STRCRYPT_ENCRYPT_ALGO(input_text, algorithm) / STRCRYPT_DECRYPT_ALGO(encrypted_text, algorithm) | 알고리즘을 지정해 암복호화. 복호화의 algorithm은 NULL이면 자동 인식 |
STRCRYPT_SEARCH_INDEX(p_value, p_domain) | 같은 값 검색용 인덱스(16진 64자). p_domain은 열을 구분하는 문자열(예: customers.ssn). Python·Node.js·Java 바인딩과 같은 값 |
STRCRYPT_ALGORITHMS() / STRCRYPT_HW_INFO() | 사용 가능한 알고리즘 목록, 하드웨어 가속 정보(JSON 문자열) |
STRCRYPT_BULK.encrypt_tab(p_in, p_algorithm, p_out, p_aads, p_threads) | 평문 컬렉션(STRCRYPT_VC_TAB)을 한 번에 암호화 |
STRCRYPT_BULK.decrypt_tab(p_in, p_out, p_aads, p_threads) | 암호문 컬렉션을 한 번에 복호화 |
STRCRYPT_BULK.encrypt_rows(p_cur, p_algorithm, p_chunk) | 커서를 청크로 읽어 (키, 암호문) 행을 내보내는 파이프라인 함수 |
STRCRYPT_BULK.decrypt_rows(p_cur, p_chunk) | 커서를 청크로 읽어 (키, 평문) 행을 내보내는 파이프라인 함수 |
시험 환경
| 환경 | 방법 |
|---|---|
| Oracle AI Database 26ai Free (Oracle Linux 8.10, glibc 2.28) | 이 페이지의 배포 파일과 명령을 그대로 실행 |
| Oracle Database 19c Enterprise Edition (Oracle Linux 7.9, glibc 2.17) | 같은 소스를 glibc 2.17용으로 빌드한 libstrcrypt.so로 바꿔 나머지 명령을 실행 |
두 환경 모두 1~7단계, SQL 함수와 배치 처리 예시(250행 왕복), 권한 회수 후 허용하지 않은 사용자의 호출 거부를 확인했습니다(2026-09-17).