6. Februar 2008

MD5-Prüfsummen aus BLOBs oder CLOBs berechnen

English title: Get MD5 Checksum from a LOB

Es gibt auch in der Datenbank viele Anwendungsfälle, in denen man Prüfsummen berechnen muss. Meist werden MD5-Prüfsummen verwendet. So kann man damit Duplikate erkennen oder die Integrität einer heruntergeladenen Datei überprüfen. Und wenn die Datei aus der Datenbank kommen soll, muss diese Prüfsumme auch in der Datenbank berechnet werden. Doch wie kann man eine MD5-Checksumme in der Datenbank berechnen?
Eine erste Idee ist es, nach vorhandenen PL/SQL-Prozeduren oder Funktionen zu suchen, man findet auch recht schnell DBMS_UTILITY.GET_HASH_VALUE. Allerdings funktioniert diese nur mit VARCHAR2-Variablen, womit wir auf 32Kb beschränkt wären.
Die Lösung lautet Java in der Datenbank. Java bietet im Package java.security standardmäßig Methoden zum Berechnen von MD5-Prüfsummen an - und die können wir auch in der Datenbank nutzen. Der folgende Code erstellt eine entsprechende Java-Klasse und die passenden PL/SQL-Wrapper zur Übergabe von BLOB oder CLOB-Datentypen.
Calculating checksums can be an important task also for the PL/SQL developer. A checksum (MD5 is used in most cases) can be used to determine whether a specific LOB already exists in the database. Another application is to provide a checksum to the end user in order to verify whether the integrity of a downloaded file. And if the file is being downloaded from the database ... the checksum has to be calculated in the database as well
A first idea is to search for available PL/SQL procedures or functions - but the only available function DBMS_UTILITY.GET_HASH_VALUE accepts only VARCHAR2 as input parameter - so we cannot use it for large objects (CLOB, BLOB).
The solution is again Java in the database. The package java.security contains methods for calculating MD5 checksums - by default. So we can use these methods within the database. All we then need is a PL/SQL wrapper which maked the java methods available to the SQL or PL/SQL engine. And this code follows here:
create or replace java source named "MD5" as 
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.Clob;
import java.sql.Blob;
 
public class MD5 {
  public static String getMD5HashFromClob(Clob inhalt) throws Exception{
    String sChecksum = null;

    if (inhalt != null) {
      try {
        String data = inhalt.getSubString(1L, (int)inhalt.length());
        MessageDigest md5 = MessageDigest.getInstance("MD5");
        md5.update(data.getBytes());
        byte result[] = md5.digest();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < result.length; i++) {
            String s = Integer.toHexString(result[i]);
            int length = s.length();
            if (length >= 2) {
                sb.append(s.substring(length - 2, length));
            } else {
                sb.append("0");
                sb.append(s);
            }
        }
        sChecksum = sb.toString();
      } catch (NoSuchAlgorithmException e) {
        // do nothing
      }
    }
    return sChecksum;
  } 

  public static String getMD5HashFromBlob(Blob inhalt) throws Exception{
    String sChecksum = null;

    if (inhalt != null) {
      try {
        MessageDigest md5 = MessageDigest.getInstance("MD5");
        md5.update(inhalt.getBytes(1L, (int)inhalt.length()));
        byte result[] = md5.digest();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < result.length; i++) {
            String s = Integer.toHexString(result[i]);
            int length = s.length();
            if (length >= 2) {
                sb.append(s.substring(length - 2, length));
            } else {
                sb.append("0");
                sb.append(s);
            }
        }
        sChecksum = sb.toString();
      } catch (NoSuchAlgorithmException e) {
        // do nothing
      }
    }
    return sChecksum;
  }
}
/
sho err

alter java source "MD5" compile
/
sho err

CREATE OR REPLACE FUNCTION get_md5_CLOB(inhalt CLOB) RETURN VARCHAR2 DETERMINISTIC
AS LANGUAGE JAVA
name 'MD5.getMD5HashFromClob(java.sql.Clob) return java.lang.String';
/

CREATE OR REPLACE FUNCTION get_md5_BLOB(inhalt BLOB) RETURN VARCHAR2 DETERMINISTIC
AS LANGUAGE JAVA
name 'MD5.getMD5HashFromBlob(java.sql.Blob) return java.lang.String';
/

Vielen Dank übrigens an Michael Seiwert aus Hamburg für die Idee und auch Hilfe bei der Umsetzung
Credits to Michael Seiwert from Hamburg for the initial idea and for significant parts of code

Kommentare:

BigBlue2000 hat gesagt…

Ich finde dies sehr umständlich, da es in PLSQL doch auch (wesentlich einfacher) geht:

declare
v_clob CLOB;
v_hash VARCHAR2(100);
begin

v_clob := TO_CLOB('I need MD5');

v_hash := RAWTOHEX(dbms_crypto.Hash(src => v_clob, typ => dbms_crypto.HASH_MD5));

dbms_output.put_line(v_hash);

end;

Abgesehen davon liefert m.E. die Javafunktion nicht den korrekten Hashwert.

Carsten Czarski hat gesagt…

Richtig

Wieder der beste Beweis dafür, dass die Datenbank eine Menge kann ... DBMS_CRYPTO hatte ich ganz aus den Augen verloren. Ab Oracle10g kann man das Gleiche mit reinem PL/SQL und DBMS_CRYPTO machen - danke für den Kommentar!

Die Java-Bibliothek liefert im Prinzip schon die richtige MD5-Summe. In meinem Java-Code war allerdings ein kleiner Fehler (das CLOB wurde nicht richtig ausgelesen); daher kamen wohl die Unterschiede. Ist korrigiert; nun sollten beide Varianten die gleichen Ergebnisse bringen.

Bleibt nur noch zu sagen: Ganz nutzlos ist es (hoffentlich) nicht; in Oracle9i gibt es noch kein DBMS_CRYPTO ....

Viele Grüße

-Carsten

Beliebte Postings