Initial V2 commit

This commit is contained in:
Hamza-Ayed
2026-04-22 21:59:56 +03:00
commit 4706404488
53 changed files with 4392 additions and 0 deletions

View File

@@ -0,0 +1,104 @@
<?php
namespace App\Services;
/**
* Payload Crypto Service — AES-256-GCM
*
* Dynamic encryption for all payloads between Flutter apps and the API.
* Unlike LegacyEncryption which uses static IV, this generates a unique IV per request.
*
* Format: base64(IV + ciphertext + tag)
* - IV: 12 bytes (random per encryption)
* - Tag: 16 bytes (integrity verification)
*/
class PayloadCrypto
{
private string $key;
private const CIPHER = 'aes-256-gcm';
private const IV_LENGTH = 12;
private const TAG_LENGTH = 16;
public function __construct()
{
$keyPath = config('intaleq.legacy_enc_key_path');
if (!file_exists($keyPath)) {
throw new \RuntimeException('Encryption key not found');
}
// Derive a 32-byte key from the stored key using HKDF
$rawKey = trim(file_get_contents($keyPath));
$this->key = hash_hkdf('sha256', $rawKey, 32, 'intaleq-v2-gcm');
}
/**
* Encrypt payload for sending to Flutter app
*
* @param array|string $data Data to encrypt
* @return string Base64 encoded (IV + ciphertext + tag)
*/
public function encrypt($data): string
{
$plaintext = is_array($data) ? json_encode($data) : $data;
$iv = random_bytes(self::IV_LENGTH);
$tag = '';
$ciphertext = openssl_encrypt(
$plaintext,
self::CIPHER,
$this->key,
OPENSSL_RAW_DATA,
$iv,
$tag,
'', // Additional Authenticated Data (AAD)
self::TAG_LENGTH
);
if ($ciphertext === false) {
throw new \RuntimeException('Encryption failed');
}
// Pack: IV (12) + ciphertext (variable) + tag (16)
return base64_encode($iv . $ciphertext . $tag);
}
/**
* Decrypt payload received from Flutter app
*
* @param string $encoded Base64 encoded (IV + ciphertext + tag)
* @return string|null Decrypted plaintext or null on failure
*/
public function decrypt(string $encoded): ?string
{
$raw = base64_decode($encoded, true);
if ($raw === false || strlen($raw) < self::IV_LENGTH + self::TAG_LENGTH + 1) {
return null;
}
$iv = substr($raw, 0, self::IV_LENGTH);
$tag = substr($raw, -self::TAG_LENGTH);
$ciphertext = substr($raw, self::IV_LENGTH, -self::TAG_LENGTH);
$plaintext = openssl_decrypt(
$ciphertext,
self::CIPHER,
$this->key,
OPENSSL_RAW_DATA,
$iv,
$tag
);
return $plaintext !== false ? $plaintext : null;
}
/**
* Decrypt and decode JSON payload
*/
public function decryptJson(string $encoded): ?array
{
$plaintext = $this->decrypt($encoded);
if (!$plaintext) return null;
$data = json_decode($plaintext, true);
return is_array($data) ? $data : null;
}
}