I am trying to use DBMS_CRYPTO encrypt, but no matter how I choose some of the parameters, I encounter values for which the code throws
ORA-01890: NLS error detected ORA-06512: at "SYS.UTL_I18N", line 72 ORA-06512: at "SYS.UTL_I18N", line 353 ORA-06512: at line 26
Here is an example
declare
-- v_value VARCHAR2 (4000) := '9Ab2Ov1Bd4'; -- works
-- v_value VARCHAR2 (4000) := '7Md4Mt7Gk0'; -- works
-- v_value VARCHAR2 (4000) := '3Vf8Fi2Pa5'; -- works
-- v_value VARCHAR2(4000) := '5Vq2Dc4Cq9'; -- works as well
v_value VARCHAR2(4000) := '2Cq0Yh3Vb2'; --this does not work?
v_result VARCHAR2 (4000);
v_raw_row RAW (2000); -- stores ec binary text
v_enc_type PLS_INTEGER
:=
DBMS_CRYPTO.ENCRYPT_AES256 +
DBMS_CRYPTO.CHAIN_CBC +
DBMS_CRYPTO.PAD_ZERO; -- total encryption type
v_def_k VARCHAR2 (32) := 'mMbSmSMr_!_uAlns9asG5_a_AfhS4_3a';
begin
v_raw_row :=
DBMS_CRYPTO.ENCRYPT (
src => UTL_I18N.STRING_TO_RAW (v_value, 'AL32UTF8'),
typ => v_enc_type,
key => UTL_RAW.CAST_TO_RAW (v_def_k));
v_result := UTL_I18N.RAW_TO_CHAR (v_raw_row, 'AL32UTF8');
dbms_output.put_line(v_result);
end;
Changing the encryption type and key "solves" the issue for the one value which does not work, but it I always encounter another value which will then throw this exact exception.
I tried this in Oracle 12.2.0.1.0 as well as in 19.0.0.0.0. Exactly the same behaviour.
I guess that something I am doing is completely wrong. Any help is appreciated.
Instead of AL32UTF8 as source character set, can you please try using WE8ISO8859P1 or null.
From Oracle doc:
UTL_I18N.RAW_TO_CHAR is a function that converts raw to char data type conversion can be implemented, but not different character sets can be converted.
The second parameter should specify which characteret is passed by the raw data, ie the source character set.
Specify the character set that the RAW data was derived from. If src_charset is NULL, then the database character set is used.
so if using DB characterset or NULL, no error will be reported.
I am facing raw variable length too long issue when select the BLOB(Stored JSON string) filed value from the table.
Query:
select utl_raw.cast_to_varchar2(dbms_lob.substr(TRANSACTION_DATA)) from PS_ISA_INB_PAYLOAD_LOG;
This is my SP i have used to insert the JSON object into BLOB Field:
create or replace PROCEDURE SDIX_TICK_LOG
(
ORGANIZATIONNAME IN VARCHAR2
, TRANSACTION_TYPE IN VARCHAR2
, TRANSACTION_DATA IN BLOB
, TRANSACTION_STATUS IN VARCHAR2
) AS
l_r RAW(32767);
l_blob blob;
l_clob clob :='"ItemMasterTransfer":[{"ORACLE_UNIQUE_REC_ID":"123assd4434","CUSTOMER_ID":"PMC","ORGANIZATION_CODE":"BMftrdsM","ITEM":"696738","INVENTORY_ITEM_ID":"0000000000000000000000000000546","ORGANIZATION_ID":" ","SUBINVENTORY_CODE":"000000000000000000","LOCATOR_ID":" ","ISA_MATERIAL_GROUP":" ","TAX_GROUP":" ","CATEGORY_ID":"1956","NOUN":" ","MODIFIER":" ","MANUFACTURER_ID":" ","MFG_ITEM_ID":" ","UNIT_OF_MEASURE":"BOX","ITEM_TYPE":"P","STOCK_ENABLED_FLAG":"Y","INVENTORY_ITEM_STATUS_CODE":"A","LIST_PRICE_PER_UNIT":"0","FULL_LEAD_TIME":"0","MAX_MINMAX_QUANTITY":"10","MIN_MINMAX_QUANTITY":"10","SAFETY_LEVEL":"0","REPLENISH_TO_ORDER_FLAG":"N","UTILIZ_CD":"","CURRENCY_CD":"USD","DESCRIPTION":"","ATTRIBUTE1":" ","ATTRIBUTE2":" ","ATTRIBUTE3":" ","ATTRIBUTE4":" ","ATTRIBUTE5":" ","ATTRIBUTE6":" ","ATTRIBUTE7":" ","ATTRIBUTE8":" ","ATTRIBUTE9":" ","ATTRIBUTE10":" ","TRANSACTION_STATUS":" ","TRANS_STATUS_DESCRIPTION":" "},{"ORACLE_UNIQUE_REC_ID":"123assd4434","CUSTOMER_ID":"PMC","ORGANIZATION_CODE":"BMftrdsM","ITEM":"696738","INVENTORY_ITEM_ID":"0000000000000000000000000000546","ORGANIZATION_ID":" ","SUBINVENTORY_CODE":"000000000000000000","LOCATOR_ID":" ","ISA_MATERIAL_GROUP":" ","TAX_GROUP":" ","CATEGORY_ID":"1956","NOUN":" ","MODIFIER":" ","MANUFACTURER_ID":" ","MFG_ITEM_ID":" ","UNIT_OF_MEASURE":"BOX","ITEM_TYPE":"P","STOCK_ENABLED_FLAG":"Y","INVENTORY_ITEM_STATUS_CODE":"A","LIST_PRICE_PER_UNIT":"0","FULL_LEAD_TIME":"0","MAX_MINMAX_QUANTITY":"10","MIN_MINMAX_QUANTITY":"10","SAFETY_LEVEL":"0","REPLENISH_TO_ORDER_FLAG":"N","UTILIZ_CD":"","CURRENCY_CD":"USD","DESCRIPTION":"","ATTRIBUTE1":" ","ATTRIBUTE2":" ","ATTRIBUTE3":" ","ATTRIBUTE4":" ","ATTRIBUTE5":" ","ATTRIBUTE6":" ","ATTRIBUTE7":" ","ATTRIBUTE8":" ","ATTRIBUTE9":" ","ATTRIBUTE10":" ","TRANSACTION_STATUS":" ","TRANS_STATUS_DESCRIPTION":" "},{"ORACLE_UNIQUE_REC_ID":"123assd4434","CUSTOMER_ID":"PMC","ORGANIZATION_CODE":"BMftrdsM","ITEM":"696738","INVENTORY_ITEM_ID":"0000000000000000000000000000546","ORGANIZATION_ID":" ","SUBINVENTORY_CODE":"000000000000000000","LOCATOR_ID":" ","ISA_MATERIAL_GROUP":" ","TAX_GROUP":" ","CATEGORY_ID":"1956","NOUN":" ","MODIFIER":" ","MANUFACTURER_ID":" ","MFG_ITEM_ID":" ","UNIT_OF_MEASURE":"BOX","ITEM_TYPE":"P","STOCK_ENABLED_FLAG":"Y","INVENTORY_ITEM_STATUS_CODE":"A","LIST_PRICE_PER_UNIT":"0","FULL_LEAD_TIME":"0","MAX_MINMAX_QUANTITY":"10","MIN_MINMAX_QUANTITY":"10","SAFETY_LEVEL":"0","REPLENISH_TO_ORDER_FLAG":"N","UTILIZ_CD":"","CURRENCY_CD":"USD","DESCRIPTION":"","ATTRIBUTE1":" ","ATTRIBUTE2":" ","ATTRIBUTE3":" ","ATTRIBUTE4":" ","ATTRIBUTE5":" ","ATTRIBUTE6":" ","ATTRIBUTE7":" ","ATTRIBUTE8":" ","ATTRIBUTE9":" ","ATTRIBUTE10":" ","TRANSACTION_STATUS":" ","TRANS_STATUS_DESCRIPTION":" "}],"Organization":"PMC Biogenix","SharedSecret":"sTc1QowIu5Iy1Qt8iilnmQ==","TimeStamp":"09/28/2018 00:19:21","RowsSent":"1"}';
l_amt integer := dbms_lob.lobmaxsize;
l_dest_offset integer := 1;
l_src_offset integer := 1;
l_csid integer := dbms_lob.default_csid;
l_ctx integer := dbms_lob.default_lang_ctx;
l_warn integer;
BEGIN
dbms_lob.createTemporary( l_blob, false );
dbms_lob.convertToBlob( l_blob,
l_clob,
l_amt,
l_dest_offset,
l_src_offset,
l_csid,
l_ctx,
l_warn );
INSERT INTO PS_ISA_INB_PAYLOAD_LOG Values(ORGANIZATIONNAME,TRANSACTION_TYPE,l_blob,SYSDATE,TRANSACTION_STATUS);
END SDIX_TICK_LOG;
Your problem lies here: DBMS_LOB.SUBSTR()
DBMS_LOB is using VARCHAR2 internally, and VARCHAR2 has limit of 2000 chars. Your blob has the size of 2829 chars, therefore it is too long to be processed by DBMS_LOB.SUBSTR() at once.
You can test this by these commands:
Take only first 2000 chars from BLOB:
select utl_raw.cast_to_varchar2(dbms_lob.substr(TRANSACTION_DATA), 2000, 1) from PS_ISA_INB_PAYLOAD_LOG;
OK.
Take 2001 chars from BLOB:
select utl_raw.cast_to_varchar2(dbms_lob.substr(TRANSACTION_DATA, 2001, 1)) from PS_ISA_INB_PAYLOAD_LOG;
Error report -
SQL Error:
ORA-06502: PL/SQL: numeric or value error: raw variable length too long
ORA-06512: at line 1
06502.00000 - "PL/SQL: numeric or value error%s"
Is that possible to select entire BLOB Field value?
Basically, no. You'll need a PL/SQL function similar to the one you've already described, except this time, it will go BLOB-to-CLOB. You can call that from SQL, if it returns a CLOB or a VARCHAR2(4000).
As a side note, I question why you're taking JSON, which is character data, and storing it as a BLOB, then wanting to get character data back. Why not just store it as a CLOB in the first place?
In a Oracle 11g database I have a table with a blob column in which I want to put encrypted datas.
I have this function in my database :
create or replace FUNCTION encryptmyBLOB(content IN BLOB, key in VARCHAR2)
RETURN BLOB AS
CRYPTED BLOB;
encryption_type PLS_INTEGER :=
SYS.DBMS_CRYPTO.ENCRYPT_AES128
+ SYS.DBMS_CRYPTO.CHAIN_CBC
+ SYS.DBMS_CRYPTO.PAD_PKCS5;
BEGIN
dbms_lob.createtemporary(CRYPTED,true);
SYS.DBMS_CRYPTO.ENCRYPT(
dst => CRYPTED,
src => content,
typ => encryption_type,
key => SYS.DBMS_CRYPTO.Hash (UTL_I18N.STRING_TO_RAW (key, 'WE8ISO8859P15'), SYS.DBMS_CRYPTO.HASH_MD5),
iv => utl_raw.cast_to_raw('/myIV'));
return CRYPTED;
end if;
END;
and my application send query with params like this :
UPDATE mytable SET myColumn=encryptmyBLOB(:SERIAL,:ENCRYPT_KEY) WHERE ...
This works well when the SERIAL param contain less than approximately 4000 bytes but when SERIAL contain more data I get a database error.
ORA-01461: can bind a LONG value only for insert into a LONG column
I don't understand what I am doing wrong. I suspect that my application Oracle driver send a LONG value in my SERIAL param instead of a BLOB value but I can't confirm it and the UPDATE is correctly done if I bypass my function.
Can anyone point me in the right direction?
Many thanks.
The error is pretty clear: You column is not a BLOB-column.
You should change the datatype to BLOB, because LONG/LONG RAW is deprecated:
https://docs.oracle.com/cd/B28359_01/server.111/b28318/datatype.htm#CNCPT613
If you can't change the type of the column, you could check the size of your BLOB 'content' within you function:
DECLARE
myBlob BLOB;
mySize NUMBER;
BEGIN
myBlob := utl_raw.cast_to_raw('1234567890');
mySize := dbms_lob.getlength(myBlob);
dbms_output.put_line('Size: ' || mySize);
myBlob := utl_raw.cast_to_raw('12345678901234567890');
mySize := dbms_lob.getlength(myBlob);
dbms_output.put_line('Size: ' || mySize);
END;
How to do for encrypting a number
column in Oracle 11 (and also be able to decrypt them back) so that the same column stores crypted numbers along with (unencrypted ones (based on a boolean saved elsewhere)?
I actually did a function like this that takes a number to produce a crypted number and it worked fine in Oracle 10 but now it doesn't work anymore in ORACLE 11:
function crypt (key varchar2, n number) return number
raw_input RAW(128);
encrypted_raw RAW(2048);
raw_key_ RAW(128) := UTL_RAW.CAST_TO_RAW(CONVERT(key,'AL32UTF8','US7ASCII'));
begin
raw_input_ := UTL_RAW.cast_from_number (n);
encrypted_raw := dbms_crypto.Encrypt(src => raw_input_, typ =>DBMS_CRYPTO.DES3_CBC_PKCS5, KEY=>raw_key_);
return UTL_RAW.cast_to_number(encrypted_raw);
end;
it throws this error with no explanations:
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at "SYS.UTL_RAW", line 388
...
Thank you
Looks like you're trying to cast the encrypted raw to a number. If I understand what you want this function to do, it seems you want to test that the input number = returned number (that you can encrypt and decrypt successfully). If you wanted to just encrypt, you would return a raw, not a number.
Anyway, you should be casting the decrypted raw value to a number to test that the input number = returned number. Something like:
create or replace function test_crypt(k varchar2, n number)
return number as
raw_input_ RAW(128);
encrypted_raw RAW(2000);
decrypted_raw RAW (2000);
raw_key_ RAW(128) := UTL_RAW.CAST_TO_RAW(k);
begin
raw_input_ := UTL_RAW.cast_from_number (n);
encrypted_raw := dbms_crypto.Encrypt(src => raw_input_, typ =>DBMS_CRYPTO.DES3_CBC_PKCS5, KEY=>raw_key_);
--return UTL_RAW.cast_to_number(encrypted_raw);
-- use dbms_crypto to decrypt and return (hopefully same) number
decrypted_raw := DBMS_CRYPTO.DECRYPT
(
src => encrypted_raw,
typ => DBMS_CRYPTO.DES3_CBC_PKCS5,
key => raw_key_
);
return UTL_RAW.cast_to_number(decrypted_raw);
end;
On my system anyway, the input number = returned number (key is from dbms_crypto.randombytes(32) ). For example:
select test_crypt('50610FB89D98C7D906CB0A9917413221E4FE6FA62A9604302EE2C8F63E6BAD91', 234.21) from dual;
Output:
234.21
Due to previously poorly designed structure, the current database that I have to work with stores users' password as text.
Now, I am building a front end part that has to use those passwords and I certainly don't want to be sending passwords unencrypted.
My idea is to write an Oracle function to encrypt and decrypt text password and use those functions in the stored procedures that will return encrypted data.
What would be the best approach in Oracle to do so?
If you want to write your own functions to encrypt and decrypt data, you would simply want to call the DBMS_CRYPTO encrypt and decrypt methods with appropriate parameters (i.e. pick your encryption algorithm, your key, etc.).
Of course, if you write your own routines, assuming that you store the key in the database or somewhere the database has access to, you're not doing much for security. It's bad to send passwords unencrypted over the network but it is generally much worse to store unencrypted passwords in the database (or encrypted passwords if there is a decrypt method in the database that has access to the key to decrypt the data). It's generally a lot easier to steal data from a database than it is to sniff data getting sent over the network in order to find a password.
The right answer, of course, would be to rearchitect the system so that you don't store the passwords at all. You should be storing password hashes (which you can also generate using the DBMS_CRYPTO package) which are non-reversible.
Take a look at DBMS_CRYPTO
It has methods to encrypt and decrypt data built in. Better than writing your own.
http://docs.oracle.com/cd/B19306_01/appdev.102/b14258/d_crypto.htm
Or you could use the password hashing algorithm pbkdf2 like this http://mikepargeter.wordpress.com/2012/11/26/pbkdf2-in-oracle/
I verified the outcome with the rfc https://www.ietf.org/rfc/rfc6070.txt and it works fine.
Check this link for minimal iterations and key_size: PBKDF2 recommended key size?
Note that the result is double the length, because it is hex-encoded.
we store these columns in the db
PASSWORD_HASH VARCHAR2(512 BYTE),
PASSWORD_SALT VARCHAR2(256 BYTE),
PASSWORD_ITERATIONS NUMBER(10),
PASSWORD_HASH_METHOD VARCHAR2(30 BYTE),
PASSWORD_CHANGED_DT DATE
the hash, salt and iterations are to feed the pbkdf2 algorithm
the hash_method is for migration purposes
and the changed_dt is to expire passwords
Here's a packaged function (successful implementation) for encrypting passwords using (DBMS_CRYPTO)-
CREATE OR REPLACE
PACKAGE BODY encrypt_paswd
AS
G_CHARACTER_SET VARCHAR2(10) := 'AL32UTF8';
G_STRING VARCHAR2(32) := '12345678901234567890123456789012';
G_KEY RAW(250) := utl_i18n.string_to_raw
( data => G_STRING,
dst_charset => G_CHARACTER_SET );
G_ENCRYPTION_TYPE PLS_INTEGER := dbms_crypto.encrypt_aes256
+ dbms_crypto.chain_cbc
+ dbms_crypto.pad_pkcs5;
------------------------------------------------------------------------
--Encrypt a password
--Salt the password
------------------------------------------------------------------------
FUNCTION encrypt_val( p_val IN VARCHAR2 ) RETURN RAW
IS
l_val RAW(32) := UTL_I18N.STRING_TO_RAW( p_val, G_CHARACTER_SET );
l_encrypted RAW(32);
BEGIN
l_val := utl_i18n.string_to_raw
( data => p_val,
dst_charset => G_CHARACTER_SET );
l_encrypted := dbms_crypto.encrypt
( src => l_val,
typ => G_ENCRYPTION_TYPE,
key => G_KEY );
RETURN l_encrypted;
END encrypt_val;
END encrypt_paswd;
This uses encrypt_aes256 -"Advanced Encryption Standard. Block cipher. Uses 256-bit key size." , chain_cbc- "Cipher Block Chaining. Plaintext is XORed with the previous ciphertext block before it is encrypted." and pad_pkcs5 - "Provides padding which complies with the PKCS #5: Password-Based Cryptography Standard".
In addition to this You can create a similar function to decrypt. like -
FUNCTION decrypt_val( p_val IN RAW ) RETURN VARCHAR2
IS
l_decrypted RAW(32);
l_decrypted_string VARCHAR2(32);
l_user VARCHAR2(32);
BEGIN
SELECT user
INTO l_user
FROM dual;
if l_user = 'ADMIN' -- you can restrict usage of decrypt to certain db users only.
then
l_decrypted := dbms_crypto.decrypt
( src => p_val,
typ => G_ENCRYPTION_TYPE,
key => G_KEY );
l_decrypted_string := utl_i18n.raw_to_char
( data => l_decrypted,
src_charset => G_CHARACTER_SET );
RETURN l_decrypted_string;
else
RAISE_APPLICATION_ERROR(-20101, 'You are not authorized to use this function - decrypt_val()');
end if;
RETURN 'Unknown';
END decrypt_val;
You may also consider wrapping the package before compiling it in the database using wrap iname=package_name.pkb and then compiling the resulting plb.
user this code definitely work
create or replace PACKAGE "PKG_LOGI_PWD_REG"
AS
function ENCRYPT_VAL( P_VAL in varchar2 ) return varchar2;
function DECRYPT_VAL( P_VAL in raw ) return varchar2;
end;
/
create or replace PACKAGE BODY "PKG_LOGI_PWD_REG"
as
FUNCTION decrypt_val( p_val IN RAW ) RETURN VARCHAR2
IS
l_decrypted RAW(32);
l_decrypted_string VARCHAR2(32);
L_USER varchar2(32);
L_CHARACTER_SET varchar2(10);
L_STRING varchar2(32);
L_KEY raw(250);
L_ENCRYPTION_TYPE PLS_INTEGER;
BEGIN
L_KEY := UTL_I18N.STRING_TO_RAW
( data => '98345678901234567890123456789012',
DST_CHARSET => 'AL32UTF8' );
L_ENCRYPTION_TYPE := dbms_crypto.encrypt_aes256
+ DBMS_CRYPTO.CHAIN_CBC
+ DBMS_CRYPTO.PAD_PKCS5;
l_decrypted := dbms_crypto.decrypt
( SRC => P_VAL,
TYP => L_ENCRYPTION_TYPE,
key => L_KEY );
l_decrypted_string := utl_i18n.raw_to_char
( data => l_decrypted ,
src_charset => 'AL32UTF8' );
RETURN l_decrypted_string;
end DECRYPT_VAL;
FUNCTION encrypt_val( p_val IN VARCHAR2 ) RETURN VARCHAR2
is
L_VAL RAW(32);
L_ENCRYPTED raw(32);
L_CHARACTER_SET varchar2(10);
L_STRING varchar2(32);
L_KEY RAW(250);
L_ENCRYPTION_TYPE PLS_INTEGER;
begin
L_KEY := UTL_I18N.STRING_TO_RAW
( data => '98345678901234567890123456789012',
DST_CHARSET => 'AL32UTF8' );
L_ENCRYPTION_TYPE := dbms_crypto.encrypt_aes256
+ DBMS_CRYPTO.CHAIN_CBC
+ DBMS_CRYPTO.PAD_PKCS5;
L_VAL := utl_i18n.string_to_raw
( data => p_val,
dst_charset => 'AL32UTF8' );
L_ENCRYPTED := dbms_crypto.encrypt
( SRC => L_VAL,
TYP => L_ENCRYPTION_TYPE,
key => L_KEY );
return L_ENCRYPTED;
EXCEPTION when OTHERS then
RETURN SQLCODE||'-'||SQLERRM;
end ENCRYPT_VAL;
end PKG_LOGI_PWD_REG;
/