sql >> Base de Datos >  >> RDS >> Oracle

La forma más rápida de calcular el hash de una tabla completa

En primer lugar, creo que la forma de acercarse a los "administradores deshonestos" es con una combinación de la pista de auditoría de Oracle y Bóveda de base de datos funciones.

Dicho esto, esto es lo que podría intentar:

1) Cree una función agregada ODCI personalizada para calcular un hash de varias filas como un agregado. 2) Cree un VIRTUAL NOT NULL columna de la tabla que era un hash SHA de todas las columnas de la tabla, o todas las que le interesan proteger. Mantendrías esto todo el tiempo, básicamente intercambiando algo de insert/update/delete rendimiento a cambio de poder calcular hashes más rápidamente. 3) Cree un índice no único en esa columna virtual 4) SELECT my_aggregate_hash_function(virtual_hash_column) FROM my_table para obtener los resultados.

Aquí está el código:

Cree una función agregada para calcular un hash SHA sobre un montón de filas

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;
/

Cree una tabla de prueba para trabajar (se salta esto ya que tiene su tabla real)

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

Cree un hash de columna virtual de los datos de cada fila. Asegúrate de que sea 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 ;

Cree un índice en la columna virtual; de esta manera, puede calcular su hash con un escaneo completo del índice estrecho en lugar de un escaneo completo de la tabla gruesa

create index msi_compliance_hash_n1 on mattmsi (compliance_hash);  

Ponlo todo junto para calcular tu hash

SELECT matt_hash_aggregate(compliance_hash) from (select compliance_hash from mattmsi order by compliance_hash);

Algunos comentarios:

  1. Creo que es importante usar un hash para calcular el agregado (en lugar de simplemente hacer un SUM() sobre los hashes de nivel de fila, porque un atacante podría falsificar la suma correcta muy fácilmente.
  2. No creo que puedas (¿fácilmente?) usar consultas paralelas porque es importante que las filas se envíen a la función agregada en un orden coherente, de lo contrario, el valor hash cambiará.