[PATCH] Message digest test suite and incomplete MD4 support

Simon Josefsson jas@extundo.com
Sat, 02 Nov 2002 22:45:21 +0100


I need MD4 support so I wrote the below, but it didn't work so I added
a test suite for message digests (thus removing a TODO in
tests/basic.c), but wasn't able to get it to work anyway (the dynamic
algorithm loading stuff is rather convoluted IMHO).  The test case
fails the same way for the TIGER algorithm, so perhaps there is
something genuinely wrong.  I'm thankful for any help in getting this
to work, and to get it included in the distribution (I have signed
papers now).

Index: configure.ac
===================================================================
RCS file: /cvs/gnupg/libgcrypt/configure.ac,v retrieving revision 1.23
diff -u -p -r1.23 configure.ac --- configure.ac 20 Sep 2002 11:23:57
-0000 1.23
+++ configure.ac	2 Nov 2002 21:39:01 -0000
@@ -70,7 +70,7 @@ AC_SUBST(VERSION)
 AC_DEFINE_UNQUOTED(PACKAGE, "$PACKAGE", [Name of this package])
 AC_DEFINE_UNQUOTED(VERSION, "$VERSION", [Version of this package])
 
-static_modules="sha1 md5 rmd160"
+static_modules="sha1 md4 md5 rmd160"
 static_random_module=""
 
 AC_PROG_AWK
Index: cipher/Makefile.am
===================================================================
RCS file: /cvs/gnupg/libgcrypt/cipher/Makefile.am,v
retrieving revision 1.66
diff -u -p -r1.66 Makefile.am
--- cipher/Makefile.am	17 May 2002 08:33:16 -0000	1.66
+++ cipher/Makefile.am	2 Nov 2002 21:39:01 -0000
@@ -28,12 +28,13 @@ noinst_LTLIBRARIES = libcipher.la
 
 
 # The configure script greps the module names from the EXTRA_PROGRAMS line
-EXTRA_PROGRAMS = rndlinux rndunix rndegd rndw32 sha1 rmd160 md5 tiger
+EXTRA_PROGRAMS = rndlinux rndunix rndegd rndw32 sha1 rmd160 md4 md5 tiger
 
 EXTRA_rndlinux_SOURCES = rndlinux.c
 EXTRA_rndunix_SOURCES = rndunix.c
 EXTRA_rndegd_SOURCES = rndegd.c
 EXTRA_rndw32_SOURCES = rndw32.c
+EXTRA_md4_SOURCES = md4.c
 EXTRA_md5_SOURCES = md5.c
 EXTRA_rmd160_SOURCES = rmd160.c
 EXTRA_sha1_SOURCES = sha1.c
Index: cipher/md.c
===================================================================
RCS file: /cvs/gnupg/libgcrypt/cipher/md.c,v
retrieving revision 1.55
diff -u -p -r1.55 md.c
--- cipher/md.c	14 Oct 2002 18:07:00 -0000	1.55
+++ cipher/md.c	2 Nov 2002 21:39:01 -0000
@@ -49,6 +49,8 @@ static struct {
   { "1.2.840.113549.2.5",   GCRY_MD_MD5 },
   /* GNU.digestAlgorithm TIGER */
   { "1.3.6.1.4.1.11591.12.2", GCRY_MD_TIGER },
+  /* iso.member-body.us.rsadsi.digestAlgorithm.md4 */
+  { "1.2.840.113549.2.4", GCRY_MD_MD4 },
   {NULL}
 };
 
@@ -819,6 +821,7 @@ gcry_md_get_algo_dlen( int algo )
     /* we do some very quick checks here */
     switch( algo )
     {
+      case GCRY_MD_MD4:
       case GCRY_MD_MD5: return 16;
       case GCRY_MD_SHA1:
       case GCRY_MD_RMD160: return 20;
Index: cipher/md4.c
===================================================================
RCS file: cipher/md4.c
diff -N cipher/md4.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ cipher/md4.c	2 Nov 2002 21:39:01 -0000
@@ -0,0 +1,397 @@
+/* md4.c - MD4 Message-Digest Algorithm
+ * Copyright (C) 2002 Free Software Foundation, Inc.
+ *
+ * This file is part of Libgcrypt.
+ *
+ * Libgcrypt is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * Libgcrypt is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ *
+ * Based on md5.c in libgcrypt, but rewritten to compute md4 checksums
+ * using a public domain md4 implementation with the following comments:
+ *
+ * Modified by Wei Dai from Andrew M. Kuchling's md4.c
+ * The original code and all modifications are in the public domain.
+ *
+ * This is the original introductory comment:
+ *
+ *  md4.c : MD4 hash algorithm.
+ *
+ * Part of the Python Cryptography Toolkit, version 1.1
+ *
+ * Distribute and use freely; there are no restrictions on further
+ * dissemination and usage except those imposed by the laws of your
+ * country of residence.
+ *
+ */
+
+/* MD4 test suite:
+ * MD4 ("") = 31d6cfe0d16ae931b73c59d7e0c089c0
+ * MD4 ("a") = bde52cb31de33e46245e05fbdbd6fb24
+ * MD4 ("abc") = a448017aaf21d8525fc10ae87aa6729d
+ * MD4 ("message digest") = d9130a8164549fe818874806e1c7014b
+ * MD4 ("abcdefghijklmnopqrstuvwxyz") = d79e1c308aa5bbcdeea8ed63df412da9
+ * MD4 ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") =
+ * 043f8582f241db351ce627e153e7f0e4
+ * MD4 ("123456789012345678901234567890123456789012345678901234567890123456
+ * 78901234567890") = e33b4ddc9c38f2199c3e7b164fcc0536
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include "g10lib.h"
+#include "memory.h"
+#include "dynload.h"
+
+#include "bithelp.h"
+
+
+typedef struct {
+    u32 A,B,C,D;	  /* chaining variables */
+    u32  nblocks;
+    byte buf[64];
+    int  count;
+} MD4_CONTEXT;
+
+
+static void
+md4_init( MD4_CONTEXT *ctx )
+{
+    ctx->A = 0x67452301;
+    ctx->B = 0xefcdab89;
+    ctx->C = 0x98badcfe;
+    ctx->D = 0x10325476;
+
+    ctx->nblocks = 0;
+    ctx->count = 0;
+}
+
+static void
+burn_stack (int bytes)
+{
+    char buf[128];
+    
+    memset (buf, 0, sizeof buf);
+    bytes -= sizeof buf;
+    if (bytes > 0)
+        burn_stack (bytes);
+}
+
+#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z))))
+#define G(x, y, z) (((x) & (y)) | ((x) & (z)) | ((y) & (z)))
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+
+
+/****************
+ * transform n*64 bytes
+ */
+static void
+/*transform( MD4_CONTEXT *ctx, const void *buffer, size_t len )*/
+transform( MD4_CONTEXT *ctx, byte *data )
+{
+    u32 correct_words[16];
+    register u32 A = ctx->A;
+    register u32 B = ctx->B;
+    register u32 C = ctx->C;
+    register u32 D = ctx->D;
+    u32 *cwp = correct_words;
+
+#ifdef BIG_ENDIAN_HOST
+    { int i;
+      byte *p2, *p1;
+      for(i=0, p1=data, p2=(byte*)correct_words; i < 16; i++, p2 += 4 ) {
+	p2[3] = *p1++;
+	p2[2] = *p1++;
+	p2[1] = *p1++;
+	p2[0] = *p1++;
+      }
+    }
+#else
+    memcpy( correct_words, data, 64 );
+#endif
+
+    /* Round 1.  */
+#define function(a,b,c,d,k,s) a=rol(a+F(b,c,d)+data[k],s);
+          function(A,B,C,D, 0, 3);
+          function(D,A,B,C, 1, 7);
+          function(C,D,A,B, 2,11);
+          function(B,C,D,A, 3,19);
+          function(A,B,C,D, 4, 3);
+          function(D,A,B,C, 5, 7);
+          function(C,D,A,B, 6,11);
+          function(B,C,D,A, 7,19);
+          function(A,B,C,D, 8, 3);
+          function(D,A,B,C, 9, 7);
+          function(C,D,A,B,10,11);
+          function(B,C,D,A,11,19);
+          function(A,B,C,D,12, 3);
+          function(D,A,B,C,13, 7);
+          function(C,D,A,B,14,11);
+          function(B,C,D,A,15,19);
+
+#undef function
+
+    /* Round 2.  */
+#define function(a,b,c,d,k,s) a=rol(a+G(b,c,d)+data[k]+0x5a827999,s);
+
+          function(A,B,C,D, 0, 3);
+          function(D,A,B,C, 4, 5);
+          function(C,D,A,B, 8, 9);
+          function(B,C,D,A,12,13);
+          function(A,B,C,D, 1, 3);
+          function(D,A,B,C, 5, 5);
+          function(C,D,A,B, 9, 9);
+          function(B,C,D,A,13,13);
+          function(A,B,C,D, 2, 3);
+          function(D,A,B,C, 6, 5);
+          function(C,D,A,B,10, 9);
+          function(B,C,D,A,14,13);
+          function(A,B,C,D, 3, 3);
+          function(D,A,B,C, 7, 5);
+          function(C,D,A,B,11, 9);
+          function(B,C,D,A,15,13);
+
+#undef function
+
+    /* Round 3.  */
+#define function(a,b,c,d,k,s) a=rol(a+H(b,c,d)+data[k]+0x6ed9eba1,s);
+
+          function(A,B,C,D, 0, 3);
+          function(D,A,B,C, 8, 9);
+          function(C,D,A,B, 4,11);
+          function(B,C,D,A,12,15);
+          function(A,B,C,D, 2, 3);
+          function(D,A,B,C,10, 9);
+          function(C,D,A,B, 6,11);
+          function(B,C,D,A,14,15);
+          function(A,B,C,D, 1, 3);
+          function(D,A,B,C, 9, 9);
+          function(C,D,A,B, 5,11);
+          function(B,C,D,A,13,15);
+          function(A,B,C,D, 3, 3);
+          function(D,A,B,C,11, 9);
+          function(C,D,A,B, 7,11);
+          function(B,C,D,A,15,15);
+
+
+    /* Put checksum in context given as argument.  */
+    ctx->A += A;
+    ctx->B += B;
+    ctx->C += C;
+    ctx->D += D;
+}
+
+
+
+/* The routine updates the message-digest context to
+ * account for the presence of each of the characters inBuf[0..inLen-1]
+ * in the message whose digest is being computed.
+ */
+static void
+md4_write( MD4_CONTEXT *hd, byte *inbuf, size_t inlen)
+{
+    if( hd->count == 64 ) { /* flush the buffer */
+	transform( hd, hd->buf );
+        burn_stack (80+6*sizeof(void*));
+	hd->count = 0;
+	hd->nblocks++;
+    }
+    if( !inbuf )
+	return;
+    if( hd->count ) {
+	for( ; inlen && hd->count < 64; inlen-- )
+	    hd->buf[hd->count++] = *inbuf++;
+	md4_write( hd, NULL, 0 );
+	if( !inlen )
+	    return;
+    }
+    burn_stack (80+6*sizeof(void*));
+
+    while( inlen >= 64 ) {
+	transform( hd, inbuf );
+	hd->count = 0;
+	hd->nblocks++;
+	inlen -= 64;
+	inbuf += 64;
+    }
+    for( ; inlen && hd->count < 64; inlen-- )
+	hd->buf[hd->count++] = *inbuf++;
+
+}
+
+
+
+/* The routine final terminates the message-digest computation and
+ * ends with the desired message digest in mdContext->digest[0...15].
+ * The handle is prepared for a new MD4 cycle.
+ * Returns 16 bytes representing the digest.
+ */
+
+static void
+md4_final( MD4_CONTEXT *hd )
+{
+    u32 t, msb, lsb;
+    byte *p;
+
+    md4_write(hd, NULL, 0); /* flush */;
+
+    t = hd->nblocks;
+    /* multiply by 64 to make a byte count */
+    lsb = t << 6;
+    msb = t >> 26;
+    /* add the count */
+    t = lsb;
+    if( (lsb += hd->count) < t )
+	msb++;
+    /* multiply by 8 to make a bit count */
+    t = lsb;
+    lsb <<= 3;
+    msb <<= 3;
+    msb |= t >> 29;
+
+    if( hd->count < 56 ) { /* enough room */
+	hd->buf[hd->count++] = 0x80; /* pad */
+	while( hd->count < 56 )
+	    hd->buf[hd->count++] = 0;  /* pad */
+    }
+    else { /* need one extra block */
+	hd->buf[hd->count++] = 0x80; /* pad character */
+	while( hd->count < 64 )
+	    hd->buf[hd->count++] = 0;
+	md4_write(hd, NULL, 0);  /* flush */;
+	memset(hd->buf, 0, 56 ); /* fill next block with zeroes */
+    }
+    /* append the 64 bit count */
+    hd->buf[56] = lsb	   ;
+    hd->buf[57] = lsb >>  8;
+    hd->buf[58] = lsb >> 16;
+    hd->buf[59] = lsb >> 24;
+    hd->buf[60] = msb	   ;
+    hd->buf[61] = msb >>  8;
+    hd->buf[62] = msb >> 16;
+    hd->buf[63] = msb >> 24;
+    transform( hd, hd->buf );
+    burn_stack (80+6*sizeof(void*));
+
+    p = hd->buf;
+  #ifdef BIG_ENDIAN_HOST
+    #define X(a) do { *p++ = hd->a      ; *p++ = hd->a >> 8;      \
+		      *p++ = hd->a >> 16; *p++ = hd->a >> 24; } while(0)
+  #else /* little endian */
+    #define X(a) do { *(u32*)p = (*hd).a ; p += 4; } while(0)
+  #endif
+    X(A);
+    X(B);
+    X(C);
+    X(D);
+  #undef X
+
+}
+
+static byte *
+md4_read( MD4_CONTEXT *hd )
+{
+    return hd->buf;
+}
+
+/****************
+ * Return some information about the algorithm.  We need algo here to
+ * distinguish different flavors of the algorithm.
+ * Returns: A pointer to string describing the algorithm or NULL if
+ *	    the ALGO is invalid.
+ */
+static const char *
+md4_get_info( int algo, size_t *contextsize,
+	       byte **r_asnoid, int *r_asnlen, int *r_mdlen,
+	       void (**r_init)( void *c ),
+	       void (**r_write)( void *c, byte *buf, size_t nbytes ),
+	       void (**r_final)( void *c ),
+	       byte *(**r_read)( void *c )
+	     )
+{
+    static byte asn[18] = /* Object ID is 1.2.840.113549.2.4 */
+		    { 0x30, 0x20, 0x30, 0x0c, 0x06, 0x08, 0x2a, 0x86,0x48,
+		      0x86, 0xf7, 0x0d, 0x02, 0x04, 0x05, 0x00, 0x04, 0x10 };
+
+    if( algo != 11 )
+	return NULL;
+
+    *contextsize = sizeof(MD4_CONTEXT);
+    *r_asnoid = asn;
+    *r_asnlen = DIM(asn);
+    *r_mdlen = 16;
+    *(void  (**)(MD4_CONTEXT *))r_init		       = md4_init;
+    *(void  (**)(MD4_CONTEXT *, byte*, size_t))r_write = md4_write;
+    *(void  (**)(MD4_CONTEXT *))r_final 	       = md4_final;
+    *(byte *(**)(MD4_CONTEXT *))r_read		       = md4_read;
+
+    return "MD4";
+}
+
+
+#ifndef IS_MODULE
+static
+#endif
+const char * const gnupgext_version = "MD4 ($Revision: 1.23 $)";
+
+static struct {
+    int class;
+    int version;
+    int  value;
+    void (*func)(void);
+} func_table[] = {
+    { 10, 1, 0, (void(*)(void))md4_get_info },
+    { 11, 1, 1 },
+};
+
+
+#ifndef IS_MODULE
+static
+#endif
+void *
+gnupgext_enum_func( int what, int *sequence, int *class, int *vers )
+{
+    void *ret;
+    int i = *sequence;
+
+    do {
+	if( i >= DIM(func_table) || i < 0 )
+	    return NULL;
+	*class = func_table[i].class;
+	*vers  = func_table[i].version;
+	switch( *class ) {
+	  case 11: case 21: case 31: ret = &func_table[i].value; break;
+	  default:		     ret = func_table[i].func; break;
+	}
+	i++;
+    } while( what && what != *class );
+
+    *sequence = i;
+    return ret;
+}
+
+
+
+
+#ifndef IS_MODULE
+void
+_gcry_md4_constructor(void)
+{
+    _gcry_register_internal_cipher_extension( gnupgext_version, gnupgext_enum_func );
+}
+#endif
+
+/* end of file */
Index: doc/gcrypt.texi
===================================================================
RCS file: /cvs/gnupg/libgcrypt/doc/gcrypt.texi,v
retrieving revision 1.3
diff -u -p -r1.3 gcrypt.texi
--- doc/gcrypt.texi	14 Aug 2002 19:07:55 -0000	1.3
+++ doc/gcrypt.texi	2 Nov 2002 21:39:01 -0000
@@ -232,7 +232,7 @@ directory in which the header file is lo
 file search path (via the @option{-I} option).
 
 However, the path to the include file is determined at the time the
-source is configured.  To solve this problem, `Libgrypt' ships with a small
+source is configured.  To solve this problem, `Libgcrypt' ships with a small
 helper program @command{libgcrypt-config} that knows the path to the
 include file and other configuration options.  The options that need
 to be added to the compiler invocation at compile time are output by
@@ -943,7 +943,7 @@ using a random quality as defined by @va
 @deftypefun void *gcry_random_bytes_secure (size_t @var{nbytes}, enum gcry_random_level @var{level})
 
 Allocate a memory block consisting of @var{nbytes} fresh random bytes
-using a random quality as defined by @var{level}.  This fuinction
+using a random quality as defined by @var{level}.  This function
 differs from @code{gcry_random_bytes} in that the returned buffer is
 allcated in a ``secure'' area of the memory.
 @end deftypefun
Index: src/gcrypt.h
===================================================================
RCS file: /cvs/gnupg/libgcrypt/src/gcrypt.h,v
retrieving revision 1.63
diff -u -p -r1.63 gcrypt.h
--- src/gcrypt.h	20 Sep 2002 11:10:06 -0000	1.63
+++ src/gcrypt.h	2 Nov 2002 21:39:01 -0000
@@ -675,7 +675,8 @@ enum gcry_md_algos
     GCRY_MD_HAVAL   = 7,   /* HAVAL, 5 pass, 160 bit. */
     GCRY_MD_SHA256  = 8,
     GCRY_MD_SHA384  = 9,
-    GCRY_MD_SHA512  = 10
+    GCRY_MD_SHA512  = 10,
+    GCRY_MD_MD4     = 11
   };
 
 /* Flags used with the open function. */
Index: tests/basic.c
===================================================================
RCS file: /cvs/gnupg/libgcrypt/tests/basic.c,v
retrieving revision 1.4
diff -u -p -r1.4 basic.c
--- tests/basic.c	26 Aug 2002 10:36:31 -0000	1.4
+++ tests/basic.c	2 Nov 2002 21:39:01 -0000
@@ -160,11 +160,121 @@ check_ciphers (void)
   /* TODO: add some extra encryption to test the higher level functions */
 }
 
+static void
+check_one_md (int algo, char *data, int len, char *expect)
+{
+    GCRY_MD_HD hd;
+    char *p;
+    int mdlen;
+
+    hd = gcry_md_open (algo, 0);
+    if (!hd) {
+        fail ("algo %d, grcy_md_open failed: %s\n",
+              algo, gcry_strerror (-1) );
+        return;
+    }
+
+    mdlen = gcry_md_get_algo_dlen(algo);
+    if (mdlen < 1 || mdlen > 500) {
+        fail ("algo %d, grcy_md_get_algo_dlen failed: %d\n", algo, mdlen);
+        return;
+    }
+    
+    gcry_md_write (hd, data, len);
+
+    p = gcry_md_read (hd, algo);
+
+    if ( memcmp (p, expect, mdlen) )
+        fail ("algo %d, digest mismatch\n", algo);
+
+    gcry_md_close (hd);
+}
 
 static void
 check_digests ()
 {
-    /* TODO */
+  static struct algos {
+    int md;
+    char *data;
+    char *expect;
+  } algos[] = {
+    { GCRY_MD_MD4, "",
+      "\x31\xD6\xCF\xE0\xD1\x6A\xE9\x31\xB7\x3C\x59\xD7\xE0\xC0\x89\xC0" },
+    { GCRY_MD_MD4, "a",
+      "\xBD\xE5\x2C\xB3\x1D\xE3\x3E\x46\x24\x5E\x05\xFB\xDB\xD6\xFB\x24" },
+    { GCRY_MD_MD5, "",
+      "\xD4\x1D\x8C\xD9\x8F\x00\xB2\x04\xE9\x80\x09\x98\xEC\xF8\x42\x7E" },
+    { GCRY_MD_MD5, "a", 
+      "\x0C\xC1\x75\xB9\xC0\xF1\xB6\xA8\x31\xC3\x99\xE2\x69\x77\x26\x61" },
+    { GCRY_MD_MD5, "abc",
+      "\x90\x01\x50\x98\x3C\xD2\x4F\xB0\xD6\x96\x3F\x7D\x28\xE1\x7F\x72" },
+    { GCRY_MD_MD5, "message digest", 
+      "\xF9\x6B\x69\x7D\x7C\xB7\x93\x8D\x52\x5A\x2F\x31\xAA\xF1\x61\xD0"},
+    { GCRY_MD_SHA1, "abc",
+      "\xA9\x99\x3E\x36\x47\x06\x81\x6A\xBA\x3E"
+      "\x25\x71\x78\x50\xC2\x6C\x9C\xD0\xD8\x9D"},
+    { GCRY_MD_SHA1, "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+      "\x84\x98\x3E\x44\x1C\x3B\xD2\x6E\xBA\xAE"
+      "\x4A\xA1\xF9\x51\x29\xE5\xE5\x46\x70\xF1" },
+    { GCRY_MD_RMD160, "",
+      "\x9c\x11\x85\xa5\xc5\xe9\xfc\x54\x61\x28"
+      "\x08\x97\x7e\xe8\xf5\x48\xb2\x25\x8d\x31" },
+    { GCRY_MD_RMD160, "a",
+      "\x0b\xdc\x9d\x2d\x25\x6b\x3e\xe9\xda\xae"
+      "\x34\x7b\xe6\xf4\xdc\x83\x5a\x46\x7f\xfe" },
+    { GCRY_MD_RMD160, "abc",
+      "\x8e\xb2\x08\xf7\xe0\x5d\x98\x7a\x9b\x04"
+      "\x4a\x8e\x98\xc6\xb0\x87\xf1\x5a\x0b\xfc" },
+    { GCRY_MD_RMD160, "message digest",
+      "\x5d\x06\x89\xef\x49\xd2\xfa\xe5\x72\xb8"
+      "\x81\xb1\x23\xa8\x5f\xfa\x21\x59\x5f\x36" },
+#if 0
+    { GCRY_MD_TIGER, "",
+      "\x24\xF0\x13\x0C\x63\xAC\x93\x32\x16\x16\x6E\x76"
+      "\xB1\xBB\x92\x5F\xF3\x73\xDE\x2D\x49\x58\x4E\x7A" },
+    { GCRY_MD_TIGER, "abc",
+      "\xF2\x58\xC1\xE8\x84\x14\xAB\x2A\x52\x7A\xB5\x41"
+      "\xFF\xC5\xB8\xBF\x93\x5F\x7B\x95\x1C\x13\x29\x51" },
+    { GCRY_MD_TIGER, "Tiger",
+      "\x9F\x00\xF5\x99\x07\x23\x00\xDD\x27\x6A\xBB\x38"
+      "\xC8\xEB\x6D\xEC\x37\x79\x0C\x11\x6F\x9D\x2B\xDF" },
+    { GCRY_MD_TIGER, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefg"
+      "hijklmnopqrstuvwxyz0123456789+-",
+      "\x87\xFB\x2A\x90\x83\x85\x1C\xF7\x47\x0D\x2C\xF8"
+      "\x10\xE6\xDF\x9E\xB5\x86\x44\x50\x34\xA5\xA3\x86" },
+    { GCRY_MD_TIGER, "ABCDEFGHIJKLMNOPQRSTUVWXYZ=abcdef"
+      "ghijklmnopqrstuvwxyz+0123456789",
+      "467DB80863EBCE488DF1CD1261655DE957896565975F9197" },
+    { GCRY_MD_TIGER, "Tiger - A Fast New Hash Function, "
+      "by Ross Anderson and Eli Biham",
+      "0C410A042968868A1671DA5A3FD29A725EC1E457D3CDB303" },
+    { GCRY_MD_TIGER, "Tiger - A Fast New Hash Function, "
+      "by Ross Anderson and Eli Biham, proceedings of Fa"
+      "st Software Encryption 3, Cambridge.",
+      "EBF591D5AFA655CE7F22894FF87F54AC89C811B6B0DA3193" },
+    { GCRY_MD_TIGER, "Tiger - A Fast New Hash Function, "
+      "by Ross Anderson and Eli Biham, proceedings of Fa"
+      "st Software Encryption 3, Cambridge, 1996.",
+      "3D9AEB03D1BD1A6357B2774DFD6D5B24DD68151D503974FC" },
+    { GCRY_MD_TIGER, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefgh"
+      "ijklmnopqrstuvwxyz0123456789+-ABCDEFGHIJKLMNOPQRS"
+      "TUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-",
+      "00B83EB4E53440C5 76AC6AAEE0A74858 25FD15E70A59FFE4" },
+#endif
+    { 0 }
+  };
+  int i;
+
+  for (i=0; algos[i].md; i++ ) 
+    {
+      if (verbose)
+        fprintf (stderr, "checking `%s'\n", gcry_md_algo_name (algos[i].md));
+                 
+      check_one_md (algos[i].md, algos[i].data, strlen(algos[i].data), 
+		    algos[i].expect);
+    }
+
+  /* TODO: test HMAC mode */
 }