whisper-money/app/Services/Demo/DemoEncryptionService.php

103 lines
2.4 KiB
PHP

<?php
namespace App\Services\Demo;
class DemoEncryptionService
{
private const PBKDF2_ITERATIONS = 100000;
private const SALT_LENGTH = 16;
private const IV_LENGTH = 12;
private const KEY_LENGTH = 32;
/**
* Generate a deterministic salt from a seed string.
*/
public function generateSalt(string $seed = 'demo'): string
{
$salt = hash('sha256', $seed, true);
return base64_encode(substr($salt, 0, self::SALT_LENGTH));
}
/**
* Derive an AES key from password and salt using PBKDF2.
*/
public function deriveKey(string $password, string $saltBase64): string
{
$salt = base64_decode($saltBase64);
return hash_pbkdf2(
'sha256',
$password,
$salt,
self::PBKDF2_ITERATIONS,
self::KEY_LENGTH,
true
);
}
/**
* Encrypt plaintext using AES-256-GCM.
*
* @return array{encrypted: string, iv: string}
*/
public function encrypt(string $plaintext, string $key, ?string $deterministicIvSeed = null): array
{
if ($deterministicIvSeed !== null) {
$iv = substr(hash('sha256', $deterministicIvSeed, true), 0, self::IV_LENGTH);
} else {
$iv = random_bytes(self::IV_LENGTH);
}
$tag = '';
$encrypted = openssl_encrypt(
$plaintext,
'aes-256-gcm',
$key,
OPENSSL_RAW_DATA,
$iv,
$tag,
'',
16
);
$encryptedWithTag = $encrypted.$tag;
return [
'encrypted' => base64_encode($encryptedWithTag),
'iv' => base64_encode($iv),
];
}
/**
* Decrypt ciphertext using AES-256-GCM.
*/
public function decrypt(string $encryptedBase64, string $key, string $ivBase64): string
{
$encryptedWithTag = base64_decode($encryptedBase64);
$iv = base64_decode($ivBase64);
$tagLength = 16;
$encrypted = substr($encryptedWithTag, 0, -$tagLength);
$tag = substr($encryptedWithTag, -$tagLength);
$decrypted = openssl_decrypt(
$encrypted,
'aes-256-gcm',
$key,
OPENSSL_RAW_DATA,
$iv,
$tag
);
if ($decrypted === false) {
throw new \RuntimeException('Decryption failed');
}
return $decrypted;
}
}