improved security
Some checks failed
PHPUnit / test (push) Has been cancelled

This commit is contained in:
2026-06-24 09:37:08 +02:00
parent 623fe0f464
commit b02f264b23
5 changed files with 65 additions and 7 deletions

View File

@ -1,14 +1,16 @@
<?php
/**
* Application-layer encryption for sensitive mobile API payloads.
* AES-256-CBC (IV prepended) + HKDF-SHA256 session key from the Bearer token (info: qdb-aes).
* AES-256-GCM (IV prepended) + HKDF-SHA256 session key from the Bearer token (info: qdb-aes).
* Legacy CBC envelopes are accepted for compatibility with older app builds.
*/
function qdb_sensitive_envelope(string $plainJson, string $tokenHex): array {
$key = hkdf_session_key_from_token($tokenHex);
return [
'encrypted' => true,
'payload' => base64_encode(aes256_cbc_encrypt_bytes($plainJson, $key)),
'alg' => 'A256GCM',
'payload' => base64_encode(qdb_aes256_gcm_encrypt_bytes($plainJson, $key)),
];
}
@ -21,5 +23,38 @@ function qdb_decrypt_sensitive_envelope(array $envelope, string $tokenHex): stri
throw new InvalidArgumentException('Invalid base64 payload');
}
$key = hkdf_session_key_from_token($tokenHex);
return aes256_cbc_decrypt_bytes($raw, $key);
$alg = isset($envelope['alg']) && is_string($envelope['alg']) ? $envelope['alg'] : 'A256CBC';
return match ($alg) {
'A256GCM' => qdb_aes256_gcm_decrypt_bytes($raw, $key),
'A256CBC' => aes256_cbc_decrypt_bytes($raw, $key),
default => throw new InvalidArgumentException('Unsupported encrypted envelope algorithm'),
};
}
function qdb_aes256_gcm_encrypt_bytes(string $plain, string $key): string {
$key = str_pad(substr($key, 0, 32), 32, "\0");
$iv = random_bytes(12);
$tag = '';
$cipher = openssl_encrypt($plain, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag, '');
if ($cipher === false || strlen($tag) !== 16) {
throw new RuntimeException('openssl_encrypt failed');
}
return $iv . $cipher . $tag;
}
function qdb_aes256_gcm_decrypt_bytes(string $data, string $key): string {
$ivLen = 12;
$tagLen = 16;
if (strlen($data) <= $ivLen + $tagLen) {
throw new InvalidArgumentException('cipher too short');
}
$key = str_pad(substr($key, 0, 32), 32, "\0");
$iv = substr($data, 0, $ivLen);
$tag = substr($data, -$tagLen);
$ct = substr($data, $ivLen, -$tagLen);
$plain = openssl_decrypt($ct, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag, '');
if ($plain === false) {
throw new InvalidArgumentException('openssl_decrypt failed');
}
return $plain;
}