{******************************************************************************}
{* DCPcrypt v2.0 written by David Barton (crypto@cityinthesky.co.uk) **********}
{******************************************************************************}
{* Main component definitions *************************************************}
{******************************************************************************}
{* Copyright (C) 1999-2003 David Barton                                       *}
{* Copyright (C) 2018 Alexander Koblov (alexx2000@mail.ru)                    *}
{* Permission is hereby granted, free of charge, to any person obtaining a    *}
{* copy of this software and associated documentation files (the "Software"), *}
{* to deal in the Software without restriction, including without limitation  *}
{* the rights to use, copy, modify, merge, publish, distribute, sublicense,   *}
{* and/or sell copies of the Software, and to permit persons to whom the      *}
{* Software is furnished to do so, subject to the following conditions:       *}
{*                                                                            *}
{* The above copyright notice and this permission notice shall be included in *}
{* all copies or substantial portions of the Software.                        *}
{*                                                                            *}
{* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *}
{* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,   *}
{* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL    *}
{* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *}
{* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING    *}
{* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER        *}
{* DEALINGS IN THE SOFTWARE.                                                  *}
{******************************************************************************}
unit DCPcrypt2;

{$MODE Delphi}

interface
uses
  Classes, Sysutils, DCPbase64;

//{$DEFINE DCP1COMPAT}  { DCPcrypt v1.31 compatiblity mode - see documentation }

{******************************************************************************}
    { A few predefined types to help out }

type
  {$IFNDEF FPC}
  Pbyte= ^byte;
  Pword= ^word;
  Pdword= ^dword;
  Pint64= ^int64;
  dword= longword;
  Pwordarray= ^Twordarray;
  Twordarray= array[0..19383] of word;
  {$ENDIF}
  Pdwordarray= ^Tdwordarray;
  Tdwordarray= array[0..8191] of dword;


{******************************************************************************}
    { The base class from which all hash algorithms are to be derived  }

type
  EDCP_hash= class(Exception);
  TDCP_hash= class(TComponent)
  protected
    fInitialized: boolean;  { Whether or not the algorithm has been initialized }

    procedure DeadInt(Value: integer);   { Knudge to display vars in the object inspector   }
    procedure DeadStr(Value: string);    { Knudge to display vars in the object inspector   }
  
  private
    function _GetId: integer;
    function _GetAlgorithm: string;
    function _GetHashSize: integer; 

  public
    property Initialized: boolean
      read fInitialized;

    class function GetId: integer; virtual;
      { Get the algorithm id }
    class function GetAlgorithm: string; virtual;
      { Get the algorithm name }
    class function GetHashSize: integer; virtual;
      { Get the size of the digest produced - in bits }
    class function SelfTest: boolean; virtual;
      { Tests the implementation with several test vectors }

    procedure Init; virtual;
      { Initialize the hash algorithm }
    procedure Final(var Digest); virtual;
      { Create the final digest and clear the stored information.
        The size of the Digest var must be at least equal to the hash size }
    procedure Burn; virtual;
      { Clear any stored information with out creating the final digest }

    procedure Update(const Buffer; Size: longword); virtual;
      { Update the hash buffer with Size bytes of data from Buffer }
    procedure UpdateStream(Stream: TStream; Size: QWord);
      { Update the hash buffer with Size bytes of data from the stream }
    procedure UpdateStr(const Str: string);
      { Update the hash buffer with the string }

    destructor Destroy; override;

  published
    property Id: integer
      read _GetId write DeadInt;
    property Algorithm: string
      read _GetAlgorithm write DeadStr;
    property HashSize: integer
      read _GetHashSize write DeadInt;
  end;
  TDCP_hashclass= class of TDCP_hash;


{******************************************************************************}
    { The base class from which all encryption components will be derived. }
    { Stream ciphers will be derived directly from this class where as     }
    { Block ciphers will have a further foundation class TDCP_blockcipher. }

type
  EDCP_cipher= class(Exception);
  TDCP_cipher= class(TComponent)
  protected
    fInitialized: boolean;  { Whether or not the key setup has been done yet }

    procedure DeadInt(Value: integer);   { Knudge to display vars in the object inspector   }
    procedure DeadStr(Value: string);    { Knudge to display vars in the object inspector   }

  private
    function _GetId: integer;
    function _GetAlgorithm: string;
    function _GetMaxKeySize: integer; 

  public
    property Initialized: boolean
      read fInitialized;

    class function GetId: integer; virtual;
      { Get the algorithm id }
    class function GetAlgorithm: string; virtual;
      { Get the algorithm name }
    class function GetMaxKeySize: integer; virtual;
      { Get the maximum key size (in bits) }
    class function SelfTest: boolean; virtual;
      { Tests the implementation with several test vectors }

    procedure Init(const Key; Size: longword; InitVector: pointer); virtual;
      { Do key setup based on the data in Key, size is in bits }
    procedure InitStr(const Key: string; HashType: TDCP_hashclass);
      { Do key setup based on a hash of the key string }
    procedure Burn; virtual;
      { Clear all stored key information }
    procedure Reset; virtual;
      { Reset any stored chaining information }
    procedure Encrypt(const Indata; var Outdata; Size: longword); virtual;
      { Encrypt size bytes of data and place in Outdata }
    procedure Decrypt(const Indata; var Outdata; Size: longword); virtual;
      { Decrypt size bytes of data and place in Outdata }
    function EncryptStream(InStream, OutStream: TStream; Size: longword): longword;
      { Encrypt size bytes of data from InStream and place in OutStream }
    function DecryptStream(InStream, OutStream: TStream; Size: longword): longword;
      { Decrypt size bytes of data from InStream and place in OutStream }
    function EncryptString(const Str: string): string; virtual;
      { Encrypt a string and return Base64 encoded }
    function DecryptString(const Str: string): string; virtual;
      { Decrypt a Base64 encoded string }

    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

  published
    property Id: integer
      read _GetId write DeadInt;
    property Algorithm: string
      read _GetAlgorithm write DeadStr;
    property MaxKeySize: integer
      read _GetMaxKeySize write DeadInt;
  end;
  TDCP_cipherclass= class of TDCP_cipher;


{******************************************************************************}
    { The base class from which all block ciphers are to be derived, this  }
    { extra class takes care of the different block encryption modes.      }

type
  TDCP_ciphermode= (cmCBC, cmCFB8bit, cmCFBblock, cmOFB, cmCTR); // cmCFB8bit is equal to DCPcrypt v1.xx's CFB mode
  EDCP_blockcipher= class(EDCP_cipher);
  TDCP_blockcipher= class(TDCP_cipher)
  protected
    fCipherMode: TDCP_ciphermode;  { The cipher mode the encrypt method uses  }

    procedure InitKey(const Key; Size: longword); virtual;

  private
    function _GetBlockSize: integer;

  public
    class function GetBlockSize: integer; virtual;
      { Get the block size of the cipher (in bits) }

    procedure SetIV(const Value); virtual;
      { Sets the IV to Value and performs a reset }
    procedure GetIV(var Value); virtual;
      { Returns the current chaining information, not the actual IV }

    procedure Encrypt(const Indata; var Outdata; Size: longword); override;
      { Encrypt size bytes of data and place in Outdata using CipherMode }
    procedure Decrypt(const Indata; var Outdata; Size: longword); override;
      { Decrypt size bytes of data and place in Outdata using CipherMode }
    function EncryptString(const Str: string): string; override;
      { Encrypt a string and return Base64 encoded }
    function DecryptString(const Str: string): string; override;
      { Decrypt a Base64 encoded string }
    procedure EncryptECB(const Indata; var Outdata); virtual; 
      { Encrypt a block of data using the ECB method of encryption }
    procedure DecryptECB(const Indata; var Outdata); virtual; 
      { Decrypt a block of data using the ECB method of decryption }
    procedure EncryptCBC(const Indata; var Outdata; Size: longword); virtual; 
      { Encrypt size bytes of data using the CBC method of encryption }
    procedure DecryptCBC(const Indata; var Outdata; Size: longword); virtual; 
      { Decrypt size bytes of data using the CBC method of decryption }
    procedure EncryptCFB8bit(const Indata; var Outdata; Size: longword); virtual; 
      { Encrypt size bytes of data using the CFB (8 bit) method of encryption }
    procedure DecryptCFB8bit(const Indata; var Outdata; Size: longword); virtual; 
      { Decrypt size bytes of data using the CFB (8 bit) method of decryption }
    procedure EncryptCFBblock(const Indata; var Outdata; Size: longword); virtual; 
      { Encrypt size bytes of data using the CFB (block) method of encryption }
    procedure DecryptCFBblock(const Indata; var Outdata; Size: longword); virtual; 
      { Decrypt size bytes of data using the CFB (block) method of decryption }
    procedure EncryptOFB(const Indata; var Outdata; Size: longword); virtual; 
      { Encrypt size bytes of data using the OFB method of encryption }
    procedure DecryptOFB(const Indata; var Outdata; Size: longword); virtual; 
      { Decrypt size bytes of data using the OFB method of decryption }
    procedure EncryptCTR(const Indata; var Outdata; Size: longword); virtual; 
      { Encrypt size bytes of data using the CTR method of encryption }
    procedure DecryptCTR(const Indata; var Outdata; Size: longword); virtual;
      { Decrypt size bytes of data using the CTR method of decryption }

    constructor Create(AOwner: TComponent); override;

  published
    property BlockSize: integer
      read _GetBlockSize write DeadInt;
    property CipherMode: TDCP_ciphermode
      read fCipherMode write fCipherMode default cmCBC;
  end;
  TDCP_blockcipherclass= class of TDCP_blockcipher;


{******************************************************************************}
    { Helper functions }

procedure XorBlock(var InData1, InData2; Size: longword);
// Supposed to be an optimized version of XorBlock() using 32-bit xor
procedure XorBlockEx(var InData1, InData2; Size: longword);
// removes the compiler hint due to first param being 'var' instead of 'out'
procedure dcpFillChar(out x; count: SizeInt; Value: Byte); overload;
procedure dcpFillChar(out x; count: SizeInt; Value: Char); overload;
procedure ZeroMemory(Destination: Pointer; Length: PtrUInt);

{$IF DEFINED(CPUX86_64)}

function BMI2Support: LongBool;
function SSSE3Support: LongBool;

{$ENDIF}

implementation

{$Q-}{$R-}


{** TDCP_hash *****************************************************************}

procedure TDCP_hash.DeadInt(Value: integer);
begin
end;

procedure TDCP_hash.DeadStr(Value: string);
begin
end;

function TDCP_hash._GetId: integer;
begin
  Result:= GetId;
end;

function TDCP_hash._GetAlgorithm: string;
begin
  Result:= GetAlgorithm;
end;

function TDCP_hash._GetHashSize: integer;
begin
  Result:= GetHashSize;
end; 

class function TDCP_hash.GetId: integer;
begin
  Result:= -1;
end;

class function TDCP_hash.GetAlgorithm: string;
begin
  Result:= '';
end;

class function TDCP_hash.GetHashSize: integer;
begin
  Result:= -1;
end;

class function TDCP_hash.SelfTest: boolean;
begin
  Result:= false;
end;

procedure TDCP_hash.Init;
begin
end;

procedure TDCP_hash.Final(var Digest);
begin
end;

procedure TDCP_hash.Burn;
begin
end;

procedure TDCP_hash.Update(const Buffer; Size: longword);
begin
end;

procedure TDCP_hash.UpdateStream(Stream: TStream; Size: QWord);
var
  Buffer: array[0..8191] of byte;
  i, read: integer;
begin
  dcpFillChar(Buffer, SizeOf(Buffer), 0);
  for i:= 1 to (Size div Sizeof(Buffer)) do
  begin
    read:= Stream.Read(Buffer,Sizeof(Buffer));
    Update(Buffer,read);
  end;
  if (Size mod Sizeof(Buffer))<> 0 then
  begin
    read:= Stream.Read(Buffer,Size mod Sizeof(Buffer));
    Update(Buffer,read);
  end;
end;

procedure TDCP_hash.UpdateStr(const Str: string);
begin
  Update(Str[1],Length(Str));
end;

destructor TDCP_hash.Destroy;
begin
  if fInitialized then
    Burn;
  inherited Destroy;
end;


{** TDCP_cipher ***************************************************************}

procedure TDCP_cipher.DeadInt(Value: integer);
begin
end;

procedure TDCP_cipher.DeadStr(Value: string);
begin
end;

function TDCP_cipher._GetId: integer;
begin
  Result:= GetId;
end;

function TDCP_cipher._GetAlgorithm: string;
begin
  Result:= GetAlgorithm;
end;

function TDCP_cipher._GetMaxKeySize: integer;
begin
  Result:= GetMaxKeySize;
end; 

class function TDCP_cipher.GetId: integer;
begin
  Result:= -1;
end;

class function TDCP_cipher.GetAlgorithm: string;
begin
  Result:= '';
end;

class function TDCP_cipher.GetMaxKeySize: integer;
begin
  Result:= -1;
end;

class function TDCP_cipher.SelfTest: boolean;
begin
  Result:= false;
end;

procedure TDCP_cipher.Init(const Key; Size: longword; InitVector: pointer);
begin
  if fInitialized then
    Burn;
  if (Size <= 0) or ((Size and 3)<> 0) or (Size> longword(GetMaxKeySize)) then
    raise EDCP_cipher.Create('Invalid key size')
  else
    fInitialized:= true;
end;

procedure TDCP_cipher.InitStr(const Key: string; HashType: TDCP_hashclass);
var
  Hash: TDCP_hash;
  Digest: pointer;
begin
  if fInitialized then
    Burn;
  try
    GetMem(Digest,HashType.GetHashSize div 8);
    Hash:= HashType.Create(Self);
    Hash.Init;
    Hash.UpdateStr(Key);
    Hash.Final(Digest^);
    Hash.Free;
    if MaxKeySize< HashType.GetHashSize then
    begin
      Init(Digest^,MaxKeySize,nil);
    end
    else
    begin
      Init(Digest^,HashType.GetHashSize,nil);
    end;
    FillChar(Digest^,HashType.GetHashSize div 8,$FF);
    FreeMem(Digest);
  except
    raise EDCP_cipher.Create('Unable to allocate sufficient memory for hash digest');
  end;
end;

procedure TDCP_cipher.Burn;
begin
  fInitialized:= false;
end;

procedure TDCP_cipher.Reset;
begin
end;

procedure TDCP_cipher.Encrypt(const Indata; var Outdata; Size: longword);
begin
end;

procedure TDCP_cipher.Decrypt(const Indata; var Outdata; Size: longword);
begin
end;

function TDCP_cipher.EncryptStream(InStream, OutStream: TStream; Size: longword): longword;
var
  Buffer: array[0..8191] of byte;
  i, Read: longword;
begin
  dcpFillChar(Buffer, SizeOf(Buffer), 0);
  Result:= 0;
  for i:= 1 to (Size div Sizeof(Buffer)) do
  begin
    Read:= InStream.Read(Buffer,Sizeof(Buffer));
    Inc(Result,Read);
    Encrypt(Buffer,Buffer,Read);
    OutStream.Write(Buffer,Read);
  end;
  if (Size mod Sizeof(Buffer))<> 0 then
  begin
    Read:= InStream.Read(Buffer,Size mod Sizeof(Buffer));
    Inc(Result,Read);
    Encrypt(Buffer,Buffer,Read);
    OutStream.Write(Buffer,Read);
  end;
end;

function TDCP_cipher.DecryptStream(InStream, OutStream: TStream; Size: longword): longword;
var
  Buffer: array[0..8191] of byte;
  i, Read: longword;
begin
  dcpFillChar(Buffer, SizeOf(Buffer), 0);
  Result:= 0;
  for i:= 1 to (Size div Sizeof(Buffer)) do
  begin
    Read:= InStream.Read(Buffer,Sizeof(Buffer));
    Inc(Result,Read);
    Decrypt(Buffer,Buffer,Read);
    OutStream.Write(Buffer,Read);
  end;
  if (Size mod Sizeof(Buffer))<> 0 then
  begin
    Read:= InStream.Read(Buffer,Size mod Sizeof(Buffer));
    Inc(Result,Read);
    Decrypt(Buffer,Buffer,Read);
    OutStream.Write(Buffer,Read);
  end;
end;

function TDCP_cipher.EncryptString(const Str: string): string;
begin
  SetLength(Result,Length(Str));
  Encrypt(Str[1],Result[1],Length(Str));
  Result:= Base64EncodeStr(Result);
end;

function TDCP_cipher.DecryptString(const Str: string): string;
begin
  Result:= Base64DecodeStr(Str);
  Decrypt(Result[1],Result[1],Length(Result));
end;

constructor TDCP_cipher.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  Burn;
end;

destructor TDCP_cipher.Destroy;
begin
  if fInitialized then
    Burn;
  inherited Destroy;
end;


{** TDCP_blockcipher **********************************************************}

procedure TDCP_blockcipher.InitKey(const Key; Size: longword);
begin
end;

function TDCP_blockcipher._GetBlockSize: integer;
begin
  Result:= GetBlockSize;
end;

class function TDCP_blockcipher.GetBlockSize: integer;
begin
  Result:= -1;
end;

procedure TDCP_blockcipher.SetIV(const Value);
begin
end;

procedure TDCP_blockcipher.GetIV(var Value);
begin
end;

procedure TDCP_blockcipher.Encrypt(const Indata; var Outdata; Size: longword);
begin
  case fCipherMode of
    cmCBC: EncryptCBC(Indata,Outdata,Size);
    cmCFB8bit: EncryptCFB8bit(Indata,Outdata,Size);
    cmCFBblock: EncryptCFBblock(Indata,Outdata,Size);
    cmOFB: EncryptOFB(Indata,Outdata,Size);
    cmCTR: EncryptCTR(Indata,Outdata,Size);
  end;
end;

function TDCP_blockcipher.EncryptString(const Str: string): string;
begin
  SetLength(Result,Length(Str));
  EncryptCFB8bit(Str[1],Result[1],Length(Str));
  Result:= Base64EncodeStr(Result);
end;

function TDCP_blockcipher.DecryptString(const Str: string): string;
begin
  Result:= Base64DecodeStr(Str);
  DecryptCFB8bit(Result[1],Result[1],Length(Result));
end;

procedure TDCP_blockcipher.Decrypt(const Indata; var Outdata; Size: longword);
begin
  case fCipherMode of
    cmCBC: DecryptCBC(Indata,Outdata,Size);
    cmCFB8bit: DecryptCFB8bit(Indata,Outdata,Size);
    cmCFBblock: DecryptCFBblock(Indata,Outdata,Size);
    cmOFB: DecryptOFB(Indata,Outdata,Size);
    cmCTR: DecryptCTR(Indata,Outdata,Size);
  end;
end;

procedure TDCP_blockcipher.EncryptECB(const Indata; var Outdata);
begin
end;

procedure TDCP_blockcipher.DecryptECB(const Indata; var Outdata);
begin
end;

procedure TDCP_blockcipher.EncryptCBC(const Indata; var Outdata; Size: longword);
begin
end;

procedure TDCP_blockcipher.DecryptCBC(const Indata; var Outdata; Size: longword);
begin
end;

procedure TDCP_blockcipher.EncryptCFB8bit(const Indata; var Outdata; Size: longword);
begin
end;

procedure TDCP_blockcipher.DecryptCFB8bit(const Indata; var Outdata; Size: longword);
begin
end;

procedure TDCP_blockcipher.EncryptCFBblock(const Indata; var Outdata; Size: longword);
begin
end;

procedure TDCP_blockcipher.DecryptCFBblock(const Indata; var Outdata; Size: longword);
begin
end;

procedure TDCP_blockcipher.EncryptOFB(const Indata; var Outdata; Size: longword);
begin
end;

procedure TDCP_blockcipher.DecryptOFB(const Indata; var Outdata; Size: longword);
begin
end;

procedure TDCP_blockcipher.EncryptCTR(const Indata; var Outdata; Size: longword);
begin
end;

procedure TDCP_blockcipher.DecryptCTR(const Indata; var Outdata; Size: longword);
begin
end;

constructor TDCP_blockcipher.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  fCipherMode:= cmCBC;
end;


{** Helpher functions *********************************************************}
procedure XorBlock(var InData1, InData2; Size: longword);
var
  b1: PByteArray;
  b2: PByteArray;
  i: longword;
begin
  b1 := @InData1;
  b2 := @InData2;
  for i := 0 to size-1 do
    b1[i] := b1[i] xor b2[i];
end;

procedure dcpFillChar(out x; count: SizeInt; Value: Byte);
begin
  {$HINTS OFF}
  FillChar(x, count, value);
  {$HINTS ON}
end;

procedure ZeroMemory(Destination: Pointer; Length: PtrUInt);
begin
  FillChar(Destination^, Length, 0);
end;

procedure dcpFillChar(out x; count: SizeInt; Value: Char);
begin
  {$HINTS OFF}
  FillChar(x, count, Value);
  {$HINTS ON}
end;

// Supposed to be an optimized version of XorBlock() using 32-bit xor
procedure XorBlockEx(var InData1, InData2; Size: longword);
var
  l1: PIntegerArray;
  l2: PIntegerArray;
  b1: PByteArray;
  b2: PByteArray;
  i: integer;
  c: integer;
begin
  l1 := @inData1;
  l2 := @inData2;
  for i := 0 to size div sizeof(LongWord)-1 do
    l1[i] := l1[i] xor l2[i];

  // the rest of the buffer (3 bytes)
  c := size mod sizeof(longWord);
  if c > 0 then begin
    b1 := @InData1;
    b2 := @InData2;
    for i := (size-c) to size-1 do
      b1[i] := b1[i] xor b2[i];
  end;
end;

{$IF DEFINED(CPUX86_64)}

function BMI2Support: LongBool; assembler; nostackframe;
asm
  pushq %rbx
  movl $7,%eax
  movl $0,%ecx
  cpuid
  andl $256,%ebx
  movl %ebx,%eax
  popq %rbx
end;

function SSSE3Support: LongBool; assembler; nostackframe;
asm
  pushq %rbx
  movl $1,%eax
  cpuid
  andl $512,%ecx
  movl %ecx,%eax
  popq %rbx
end;

{$ENDIF}

end.

