计算整个表哈希值的最快方法

7
我们需要能够计算外部环境下的表哈希值,并将其与内部环境下预先计算的哈希值进行比较。这样做的目的是确保外部环境中的数据没有被“不法”数据库管理员篡改。用户坚持要使用这个功能
目前,我们通过计算每个列值的单独哈希值,在列哈希上执行位异或以获取行哈希,然后在所有行哈希上执行位异或以得到表哈希。以下是伪代码:
cursor hash_cur is
select /*+ PARALLEL(4)*/ dbms_crypto.mac(column1_in_raw_type, HMAC_SH512, string_to_raw('COLUMN1_NAME')) as COLUMN1_NAME
       ...
from TABLE_NAME;

open hash_cur;
fetch hash_cur bulk collect into hashes;
close hash_cur;

for i in 1..hashes.count
loop
  rec := hashes(i);
  record_xor = rec.COLUMN1;
  record_xor = bit_xor(record_xor, rec.COLUMN2);
  ...
  record_xor = bit_xor(record_xor, rec.COLUMNN);

  table_xor = bit_xor(table_xor, record_xor);
end loop;

使用dbms_job可以并行运行上述伪脚本。

问题在于,对于某些表,我们有数千兆字节的数据,目前的性能不符合我们想要实现的性能。哈希必须“即时”完成,因为用户希望自己执行哈希检查。

  1. 你们有更好的方法来执行整个表的哈希,或者基本上比较通过低延迟和相对低带宽的网络连接到不同环境中的表吗?

我认为这个操作更多地是CPU绑定而不是I/O绑定。我考虑将表数据存储在blob中,其中数据按记录,然后按列适当排列。然后对输出文件执行哈希。这应该使操作完全成为I/O绑定。

  1. 最快的方法是什么?是否有办法在查询的select子句中执行此操作,以消除任何开销PL/SQL-to-SQL引擎上下文切换?
    • 我考虑为此修改全局blob
    • 还希望消除批量收集结果的I/O开销。

任何可以帮助我编写性能更好的脚本的建议都将不胜感激。谢谢。


我不确定,但您可以将所有列数据附加到单个列中,并创建一个单一的哈希键,然后对另一个表执行相同操作并比较这些哈希键。 - vishnu sable
@VladimirBaranov他无法这样做,因为哈希计算的入口是一个还会动态生成脚本的Web应用程序。没有任何脚本存储在数据库中。我不知道数据库管理员如何能够克服这个问题。 - user3367701
这可能是这个问题的重复。 - Jon Heller
1
你认为你的脚本能够检测到两个不同行中的值被交换了吗? - David Aldridge
@DavidAldridge,最初你是对的,然而,只有在主键值未被交换时,数据交换才会有影响,如果主键值没有被交换,那么这应该会导致不同的哈希值。 - user3367701
显示剩余4条评论
2个回答

4
首先,我认为处理“流氓管理员”的方法是结合 Oracle 的审计跟踪和数据库保险库功能。
话虽如此,以下是我可能会尝试的方法:
1)创建一个自定义的 ODCI 聚合函数,以计算多行的哈希值作为聚合。 2)在表上创建一个“VIRTUAL NOT NULL”列,该列是表中所有列或您关心保护的列的 SHA 哈希值。您将一直保留这个值,基本上牺牲了一些插入/更新/删除性能,以换取更快地计算哈希值。 3)在虚拟列上创建一个非唯一索引。 4)使用“SELECT my_aggregate_hash_function (virtual_hash_column) FROM my_table”来获取结果。
以下是代码:
CREATE OR REPLACE TYPE matt_hash_aggregate_impl AS OBJECT
(
  hash_value RAW(32000),
  CONSTRUCTOR FUNCTION matt_hash_aggregate_impl(SELF IN OUT NOCOPY matt_hash_aggregate_impl ) RETURN SELF AS RESULT,  
-- Called to initialize a new aggregation context
-- For analytic functions, the aggregation context of the *previous* window is passed in, so we only need to adjust as needed instead 
-- of creating the new aggregation context from scratch
  STATIC FUNCTION ODCIAggregateInitialize (sctx IN OUT matt_hash_aggregate_impl) RETURN NUMBER,
-- Called when a new data point is added to an aggregation context  
  MEMBER FUNCTION ODCIAggregateIterate (self IN OUT matt_hash_aggregate_impl, value IN raw ) RETURN NUMBER,
-- Called to return the computed aggragate from an aggregation context
  MEMBER FUNCTION ODCIAggregateTerminate (self IN matt_hash_aggregate_impl, returnValue OUT raw, flags IN NUMBER) RETURN NUMBER,
-- Called to merge to two aggregation contexts into one (e.g., merging results of parallel slaves) 
  MEMBER FUNCTION ODCIAggregateMerge (self IN OUT matt_hash_aggregate_impl, ctx2 IN matt_hash_aggregate_impl) RETURN NUMBER,
  -- ODCIAggregateDelete
  MEMBER FUNCTION ODCIAggregateDelete(self IN OUT matt_hash_aggregate_impl, value raw) RETURN NUMBER  
);

/

CREATE OR REPLACE TYPE BODY matt_hash_aggregate_impl IS

CONSTRUCTOR FUNCTION matt_hash_aggregate_impl(SELF IN OUT NOCOPY matt_hash_aggregate_impl ) RETURN SELF AS RESULT IS
BEGIN
  SELF.hash_value := null;
  RETURN;
END;


STATIC FUNCTION ODCIAggregateInitialize (sctx IN OUT matt_hash_aggregate_impl) RETURN NUMBER IS
BEGIN
  sctx := matt_hash_aggregate_impl ();
  RETURN ODCIConst.Success;
END;


MEMBER FUNCTION ODCIAggregateIterate (self IN OUT matt_hash_aggregate_impl, value IN raw ) RETURN NUMBER IS
BEGIN
  IF self.hash_value IS NULL THEN
    self.hash_value := dbms_crypto.hash(value, dbms_crypto.hash_sh1);
  ELSE 
      self.hash_value := dbms_crypto.hash(self.hash_value || value, dbms_crypto.hash_sh1);
  END IF;
  RETURN ODCIConst.Success;
END;

MEMBER FUNCTION ODCIAggregateTerminate (self IN matt_hash_aggregate_impl, returnValue OUT raw, flags IN NUMBER) RETURN NUMBER IS
BEGIN
  returnValue := dbms_crypto.hash(self.hash_value,dbms_crypto.hash_sh1);
  RETURN ODCIConst.Success;
END;

MEMBER FUNCTION ODCIAggregateMerge (self IN OUT matt_hash_aggregate_impl, ctx2 IN matt_hash_aggregate_impl) RETURN NUMBER IS
BEGIN
    self.hash_value := dbms_crypto.hash(self.hash_value || ctx2.hash_value, dbms_crypto.hash_sh1);
  RETURN ODCIConst.Success;
END;

-- ODCIAggregateDelete
MEMBER FUNCTION ODCIAggregateDelete(self IN OUT matt_hash_aggregate_impl, value raw) RETURN NUMBER IS
BEGIN
  raise_application_error(-20001, 'Invalid operation -- hash aggregate function does not support windowing!');
END;  

END;
/

CREATE OR REPLACE FUNCTION matt_hash_aggregate ( input raw) RETURN raw
PARALLEL_ENABLE AGGREGATE USING matt_hash_aggregate_impl;
/

创建一个测试表以进行操作(如果您有真实的表,则可以跳过此步骤)

create table mattmsi as select * from mtl_system_items where rownum <= 200000;

创建一个虚拟列的哈希值,包含每行的数据。确保它是NOT NULL

alter table mattmsi add compliance_hash generated always as ( dbms_crypto.hash(to_clob(inventory_item_id || segment1 || last_update_date || created_by || description), 3 /*dbms_crypto.hash_sh1*/) ) VIRTUAL not null ;

在虚拟列上创建索引,这样您就可以使用窄索引的全扫描来计算哈希值,而不是对整个大表进行全扫描。
create index msi_compliance_hash_n1 on mattmsi (compliance_hash);  

将它们结合起来计算哈希值。
SELECT matt_hash_aggregate(compliance_hash) from (select compliance_hash from mattmsi order by compliance_hash);

以下是我的一些评论:

  1. 我认为使用哈希来计算聚合值非常重要,而不仅仅是在行级哈希上执行SUM(),因为攻击者可以很容易地伪造正确的总和。
  2. 我认为你不能(容易地?)使用并行查询,因为重要的是以一致的顺序将行提供给聚合函数,否则哈希值将发生变化。

你可能也想对matt_hash_aggregate_impl的源代码进行哈希处理。 - David Aldridge
OP在另一条评论中说,嫌疑的DBA可能无法访问哈希检查算法,但也许这样做不会有害。再次强调,我认为Oracle数据库保险库和审计可能是更强大的解决问题的方式。 - Matthew McPeak
谢谢Matthew,我以前从未接触过虚拟列,它们似乎很有前途。然而,它是否会像触发器中那样存在相同的“安全问题”,即“流氓管理员”可以禁用哈希计算触发器,以便数据更改不会反映在哈希中? - user3367701
在进一步了解虚拟列后,我发现在我的情况下并没有优势,因为虚拟列的值每次都会重新计算。此外,有人可以随时删除虚拟列。最好创建一个物理列,并创建一个触发器来计算物理列的值。 - user3367701
@MatthewMcPeak 一个流氓管理员不可能只是创建虚拟列哈希的副本到某个位置,然后修改虚拟列计算以引用复制的虚拟哈希的副本吗? - user3367701
显示剩余2条评论

0

你可以使用ORA_HASH函数,可以将多个列作为表达式传递进去。

select sum(ORA_HASH(col1||col2||col3)) as hash from my_table

但是在AskTom上,有类似的讨论,为什么这不是一个好的做法:为表内容创建唯一的HASH值


我认为sum(hash)不够强大。一个流氓的DBA可能无法轻松地计算出单个行以给定特定哈希值,但他可以轻松地插入/删除具有真实哈希值的行,以使sum()返回正确的值。 - Matthew McPeak
有人能解释一下在这种情况下sum函数是做什么的吗?我以为sum只支持十进制数字的算术运算? - Learner
1
根据ORA_HASH自己的文档,它返回一个数字(因此每行一个),所以将它们加在一起没有问题。但这也表明上述方法无法检测到某些更改,因为它会对所有值求和。如果一个表有2行,其ORA_HASH分别为-2和+2,另一个表的ORA_HASH值为-1和+1,则SELECT语句将给出相同的结果,并且您将认为这些表是相同的,而实际上它们并不相同(当然,这只是一个简单的例子,但显示了真正的问题)。 - Patrick Mevzek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接