{*****************************************************************************
The DEC team (see file NOTICE.txt) licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. A copy of this licence is found in the root directory
of this project in the file LICENCE.txt or alternatively at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*****************************************************************************}
unit DECCipherPaddings;
{$INCLUDE DECOptions.inc}
interface
uses
{$IFDEF FPC}
SysUtils,
{$ELSE}
System.SysUtils,
{$ENDIF}
DECRandom,
DECTypes;
type
/// <summary>
/// Base class for implementing block padding algorithms.
/// </summary>
/// <remarks>
/// Padding algorithms are used to fill data to a specific block size when the
/// data length is not an integer multiple of the block size.
/// This abstract class defines the basic interfaces for adding, validating,
/// and removing padding.
/// </remarks>
TDECPadding =
class abstract
protected
class procedure DoPadding(
var Data : TBytes;
Start : Integer);
virtual;
abstract;
public
/// <summary>
/// Adds padding to the specified data to align it with the given block size.
/// </summary>
/// <param name="Data">
/// The data to be padded.
/// </param>
/// <param name="BlockSize">
/// The block size to align the data with.
/// </param>
/// <returns>
/// The padded data.
/// </returns>
/// <remarks>
/// The specific method of padding depends on the implementation of the subclass.
/// </remarks>
class function AddPadding(
const Data : TBytes;
BlockSize : Integer;
MinLength : Integer = 0): TBytes;
overload;
virtual;
abstract;
// <summary>
/// Adds PKCS#7 padding to a string.
/// </summary>
/// <param name="data">
/// The string to which padding should be added.
/// </param>
/// <param name="BlockSize">
/// The block size in byte to align the data with.
/// </param>
/// <returns>
/// A new byte string with PKCS#7 padding applied.
/// </returns>
/// <remarks>
/// PKCS#7 padding, as defined in RFC 5652 (which updates RFC 2315), adds
/// bytes to the end of the data so that the total length is a multiple of
/// the block size. Each padding byte contains the number of padding bytes
/// added. For example, if 5 bytes of padding are needed, each of the 5
/// padding bytes will have the value $5.
/// <para>
/// Call this method before starting encryption.
// </para>
/// </remarks>
class function AddPadding(
const Data :
string;
BlockSize : Integer;
MinLength : Integer = 0):
string;
overload;
virtual;
abstract;
// <summary>
/// Adds PKCS#7 padding to a raw byte string.
/// </summary>
/// <param name="data">
/// The raw byte string to which padding should be added.
/// </param>
/// <param name="BlockSize">
/// The block size in byte to align the data with.
/// </param>
/// <returns>
/// A new byte raw byte string with PKCS#7 padding applied.
/// </returns>
/// <remarks>
/// PKCS#7 padding, as defined in RFC 5652 (which updates RFC 2315), adds
/// bytes to the end of the data so that the total length is a multiple of
/// the block size. Each padding byte contains the number of padding bytes
/// added. For example, if 5 bytes of padding are needed, each of the 5
/// padding bytes will have the value $5.
/// <para>
/// Call this method before starting encryption.
/// </para>
/// </remarks>
class function AddPadding(
const Data : RawByteString;
BlockSize : Integer;
MinLength : Integer = 0): RawByteString;
overload;
virtual;
abstract;
/// <summary>
/// Checks if the specified data contains valid padding.
/// </summary>
/// <param name="Data">
/// The data to be checked.
/// </param>
/// <param name="BlockSize">
/// The expected block size.
/// </param>
/// <returns>
/// True if the padding is valid; otherwise, False.
/// </returns>
/// <remarks>
/// This method is used to ensure the integrity and consistency of the padding.
/// </remarks>
class function HasValidPadding(
const Data : TBytes;
BlockSize : Integer = 0): Boolean;
virtual;
abstract;
/// <summary>
/// Removes padding from the specified data.
/// </summary>
/// <param name="Data">
/// The data from which padding will be removed.
/// </param>
/// <param name="BlockSize">
/// The block size in bytes used for padding.
/// </param>
/// <returns>
/// The original data without padding.
/// </returns>
/// <remarks>
/// This method assumes that the padding has already been validated.
/// </remarks>
class function RemovePadding(
const Data : TBytes;
BlockSize : Integer = 0): TBytes;
overload;
virtual;
abstract;
// <summary>
/// Removes PKCS#7 padding from a raw byte string.
/// </summary>
/// <param name="data">
/// The padded byte raw byte string.
/// </param>
/// <param name="BlockSize">
/// The block size in bytes used for padding.
/// </param>
/// <returns>
/// A new raw byte string with the padding removed. Raises an exception
/// if the padding is invalid.
/// </returns>
/// <exception cref="EDECCipherException">
/// Raised if the padding is invalid or missing.
/// </exception>
/// <remarks>
/// This function checks for valid PKCS#7 padding and raises an
/// `EDECCipherException` exception if the padding is incorrect. This
/// includes cases where the final bytes do not match the pad count or if
/// the pad count is greater than the block size.
/// <para>
/// Call this method after decryption.
/// </para>
/// </remarks>
class function RemovePadding(
const Data : RawByteString;
BlockSize : Integer = 0): RawByteString;
overload;
virtual;
abstract;
// <summary>
/// Removes PKCS#7 padding from a string.
/// </summary>
/// <param name="data">
/// The padded byte raw byte string.
/// </param>
/// <param name="BlockSize">
/// The block size in bytes used for padding.
/// </param>
/// <returns>
/// A new raw byte string with the padding removed. Raises an exception
/// if the padding is invalid.
/// </returns>
/// <exception cref="EDECCipherException">
/// Raised if the padding is invalid or missing.
/// </exception>
/// <remarks>
/// This function checks for valid PKCS#7 padding and raises an
/// `EDECCipherException` exception if the padding is incorrect. This
/// includes cases where the final bytes do not match the pad count or if
/// the pad count is greater than the block size.
/// <para>
/// Call this method after decryption.
/// </para>
/// </remarks>
class function RemovePadding(
const Data :
string;
BlockSize : Integer = 0):
string;
overload;
virtual;
abstract;
end;
TDECPaddingCommon =
class(TDECPadding)
protected
class function CalculatePaddingLength(DataLength : Integer ;
BlockSize: Integer;
MinLength: Integer = 0): Integer;
public
class function AddPadding(
const Data:
string;
BlockSize: Integer;
MinLength : Integer = 0):
string;
override;
class function AddPadding(
const Data : RawByteString;
BlockSize : Integer;
MinLength : Integer = 0): RawByteString;
override;
class function RemovePadding(
const Data :
string;
BlockSize : Integer = 0):
string;
override;
class function RemovePadding(
const Data : RawByteString;
BlockSize : Integer = 0): RawByteString;
override;
end;
/// <summary>
/// Implementation of the PKCS7 padding algorithm.
/// </summary>
/// <remarks>
/// PKCS7 padding is a standard algorithm used in symmetric cryptosystems like AES.
/// It appends the number of padding bytes as the value of the padding itself.
/// </remarks>
TDECPKCS7Padding =
class(TDECPaddingCommon)
protected
class procedure DoPadding(
var Data : TBytes;
Start : Integer);
reintroduce;
virtual;
public
/// <summary>
/// Adds PKCS7 padding to the specified data.
/// </summary>
/// <param name="Data">
/// The data to be padded.
/// </param>
/// <param name="BlockSize">
/// The block size in byte to align the data with.
/// </param>
/// <returns>
/// The padded data following the PKCS7 algorithm.
/// </returns>
class function AddPadding(
const Data: TBytes; BlockSize: Integer; MinLength:
Integer = 0): TBytes;
override;
/// <summary>
/// Checks if the specified data contains valid padding.
/// </summary>
/// <param name="Data">
/// The data to be checked.
/// </param>
/// <param name="BlockSize">
/// The expected block size.
/// </param>
/// <returns>
/// True if the padding is valid; otherwise, False.
/// </returns>
/// <remarks>
/// This method is used to ensure the integrity and consistency of the padding.
/// </remarks>
class function HasValidPadding(
const Data : TBytes;
BlockSize : Integer = 0): Boolean;
override;
/// <summary>
/// Removes PKCS7 padding from the specified data.
/// </summary>
/// <param name="Data">
/// The data from which padding will be removed.
/// </param>
/// <param name="BlockSize">
/// The block size used for padding.
/// </param>
/// <exception cref="EDECCipherException">
/// Raised if the padding is invalid or missing.
/// </exception>
/// <returns>
/// The original data without padding.
/// </returns>
class function RemovePadding(
const Data : TBytes;
BlockSize : Integer = 0): TBytes;
override;
end;
TDECPKCS5Padding =
class(TDECPaddingCommon)
protected
class procedure DoPadding(
var Data : TBytes;
Start : Integer);
reintroduce;
virtual;
public
class function AddPadding(
const Data: TBytes; BlockSize: Integer; MinLength:
Integer = 0): TBytes;
override;
class function HasValidPadding(
const Data : TBytes;
BlockSize : Integer = 0): Boolean;
override;
class function RemovePadding(
const Data : TBytes;
BlockSize : Integer = 0): TBytes;
override;
end;
// https://www.ibm.com/docs/en/linux-on-systems?topic=processes-ansi-x923-cipher-block-chaining
// https://crypto.stackexchange.com/questions/61689/what-is-ansi-x-923-padding-standard
// Doesn't need specific fill for padding (like random, 0, or soecific value) but limited to block size of 8
TDECANSIX923Padding =
class(TDECPaddingCommon)
protected
class procedure DoPadding(
var Data : TBytes;
Start : Integer);
reintroduce;
virtual;
public
class function AddPadding(
const Data: TBytes; BlockSize: Integer; MinLength:
Integer = 0): TBytes;
override;
class function HasValidPadding(
const Data : TBytes;
BlockSize : Integer = 0): Boolean;
override;
class function RemovePadding(
const Data : TBytes;
BlockSize : Integer = 0): TBytes;
override;
end;
// both are limited to 8 bytes in their original references so both should not be used with AES or any modern standard
// but many modern uses and implementation remove this limitiation
// for Ansi X9.23 random fill used here, but in many implmentation it is zero, while ISO 10126 is random fill
TDECISO10126Padding =
class(TDECPaddingCommon)
protected
class procedure DoPadding(
var Data : TBytes;
Start : Integer);
reintroduce;
virtual;
public
class function AddPadding(
const Data: TBytes; BlockSize: Integer; MinLength:
Integer = 0): TBytes;
override;
class function HasValidPadding(
const Data : TBytes;
BlockSize : Integer = 0): Boolean;
override;
class function RemovePadding(
const Data : TBytes;
BlockSize : Integer = 0): TBytes;
override;
end;
TDECISO7816Padding =
class(TDECPaddingCommon)
protected
class procedure DoPadding(
var Data : TBytes; Start : Integer);
reintroduce;
virtual;
public
class function AddPadding(
const Data: TBytes; BlockSize: Integer; MinLength:
Integer = 0): TBytes;
override;
class function HasValidPadding(
const Data : TBytes;
BlockSize : Integer = 0): Boolean;
override;
class function RemovePadding(
const Data : TBytes;
BlockSize : Integer = 0): TBytes;
override;
end;
TDECOneZeroesPadding = TDECISO7816Padding;
// W3C padding mainly used in XML encryption
// https://www.w3.org/TR/xmlenc-core1/#sec-Padding
// It require arbitrary fill (can be random), so we can use our ISO 10126
TDECW3CPadding = TDECISO10126Padding;
// additional resources
// https://www.cryptosys.net/pki/manpki/pki_paddingschemes.html
// https://crypto.stackexchange.com/questions/31372/what-are-the-relative-merits-of-padding-algorithms-pkcs7-iso7816-and-x923
implementation
uses
DECUtil;
{ TPaddingCommon }
class function TDECPaddingCommon.CalculatePaddingLength(DataLength, BlockSize, MinLength: Integer): Integer;
begin
if (MinLength < 0)
or (DataLength < 0)
or (BlockSize < 0)
or (MinLength
or BlockSize = 0)
then
begin
Result := -1;
end
else if MinLength <= DataLength
then
begin
Result := DataLength + BlockSize - (DataLength
mod BlockSize);
end
else
begin
Result := MinLength;
if BlockSize > 0
then
Result := Result + BlockSize - ((MinLength - 1)
mod BlockSize) - 1;
end;
end;
class function TDECPaddingCommon.AddPadding(
const Data: RawByteString; BlockSize, MinLength: Integer): RawByteString;
var
Buf: TBytes;
begin
Buf := AddPadding(RawStringToBytes(Data), BlockSize);
Result := BytesToRawString(Buf);
ProtectBytes(Buf);
end;
class function TDECPaddingCommon.AddPadding(
const Data:
string; BlockSize, MinLength: Integer):
string;
var
Buf: TBytes;
begin
Buf := AddPadding(StringToBytes(Data), BlockSize);
Result := BytesToString(Buf);
ProtectBytes(Buf);
end;
class function TDECPaddingCommon.RemovePadding(
const Data: RawByteString; BlockSize: Integer = 0): RawByteString;
var
Buf: TBytes;
begin
Buf := RemovePadding(RawStringToBytes(Data), BlockSize);
Result := BytesToRawString(Buf);
ProtectBytes(Buf);
end;
class function TDECPaddingCommon.RemovePadding(
const Data:
string; BlockSize: Integer = 0):
string;
var
Buf: TBytes;
begin
Buf := RemovePadding(StringToBytes(Data), BlockSize);
Result := BytesToString(Buf);
ProtectBytes(Buf);
end;
{ TPKCS7Padding }
class procedure TDECPKCS7Padding.DoPadding(
var Data: TBytes; Start: Integer);
var
I: Integer;
PadByte: Byte;
begin
PadByte := Length(Data) - Start;
if PadByte < 1
then
raise EDECCipherException.Create('
Invalid PKCS#7 Padding operation');
for I := Start
to High(Data)
do
Data[I] := PadByte;
end;
class function TDECPKCS7Padding.AddPadding(
const Data: TBytes; BlockSize: Integer; MinLength: Integer = 0): TBytes;
var
ResLength: Integer;
begin
ResLength := CalculatePaddingLength(Length(Data), BlockSize, MinLength);
if (ResLength < 0)
or (ResLength - Length(Data) > 255)
then
raise EDECCipherException.Create('
Invalid PKCS#7 Padding operation');
SetLength(Result, ResLength);
if Length(Data) > 0
then
Move(Data[0], Result[0], Length(Data));
DoPadding(Result, Length(Data));
end;
class function TDECPKCS7Padding.HasValidPadding(
const Data: TBytes; BlockSize: integer = 0): boolean;
var
PadLength: Integer;
I: Integer;
begin
Result := False;
if (Length(Data) = 0)
or ((BlockSize > 0)
and (Length(Data)
mod BlockSize <> 0))
then
exit;
PadLength := Data[High(Data)];
for I := Length(Data) - PadLength
to High(Data)
do
if Data[I] <> PadLength
then
exit;
Result := True;
end;
class function TDECPKCS7Padding.RemovePadding(
const Data: TBytes; BlockSize: integer = 0): TBytes;
var
PadLength: Integer;
begin
if not HasValidPadding(Data, BlockSize)
then
raise EDECCipherException.Create('
Invalid PKCS#7 padding');
PadLength := Data[High(Data)];
SetLength(Result, Length(Data) - PadLength);
if length(Result) > 0
then
Move(Data[0], Result[0], Length(Result));
end;
{ TDECPKCS5Padding }
class procedure TDECPKCS5Padding.DoPadding(
var Data: TBytes; Start: Integer);
var
I: Integer;
PadByte: Byte;
begin
PadByte := Length(Data) - Start;
// limiting PadByte to less 8 is the correct form , but its usage is really outdated, by removing this
// but by removing the limit we almost have the PKCS7, which should be used instead
if (PadByte < 1)
{or (PadByte>8)} then
raise EDECCipherException.Create('
Invalid PKCS#5 Padding operation');
for I := Start
to High(Data)
do
Data[I] := PadByte;
end;
class function TDECPKCS5Padding.AddPadding(
const Data: TBytes; BlockSize, MinLength: Integer): TBytes;
var
ResLength: Integer;
begin
ResLength := CalculatePaddingLength(Length(Data), BlockSize, MinLength);
if (ResLength < 0)
or (ResLength - Length(Data) > 255)
then
raise EDECCipherException.Create('
Invalid PKCS#5 Padding operation');
SetLength(Result, ResLength);
if Length(Data) > 0
then
Move(Data[0], Result[0], Length(Data));
DoPadding(Result, Length(Data));
end;
class function TDECPKCS5Padding.HasValidPadding(
const Data: TBytes; BlockSize: Integer): Boolean;
var
PadLength: Integer;
I: Integer;
begin
Result := False;
if (Length(Data) = 0)
or ((BlockSize > 0)
and (Length(Data)
mod BlockSize <> 0))
then
exit;
PadLength := Data[High(Data)];
if PadLength > 8
then // PKCS5 is outdated and limited to 8 bytes
Exit;
for I := Length(Data) - PadLength
to High(Data)
do
if Data[I] <> PadLength
then
exit;
Result := True;
end;
class function TDECPKCS5Padding.RemovePadding(
const Data: TBytes; BlockSize: Integer): TBytes;
var
PadLength: Integer;
begin
if not HasValidPadding(Data, BlockSize)
then
raise EDECCipherException.Create('
Invalid PKCS#5 padding');
PadLength := Data[High(Data)];
SetLength(Result, Length(Data) - PadLength);
if length(Result) > 0
then
Move(Data[0], Result[0], Length(Result));
end;
{ TANSIX923Padding }
class procedure TDECANSIX923Padding.DoPadding(
var Data: TBytes; Start: Integer);
var
PadByte: Byte;
begin
PadByte := Length(Data) - Start;
if PadByte < 1
then
raise EDECCipherException.Create('
Invalid ANSI X9.23 Padding operation');
//RandomBuffer(Data[Start], PadByte); // comment for zero fill
Data[High(Data)] := PadByte;
end;
class function TDECANSIX923Padding.AddPadding(
const Data: TBytes; BlockSize, MinLength: Integer): TBytes;
var
ResLength: Integer;
begin
{if (BlockSize <> 8) and (MinLength mod 8 <> 0)and ((Length(Data)-MinLength) >= 8) then // for limiting the length to one block of 8 bytes
raise EDECCipherException.Create('Invalid ANSI X9.23 Padding operation'); }
ResLength := CalculatePaddingLength(Length(Data), BlockSize, MinLength);
if (ResLength < 0)
or (ResLength - Length(Data) > 255)
then
raise EDECCipherException.Create('
Invalid ANSI X9.23 Padding operation');
SetLength(Result, ResLength);
if Length(Data) > 0
then
Move(Data[0], Result[0], Length(Data));
DoPadding(Result, Length(Data));
end;
class function TDECANSIX923Padding.HasValidPadding(
const Data: TBytes; BlockSize: Integer): Boolean;
var
PadLength: Integer;
begin
Result := False;
{if (Length(Data) = 0) or ((BlockSize <> 8)and (BlockSize<>0) and (Length(Data) mod BlockSize <> 0)) then // for limiting the length to one block of 8 bytes
exit; }
if (Length(Data) = 0)
or ((BlockSize > 0)
and (Length(Data)
mod BlockSize <> 0))
then
exit;
PadLength := Data[High(Data)];
{if PadLength > 8 then // for limiting the length to one block of 8 bytes
Exit;}
if PadLength > Length(Data)
then
Exit;
Result := True;
end;
class function TDECANSIX923Padding.RemovePadding(
const Data: TBytes; BlockSize: Integer): TBytes;
var
PadLength: Integer;
begin
if not HasValidPadding(Data, BlockSize)
then
raise EDECCipherException.Create('
Invalid ANSI X9.23 padding');
PadLength := Data[High(Data)];
SetLength(Result, Length(Data) - PadLength);
if length(Result) > 0
then
Move(Data[0], Result[0], Length(Result));
end;
{ TDECISO10126Padding }
class procedure TDECISO10126Padding.DoPadding(
var Data: TBytes; Start: Integer);
var
PadByte: Byte;
begin
PadByte := Length(Data) - Start;
if PadByte < 1
then
raise EDECCipherException.Create('
Invalid ISO 10126 Padding operation');
RandomBuffer(Data[Start], PadByte);
Data[High(Data)] := PadByte;
end;
class function TDECISO10126Padding.AddPadding(
const Data: TBytes; BlockSize, MinLength: Integer): TBytes;
var
ResLength: Integer;
begin
ResLength := CalculatePaddingLength(Length(Data), BlockSize, MinLength);
if (ResLength < 0)
or (ResLength - Length(Data) > 255)
then
raise EDECCipherException.Create('
Invalid ISO 10126 Padding operation');
SetLength(Result, ResLength);
if Length(Data) > 0
then
Move(Data[0], Result[0], Length(Data));
DoPadding(Result, Length(Data));
end;
class function TDECISO10126Padding.HasValidPadding(
const Data: TBytes; BlockSize: Integer): Boolean;
var
PadLength: Integer;
begin
Result := False;
if (Length(Data) = 0)
or ((BlockSize > 0)
and (Length(Data)
mod BlockSize <> 0))
then
exit;
PadLength := Data[High(Data)];
if PadLength > Length(Data)
then
Exit;
Result := True;
end;
class function TDECISO10126Padding.RemovePadding(
const Data: TBytes; BlockSize: Integer): TBytes;
var
PadLength: Integer;
begin
if not HasValidPadding(Data, BlockSize)
then
raise EDECCipherException.Create('
Invalid ISO 10126 padding');
PadLength := Data[High(Data)];
SetLength(Result, Length(Data) - PadLength);
if length(Result) > 0
then
Move(Data[0], Result[0], Length(Result));
end;
{ TDECISO7816Padding }
class procedure TDECISO7816Padding.DoPadding(
var Data: TBytes; Start: Integer);
begin
Data[Start] := $80;
// first bit is one followed by zeros;
end;
class function TDECISO7816Padding.AddPadding(
const Data: TBytes; BlockSize, MinLength: Integer): TBytes;
var
ResLength: Integer;
begin
ResLength := CalculatePaddingLength(Length(Data), BlockSize, MinLength);
if (ResLength < 0)
then
raise EDECCipherException.Create('
Invalid ISO 7816 Padding operation');
SetLength(Result, ResLength);
if Length(Data) > 0
then
Move(Data[0], Result[0], Length(Data));
DoPadding(Result, Length(Data));
end;
class function TDECISO7816Padding.HasValidPadding(
const Data: TBytes; BlockSize: Integer): Boolean;
var
I: Integer;
begin
Result := False;
if (Length(Data) = 0)
or ((BlockSize > 0)
and (Length(Data)
mod BlockSize <> 0))
then
exit;
I := High(Data);
while I > 0
do
if Data[I] <> 00
then
Break
else
Dec(I);
if Data[I] <> $80
then
Exit;
Result := True;
end;
class function TDECISO7816Padding.RemovePadding(
const Data: TBytes; BlockSize: Integer): TBytes;
var
I: Integer;
begin
if (Length(Data) = 0)
or ((BlockSize > 0)
and (Length(Data)
mod BlockSize <> 0))
then
raise EDECCipherException.Create('
Invalid ISO 7816 padding');
I := High(Data);
while I > 0
do
if Data[I] <> 00
then
Break
else
Dec(I);
if (Data[I] <> $80)
then
raise EDECCipherException.Create('
Invalid ISO 7816 padding');
SetLength(Result, I);
if length(Result) > 0
then
Move(Data[0], Result[0], Length(Result));
end;
end.