258 lines
7.9 KiB
Plaintext
258 lines
7.9 KiB
Plaintext
|
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.
|