- Mise à jour VERSION vers 3.3.4 - Optimisations et révisions architecture API (deploy-api.sh, scripts de migration) - Ajout documentation Stripe Tap to Pay complète - Migration vers polices Inter Variable pour Flutter - Optimisations build Android et nettoyage fichiers temporaires - Amélioration système de déploiement avec gestion backups - Ajout scripts CRON et migrations base de données 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
189 lines
5.4 KiB
PHP
189 lines
5.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* Service de chiffrement/déchiffrement AES-256-CBC
|
|
* Compatible avec le système de chiffrement de l'API Geosector
|
|
*/
|
|
class CryptoService {
|
|
private string $encryptionKey;
|
|
private string $cipher = 'AES-256-CBC';
|
|
|
|
public function __construct(string $encryptionKey) {
|
|
// Décoder la clé base64
|
|
$this->encryptionKey = base64_decode($encryptionKey);
|
|
|
|
if (strlen($this->encryptionKey) !== 32) {
|
|
throw new RuntimeException("La clé de chiffrement doit faire 32 bytes (256 bits)");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Déchiffre les données "searchable" (encrypted_user_name, encrypted_email)
|
|
* Format: base64 simple avec IV fixe
|
|
*/
|
|
public function decryptSearchable(?string $encryptedData): ?string {
|
|
if (empty($encryptedData)) {
|
|
return null;
|
|
}
|
|
|
|
$encrypted = base64_decode($encryptedData);
|
|
if ($encrypted === false) {
|
|
return null;
|
|
}
|
|
|
|
$iv = str_repeat("\0", 16); // IV fixe
|
|
|
|
$decrypted = openssl_decrypt($encrypted, $this->cipher, $this->encryptionKey, 0, $iv);
|
|
|
|
if ($decrypted === false) {
|
|
return null;
|
|
}
|
|
|
|
// Supprimer le caractère de contrôle ajouté
|
|
if (substr($decrypted, -1) === "\x01") {
|
|
return substr($decrypted, 0, -1);
|
|
}
|
|
|
|
return $decrypted;
|
|
}
|
|
|
|
/**
|
|
* Déchiffre les données avec IV aléatoire (encrypted_name, encrypted_phone, etc.)
|
|
* Format: base64(IV + encrypted)
|
|
*/
|
|
public function decryptWithIV(?string $encryptedData): ?string {
|
|
if (empty($encryptedData)) {
|
|
return null;
|
|
}
|
|
|
|
$data = base64_decode($encryptedData);
|
|
if ($data === false) {
|
|
return null;
|
|
}
|
|
|
|
$ivLength = openssl_cipher_iv_length($this->cipher);
|
|
|
|
if (strlen($data) <= $ivLength) {
|
|
return null;
|
|
}
|
|
|
|
$iv = substr($data, 0, $ivLength);
|
|
$encrypted = substr($data, $ivLength);
|
|
|
|
$decrypted = openssl_decrypt($encrypted, $this->cipher, $this->encryptionKey, 0, $iv);
|
|
|
|
return $decrypted !== false ? $decrypted : null;
|
|
}
|
|
|
|
/**
|
|
* Déchiffre une valeur chiffrée
|
|
*
|
|
* @param string|null $encryptedValue Valeur chiffrée (format base64:iv:data)
|
|
* @return string|null Valeur déchiffrée ou null
|
|
*/
|
|
public function decrypt(?string $encryptedValue): ?string {
|
|
if (empty($encryptedValue)) {
|
|
return null;
|
|
}
|
|
|
|
// Le format de l'API est : base64:iv:encrypted_data
|
|
$parts = explode(':', $encryptedValue);
|
|
|
|
if (count($parts) !== 3 || $parts[0] !== 'base64') {
|
|
// Format invalide, peut-être déjà en clair
|
|
return $encryptedValue;
|
|
}
|
|
|
|
$iv = base64_decode($parts[1]);
|
|
$encrypted = base64_decode($parts[2]);
|
|
|
|
if ($iv === false || $encrypted === false) {
|
|
throw new RuntimeException("Impossible de décoder les données chiffrées");
|
|
}
|
|
|
|
$decrypted = openssl_decrypt(
|
|
$encrypted,
|
|
$this->cipher,
|
|
$this->encryptionKey,
|
|
OPENSSL_RAW_DATA,
|
|
$iv
|
|
);
|
|
|
|
if ($decrypted === false) {
|
|
throw new RuntimeException("Échec du déchiffrement : " . openssl_error_string());
|
|
}
|
|
|
|
return $decrypted;
|
|
}
|
|
|
|
/**
|
|
* Chiffre une valeur
|
|
*
|
|
* @param string $value Valeur à chiffrer
|
|
* @return string Valeur chiffrée (format base64:iv:data)
|
|
*/
|
|
public function encrypt(string $value): string {
|
|
$ivLength = openssl_cipher_iv_length($this->cipher);
|
|
$iv = openssl_random_pseudo_bytes($ivLength);
|
|
|
|
$encrypted = openssl_encrypt(
|
|
$value,
|
|
$this->cipher,
|
|
$this->encryptionKey,
|
|
OPENSSL_RAW_DATA,
|
|
$iv
|
|
);
|
|
|
|
if ($encrypted === false) {
|
|
throw new RuntimeException("Échec du chiffrement : " . openssl_error_string());
|
|
}
|
|
|
|
return 'base64:' . base64_encode($iv) . ':' . base64_encode($encrypted);
|
|
}
|
|
|
|
/**
|
|
* Déchiffre plusieurs colonnes d'un tableau
|
|
*
|
|
* @param array $row Ligne de base de données
|
|
* @param array $encryptedColumns Liste des colonnes à déchiffrer (sans le préfixe encrypted_)
|
|
* @return array Tableau avec colonnes déchiffrées
|
|
*/
|
|
public function decryptRow(array $row, array $encryptedColumns): array {
|
|
$decrypted = $row;
|
|
|
|
foreach ($encryptedColumns as $column) {
|
|
$encryptedColumn = 'encrypted_' . $column;
|
|
|
|
if (isset($row[$encryptedColumn])) {
|
|
$decrypted[$column] = $this->decrypt($row[$encryptedColumn]);
|
|
}
|
|
}
|
|
|
|
return $decrypted;
|
|
}
|
|
|
|
/**
|
|
* Déchiffre les colonnes encrypted_* d'un utilisateur
|
|
*
|
|
* @param array $user Données utilisateur
|
|
* @return array Utilisateur avec données déchiffrées
|
|
*/
|
|
public function decryptUser(array $user): array {
|
|
$columns = ['user_name', 'email', 'name', 'phone', 'mobile'];
|
|
return $this->decryptRow($user, $columns);
|
|
}
|
|
|
|
/**
|
|
* Déchiffre les colonnes encrypted_* d'une entité
|
|
*
|
|
* @param array $entite Données entité
|
|
* @return array Entité avec données déchiffrées
|
|
*/
|
|
public function decryptEntite(array $entite): array {
|
|
$columns = ['name', 'email', 'phone', 'mobile', 'iban', 'bic'];
|
|
return $this->decryptRow($entite, $columns);
|
|
}
|
|
}
|