dbms_crypto是Oracle 10g开始内置的加密包,位于SYS用户下,封装了对称加密、哈希计算、随机数生成等常用密码学功能。对于存储身份证号、手机号、银行卡这类敏感字段的系统来说,用它在数据库层面做加密是一种成本很低的方案,不需要改动应用架构,写几个PL/SQL函数就能覆盖增删改查的加密解密需求。本文从包的授权开始,逐步演示加密、解密、哈希的完整用法。

一、开启dbms_crypto的使用权限
dbms_crypto默认只有SYS用户可以使用,普通业务用户第一次调用时会报PLS-00201错误,提示标识符DBMS_CRYPTO必须声明。解决办法是用SYSDBA登录后给业务用户授权:
-- 以SYSDBA身份执行 SQL> conn / as sysdba SQL> GRANT EXECUTE ON dbms_crypto TO your_user;
授权完成后建议先跑一个简单的验证,确认包可以正常访问。需要注意的是,dbms_crypto只在Oracle 10g及以后版本提供,9i以前的版本对应的是dbms_obfuscation_toolkit,那个包只支持DES且密钥长度受限,如果还在用老版本建议评估升级。
验证方式很简单,直接查询加密算法常量是否可见:
SELECT dbms_crypto.ENCRYPT_AES256 FROM dual; -- 返回数字即说明包可用
二、加密与解密函数的编写
dbms_crypto的核心入口是dbms_crypto.encrypt和dbms_crypto.decrypt两个函数,两者参数一致:src是RAW类型的原文,typ指定加密算法和填充模式的组合,key是密钥,iv是初始向量。typ参数并不是随便填的,它等于算法常量加链接模式常量加填充常量之和,例如AES256加密、CBC模式、PKCS5填充就写成dbms_crypto.ENCRYPT_AES256 + dbms_crypto.CHAIN_CBC + dbms_crypto.PAD_PKCS5。
下面是一套完整的加密解密函数,可以直接在业务用户下创建:
CREATE OR REPLACE PACKAGE pkg_crypto AS
FUNCTION encrypt_data(p_plain IN VARCHAR2,
p_key IN VARCHAR2) RETURN RAW;
FUNCTION decrypt_data(p_cipher IN RAW,
p_key IN VARCHAR2) RETURN VARCHAR2;
END pkg_crypto;
/
CREATE OR REPLACE PACKAGE BODY pkg_crypto AS
-- 加密:AES256 + CBC + PKCS5
FUNCTION encrypt_data(p_plain IN VARCHAR2,
p_key IN VARCHAR2) RETURN RAW IS
v_key RAW(32);
v_typ PLS_INTEGER;
v_enc RAW(2048);
BEGIN
v_key := utl_raw.cast_to_raw(p_key);
-- 密钥长度必须是32字节,不足补齐
v_key := rpad(v_key, 64, '0');
v_typ := dbms_crypto.ENCRYPT_AES256
+ dbms_crypto.CHAIN_CBC
+ dbms_crypto.PAD_PKCS5;
v_enc := dbms_crypto.encrypt(src => utl_raw.cast_to_raw(p_plain),
typ => v_typ,
key => v_key,
iv => NULL);
RETURN v_enc;
END;
FUNCTION decrypt_data(p_cipher IN RAW,
p_key IN VARCHAR2) RETURN VARCHAR2 IS
v_key RAW(32);
v_typ PLS_INTEGER;
v_dec RAW(2048);
BEGIN
v_key := rpad(utl_raw.cast_to_raw(p_key), 64, '0');
v_typ := dbms_crypto.ENCRYPT_AES256
+ dbms_crypto.CHAIN_CBC
+ dbms_crypto.PAD_PKCS5;
v_dec := dbms_crypto.decrypt(src => p_cipher,
typ => v_typ,
key => v_key,
iv => NULL);
RETURN utl_raw.cast_to_varchar2(v_dec);
END;
END pkg_crypto;
/调用测试:先加密再解密,如果返回原文说明整条链路通了。
DECLARE
v_raw RAW(2048);
v_txt VARCHAR2(200);
BEGIN
v_raw := pkg_crypto.encrypt_data('测试数据13800138000', 'mySecretKey');
dbms_output.put_line('密文:' || rawtohex(v_raw));
v_txt := pkg_crypto.decrypt_data(v_raw, 'mySecretKey');
dbms_output.put_line('明文:' || v_txt);
END;
/这里有个细节要说明:iv参数传NULL时,Oracle会使用默认的全零初始向量。生产环境更严谨的做法是用dbms_crypto.randombytes生成随机iv,并把iv与密文拼接存库,解密时再拆出来,这样同样的明文每次加密出来的密文都不同,能防止模式分析攻击。
三、算法选择与常见坑
dbms_crypto支持的算法常量包括ENCRYPT_DES、ENCRYPT_3DES_2KEY、ENCRYPT_AES128、ENCRYPT_AES192、ENCRYPT_AES256等。DES因为密钥只有56位,已经不安全,新系统一律不要用;3DES安全性尚可但性能只有AES的一半左右;没有特殊兼容要求时首选AES,密钥长度选128或256均可。链接模式推荐CBC,填充模式用PKCS5,这是最稳妥的组合。
密钥长度必须严格匹配:AES256要求32字节,AES128要求16字节。utl_raw.cast_to_raw是按字符ASCII码逐个转字节的,一个字符对应两个十六进制位,所以32字节的密钥需要64位长度的十六进制串。上面例子中用rpad补齐是一种偷懒做法,更规范的方式是对密钥先做SHA256哈希,天然得到32字节的定长结果:
v_key := dbms_crypto.hash(src => utl_raw.cast_to_raw(p_key),
typ => dbms_crypto.HASH_SH256);中文乱码是最常见的坑。因为函数内部以RAW字节流处理数据,utl_raw.cast_to_varchar2返回的结果取决于数据库字符集。如果数据库是GBK而客户端是UTF8,直接存储解密结果可能乱码。稳妥的处理是用utl_i18n.string_to_raw显式指定字符集:
-- 加密前:指定UTF8编码转RAW v_src := utl_i18n.string_to_raw(p_plain, 'AL32UTF8'); -- 解密后:RAW转回UTF8字符串 v_txt := utl_i18n.raw_to_char(v_dec, 'AL32UTF8');
最后提醒两点:一是密文必须用RAW类型或转成十六进制字符串存储,字段长度要留足,AES加密后长度大约是明文字节数向上取整到16的倍数再加16;二是密钥不要硬编码在包体里,建议配合Oracle Wallet或者独立密钥表加权限控制,否则拿到代码的人等于拿到密钥,加密形同虚设。
四、哈希与随机数的补充用法
除了对称加密,dbms_crypto还提供哈希和MAC计算,常用于密码存储和数据完整性校验。密码存储推荐SHA256起步,且不要只对明文做一次哈希,应加盐处理:
DECLARE
v_salt RAW(16);
v_hash RAW(32);
BEGIN
v_salt := dbms_crypto.randombytes(16); -- 生成16字节随机盐
v_hash := dbms_crypto.hash(
src => utl_raw.concat(v_salt, utl_raw.cast_to_raw('用户密码')),
typ => dbms_crypto.HASH_SH256);
dbms_output.put_line('盐:' || rawtohex(v_salt));
dbms_output.put_line('加盐哈希:' || rawtohex(v_hash));
END;
/dbms_crypto.mac则是在哈希基础上引入密钥的HMAC实现,适合接口签名场景,即使盐值泄露,没有密钥也难以伪造。另外dbms_crypto.randombytes和dbms_crypto.randominteger可以用来生成随机数据,比SQL里的dbms_random质量更高,生成验证码、临时令牌时优先用它。
整体来说,dbms_crypto的API设计比较底层,直接调用容易在字符集和密钥长度上踩坑,但只要把这些细节封装成规范的包,再配合触发器或视图做透明加解密,就能以很小的改造成本满足绝大多数合规审计要求。
Oracle dbms_crypto加密解密PL/SQL加密函数修改时间:2026-09-15 15:03:40