{
  $Project$
  $Workfile$
  $Revision$
  $DateUTC$
  $Id$

  This file is part of the Indy (Internet Direct) project, and is offered
  under the dual-licensing agreement described on the Indy website.
  (http://www.indyproject.org/)

  Copyright:
   (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
}
{
  $Log$
}
{
  HMAC specification on the NIST website
  http://csrc.nist.gov/publications/fips/fips198/fips-198a.pdf
}

unit IdHMAC;

interface

{$i IdCompilerDefines.inc}

uses
  Classes,
  IdFIPS,
  IdGlobal, IdHash;

type
  TIdHMACKeyBuilder = class(TObject)
  public
    class function Key(const ASize: Integer) : TIdBytes;
    class function IV(const ASize: Integer) : TIdBytes;
  end;

  TIdHMAC = class
  protected
    FHashSize: Integer; // n bytes
    FBlockSize: Integer; // n bytes
    FKey: TIdBytes;
    FHash: TIdHash;
    FHashName: string;
    procedure InitHash; virtual; abstract;
    procedure InitKey;
    procedure SetHashVars; virtual; abstract;
    function HashValueNative(const ABuffer: TIdBytes; const ATruncateTo: Integer = -1) : TIdBytes; // for now, supply in bytes
    function HashValueIntF(const ABuffer: TIdBytes; const ATruncateTo: Integer = -1) : TIdBytes; // for now, supply in bytes
    function IsIntFAvail : Boolean; virtual;
    function InitIntFInst(const AKey : TIdBytes) : TIdHMACIntCtx; virtual; abstract;
  public
    constructor Create; virtual;
    destructor Destroy; override;
    function HashValue(const ABuffer: TIdBytes; const ATruncateTo: Integer = -1) : TIdBytes; // for now, supply in bytes
    property HashSize: Integer read FHashSize;
    property BlockSize: Integer read FBlockSize;
    property HashName: string read FHashName;
    property Key: TIdBytes read FKey write FKey;
  end;

implementation

uses
  SysUtils;

{ TIdHMACKeyBuilder }

class function TIdHMACKeyBuilder.Key(const ASize: Integer): TIdBytes;
var
  I: Integer;
begin
  SetLength(Result, ASize);
  for I := Low(Result) to High(Result) do begin
    Result[I] := Byte(Random(255));
  end;
end;

class function TIdHMACKeyBuilder.IV(const ASize: Integer): TIdBytes;
var
  I: Integer;
begin
  SetLength(Result, ASize);
  for I := Low(Result) to High(Result) do begin
    Result[I] := Byte(Random(255));
  end;
end;

{ TIdHMAC }

constructor TIdHMAC.Create;
begin
  inherited Create;
  SetLength(FKey, 0);
  SetHashVars;
  if IsHMACAvail then begin
     FHash := nil;
  end else begin
    InitHash;
  end;
end;

destructor TIdHMAC.Destroy;
begin
  FreeAndNil(FHash);
  inherited Destroy;
end;

function TIdHMAC.HashValueNative(const ABuffer: TIdBytes; const ATruncateTo: Integer = -1) : TIdBytes; // for now, supply in bytes
const
  CInnerPad : Byte = $36;
  COuterPad : Byte = $5C;
var
  TempBuffer1: TIdBytes;
  TempBuffer2: TIdBytes;
  LKey: TIdBytes;
  I: Integer;
begin
  InitKey;
  LKey := Copy(FKey, 0, MaxInt);
  SetLength(LKey, FBlockSize);
  SetLength(TempBuffer1, FBlockSize + Length(ABuffer));
  for I := Low(LKey) to High(LKey) do begin
    TempBuffer1[I] := LKey[I] xor CInnerPad;
  end;
  CopyTIdBytes(ABuffer, 0, TempBuffer1, Length(LKey), Length(ABuffer));
  TempBuffer2 := FHash.HashBytes(TempBuffer1);
  SetLength(TempBuffer1, 0);
  SetLength(TempBuffer1, FBlockSize + FHashSize);
  for I := Low(LKey) to High(LKey) do begin
    TempBuffer1[I] := LKey[I] xor COuterPad;
  end;
  CopyTIdBytes(TempBuffer2, 0, TempBuffer1, Length(LKey), Length(TempBuffer2));
  Result := FHash.HashBytes(TempBuffer1);
  SetLength(TempBuffer1, 0);
  SetLength(TempBuffer2, 0);
  SetLength(LKey, 0);
  if ATruncateTo > -1 then begin
    SetLength(Result, ATruncateTo);
  end;
end;

function TIdHMAC.HashValueIntF(const ABuffer: TIdBytes; const ATruncateTo: Integer = -1) : TIdBytes; // for now, supply in bytes
var
  LCtx : TIdHMACIntCtx;
begin
  if FKey = nil then begin
    FKey := TIdHMACKeyBuilder.Key(FHashSize);
  end;
  LCtx := InitIntFInst(FKey);
  try
    UpdateHMACInst(LCtx,ABuffer);
  finally
    Result := FinalHMACInst(LCtx);
  end;
  if (ATruncateTo >-1) and (ATruncateTo < Length(Result)) then begin
    SetLength(Result, ATruncateTo);
  end;
end;

function TIdHMAC.HashValue(const ABuffer: TIdBytes; const ATruncateTo: Integer = -1): TIdBytes; // for now, supply in bytes
begin
  if IsIntFAvail then begin
    Result := HashValueIntF(ABuffer,ATruncateTo);
  end else begin
    Result := HashValueNative(ABuffer,ATruncateTo);
  end;
end;

procedure TIdHMAC.InitKey;
begin
  if FKey = nil then begin
    FKey := TIdHMACKeyBuilder.Key(FHashSize);
  end
  else if Length(FKey) > FBlockSize then begin
    FKey := FHash.HashBytes(FKey);
  end;
end;

function TIdHMAC.IsIntFAvail: Boolean;
begin
  Result := IsHMACAvail;
end;

initialization
  Randomize;

end.
