简体   繁体   English

Oracle SQL 将触发器插入 Hash 密码无效(CHAR 问题)

[英]Oracle SQL Insert Trigger to Hash Password is not working (Issue with CHAR)

I'm trying to build a insert trigger to hash a string into a SH-256 password:我正在尝试为 hash 构建一个插入触发器,将字符串转换为 SH-256 密码:

DROP TABLE client;

CREATE TABLE client (
    id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    password CHAR(64) NOT NULL
);


-- Add Insert Trigger
CREATE OR REPLACE TRIGGER client_hash_trigger
    BEFORE INSERT
    ON client
    FOR EACH ROW
DECLARE 
    password_hash CHAR(64);
BEGIN
    SELECT STANDARD_HASH(:NEW.password, 'SHA256') INTO password_hash FROM dual;
    :NEW.password := password_hash;
END;


INSERT INTO client VALUES (DEFAULT, 'BruteForce');

When I run the SQL I'll get a wrong hash in my table.当我运行 SQL 时,我的表中会出现错误的 hash。 When I change booth char to varchar it's working.当我将booth char更改为varchar时,它正在工作。 When I put the STANDARD_HASH directly into the insert line it working as well.当我将 STANDARD_HASH 直接放入插入行时,它也可以正常工作。 Where is the issue?问题出在哪里?

You use a char column to store a RAW value, returned by standard_hash ;您使用char列存储由standard_hash返回的RAW值; you should cast your RAW to a different type, for example by UTL_RAW procedures.您应该将RAW转换为不同的类型,例如通过UTL_RAW程序。

Also, consider that when you define a column as char(64) you mean that all the values will be 64 characters long, so 'BruteForce' will be padded to 64 characters and the hash will be computed on the padded string.另外,请考虑当您将列定义为char(64)时,您的意思是所有值的长度均为 64 个字符,因此'BruteForce'将被填充为 64 个字符,并且 hash 将在填充的字符串上计算。

If you try to add something like如果您尝试添加类似

dbms_output.put_line('Password is: "' || :NEW.password || '"');

to your trigger, you will see:到你的触发器,你会看到:

Password is: "BruteForce                                                      "

Varchar2 , differently will only use the characters you need, so the hash would be computed exactly on 'BruteForce' , that's why if you switch to varchar2 you see a different hash result. Varchar2 ,不同的是只会使用您需要的字符,因此 hash 将在'BruteForce'上精确计算,这就是为什么如果您切换到varchar2您会看到不同的 hash 结果。

If you write something like如果你写类似

INSERT INTO client VALUES (DEFAULT, STANDARD_HASH('BruteForce', 'SHA256'));

the hash is computed not on che column content (which is a char(64) , thus being 64 characters long), but over the string 'BruteForce' and the resulting hash in casted into char(64) . hash 不是在 che 列内容(这是一个char(64) ,因此长度为 64 个字符)上计算,而是在字符串'BruteForce'和生成的 hash 中计算,并转换为char(64) What Oracle does is: Oracle 所做的是:

  1. compute the hash计算 hash
  2. cast it to char(64)将其转换为char(64)

The issue with the padding comes when you use the trigger because the trigger is based on the table structure, so :NEW.password is a char(64) value and the hash is computed over the already casted value, so over a string made by 64 characters.使用触发器时会出现填充问题,因为触发器基于表结构,因此:NEW.password是一个char(64)值,并且 hash 是根据已转换的值计算的,因此通过由64 个字符。 What happens with the trigger is触发器会发生什么

  1. cast the string to char(64) , because you are inserting in a char(64) column, thus padding将字符串转换为char(64) ,因为您要插入char(64)列,因此填充
  2. compute the hash计算 hash

[TL;DR] Use STANDARD_HASH( RTRIM(:new.PASSWORD ), 'SHA256' ) as you want to generate the password on 'BruteForce' and not 'BruteForce ' (etc.) padded with white-space to a length of 64 characters (which is what using CHAR(64) would give you). [TL;DR] 使用STANDARD_HASH( RTRIM(:new.PASSWORD ), 'SHA256' )因为你想在'BruteForce'上生成密码,而不是在'BruteForce ' (等)上用空格填充到 64字符(这是使用CHAR(64)会给你的)。


If you don't want to use a salt (which you should be using in this day-and-age) then just right-trim the password to get rid of the trailing white-space that the CHAR data type have padded the string with:如果您不想使用盐(您应该在当今时代使用),那么只需正确修剪密码以消除CHAR数据类型填充字符串的尾随空格:

CREATE OR REPLACE TRIGGER client_hash_trigger
BEFORE INSERT ON client
FOR EACH ROW
BEGIN
  SELECT STANDARD_HASH( RTRIM( :new.PASSWORD ), 'SHA256' )
  INTO   :new.PASSWORD
  FROM   DUAL;
END;
/

Then:然后:

CREATE TABLE client (
    id            NUMBER(10,0)
                  GENERATED ALWAYS AS IDENTITY
                  CONSTRAINT client__id__pk PRIMARY KEY,
    password      CHAR(64)
                  NOT NULL
);

INSERT INTO client (id, password) VALUES (DEFAULT, 'BruteForce');

Will output:请问output:

SELECT id, password FROM client;
 ID |身份证 | PASSWORD -: |:--------------------------------------------------------------- 1 |密码 -: |:--------------------------------------------- ------------------ 1 | 6BAFE79E0E7360094A490C0B84F2D5BA1C5A6E5C6FE1E45C2E7850966698AF29 6BAFE79E0E7360094A490C0B84F2D5BA1C5A6E5C6FE1E45C2E7850966698AF29

Hashing and Salting散列和加盐

Adapted from this answer改编自这个答案

If you also want to follow best practice, then you should salt the password before hashing:如果您还想遵循最佳实践,那么您应该在散列之前对密码进行加盐:

CREATE TABLE client (
    id            NUMBER(10,0)
                  GENERATED ALWAYS AS IDENTITY
                  CONSTRAINT client__id__pk PRIMARY KEY,
    password      CHAR(64)
                  NOT NULL,
    password_salt VARCHAR2(61)
                  NOT NULL
);

Then your trigger is:那么你的触发器是:

CREATE TRIGGER client_hash_trigger
BEFORE INSERT OR UPDATE ON client
FOR EACH ROW
BEGIN
  IF :new.PASSWORD = :old.PASSWORD THEN
    -- Assume things haven't changed (The chances of a hash collision are vanishingly small).
    -- Make sure the old salt is not replaced if the password hash hasn't changed.
    :new.PASSWORD_SALT := :old.PASSWORD_SALT;
  ELSE
    -- Regenerate a new salt and hash the password.
    :new.PASSWORD_SALT := DBMS_RANDOM.STRING( 'P', FLOOR( DBMS_RANDOM.VALUE( 40, 61 ) ) );
    SELECT STANDARD_HASH ( :new.PASSWORD_SALT || RTRIM( :new.PASSWORD ), 'SHA256' )
    INTO   :new.PASSWORD
    FROM   DUAL;
  END IF;
END;
/

And then:接着:

INSERT INTO client (id, password) VALUES (DEFAULT, 'BruteForce');

May output: 5 月 output:

SELECT * FROM client;
 ID |身份证 | PASSWORD |密码 | PASSWORD_SALT -: |:--------------------------------------------------------------- |:------------------------------------------ 1 | PASSWORD_SALT -: |:--------------------------------------------- ------------------ |:------------------------------ ------------ 1 | 7465541FDF9379B2112D4E92150F594732139E94B79D0EB0247593B4E8CEB3E4 | 7465541FDF9379B2112D4E92150F594732139E94B79D0EB0247593B4E8CEB3E4 | dhj4GOC8E(xA&8b9f)j@"Y- o$G.UECR\go.SrFaZ<& dhj4GOC8E(xA&8b9f)j@"Y-o$G.UECR\go.SrFaZ<&

db<>fiddle here db<> 在这里摆弄

A slight change to MT0 's answer to remove the context switch in the trigger by using dbms_crypto instead of standard_hash .通过使用dbms_crypto而不是standard_hashMT0答案稍作更改,以删除触发器中的上下文切换。

CREATE OR REPLACE TRIGGER client_hash_trigger
BEFORE INSERT ON client
FOR EACH ROW
BEGIN
  :new.PASSWORD := rawtohex(dbms_crypto.hash(src => utl_raw.cast_to_raw(:new.PASSWORD),
                                             typ => dbms_crypto.hash_sh256));
END;
/

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM