Table of Contents

Using Tiger

Tiger is a 1995 cryptographic hash by Anderson and Biham, optimized for 64-bit platforms and widely deployed in file-transfer and content-addressing systems (most famously Direct Connect's Tiger Tree Hash). It processes 64-byte blocks and produces a 192-bit digest by default, with 160-bit and 128-bit truncations available.

Tiger derives from HashAlgorithm, so any API that accepts a standard .NET hash accepts Tiger.

For new work where interoperability isn't a constraint, prefer the BCL's hardware-accelerated System.Security.Cryptography.SHA256 or System.Security.Cryptography.SHA512. Use Tiger when you need to match an existing Tiger-based system, or as part of a Tiger Tree Hash (see the Merkle tree guide).

Fixed sizes at a glance

Parameter Size Notes
Block size 64 bytes (512 bits) Fixed.
Output 192 bits (default), 160 bits, or 128 bits Configurable via HashSize.
Variant TigerHashingVariant.Tiger (padding byte 0x01) or .Tiger2 (padding byte 0x80) Default is Tiger.

Pattern 1 - a default 192-bit digest

using System.Text;
using Bodu.Security.Cryptography;

byte[] data = Encoding.UTF8.GetBytes("the quick brown fox");

using var tiger = new Tiger();                  // 192-bit, Tiger variant
byte[] digest   = tiger.ComputeHash(data);      // 24 bytes
string hex      = Convert.ToHexString(digest);

This matches the digest that a reference Tiger implementation would produce for the same input - 24 bytes, in the standard output layout.

Pattern 2 - choose a truncation

Tiger accepts three output widths. 192 is the "real" Tiger; 160 and 128 are truncations of the same 192-bit internal state, useful when an external protocol fixes the digest size.

using Bodu.Security.Cryptography;

using var tiger128 = new Tiger(hashSize: 128);  // 16-byte digest
using var tiger160 = new Tiger(hashSize: 160);  // 20-byte digest
using var tiger192 = new Tiger(hashSize: 192);  // 24-byte digest (the default)

HashSize is also settable on an existing instance, but only before the first block is processed:

using var tiger = new Tiger { HashSize = 160 };
byte[] digest = tiger.ComputeHash(data);

Attempting to change HashSize after ComputeHash / TransformBlock has begun throws - the schedule is fixed at the first call.

Pattern 3 - Tiger vs Tiger2

Tiger's original specification used a padding byte of 0x01. A later clarification (Tiger2) changed the padding byte to 0x80 to match the convention used by SHA-2 and other Merkle-Damgård hashes. The two variants produce different digests for the same input.

using Bodu.Security.Cryptography;

using var tiger  = new Tiger  { Variant = TigerHashingVariant.Tiger  };   // default
using var tiger2 = new Tiger  { Variant = TigerHashingVariant.Tiger2 };

byte[] d1 = tiger .ComputeHash(data);
byte[] d2 = tiger2.ComputeHash(data);
// d1 != d2 - different padding byte → different digest.

Match the variant to whatever the interoperating system speaks. AlgorithmName reflects the configured hash size (it follows the Tiger/{bits} convention regardless of variant; include the variant alongside it when logging):

using var tiger = new Tiger { Variant = TigerHashingVariant.Tiger2, HashSize = 160 };
Console.WriteLine($"{tiger.AlgorithmName} ({tiger.Variant})");   // "Tiger/160 (Tiger2)"

Pattern 4 - streaming a file

Tiger plugs into any BCL API that takes a HashAlgorithm:

using Bodu.Security.Cryptography;

using var tiger = new Tiger();

using var stream = File.OpenRead("archive.bin");
byte[] digest = tiger.ComputeHash(stream);

For larger files where you want to verify ranges of the file without rehashing the whole thing, pair Tiger with MerkleTree to build a Tiger Tree Hash - see the Merkle trees guide.

To feed several discontiguous spans into one digest, use the TransformBlock / TransformFinalBlock pair, or the HashAlgorithmExtensions AppendData helper (which takes a ReadOnlySpan<byte>):

using Bodu.Security.Cryptography;
using Bodu.Security.Cryptography.Extensions;

using var tiger = new Tiger();
tiger.AppendData(header);
tiger.AppendData(body);
tiger.TransformFinalBlock(Array.Empty<byte>(), 0, 0);

byte[] digest = tiger.Hash!;

Pattern 5 - verifying a digest

Compare digests in constant time. The VerifyHash helper in Bodu.Security.Cryptography.Extensions wraps CryptographicOperations.FixedTimeEquals:

using Bodu.Security.Cryptography;
using Bodu.Security.Cryptography.Extensions;

byte[] expected = LoadExpectedDigest("report.pdf");

using var tiger = new Tiger();
bool ok = tiger.VerifyHash(fileBytes, expected);

A plain SequenceEqual leaks timing information and is unsafe when the result drives an authentication decision.

Security caveats

  • Status. Tiger is unbroken for pre-image and second-pre-image resistance. A 1-bit-rotation collision attack on reduced-round Tiger exists in the literature, and the 192-bit width gives only 96-bit collision resistance by the birthday bound - comfortably above brute force today, but below the 128-bit collision margin a 256-bit hash provides. For long-lived collision-critical commitments prefer SHA-256 or wider.
  • Output width vs security level. An n-bit digest gives ≈ n-bit pre-image resistance and ≈ n/2-bit collision resistance. The 128- and 160-bit truncations are correspondingly weaker against collisions - choose them only when an external protocol fixes the width.
  • Length extension. Tiger is a Merkle-Damgård design and inherits the length-extension property. Do not authenticate with key ‖ message; use HMAC-Tiger, or reach for a native keyed hash (Skein, BLAKE2b) or a MAC (SipHash, Poly1305).

When to use Tiger

  • Interoperability with systems that already use Tiger - Direct Connect, the tth: URN scheme, older archive formats.
  • Tiger Tree Hash (Merkle tree of Tiger leaves) for content-addressable stores - pair with MerkleTree.
  • Educational or research settings where the round structure and S-boxes are the object of study.

For new work without an interoperability constraint, prefer SHA-2 or SHA-3 from the BCL - both are hardware-accelerated on modern CPUs and have broader analysis behind them.

Where to go next