使用DBMS_CRYPTO AES算法实现固定长度加密的技术咨询
Great question! Let's walk through how to implement fixed-length, reversible encryption for arbitrary input lengths using Oracle's DBMS_CRYPTO and AES—exactly what you're asking for.
Problem Breakdown
First, why can't you just encrypt directly? AES is a block cipher, meaning it processes data in fixed 16-byte chunks. When your input length isn't a multiple of 16 bytes, padding is added. This means different input lengths will produce different-length ciphertexts. To get a fixed output length while keeping decryption possible, we need two key steps:
- Convert any input into a fixed-length, reversible byte sequence (no one-way hashing—we need to get the original data back!)
- Encrypt that fixed-length sequence with AES, then encode it to a consistent-length string.
Core Approach
We'll use:
- Lossless compression (Oracle's
UTL_COMPRESS) to shrink arbitrary input text into a smaller byte stream, then pad it to a fixed buffer size. - AES-256-CBC (secure, widely supported) with a random IV (initialization vector) to encrypt the fixed buffer.
- Base64 encoding to convert the combined IV + ciphertext into a fixed-length string.
Step-by-Step Implementation
All code uses Oracle's built-in packages—no external dependencies needed.
1. Define Configuration & Helper Functions
First, set constants for your encryption setup, plus helper functions to handle compression/padding:
-- Configuration constants (tweak these for your business needs) DEFINE AES_KEY_SIZE = 256; -- 256-bit key = 32 bytes DEFINE FIXED_PLAINTEXT_BUFFER = 128; -- Fixed size for compressed input (bytes) DEFINE AES_BLOCK_SIZE = 16; -- AES standard block size (never change this) -- Secure key retrieval (store keys in Oracle Wallet, NOT hardcoded!) CREATE OR REPLACE FUNCTION get_aes_secret RETURN RAW IS BEGIN -- Replace with logic to fetch from Wallet/encrypted config table RETURN UTL_RAW.CAST_TO_RAW('your-32-byte-secret-key-here-12345678'); END; / -- Compress input and pad to fixed buffer length (reversible) CREATE OR REPLACE FUNCTION compress_to_fixed_buffer(p_input IN CLOB) RETURN RAW IS l_compressed RAW; l_padded_raw RAW; BEGIN -- Compress input text to bytes l_compressed := UTL_COMPRESS.lz_compress(UTL_RAW.CAST_TO_RAW(p_input)); -- Validate compressed size fits our fixed buffer IF UTL_RAW.LENGTH(l_compressed) > FIXED_PLAINTEXT_BUFFER THEN RAISE_APPLICATION_ERROR(-20001, 'Input too large—compressed data exceeds fixed buffer limit'); END IF; -- Pad with null bytes to reach fixed length l_padded_raw := UTL_RAW.CONCAT( l_compressed, UTL_RAW.COPIES(UTL_RAW.CAST_TO_RAW(CHR(0)), FIXED_PLAINTEXT_BUFFER - UTL_RAW.LENGTH(l_compressed)) ); RETURN l_padded_raw; END; / -- Reverse the padding/compression to get original input CREATE OR REPLACE FUNCTION decompress_from_fixed_buffer(p_padded_raw IN RAW) RETURN CLOB IS l_compressed RAW; BEGIN -- Strip trailing null padding l_compressed := RTRIM(p_padded_raw, UTL_RAW.CAST_TO_RAW(CHR(0))); -- Decompress back to text RETURN UTL_RAW.CAST_TO_CLOB(UTL_COMPRESS.lz_uncompress(l_compressed)); END; /
2. Fixed-Length Encryption Function
This function takes any input, processes it, and returns a fixed-length Base64 string:
CREATE OR REPLACE FUNCTION fixed_len_aes_encrypt(p_input IN CLOB) RETURN VARCHAR2 IS l_key RAW(32) := get_aes_secret(); l_iv RAW(16) := DBMS_CRYPTO.RANDOMBYTES(AES_BLOCK_SIZE); -- Random IV for security l_fixed_plaintext RAW(128) := compress_to_fixed_buffer(p_input); l_ciphertext RAW; l_full_payload RAW; BEGIN -- AES-256-CBC encryption with PKCS5 padding (Oracle's default) l_ciphertext := DBMS_CRYPTO.ENCRYPT( src => l_fixed_plaintext, typ => DBMS_CRYPTO.ENCRYPT_AES256 + DBMS_CRYPTO.CHAIN_CBC + DBMS_CRYPTO.PAD_PKCS5, key => l_key, iv => l_iv ); -- Combine IV + ciphertext (fixed total bytes: 16 + 128 = 144) l_full_payload := UTL_RAW.CONCAT(l_iv, l_ciphertext); -- Convert to Base64 (144 bytes = 192 characters, fixed length!) RETURN UTL_ENCODE.base64_encode(l_full_payload); END; /
3. Decryption Function
Reverse the process to get back the original input:
CREATE OR REPLACE FUNCTION fixed_len_aes_decrypt(p_input IN VARCHAR2) RETURN CLOB IS l_key RAW(32) := get_aes_secret(); l_full_payload RAW; l_iv RAW(16); l_ciphertext RAW(128); l_fixed_plaintext RAW(128); BEGIN -- Decode Base64 back to raw bytes l_full_payload := UTL_ENCODE.base64_decode(p_input); -- Split IV and ciphertext from the payload l_iv := UTL_RAW.SUBSTR(l_full_payload, 1, AES_BLOCK_SIZE); l_ciphertext := UTL_RAW.SUBSTR(l_full_payload, AES_BLOCK_SIZE + 1, FIXED_PLAINTEXT_BUFFER); -- AES decryption l_fixed_plaintext := DBMS_CRYPTO.DECRYPT( src => l_ciphertext, typ => DBMS_CRYPTO.ENCRYPT_AES256 + DBMS_CRYPTO.CHAIN_CBC + DBMS_CRYPTO.PAD_PKCS5, key => l_key, iv => l_iv ); -- Decompress and return original text RETURN decompress_from_fixed_buffer(l_fixed_plaintext); END; /
Critical Notes
- Key Security: Never hardcode your AES key. Use Oracle Wallet or an encrypted configuration table to store and retrieve keys securely.
- Input Limits: Adjust
FIXED_PLAINTEXT_BUFFERbased on your maximum expected input size (after compression). If inputs are too large, the function will throw an error—this is intentional to prevent buffer overflow. - Security Upgrade: For better tamper protection, switch to AES-GCM mode (authenticated encryption). Just replace the
typparameter inDBMS_CRYPTO.ENCRYPT/DECRYPTwithDBMS_CRYPTO.ENCRYPT_AES256 + DBMS_CRYPTO.CHAIN_GCM(no padding needed for GCM). - Encoding Choice: If you prefer hexadecimal over Base64, replace
UTL_ENCODE.base64_encode/decodewithRAWTOHEX/HEXTORAW—this will give a fixed 288-character string instead of 192.
内容的提问来源于stack exchange,提问作者Daicy Angelino

