xtool/contrib/fundamentals/Cipher/flcCipher.pas

1298 lines
44 KiB
ObjectPascal

{******************************************************************************}
{ }
{ Library: Fundamentals 5.00 }
{ File name: flcCipher.pas }
{ File version: 5.11 }
{ Description: Cipher library }
{ }
{ Copyright: Copyright (c) 2007-2020, David J Butler }
{ All rights reserved. }
{ This file is licensed under the BSD License. }
{ See http://www.opensource.org/licenses/bsd-license.php }
{ Redistribution and use in source and binary forms, with }
{ or without modification, are permitted provided that }
{ the following conditions are met: }
{ Redistributions of source code must retain the above }
{ copyright notice, this list of conditions and the }
{ following disclaimer. }
{ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND }
{ CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED }
{ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED }
{ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A }
{ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL }
{ THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, }
{ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR }
{ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, }
{ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF }
{ USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) }
{ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER }
{ IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING }
{ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE }
{ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE }
{ POSSIBILITY OF SUCH DAMAGE. }
{ }
{ Github: https://github.com/fundamentalslib }
{ E-mail: fundamentals.library at gmail.com }
{ }
{ Revision history: }
{ }
{ 2007/01/05 0.01 Initial version }
{ 2007/01/07 0.02 ECB and padding support }
{ 2008/06/15 0.03 CBC mode }
{ 2008/06/17 0.04 CFB and OFB modes }
{ 2010/11/18 4.05 Revision }
{ 2010/12/16 4.06 AES cipher }
{ 2011/08/09 4.07 Revision }
{ 2015/03/21 4.08 Revision }
{ 2016/01/09 5.09 Revised for Fundamentals 5. }
{ 2018/06/17 5.10 Type changes. }
{ 2019/06/09 5.11 Tests for Triple-DES-EDE-3. }
{ }
{ Supported compilers: }
{ }
{ Delphi 5 Win32 4.08 2015/03/31 }
{ Delphi 7 Win32 5.10 2019/02/24 }
{ Delphi XE7 Win32 5.09 2016/01/09 }
{ Delphi XE7 Win64 5.09 2016/01/09 }
{ Delphi 10 Win32 5.09 2016/01/09 }
{ Delphi 10 Win64 5.09 2016/01/09 }
{ }
{ Todo: }
{ - Split out tests }
{******************************************************************************}
{.DEFINE Cipher_SupportRSA}
{$INCLUDE flcCipher.inc}
unit flcCipher;
interface
uses
{ System }
SysUtils,
{ Fundamentals }
flcStdTypes,
flcUtils,
{ Cipher }
flcCipherUtils,
flcCipherRC2,
flcCipherRC4,
flcCipherDES,
flcCipherAES
{$IFDEF Cipher_SupportRSA},
flcCipherRSA
{$ENDIF};
{ }
{ Cipher information }
{ }
type
TCipherType = (
ctNone,
ctRC2, // RC2
ctRC4, // RC4
ctDES, // DES
ctDES40, // DES-40
ctTripleDESEDE, // Triple-DES-EDE 2 keys
ctTripleDES3EDE, // Triple-DES-EDE 3 keys
ctTripleDES3EEE, // Triple-DES-EEE 3 keys
ctAES, // AES-128, AES-192, AES-256
ctRSAOAEP1024 // RSA-OAEP-1024
);
TCipherOperation = (
coEncrypt,
coDecrypt);
TCipherInitFunc =
procedure (const Operation: TCipherOperation;
const KeyBuffer: Pointer; const KeyBufferSize: Integer;
const KeyBits: Integer;
const ContextBuffer: Pointer);
TCipherBufferFunc =
procedure (const Context, Data: Pointer; const DataSize: Integer);
TCipherFinaliseFunc =
procedure (const Context: Pointer);
TCipherBufferMode = (
cbmStream,
cbmMultipleBlocks,
cbmSingleBlock);
TCipherInfo = record
ValidCipher : Boolean;
ShortName : RawByteString;
BufferMode : TCipherBufferMode;
InBlockSize : Integer;
OutBlockSize : Integer;
KeyBitsMin : Integer;
KeyBitsMax : Integer;
ContextSize : Integer;
InitFunc : TCipherInitFunc;
EncryptFunc : TCipherBufferFunc;
DecryptFunc : TCipherBufferFunc;
FinaliseFunc : TCipherFinaliseFunc;
end;
PCipherInfo = ^TCipherInfo;
function GetCipherInfo(const Cipher: TCipherType): PCipherInfo;
{ }
{ Cipher }
{ }
type
TCipherMode = (
cmECB, // Electronic Codebook
cmCBC, // Cipher Block Chaining
cmCFB, // Cipher Feedback
cmOFB // Output Feedback
);
TCipherPadding = (
cpNone, // Padded with zeros (size of decrypted text may be longer
// than original text if original text size is not a
// multiple of cipher's block size; used with test cases)
cpPadSizeByte, // Padded with zeros and pad size byte
cpPadPKCS5); // Padded with pad size byte repeated
const
MaxCipherContextSize = 384;
type
TCipherState = record
Operation : TCipherOperation;
Cipher : TCipherType;
Mode : TCipherMode;
Padding : TCipherPadding;
KeyBits : Integer;
CipherInfo : PCipherInfo;
Context : array[0..MaxCipherContextSize - 1] of Byte;
end;
const
CipherStateSize = SizeOf(TCipherState);
{ }
{ Encrypt }
{ }
procedure EncryptInit(
var State: TCipherState;
const Cipher: TCipherType; const Mode: TCipherMode;
const Padding: TCipherPadding;
const KeyBits: Integer;
const KeyBuffer: Pointer; const KeyBufferSize: Integer);
function EncryptBuf(
var State: TCipherState;
const InputBuffer: Pointer; const InputBufferSize: Integer;
const OutputBuffer: Pointer; const OutputBufferSize: Integer;
const InitVectorBuffer: Pointer; const InitVectorBufferSize: Integer): Integer;
procedure EncryptFinalise(var State: TCipherState);
function EncryptedSize(
const Cipher: TCipherType; const Padding: TCipherPadding;
const BufferSize: Integer): Integer;
function Encrypt(
const Cipher: TCipherType; const Mode: TCipherMode;
const Padding: TCipherPadding;
const KeyBits: Integer;
const KeyBuffer: Pointer; const KeyBufferSize: Integer;
const InputBuffer: Pointer; const InputBufferSize: Integer;
const OutputBuffer: Pointer; const OutputBufferSize: Integer;
const InitVectorBuffer: Pointer; const InitVectorBufferSize: Integer): Integer; overload;
function Encrypt(
const Cipher: TCipherType; const Mode: TCipherMode;
const Padding: TCipherPadding;
const KeyBits: Integer;
const Key, Data, InitVector: RawByteString): RawByteString; overload;
{ }
{ Decrypt }
{ }
procedure DecryptInit(
var State: TCipherState;
const Cipher: TCipherType; const Mode: TCipherMode;
const Padding: TCipherPadding;
const KeyBits: Integer;
const KeyBuffer: Pointer; const KeyBufferSize: Integer);
function DecryptBuf(
var State: TCipherState;
const Buffer: Pointer; const BufferSize: Integer;
const InitVectorBuffer: Pointer; const InitVectorBufferSize: Integer): Integer;
procedure DecryptFinalise(var State: TCipherState);
function Decrypt(
const Cipher: TCipherType; const Mode: TCipherMode;
const Padding: TCipherPadding;
const KeyBits: Integer;
const KeyBuffer: Pointer; const KeyBufferSize: Integer;
const Buffer: Pointer; const BufferSize: Integer;
const InitVectorBuffer: Pointer; const InitVectorBufferSize: Integer): Integer; overload;
function Decrypt(
const Cipher: TCipherType; const Mode: TCipherMode;
const Padding: TCipherPadding;
const KeyBits: Integer;
const Key, Data, InitVector: RawByteString): RawByteString; overload;
{ }
{ Test cases }
{ }
{$IFDEF CIPHER_TEST}
procedure Test;
{$ENDIF}
implementation
uses
{ Fundamentals }
flcHugeInt,
{ Cipher }
flcCipherRandom;
{ }
{ RC2 cipher }
{ }
const
RC2ContextSize = Sizeof(TRC2CipherKey);
procedure CipherRC2Init(const Operation: TCipherOperation;
const KeyBuffer: Pointer; const KeyBufferSize: Integer;
const KeyBits: Integer; const ContextBuffer: Pointer);
begin
RC2Init(KeyBuffer^, KeyBufferSize, KeyBits, PRC2CipherKey(ContextBuffer)^);
end;
procedure CipherRC2EncryptBuffer(const Context, Data: Pointer; const DataSize: Integer);
begin
Assert(DataSize = RC2BlockSize);
RC2EncryptBlock(PRC2CipherKey(Context)^, PRC2Block(Data)^);
end;
procedure CipherRC2DecryptBuffer(const Context, Data: Pointer; const DataSize: Integer);
begin
Assert(DataSize = RC2BlockSize);
RC2DecryptBlock(PRC2CipherKey(Context)^, PRC2Block(Data)^);
end;
{ }
{ RC4 cipher }
{ }
const
RC4ContextSize = Sizeof(TRC4Context);
procedure CipherRC4Init(const Operation: TCipherOperation;
const KeyBuffer: Pointer; const KeyBufferSize: Integer;
const KeyBits: Integer; const ContextBuffer: Pointer);
begin
RC4Init(KeyBuffer^, KeyBufferSize, PRC4Context(ContextBuffer)^);
end;
procedure CipherRC4Buffer(const Context, Data: Pointer; const DataSize: Integer);
begin
RC4Buffer(PRC4Context(Context)^, Data^, DataSize);
end;
{ }
{ DES cipher }
{ }
const
DESContextSize = Sizeof(TDESContext);
DESKeySize = Sizeof(TDESKey);
procedure CipherDESInit(const Operation: TCipherOperation;
const KeyBuffer: Pointer; const KeyBufferSize: Integer;
const KeyBits: Integer; const ContextBuffer: Pointer);
begin
if KeyBufferSize <> DESKeySize then
raise ECipher.Create(CipherError_InvalidKeySize, 'Invalid DES key length');
DESInit(Operation = coEncrypt, PDESKey(KeyBuffer)^, PDESContext(ContextBuffer)^);
end;
procedure CipherDESBuffer(const Context, Data: Pointer; const DataSize: Integer);
begin
Assert(DataSize = DESBlockSize);
DESBuffer(PDESContext(Context)^, PDESBlock(Data)^);
end;
{ }
{ DES-40 cipher }
{ }
const
DES40ContextSize = DESContextSize;
DES40KeySize = DESKeySize;
procedure CipherDES40Init(const Operation: TCipherOperation;
const KeyBuffer: Pointer; const KeyBufferSize: Integer;
const KeyBits: Integer; const ContextBuffer: Pointer);
var DES40Key : TDESKey;
begin
if KeyBufferSize <> DES40KeySize then
raise ECipher.Create(CipherError_InvalidKeySize, 'Invalid DES key length');
Move(KeyBuffer, DES40Key, DES40KeySize);
DESKeyConvertToDES40(DES40Key);
DESInit(Operation = coEncrypt, DES40Key, PDESContext(ContextBuffer)^);
end;
procedure CipherDES40Buffer(const Context, Data: Pointer; const DataSize: Integer);
begin
Assert(DataSize = DESBlockSize);
DESBuffer(PDESContext(Context)^, PDESBlock(Data)^);
end;
{ }
{ Triple-DES cipher }
{ }
const
TripleDESContextSize = Sizeof(TTripleDESContext);
procedure CipherTripleDESInit(const Operation: TCipherOperation;
const KeyBuffer: Pointer; const KeyBufferSize: Integer;
const KeyBits: Integer; const ContextBuffer: Pointer);
begin
if KeyBufferSize <> SizeOf(TTripleDESKey) then
raise ECipher.Create(CipherError_InvalidKeySize, 'Invalid DES key length');
TripleDESInit(Operation = coEncrypt, PTripleDESKey(KeyBuffer)^,
PTripleDESContext(ContextBuffer)^);
end;
procedure CipherTripleDESBuffer(const Context, Data: Pointer; const DataSize: Integer);
begin
Assert(DataSize = DESBlockSize);
TripleDESBuffer(PTripleDESContext(Context)^, PDESBlock(Data)^);
end;
{ }
{ Triple-DES-3 cipher }
{ }
const
TripleDES3ContextSize = Sizeof(TTripleDES3Context);
procedure CipherTripleDES3EDEInit(const Operation: TCipherOperation;
const KeyBuffer: Pointer; const KeyBufferSize: Integer;
const KeyBits: Integer; const ContextBuffer: Pointer);
begin
if KeyBufferSize <> SizeOf(TTripleDES3Key) then
raise ECipher.Create(CipherError_InvalidKeySize, 'Invalid DES key length');
TripleDES3Init(Operation = coEncrypt, PTripleDES3Key(KeyBuffer)^, False,
PTripleDES3Context(ContextBuffer)^);
end;
procedure CipherTripleDES3EEEInit(const Operation: TCipherOperation;
const KeyBuffer: Pointer; const KeyBufferSize: Integer;
const KeyBits: Integer; const ContextBuffer: Pointer);
begin
if KeyBufferSize <> SizeOf(TTripleDES3Key) then
raise ECipher.Create(CipherError_InvalidKeySize, 'Invalid DES key length');
TripleDES3Init(Operation = coEncrypt, PTripleDES3Key(KeyBuffer)^, True,
PTripleDES3Context(ContextBuffer)^);
end;
procedure CipherTripleDES3BufferEncrypt(const Context, Data: Pointer; const DataSize: Integer);
begin
Assert(DataSize = DESBlockSize);
TripleDES3BufferEncrypt(PTripleDES3Context(Context)^, PDESBlock(Data)^);
end;
procedure CipherTripleDES3BufferDecrypt(const Context, Data: Pointer; const DataSize: Integer);
begin
Assert(DataSize = DESBlockSize);
TripleDES3BufferDecrypt(PTripleDES3Context(Context)^, PDESBlock(Data)^);
end;
{ }
{ AES cipher }
{ }
const
AESContextSize = Sizeof(TAESContext);
procedure CipherAESInit(const Operation: TCipherOperation;
const KeyBuffer: Pointer; const KeyBufferSize: Integer;
const KeyBits: Integer; const ContextBuffer: Pointer);
begin
AESInit(PAESContext(ContextBuffer)^, KeyBits, KeyBuffer^, KeyBufferSize);
end;
procedure CipherAESEncryptBuffer(const Context, Data: Pointer; const DataSize: Integer);
begin
Assert(DataSize = AESBlockSize);
AESEncryptBlock(PAESContext(Context)^, Data^, DataSize, Data^, DataSize);
end;
procedure CipherAESDecryptBuffer(const Context, Data: Pointer; const DataSize: Integer);
begin
Assert(DataSize = AESBlockSize);
AESDecryptBlock(PAESContext(Context)^, Data^, DataSize, Data^, DataSize);
end;
procedure CipherAESFinalise(const Context: Pointer);
begin
AESFinalise(PAESContext(Context)^);
end;
{ }
{ RSA cipher }
{ Note: Stub under development }
{ }
{$IFDEF Cipher_SupportRSA}
type
TRSAOAEP1024Context = packed record
Encrypt : Boolean;
PublicKey : TRSAPublicKey;
PrivateKey : TRSAPrivateKey;
end;
PRSAOAEP1024Context = ^TRSAOAEP1024Context;
const
RSAOAEP1024ContextSize = SizeOf(TRSAOAEP1024Context);
procedure CipherRSAOAEP1024Init(const Operation: TCipherOperation;
const KeyBuffer: Pointer; const KeyBufferSize: Integer;
const KeyBits: Integer; const ContextBuffer: Pointer);
const
KeyPartBits = 1024;
KeyPartSize = 1024 div 8;
var C : PRSAOAEP1024Context;
P, Q : PByte;
begin
C := ContextBuffer;
C^.Encrypt := Operation = coEncrypt;
RSAPublicKeyInit(C^.PublicKey);
RSAPrivateKeyInit(C^.PrivateKey);
if (KeyBufferSize <> KeyPartSize * 2) or (KeyBits <> KeyPartBits) then
raise ECipher.Create(CipherError_InvalidKeySize, 'Invalid RSA key length');
P := @KeyBuffer;
Q := P;
Inc(Q, KeyPartSize);
if Operation = coEncrypt then
RSAPublicKeyAssignBuf(C^.PublicKey, KeyPartBits, P^, KeyPartSize, Q^, KeyPartSize, False)
else
RSAPrivateKeyAssignBuf(C^.PrivateKey, KeyPartBits, P^, KeyPartSize, Q^, KeyPartSize, False);
end;
procedure CipherRSAOAEP1024EncryptBuffer(const Context, Data: Pointer; const DataSize: Integer);
var C : PRSAOAEP1024Context;
B : array[0..127] of Byte;
begin
C := Context;
RSAEncrypt(rsaetOAEP, C^.PublicKey, Data, DataSize, B[0], SizeOf(B));
Move(B[0], Data^, 128);
end;
procedure CipherRSAOAEP1024DecryptBuffer(const Context, Data: Pointer; const DataSize: Integer);
var C : PRSAOAEP1024Context;
B : array[0..127] of Byte;
begin
C := Context;
RSADecrypt(rsaetOAEP, C^.PrivateKey, Data, DataSize, B[0], SizeOf(B));
Move(B[0], Data^, 128);
end;
procedure CipherRSAOAEP1024Finalise(const Context: Pointer);
var C : PRSAOAEP1024Context;
begin
C := Context;
RSAPrivateKeyFinalise(C^.PrivateKey);
RSAPublicKeyFinalise(C^.PublicKey);
end;
{$ENDIF}
{ }
{ Cipher mode buffer functions }
{ }
procedure XORBuffer(const Buffer1, Buffer2: Pointer; const BufferSize: Integer);
var I : Integer;
P, Q : PByte;
begin
Assert(Assigned(Buffer1));
Assert(Assigned(Buffer2));
P := Buffer1;
Q := Buffer2;
for I := 0 to BufferSize - 1 do
begin
P^ := P^ xor Q^;
Inc(P);
Inc(Q);
end;
end;
{ Cipher buffer }
procedure CipherBuffer(
const Buffer: Pointer; const BufferSize: Integer;
const CipherContext: Pointer;
const CipherFunc: TCipherBufferFunc; const CipherBlockSize: Integer);
begin
CipherFunc(CipherContext, @Buffer, BufferSize);
end;
{ ECB - Electronic codebook }
procedure ECBBuffer(
const Buffer: Pointer; const BufferSize: Integer;
const CipherContext: Pointer;
const CipherFunc: TCipherBufferFunc; const CipherBlockSize: Integer);
var P : PByte;
L, M : Integer;
begin
P := Buffer;
L := BufferSize;
while L > 0 do
begin
if L >= CipherBlockSize then
M := CipherBlockSize
else
M := L;
CipherFunc(CipherContext, P, M);
Inc(P, M);
Dec(L, M);
end;
end;
{ CBC - Cipher-block chaining }
{ See http://en.wikipedia.org/wiki/Cipher_block_chaining }
const
MaxCipherBlockSize = 4096;
procedure CBCEncode(
const Buffer: Pointer; const BufferSize: Integer;
const CipherContext: Pointer;
const CipherFunc: TCipherBufferFunc; const CipherBlockSize: Integer;
const InitVector: Pointer; const InitVectorBufferSize: Integer);
var P, F : PByte;
L : Integer;
B : array[0..MaxCipherBlockSize - 1] of Byte;
begin
Assert(BufferSize > 0);
Assert(CipherBlockSize <= MaxCipherBlockSize);
Assert(Assigned(InitVector));
Assert(InitVectorBufferSize = CipherBlockSize);
P := Buffer;
L := BufferSize;
F := InitVector;
while L >= CipherBlockSize do
begin
XORBuffer(P, F, CipherBlockSize);
CipherFunc(CipherContext, P, CipherBlockSize);
F := P;
Dec(L, CipherBlockSize);
Inc(P, CipherBlockSize);
end;
if L > 0 then
begin
Move(P^, B[0], L);
FillChar(B[L], CipherBlockSize - L, 0);
XORBuffer(@B[0], F, CipherBlockSize);
CipherFunc(CipherContext, @B[0], L);
Move(B[0], P^, L);
SecureClear(B[0], CipherBlockSize);
end;
end;
procedure CBCDecode(
const Buffer: Pointer; const BufferSize: Integer;
const CipherState: Pointer;
const CipherFunc: TCipherBufferFunc; const CipherBlockSize: Integer;
const InitVector: Pointer; const InitVectorBufferSize: Integer);
var P : PByte;
L : Integer;
B : array[0..MaxCipherBlockSize - 1] of Byte;
C : array[0..MaxCipherBlockSize - 1] of Byte;
begin
Assert(BufferSize > 0);
Assert(CipherBlockSize <= MaxCipherBlockSize);
Assert(Assigned(InitVector));
Assert(InitVectorBufferSize = CipherBlockSize);
P := Buffer;
L := BufferSize;
Move(InitVector^, B[0], CipherBlockSize);
while L >= CipherBlockSize do
begin
Move(P^, C[0], CipherBlockSize);
CipherFunc(CipherState, P, CipherBlockSize);
XORBuffer(P, @B[0], CipherBlockSize);
Move(C[0], B[0], CipherBlockSize);
Dec(L, CipherBlockSize);
Inc(P, CipherBlockSize);
end;
if L > 0 then
begin
Move(P^, C[0], L);
FillChar(C[L], CipherBlockSize - L, 0);
CipherFunc(CipherState, @C[0], L);
XORBuffer(@C[0], @B[0], CipherBlockSize);
Move(C[0], P^, L);
end;
SecureClear(B[0], CipherBlockSize);
SecureClear(C[0], CipherBlockSize);
end;
{ CFB - Cipher FeedBack }
procedure CFBEncode(
const Buffer: Pointer; const BufferSize: Integer;
const CipherContext: Pointer;
const CipherFunc: TCipherBufferFunc; const CipherBlockSize: Integer;
const InitVector: Pointer; const InitVectorBufferSize: Integer);
var P : PByte;
L : Integer;
B, F : array[0..MaxCipherBlockSize - 1] of Byte;
begin
Assert(BufferSize > 0);
Assert(CipherBlockSize <= MaxCipherBlockSize);
Assert(Assigned(InitVector));
Assert(InitVectorBufferSize = CipherBlockSize);
P := Buffer;
L := BufferSize;
Move(InitVector^, F[0], CipherBlockSize);
while L >= 0 do
begin
Move(F[0], B[0], CipherBlockSize);
CipherFunc(CipherContext, @B[0], CipherBlockSize);
P^ := P^ xor B[0];
Move(F[1], F[0], CipherBlockSize - 1);
F[CipherBlockSize - 1] := P^;
Dec(L);
Inc(P);
end;
SecureClear(B[0], CipherBlockSize);
end;
procedure CFBDecode(
const Buffer: Pointer; const BufferSize: Integer;
const CipherContext: Pointer;
const CipherFunc: TCipherBufferFunc; const CipherBlockSize: Integer;
const InitVector: Pointer; const InitVectorBufferSize: Integer);
var P : PByte;
C : Byte;
L : Integer;
B, F : array[0..MaxCipherBlockSize - 1] of Byte;
begin
Assert(BufferSize > 0);
Assert(CipherBlockSize <= MaxCipherBlockSize);
Assert(Assigned(InitVector));
Assert(InitVectorBufferSize = CipherBlockSize);
P := Buffer;
L := BufferSize;
Move(InitVector^, F[0], CipherBlockSize);
while L >= 0 do
begin
Move(F[0], B[0], CipherBlockSize);
CipherFunc(CipherContext, @B[0], CipherBlockSize);
Move(F[1], F[0], CipherBlockSize - 1);
C := P^;
F[CipherBlockSize - 1] := C;
P^ := C xor B[0];
Dec(L);
Inc(P);
end;
SecureClear(B[0], CipherBlockSize);
end;
{ OFB - Output FeedBack }
procedure OFBEncode(
const Buffer: Pointer; const BufferSize: Integer;
const CipherContext: Pointer;
const CipherFunc: TCipherBufferFunc; const CipherBlockSize: Integer;
const InitVector: Pointer; const InitVectorBufferSize: Integer);
var P : PByte;
L : Integer;
B, F : array[0..MaxCipherBlockSize - 1] of Byte;
begin
Assert(BufferSize > 0);
Assert(CipherBlockSize <= MaxCipherBlockSize);
Assert(Assigned(InitVector));
Assert(InitVectorBufferSize = CipherBlockSize);
P := Buffer;
L := BufferSize;
Move(InitVector^, F[0], CipherBlockSize);
while L >= 0 do
begin
Move(F[0], B[0], CipherBlockSize);
CipherFunc(CipherContext, @B[0], CipherBlockSize);
P^ := P^ xor B[0];
Move(F[1], F[0], CipherBlockSize - 1);
F[CipherBlockSize - 1] := B[0];
Dec(L);
Inc(P);
end;
SecureClear(B[0], CipherBlockSize);
end;
procedure OFBDecode(
const Buffer: Pointer; const BufferSize: Integer;
const CipherContext: Pointer;
const CipherFunc: TCipherBufferFunc; const CipherBlockSize: Integer;
const InitVector: Pointer; const InitVectorBufferSize: Integer);
var P : PByte;
L : Integer;
B, F : array[0..MaxCipherBlockSize - 1] of Byte;
begin
Assert(BufferSize > 0);
Assert(CipherBlockSize <= MaxCipherBlockSize);
Assert(Assigned(InitVector));
Assert(InitVectorBufferSize = CipherBlockSize);
P := Buffer;
L := BufferSize;
Move(InitVector^, F[0], CipherBlockSize);
while L >= 0 do
begin
Move(F[0], B[0], CipherBlockSize);
CipherFunc(CipherContext, @B[0], CipherBlockSize);
Move(F[1], F[0], CipherBlockSize - 1);
F[CipherBlockSize - 1] := B[0];
P^ := P^ xor B[0];
Dec(L);
Inc(P);
end;
SecureClear(B[0], CipherBlockSize);
end;
{ }
{ Cipher information }
{ }
const
CipherInfo : array[TCipherType] of TCipherInfo = (
(
ValidCipher: False;
ShortName: '';
BufferMode: cbmStream;
InBlockSize: 0;
OutBlockSize: 0;
KeyBitsMin: 0;
KeyBitsMax: 0;
ContextSize: 0;
InitFunc: nil;
EncryptFunc: nil;
DecryptFunc: nil;
FinaliseFunc: nil;
),
(
ValidCipher: True;
ShortName: 'RC2';
BufferMode: cbmMultipleBlocks;
InBlockSize: RC2BlockSize;
OutBlockSize: RC2BlockSize;
KeyBitsMin: 1;
KeyBitsMax: 1024;
ContextSize: RC2ContextSize;
InitFunc: CipherRC2Init;
EncryptFunc: CipherRC2EncryptBuffer;
DecryptFunc: CipherRC2DecryptBuffer;
FinaliseFunc: nil;
),
(
ValidCipher: True;
ShortName: 'RC4';
BufferMode: cbmStream;
InBlockSize: 1024;
OutBlockSize: 1024;
KeyBitsMin: 1;
KeyBitsMax: 2048;
ContextSize: RC4ContextSize;
InitFunc: CipherRC4Init;
EncryptFunc: CipherRC4Buffer;
DecryptFunc: CipherRC4Buffer;
FinaliseFunc: nil;
),
(
ValidCipher: True;
ShortName: 'DES';
BufferMode: cbmMultipleBlocks;
InBlockSize: DESBlockSize;
OutBlockSize: DESBlockSize;
KeyBitsMin: 64;
KeyBitsMax: 64;
ContextSize: DESContextSize;
InitFunc: CipherDESInit;
EncryptFunc: CipherDESBuffer;
DecryptFunc: CipherDESBuffer;
FinaliseFunc: nil;
),
(
ValidCipher: True;
ShortName: 'DES-40';
BufferMode: cbmMultipleBlocks;
InBlockSize: DESBlockSize;
OutBlockSize: DESBlockSize;
KeyBitsMin: 64;
KeyBitsMax: 64;
ContextSize: DES40ContextSize;
InitFunc: CipherDES40Init;
EncryptFunc: CipherDES40Buffer;
DecryptFunc: CipherDES40Buffer;
FinaliseFunc: nil;
),
(
ValidCipher: True;
ShortName: 'TripleDES';
BufferMode: cbmMultipleBlocks;
InBlockSize: DESBlockSize;
OutBlockSize: DESBlockSize;
KeyBitsMin: 128;
KeyBitsMax: 128;
ContextSize: TripleDESContextSize;
InitFunc: CipherTripleDESInit;
EncryptFunc: CipherTripleDESBuffer;
DecryptFunc: CipherTripleDESBuffer;
FinaliseFunc: nil;
),
(
ValidCipher: True;
ShortName: 'TripleDES3EDE';
BufferMode: cbmMultipleBlocks;
InBlockSize: DESBlockSize;
OutBlockSize: DESBlockSize;
KeyBitsMin: 192;
KeyBitsMax: 192;
ContextSize: TripleDES3ContextSize;
InitFunc: CipherTripleDES3EDEInit;
EncryptFunc: CipherTripleDES3BufferEncrypt;
DecryptFunc: CipherTripleDES3BufferDecrypt;
FinaliseFunc: nil;
),
(
ValidCipher: True;
ShortName: 'TipleDES3EEE';
BufferMode: cbmMultipleBlocks;
InBlockSize: DESBlockSize;
OutBlockSize: DESBlockSize;
KeyBitsMin: 192;
KeyBitsMax: 192;
ContextSize: TripleDES3ContextSize;
InitFunc: CipherTripleDES3EEEInit;
EncryptFunc: CipherTripleDES3BufferEncrypt;
DecryptFunc: CipherTripleDES3BufferDecrypt;
FinaliseFunc: nil;
),
(
ValidCipher: True;
ShortName: 'AES';
BufferMode: cbmMultipleBlocks;
InBlockSize: AESBlockSize;
OutBlockSize: AESBlockSize;
KeyBitsMin: 128;
KeyBitsMax: 256;
ContextSize: AESContextSize;
InitFunc: CipherAESInit;
EncryptFunc: CipherAESEncryptBuffer;
DecryptFunc: CipherAESDecryptBuffer;
FinaliseFunc: CipherAESFinalise;
),
{$IFDEF Cipher_SupportRSA}
(
ValidCipher: True;
ShortName: 'RSA-OAEP-1024';
BufferMode: cbmSingleBlock;
InBlockSize: 0;
OutBlockSize: 128;
KeyBitsMin: 2048;
KeyBitsMax: 2048;
ContextSize: RSAOAEP1024ContextSize;
InitFunc: CipherRSAOAEP1024Init;
EncryptFunc: CipherRSAOAEP1024EncryptBuffer;
DecryptFunc: CipherRSAOAEP1024DecryptBuffer;
FinaliseFunc: CipherRSAOAEP1024Finalise;
)
{$ELSE}
(
ValidCipher: False;
ShortName: 'RSA-OAEP-1024';
BufferMode: cbmSingleBlock;
InBlockSize: 0;
OutBlockSize: 128;
KeyBitsMin: 1024;
KeyBitsMax: 1024;
ContextSize: 0;
InitFunc: nil;
EncryptFunc: nil;
DecryptFunc: nil;
FinaliseFunc: nil;
)
{$ENDIF}
);
function GetCipherInfo(const Cipher: TCipherType): PCipherInfo;
begin
Result := @CipherInfo[Cipher];
if not Result^.ValidCipher then
Result := nil;
end;
{ }
{ Cipher }
{ }
function InitCipherState(
var State: TCipherState;
const Operation: TCipherOperation;
const Cipher: TCipherType; const Mode: TCipherMode;
const Padding: TCipherPadding;
const KeyBits: Integer): PCipherInfo;
var C : PCipherInfo;
begin
C := GetCipherInfo(Cipher);
if not Assigned(C) then
raise ECipher.Create(CipherError_InvalidCipher, 'Invalid cipher');
FillChar(State, SizeOf(State), 0);
State.Operation := Operation;
State.Cipher := Cipher;
State.Mode := Mode;
State.Padding := Padding;
State.KeyBits := KeyBits;
State.CipherInfo := C;
Result := C;
end;
{ }
{ Encrypt }
{ }
procedure EncryptInit(
var State: TCipherState;
const Cipher: TCipherType; const Mode: TCipherMode;
const Padding: TCipherPadding;
const KeyBits: Integer;
const KeyBuffer: Pointer; const KeyBufferSize: Integer);
var C : PCipherInfo;
begin
C := InitCipherState(State, coEncrypt, Cipher, Mode, Padding, KeyBits);
if Assigned(C^.InitFunc) then
C^.InitFunc(coEncrypt, KeyBuffer, KeyBufferSize, KeyBits, @State.Context);
end;
procedure EncryptPadBuffer(
const Padding: TCipherPadding;
const Buffer: Pointer;
const InputBufferSize, OutputBufferSize: Integer);
var P : PByte;
I : Integer;
begin
P := Buffer;
Inc(P, InputBufferSize);
I := OutputBufferSize - InputBufferSize;
case Padding of
cpNone : FillChar(P^, I, 0);
cpPadSizeByte :
begin
FillChar(P^, I - 1, 0);
Assert(I <= $FF);
Inc(P, I - 1);
P^ := Byte(I);
end;
cpPadPKCS5 :
begin
Assert(I <= $FF);
FillChar(P^, I, Char(I));
end;
end;
end;
function EncryptedSize(
const Cipher: TCipherType; const Padding: TCipherPadding;
const BufferSize: Integer): Integer;
var C : PCipherInfo;
B : Integer;
begin
if BufferSize < 0 then
begin
Result := 0;
exit;
end;
C := GetCipherInfo(Cipher);
case C^.BufferMode of
cbmMultipleBlocks :
begin
Result := BufferSize;
B := C^.OutBlockSize;
Assert((Padding = cpNone) or (B <= $FF));
case Padding of
cpNone : ;
cpPadSizeByte : Inc(Result);
cpPadPKCS5 :
if Result mod B = 0 then
Inc(Result, B);
end;
Result := ((Result + (B - 1)) div B) * B;
end;
cbmSingleBlock : Result := C^.OutBlockSize;
cbmStream : Result := BufferSize;
else
Result := BufferSize;
end;
end;
function EncryptBuf(
var State: TCipherState;
const InputBuffer: Pointer; const InputBufferSize: Integer;
const OutputBuffer: Pointer; const OutputBufferSize: Integer;
const InitVectorBuffer: Pointer; const InitVectorBufferSize: Integer): Integer;
var C : PCipherInfo;
P : Pointer;
begin
Assert(State.Operation = coEncrypt);
Assert(Assigned(State.CipherInfo));
C := State.CipherInfo;
// validate parameters
Result := EncryptedSize(State.Cipher, State.Padding, InputBufferSize);
if Result = 0 then
exit;
if Result > OutputBufferSize then
raise ECipher.Create(CipherError_InvalidBufferSize, 'Output buffer too small');
if not Assigned(InputBuffer) or not Assigned(OutputBuffer) then
raise ECipher.Create(CipherError_InvalidBuffer, 'Invalid buffer');
// encrypt
if InputBuffer <> OutputBuffer then
Move(InputBuffer^, OutputBuffer^, InputBufferSize);
// pad buffer
if C^.BufferMode = cbmMultipleBlocks then
if InputBufferSize < Result then
EncryptPadBuffer(State.Padding, OutputBuffer, InputBufferSize, Result);
// encrypt buffer
P := @State.Context;
if C^.BufferMode = cbmSingleBlock then
CipherBuffer(OutputBuffer, Result, P, C^.EncryptFunc, C^.InBlockSize)
else
case State.Mode of
cmECB : ECBBuffer(OutputBuffer, Result, P, C^.EncryptFunc, C^.InBlockSize);
cmCBC : CBCEncode(OutputBuffer, Result, P, C^.EncryptFunc, C^.InBlockSize, InitVectorBuffer, InitVectorBufferSize);
cmCFB : CFBEncode(OutputBuffer, Result, P, C^.EncryptFunc, C^.InBlockSize, InitVectorBuffer, InitVectorBufferSize);
cmOFB : OFBEncode(OutputBuffer, Result, P, C^.EncryptFunc, C^.InBlockSize, InitVectorBuffer, InitVectorBufferSize);
else
raise ECipher.Create(CipherError_InvalidCipherMode, 'Invalid cipher mode');
end;
end;
procedure EncryptFinalise(var State: TCipherState);
var C : PCipherInfo;
begin
C := State.CipherInfo;
if Assigned(C) then
if Assigned(C^.FinaliseFunc) then
C^.FinaliseFunc(@State.Context);
SecureClear(State, SizeOf(State));
end;
function Encrypt(const Cipher: TCipherType; const Mode: TCipherMode;
const Padding: TCipherPadding;
const KeyBits: Integer;
const KeyBuffer: Pointer; const KeyBufferSize: Integer;
const InputBuffer: Pointer; const InputBufferSize: Integer;
const OutputBuffer: Pointer; const OutputBufferSize: Integer;
const InitVectorBuffer: Pointer; const InitVectorBufferSize: Integer): Integer;
var State : TCipherState;
begin
EncryptInit(
State, Cipher, Mode, Padding,
KeyBits, KeyBuffer, KeyBufferSize);
Result := EncryptBuf(
State,
InputBuffer, InputBufferSize, OutputBuffer, OutputBufferSize,
InitVectorBuffer, InitVectorBufferSize);
EncryptFinalise(State);
end;
function Encrypt(const Cipher: TCipherType; const Mode: TCipherMode;
const Padding: TCipherPadding; const KeyBits: Integer;
const Key, Data, InitVector: RawByteString): RawByteString;
var L, M : Integer;
begin
M := Length(Data);
L := EncryptedSize(Cipher, Padding, M);
SetLength(Result, L);
if L = 0 then
exit;
Encrypt(Cipher, Mode, Padding, KeyBits, PByteChar(Key), Length(Key),
PByteChar(Data), M, PByteChar(Result), L,
PByteChar(InitVector), Length(InitVector));
end;
{ }
{ Decrypt }
{ }
procedure DecryptInit(
var State: TCipherState;
const Cipher: TCipherType; const Mode: TCipherMode;
const Padding: TCipherPadding;
const KeyBits: Integer;
const KeyBuffer: Pointer; const KeyBufferSize: Integer);
var C : PCipherInfo;
begin
C := InitCipherState(State, coDecrypt, Cipher, Mode, Padding, KeyBits);
if Assigned(C^.InitFunc) then
if Mode in [cmCFB, cmOFB] then
C^.InitFunc(coEncrypt, KeyBuffer, KeyBufferSize, KeyBits, @State.Context)
else
C^.InitFunc(coDecrypt, KeyBuffer, KeyBufferSize, KeyBits, @State.Context)
end;
function DecryptBuf(
var State: TCipherState;
const Buffer: Pointer; const BufferSize: Integer;
const InitVectorBuffer: Pointer; const InitVectorBufferSize: Integer): Integer;
var C : PCipherInfo;
P : PByte;
D : Pointer;
I : Integer;
L : Byte;
N : Integer;
begin
Assert(State.Operation = coDecrypt);
Assert(Assigned(State.CipherInfo));
C := State.CipherInfo;
// decrypt buffer
D := @State.Context;
if C^.BufferMode = cbmSingleBlock then
CipherBuffer(Buffer, BufferSize, D, C^.DecryptFunc, C^.InBlockSize)
else
case State.Mode of
cmECB : ECBBuffer(Buffer, BufferSize, D, C^.DecryptFunc, C^.InBlockSize);
cmCBC : CBCDecode(Buffer, BufferSize, D, C^.DecryptFunc, C^.InBlockSize, InitVectorBuffer, InitVectorBufferSize);
cmCFB : CFBDecode(Buffer, BufferSize, D, C^.EncryptFunc, C^.InBlockSize, InitVectorBuffer, InitVectorBufferSize);
cmOFB : OFBDecode(Buffer, BufferSize, D, C^.EncryptFunc, C^.InBlockSize, InitVectorBuffer, InitVectorBufferSize);
else
raise ECipher.Create(CipherError_InvalidCipherMode, 'Invalid cipher mode');
end;
N := BufferSize;
// remove padding
if C^.BufferMode = cbmMultipleBlocks then
if State.Padding in [cpPadSizeByte, cpPadPKCS5] then
begin
// get pad size byte (last byte)
P := Buffer;
Inc(P, BufferSize - 1);
L := P^;
// validate padding
if (L > N) or (L > C^.InBlockSize) then
raise ECipher.Create(CipherError_InvalidData, 'Invalid data');
for I := 1 to L - 1 do
begin
Dec(P);
if ((State.Padding = cpPadSizeByte) and (P^ <> 0)) or
((State.Padding = cpPadPKCS5) and (P^ <> L)) then
raise ECipher.Create(CipherError_InvalidData, 'Invalid data');
end;
// decrease buffer size with padding size
Dec(N, L);
end;
// return number of decoded bytes
Result := N;
end;
procedure DecryptFinalise(var State: TCipherState);
var C : PCipherInfo;
begin
C := State.CipherInfo;
if Assigned(C) then
if Assigned(C^.FinaliseFunc) then
C^.FinaliseFunc(@State.Context);
SecureClear(State, SizeOf(State));
end;
function Decrypt(const Cipher: TCipherType; const Mode: TCipherMode;
const Padding: TCipherPadding;
const KeyBits: Integer;
const KeyBuffer: Pointer; const KeyBufferSize: Integer;
const Buffer: Pointer; const BufferSize: Integer;
const InitVectorBuffer: Pointer; const InitVectorBufferSize: Integer): Integer; overload;
var State : TCipherState;
begin
DecryptInit(State, Cipher, Mode, Padding, KeyBits, KeyBuffer, KeyBufferSize);
Result := DecryptBuf(State, Buffer, BufferSize, InitVectorBuffer, InitVectorBufferSize);
DecryptFinalise(State);
end;
function Decrypt(const Cipher: TCipherType; const Mode: TCipherMode;
const Padding: TCipherPadding; const KeyBits: Integer;
const Key, Data, InitVector: RawByteString): RawByteString;
var L, M : Integer;
begin
L := Length(Data);
SetLength(Result, L);
if L = 0 then
exit;
Move(Pointer(Data)^, Pointer(Result)^, L);
M := Decrypt(Cipher, Mode, Padding, KeyBits, PByteChar(Key), Length(Key),
PByteChar(Result), L,
PByteChar(InitVector), Length(InitVector));
if M < L then
SetLength(Result, M);
end;
{ }
{ Test }
{ }
{$IFDEF CIPHER_TEST}
{$ASSERTIONS ON}
procedure Test;
begin
Assert(RC2ContextSize = 128);
Assert(RC2ContextSize <= MaxCipherContextSize);
Assert(RC4ContextSize <= MaxCipherContextSize);
Assert(DESContextSize <= MaxCipherContextSize);
Assert(TripleDESContextSize <= MaxCipherContextSize);
Assert(TripleDES3ContextSize <= MaxCipherContextSize);
end;
{$ENDIF}
end.