add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks - Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists - Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services - Add Microsoft OIDC SSO, API token management, and user lifecycle features - Add swagger-ui vendor integration and OpenAPI spec - Add production Docker setup and bin/ scripts - Update composer dependencies, config, templates, and frontend assets throughout Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
125
lib/Support/Crypto.php
Normal file
125
lib/Support/Crypto.php
Normal file
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Support;
|
||||
|
||||
class Crypto
|
||||
{
|
||||
private const CIPHER = 'aes-256-gcm';
|
||||
|
||||
public static function isConfigured(): bool
|
||||
{
|
||||
return self::getKey() !== null;
|
||||
}
|
||||
|
||||
public static function encryptString(string $plaintext): string
|
||||
{
|
||||
$key = self::getKey();
|
||||
if ($key === null) {
|
||||
throw new \RuntimeException('Missing APP_CRYPTO_KEY');
|
||||
}
|
||||
|
||||
$iv = random_bytes(12);
|
||||
$tag = '';
|
||||
$ciphertext = openssl_encrypt(
|
||||
$plaintext,
|
||||
self::CIPHER,
|
||||
$key,
|
||||
OPENSSL_RAW_DATA,
|
||||
$iv,
|
||||
$tag
|
||||
);
|
||||
|
||||
if (!is_string($ciphertext) || strlen($tag) !== 16) {
|
||||
throw new \RuntimeException('Encryption failed');
|
||||
}
|
||||
|
||||
$payload = json_encode([
|
||||
'v' => 1,
|
||||
'iv' => self::base64UrlEncode($iv),
|
||||
'tag' => self::base64UrlEncode($tag),
|
||||
'ct' => self::base64UrlEncode($ciphertext),
|
||||
]);
|
||||
|
||||
if (!is_string($payload)) {
|
||||
throw new \RuntimeException('Encryption payload encoding failed');
|
||||
}
|
||||
|
||||
return base64_encode($payload);
|
||||
}
|
||||
|
||||
public static function decryptString(string $encoded): string
|
||||
{
|
||||
$key = self::getKey();
|
||||
if ($key === null) {
|
||||
throw new \RuntimeException('Missing APP_CRYPTO_KEY');
|
||||
}
|
||||
|
||||
$json = base64_decode($encoded, true);
|
||||
if (!is_string($json) || $json === '') {
|
||||
throw new \RuntimeException('Encrypted payload is invalid');
|
||||
}
|
||||
|
||||
$payload = json_decode($json, true);
|
||||
if (!is_array($payload)) {
|
||||
throw new \RuntimeException('Encrypted payload cannot be decoded');
|
||||
}
|
||||
|
||||
$iv = self::base64UrlDecode((string) ($payload['iv'] ?? ''));
|
||||
$tag = self::base64UrlDecode((string) ($payload['tag'] ?? ''));
|
||||
$ciphertext = self::base64UrlDecode((string) ($payload['ct'] ?? ''));
|
||||
if ($iv === '' || strlen($iv) !== 12 || $tag === '' || strlen($tag) !== 16 || $ciphertext === '') {
|
||||
throw new \RuntimeException('Encrypted payload is malformed');
|
||||
}
|
||||
|
||||
$plaintext = openssl_decrypt(
|
||||
$ciphertext,
|
||||
self::CIPHER,
|
||||
$key,
|
||||
OPENSSL_RAW_DATA,
|
||||
$iv,
|
||||
$tag
|
||||
);
|
||||
|
||||
if (!is_string($plaintext)) {
|
||||
throw new \RuntimeException('Decryption failed');
|
||||
}
|
||||
|
||||
return $plaintext;
|
||||
}
|
||||
|
||||
private static function getKey(): ?string
|
||||
{
|
||||
$raw = defined('APP_CRYPTO_KEY') ? trim((string) APP_CRYPTO_KEY) : '';
|
||||
if ($raw === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$hex = preg_match('/^[0-9a-f]{64}$/i', $raw) ? hex2bin($raw) : false;
|
||||
if (is_string($hex) && strlen($hex) === 32) {
|
||||
return $hex;
|
||||
}
|
||||
|
||||
$base64 = base64_decode($raw, true);
|
||||
if (is_string($base64) && strlen($base64) === 32) {
|
||||
return $base64;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static function base64UrlEncode(string $value): string
|
||||
{
|
||||
return rtrim(strtr(base64_encode($value), '+/', '-_'), '=');
|
||||
}
|
||||
|
||||
private static function base64UrlDecode(string $value): string
|
||||
{
|
||||
$value = strtr($value, '-_', '+/');
|
||||
$pad = strlen($value) % 4;
|
||||
if ($pad > 0) {
|
||||
$value .= str_repeat('=', 4 - $pad);
|
||||
}
|
||||
$decoded = base64_decode($value, true);
|
||||
return is_string($decoded) ? $decoded : '';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user