EonaCatCipher/Delphi/EonaCatCipher.pas

258 lines
7.9 KiB
Plaintext
Raw Permalink Normal View History

2024-09-25 19:22:33 +02:00
unit EonaCatCipher;
interface
uses
SysUtils, Classes, Hash, HMAC, Cryptography, Generics.Collections;
{*
* EonaCatCipher - Because security is key!
*
* Copyright (c) 2024 EonaCat (Jeroen Saey)
*
* https://eonacat.com/license
*
* TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
* OF SOFTWARE BY EONACAT (JEROEN SAEY)
*
* This software is provided "as is", without any express or implied warranty.
* 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.
*
* You may use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and permit persons to whom the Software is furnished
* to do so, subject to the following conditions:
*
* 1. The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* 2. The software must not be used for any unlawful purpose.
*
* For any inquiries, please contact: eonacat@gmail.com
*}
type
TEonaCatCipher = class(TObject)
private
const
DEFAULT_SALT_SIZE = 2048; // Salt size for key derivation
DEFAULT_IV_SIZE = 2048; // IV size (2048 bits)
DEFAULT_KEY_SIZE = 2048; // Key size (2048 bits)
DEFAULT_ROUNDS = 2048; // Rounds
DEFAULT_BLOCK_SIZE = 8192; // 8KB
HMAC_KEY_SIZE = 32; // Key size for HMAC (256 bits)
var
_derivedKey: TBytes; // Derived encryption key
_hmacKey: TBytes; // HMAC key
_ivSize: Integer; // IV size
_keySize: Integer; // Key size
_rounds: Integer; // Number of rounds for key derivation
_blockSize: Integer; // The size of the block that is created
function GenerateRandomBytes(size: Integer): TBytes;
function DeriveKeyAndHMAC(password: string; saltSize: Integer): TBytes;
function PBKDF2(password: string; salt: TBytes; keyLength: Integer; iterations: Integer): TBytes;
function GenerateHMAC(data: TBytes): TBytes;
function AreEqual(a, b: TBytes): Boolean;
public
constructor Create(password: string; saltSize: Integer = DEFAULT_SALT_SIZE;
ivSize: Integer = DEFAULT_IV_SIZE; keySize: Integer = DEFAULT_KEY_SIZE;
rounds: Integer = DEFAULT_ROUNDS; blockSize: Integer = DEFAULT_BLOCK_SIZE);
destructor Destroy; override;
function Encrypt(plaintext: string): TBytes;
function Decrypt(ciphertextWithHMAC: TBytes): string;
end;
implementation
constructor TEonaCatCipher.Create(password: string; saltSize: Integer;
ivSize: Integer; keySize: Integer; rounds: Integer; blockSize: Integer);
begin
inherited Create;
if password = '' then
raise Exception.Create('EonaCatCipher: Password cannot be null or empty.');
_ivSize := ivSize;
_keySize := keySize;
_rounds := rounds;
_blockSize := blockSize;
// Derive encryption key and HMAC key
(_derivedKey, _hmacKey) := DeriveKeyAndHMAC(password, saltSize);
end;
destructor TEonaCatCipher.Destroy;
begin
if Length(_derivedKey) > 0 then
FillChar(_derivedKey[0], Length(_derivedKey), 0);
if Length(_hmacKey) > 0 then
FillChar(_hmacKey[0], Length(_hmacKey), 0);
inherited Destroy;
end;
function TEonaCatCipher.GenerateRandomBytes(size: Integer): TBytes;
begin
SetLength(Result, size);
RandomBytes(Result);
end;
function TEonaCatCipher.DeriveKeyAndHMAC(password: string; saltSize: Integer): TBytes;
var
salt, encryptionKey, hmacKey: TBytes;
begin
salt := GenerateRandomBytes(saltSize);
encryptionKey := PBKDF2(password, salt, _keySize, _rounds);
hmacKey := PBKDF2(password, salt, HMAC_KEY_SIZE, _rounds);
SetLength(Result, saltSize + Length(encryptionKey));
Move(salt[0], Result[0], saltSize);
Move(encryptionKey[0], Result[saltSize], Length(encryptionKey));
// Combine encryptionKey and hmacKey if needed for further processing
end;
function TEonaCatCipher.PBKDF2(password: string; salt: TBytes; keyLength: Integer; iterations: Integer): TBytes;
var
hmac: IHMAC;
hashLength, requiredBytes, blocksNeeded, blockIndex: Integer;
derivedKey, block: TBytes;
currentBlock: TBytes;
u: TBytes;
begin
hmac := THashFactory.THMAC.CreateHMAC(THashFactory.TCrypto.CreateSHA512);
hmac.Key := TEncoding.UTF8.GetBytes(password);
hashLength := hmac.HashSize div 8;
requiredBytes := keyLength;
blocksNeeded := Ceil(requiredBytes / hashLength);
SetLength(derivedKey, requiredBytes);
SetLength(block, hashLength);
for blockIndex := 1 to blocksNeeded do
begin
SetLength(currentBlock, Length(salt) + SizeOf(Integer));
Move(salt[0], currentBlock[0], Length(salt));
Move(blockIndex, currentBlock[Length(salt)], SizeOf(Integer));
// U1 = HMAC(password, salt + blockIndex)
u := hmac.ComputeHash(currentBlock);
Move(u[0], block[0], hashLength);
Move(u[0], derivedKey[(blockIndex - 1) * hashLength], Min(hashLength, requiredBytes));
// Iterations
for var iteration := 1 to iterations - 1 do
begin
// U2 = HMAC(password, U1)
u := hmac.ComputeHash(u);
for var i := 0 to hashLength - 1 do
block[i] := block[i] xor u[i];
// Append result to derived key
Move(block[0], derivedKey[(blockIndex - 1) * hashLength], Min(hashLength, requiredBytes));
end;
end;
Result := derivedKey;
end;
function TEonaCatCipher.Encrypt(plaintext: string): TBytes;
var
iv, plaintextBytes: TBytes;
ciphertext: TBytes;
result: TBytes;
hmac: TBytes;
begin
iv := GenerateRandomBytes(_ivSize);
plaintextBytes := TEncoding.UTF8.GetBytes(plaintext);
SetLength(ciphertext, Length(plaintextBytes));
// Note: Implement the EonaCatCrypto class and its Generate method for encryption here
// using a similar approach to the C# version
// Combine IV and ciphertext
SetLength(result, _ivSize + Length(ciphertext));
Move(iv[0], result[0], _ivSize);
Move(ciphertext[0], result[_ivSize], Length(ciphertext));
// Generate HMAC for integrity check
hmac := GenerateHMAC(result);
// Combine result and HMAC
SetLength(Result, Length(result) + Length(hmac));
Move(result[0], Result[0], Length(result));
Move(hmac[0], Result[Length(result)], Length(hmac));
end;
function TEonaCatCipher.Decrypt(ciphertextWithHMAC: TBytes): string;
var
hmacOffset: Integer;
providedHMAC, ciphertext: TBytes;
calculatedHMAC: TBytes;
iv: TBytes;
encryptedData: TBytes;
decryptedData: TBytes;
begin
hmacOffset := Length(ciphertextWithHMAC) - HMAC_KEY_SIZE;
// Separate HMAC from the ciphertext
SetLength(providedHMAC, HMAC_KEY_SIZE);
Move(ciphertextWithHMAC[hmacOffset], providedHMAC[0], HMAC_KEY_SIZE);
SetLength(ciphertext, hmacOffset);
Move(ciphertextWithHMAC[0], ciphertext[0], hmacOffset);
// Verify HMAC before decrypting
calculatedHMAC := GenerateHMAC(ciphertext);
if not AreEqual(providedHMAC, calculatedHMAC) then
raise Exception.Create('EonaCatCipher: HMAC validation failed. Data may have been tampered with.');
// Extract IV
SetLength(iv, _ivSize);
Move(ciphertext[0], iv[0], _ivSize);
// Extract encrypted data
SetLength(encryptedData, Length(ciphertext) - _ivSize);
Move(ciphertext[_ivSize], encryptedData[0], Length(encryptedData));
// Decrypt
// Note: Implement the EonaCatCrypto class and its Generate method for decryption here
// using a similar approach to the C# version
Result := TEncoding.UTF8.GetString(decryptedData);
end;
function TEonaCatCipher.GenerateHMAC(data: TBytes): TBytes;
var
hmac: IHMAC;
begin
hmac := THashFactory.THMAC.CreateHMAC(THashFactory.TCrypto.CreateSHA256);
hmac.Key := _hmacKey;
Result := hmac.ComputeHash(data);
end;
function TEonaCatCipher.AreEqual(a, b: TBytes): Boolean;
var
i: Integer;
begin
Result := Length(a) = Length(b);
if Result then
begin
for i := 0 to High(a) do
if a[i] <> b[i] then
begin
Result := False;
Break;
end;
end;
end;
end.