Position actuelle: Accueil> Derniers articles> Comment utiliser hash_hmac_file () pour effectuer la protection de la somme de contrôle de sécurité sur les fichiers téléchargés?

Comment utiliser hash_hmac_file () pour effectuer la protection de la somme de contrôle de sécurité sur les fichiers téléchargés?

gitbox 2025-08-27

Idée de base (de haut niveau)

  1. Il peut vérifier que le fichier n'a pas été falsifié et que seule la partie qui sait que la clé peut générer la signature correcte. Contrairement à un hachage simple (comme SHA256), HMAC utilise des clés pour prévenir la contrefaçon.

  2. Le processus correct de téléchargement de fichiers est généralement:

    • Générer / distribuer un secret partagé (ou utiliser une clé privée côté serveur);

    • Le client ou l'expéditeur calcule le HMAC (par exemple, HMAC-SHA256) pour le fichier et envoie la signature (en-tête HTTP ou champ de formulaire) avec le téléchargement;

    • Après avoir reçu le fichier, le serveur utilise la même clé pour recalculer le HMAC et compare les signatures avec la comparaison de sécurité de synchronisation ( HASH_EQUALS ) et transmet la vérification lorsque le match est apparié.

  3. Transférer des signatures et des fichiers via HTTPS pour toujours. Les signatures seront écoutées sur HTTP en texte clair et réutilisées.


Pourquoi utiliser hash_hmac_file () ?

  • hash_hmac_file ($ algo, $ filename, $ key, $ raw_output = false) calcule hmac directement sur le fichier, et les fichiers de lecture en streaming sont traités en interne. Il n'est pas nécessaire de lire le fichier entier dans la mémoire en premier, ce qui convient aux fichiers moyens et grands.

  • Lorsque vous devez implémenter rapidement la vérification côté serveur, hash_hmac_file () est une solution concise et efficace. Cependant, pour des scénarios plus complexes (tels que le téléchargement de Shard), vous pouvez également utiliser hash_init () / hash_update () / hash_final () pour implémenter des calculs bloc par bloc.


Exemple de base: Signature du client + Vérification du serveur (fichier unique)

Ce qui suit montre une situation commune: le client utilise une clé partagée pour effectuer HMAC sur le fichier (l'exemple du client est omis), et le serveur utilise hash_hmac_file () pour vérifier le téléchargement $ _files .

Server Receiving end Verify_upload.php (exemple simple):

 <span><span><span class="hljs-meta">&lt;?php</span></span><span>
</span><span><span class="hljs-comment">// verify_upload.php</span></span><span>

</span><span><span class="hljs-comment">// 1) Stockage des clés sécurisé(Utiliser des variables d&#39;environnement dans l&#39;exemple)</span></span><span>
</span><span><span class="hljs-variable">$HMAC_KEY</span></span><span> = </span><span><span class="hljs-title function_ invoke__">getenv</span></span><span>(</span><span><span class="hljs-string">'UPLOAD_HMAC_KEY'</span></span><span>); </span><span><span class="hljs-comment">// Veuillez le définir en toute sécurité pendant le déploiement(Variables d&#39;environnement / Vault)</span></span><span>
</span><span><span class="hljs-keyword">if</span></span><span> (!</span><span><span class="hljs-variable">$HMAC_KEY</span></span><span>) {
    </span><span><span class="hljs-title function_ invoke__">http_response_code</span></span><span>(</span><span><span class="hljs-number">500</span></span><span>);
    </span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-string">"Server misconfiguration."</span></span><span>;
    </span><span><span class="hljs-keyword">exit</span></span><span>;
}

</span><span><span class="hljs-comment">// 2) Supposons que le client met la signature HTTP Header: X-File-Signature</span></span><span>
</span><span><span class="hljs-variable">$clientSig</span></span><span> = </span><span><span class="hljs-keyword">isset</span></span><span>(</span><span><span class="hljs-variable">$_SERVER</span></span><span>[</span><span><span class="hljs-string">'HTTP_X_FILE_SIGNATURE'</span></span><span>]) ? </span><span><span class="hljs-variable">$_SERVER</span></span><span>[</span><span><span class="hljs-string">'HTTP_X_FILE_SIGNATURE'</span></span><span>] : </span><span><span class="hljs-literal">null</span></span><span>;
</span><span><span class="hljs-keyword">if</span></span><span> (!</span><span><span class="hljs-variable">$clientSig</span></span><span>) {
    </span><span><span class="hljs-title function_ invoke__">http_response_code</span></span><span>(</span><span><span class="hljs-number">400</span></span><span>);
    </span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-string">"Missing signature."</span></span><span>;
    </span><span><span class="hljs-keyword">exit</span></span><span>;
}

</span><span><span class="hljs-keyword">if</span></span><span> (!</span><span><span class="hljs-keyword">isset</span></span><span>(</span><span><span class="hljs-variable">$_FILES</span></span><span>[</span><span><span class="hljs-string">'file'</span></span><span>]) || </span><span><span class="hljs-variable">$_FILES</span></span><span>[</span><span><span class="hljs-string">'file'</span></span><span>][</span><span><span class="hljs-string">'error'</span></span><span>] !== UPLOAD_ERR_OK) {
    </span><span><span class="hljs-title function_ invoke__">http_response_code</span></span><span>(</span><span><span class="hljs-number">400</span></span><span>);
    </span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-string">"Upload failed."</span></span><span>;
    </span><span><span class="hljs-keyword">exit</span></span><span>;
}

</span><span><span class="hljs-comment">// Chemin de téléchargement temporaire</span></span><span>
</span><span><span class="hljs-variable">$tmpPath</span></span><span> = </span><span><span class="hljs-variable">$_FILES</span></span><span>[</span><span><span class="hljs-string">'file'</span></span><span>][</span><span><span class="hljs-string">'tmp_name'</span></span><span>];

</span><span><span class="hljs-comment">// 3) utiliser hash_hmac_file Calculer la signature côté serveur(utiliser SHA256)</span></span><span>
</span><span><span class="hljs-variable">$algo</span></span><span> = </span><span><span class="hljs-string">'sha256'</span></span><span>;
</span><span><span class="hljs-variable">$serverSig</span></span><span> = </span><span><span class="hljs-title function_ invoke__">hash_hmac_file</span></span><span>(</span><span><span class="hljs-variable">$algo</span></span><span>, </span><span><span class="hljs-variable">$tmpPath</span></span><span>, </span><span><span class="hljs-variable">$HMAC_KEY</span></span><span>);

</span><span><span class="hljs-comment">// 4) utiliser hash_equals Faire la comparaison de la sécurité du synchronisation,Évitez la fuite d&#39;informations</span></span><span>
</span><span><span class="hljs-keyword">if</span></span><span> (!</span><span><span class="hljs-title function_ invoke__">hash_equals</span></span><span>(</span><span><span class="hljs-variable">$serverSig</span></span><span>, </span><span><span class="hljs-variable">$clientSig</span></span><span>)) {
    </span><span><span class="hljs-title function_ invoke__">http_response_code</span></span><span>(</span><span><span class="hljs-number">403</span></span><span>);
    </span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-string">"Signature mismatch. File may be tampered."</span></span><span>;
    </span><span><span class="hljs-comment">// Les journaux d&#39;audit peuvent être enregistrés:source IP、nom de fichier、Temps et attendre</span></span><span>
    </span><span><span class="hljs-keyword">exit</span></span><span>;
}

</span><span><span class="hljs-comment">// 5) La vérification de la signature est passée,Déplacer les fichiers en toute sécurité vers le répertoire final et définir les autorisations</span></span><span>
</span><span><span class="hljs-variable">$dest</span></span><span> = </span><span><span class="hljs-keyword">__DIR__</span></span><span> . </span><span><span class="hljs-string">'/uploads/'</span></span><span> . </span><span><span class="hljs-title function_ invoke__">basename</span></span><span>(</span><span><span class="hljs-variable">$_FILES</span></span><span>[</span><span><span class="hljs-string">'file'</span></span><span>][</span><span><span class="hljs-string">'name'</span></span><span>]);
</span><span><span class="hljs-keyword">if</span></span><span> (!</span><span><span class="hljs-title function_ invoke__">move_uploaded_file</span></span><span>(</span><span><span class="hljs-variable">$tmpPath</span></span><span>, </span><span><span class="hljs-variable">$dest</span></span><span>)) {
    </span><span><span class="hljs-title function_ invoke__">http_response_code</span></span><span>(</span><span><span class="hljs-number">500</span></span><span>);
    </span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-string">"Failed to store file."</span></span><span>;
    </span><span><span class="hljs-keyword">exit</span></span><span>;
}

</span><span><span class="hljs-comment">// Facultatif:Enregistrer la signature/Les métadonnées sont envoyées à la base de données pour une vérification ultérieure</span></span><span>
</span><span><span class="hljs-comment">// $db-&gt;insert('uploads', ['name'=&gt;..., 'sig'=&gt;$serverSig, ...]);</span></span><span>

</span><span><span class="hljs-title function_ invoke__">http_response_code</span></span><span>(</span><span><span class="hljs-number">200</span></span><span>);
</span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-string">"Upload verified and stored."</span></span><span>;
</span></span>

Le client doit envoyer une valeur de signature (comme une chaîne hexadécimale) avec la demande. Exemple de génération HMAC (Client PHP ou démonstration de ligne de commande):

 <span><span><span class="hljs-meta">&lt;?php</span></span><span>
</span><span><span class="hljs-comment">// client_sign.php</span></span><span>
</span><span><span class="hljs-variable">$key</span></span><span> = </span><span><span class="hljs-string">'shared-secret-key'</span></span><span>;
</span><span><span class="hljs-variable">$file</span></span><span> = </span><span><span class="hljs-string">'/path/to/file.bin'</span></span><span>;
</span><span><span class="hljs-variable">$algo</span></span><span> = </span><span><span class="hljs-string">'sha256'</span></span><span>;
</span><span><span class="hljs-variable">$sig</span></span><span> = </span><span><span class="hljs-title function_ invoke__">hash_hmac_file</span></span><span>(</span><span><span class="hljs-variable">$algo</span></span><span>, </span><span><span class="hljs-variable">$file</span></span><span>, </span><span><span class="hljs-variable">$key</span></span><span>);
</span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-variable">$sig</span></span><span>; </span><span><span class="hljs-comment">// Signature envoyée au serveur</span></span><span>
</span></span>

Dans le téléchargement HTTP réel, le client envoie l'en-tête X-File-Signature: <Sig> avec téléchargement multipart / formulaire de formulaire ou comme signature de champ de formulaire.


Grand fichier / tranche de téléchargement de slice

hash_hmac_file () lui-même peut gérer des fichiers volumineux, mais dans les scénarios de téléchargement ou de téléchargement en streaming, les HMAC doivent être calculés par blocs par bloc ou que les HMAC segmentés sont adoptés.

Solution A: Après avoir reçu le fichier fusionné, le serveur utilise hash_hmac_file () pour le vérifier.

Avantages: mise en œuvre simple; Inconvénients: il nécessite un stockage / fusion temporaire de fichiers complets sur le serveur, qui peuvent occuper IO et disque.

Solution B: calculer le bloc HMAC par bloc lors du téléchargement (le client et le serveur synchronisent chaque bloc)

  • Le client calcule le HMAC pour chaque bloc et envoie la signature du bloc; Le serveur vérifie et ajoute au fichier lors de la réception de chaque bloc. Le dernier bloc est téléchargé avec succès et le fichier complet a été vérifié.

  • Ou le client envoie les données d'origine de chaque bloc et envoie le HMAC du fichier complet à la fin (si le client peut générer la signature complète localement).

Solution C: Utilisez le hachage en streaming (côté serveur)

Si le serveur lit le flux de téléchargement directement à partir de PHP (par exemple en utilisant PSR-7 ou PHP: // Input), vous pouvez utiliser hash_init ('sha256', hash_hmac, $ key) et hash_update () lors de la lecture du flux, et enfin hash_final () . Exemple:

 <span><span><span class="hljs-meta">&lt;?php</span></span><span>
</span><span><span class="hljs-variable">$key</span></span><span> = </span><span><span class="hljs-title function_ invoke__">getenv</span></span><span>(</span><span><span class="hljs-string">'UPLOAD_HMAC_KEY'</span></span><span>);
</span><span><span class="hljs-variable">$algo</span></span><span> = </span><span><span class="hljs-string">'sha256'</span></span><span>;

</span><span><span class="hljs-comment">// initialisation HMAC Contexte</span></span><span>
</span><span><span class="hljs-variable">$context</span></span><span> = </span><span><span class="hljs-title function_ invoke__">hash_init</span></span><span>(</span><span><span class="hljs-variable">$algo</span></span><span>, HASH_HMAC, </span><span><span class="hljs-variable">$key</span></span><span>);

</span><span><span class="hljs-comment">// Supposons que nous php://input Ou les flux de fichiers sont lus Block par bloc</span></span><span>
</span><span><span class="hljs-variable">$stream</span></span><span> = </span><span><span class="hljs-title function_ invoke__">fopen</span></span><span>(</span><span><span class="hljs-string">'php://input'</span></span><span>, </span><span><span class="hljs-string">'rb'</span></span><span>);
</span><span><span class="hljs-keyword">while</span></span><span> (!</span><span><span class="hljs-title function_ invoke__">feof</span></span><span>(</span><span><span class="hljs-variable">$stream</span></span><span>)) {
    </span><span><span class="hljs-variable">$chunk</span></span><span> = </span><span><span class="hljs-title function_ invoke__">fread</span></span><span>(</span><span><span class="hljs-variable">$stream</span></span><span>, </span><span><span class="hljs-number">8192</span></span><span>);
    </span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-variable">$chunk</span></span><span> === </span><span><span class="hljs-literal">false</span></span><span>) </span><span><span class="hljs-keyword">break</span></span><span>;
    </span><span><span class="hljs-title function_ invoke__">hash_update</span></span><span>(</span><span><span class="hljs-variable">$context</span></span><span>, </span><span><span class="hljs-variable">$chunk</span></span><span>);
}
</span><span><span class="hljs-variable">$serverSig</span></span><span> = </span><span><span class="hljs-title function_ invoke__">hash_final</span></span><span>(</span><span><span class="hljs-variable">$context</span></span><span>);
</span><span><span class="hljs-title function_ invoke__">fclose</span></span><span>(</span><span><span class="hljs-variable">$stream</span></span><span>);

</span><span><span class="hljs-comment">// Alors comparez clientSig ...</span></span><span>
</span></span>

Empêcher les attaques de lecture et les métadonnées associées

Un simple HMAC ne peut garantir que l'intégrité et l'authentification (dérivées de la partie qui tient la clé), mais elle est sujette à la "lecture" (le même fichier + signature est lecture). Méthode de protection:

  • Lors de la signature, l'horodatage du fichier, le nonce (numéro aléatoire), l'ID de téléchargement, etc. sont signés ensemble , par exemple, l'épissage de ces métadonnées dans Signature = HMAC (clé, nom de fichier + ":" + file_sha256 + ":" + horodatage + ":" + nonce) , et l'envoi de l'horodatage et non-acte ensemble.

  • Le serveur vérifie l'horodatage (par exemple, autorise ± 5 minutes) et vérifie si le nonce a été utilisé (besoin de persister pendant un certain temps) pour éviter la relecture.

  • Pour l'API: utilisez un jeton de téléchargement pré-signé qui est valide pendant une courte période et intégrez l'expiration et la signature dans le jeton. Le serveur vérifie la validité du jeton avant de vérifier le fichier HMAC.

Exemple de schéma de signature (signature externe du fichier):

  • Le client demande d'abord une information de téléchargement à court terme (y compris nonce et expiry, généré et signé par le serveur);

  • Le client télécharge le fichier et met les informations d'identification et les fichiers HMAC dans l'en-tête;

  • Le serveur vérifie d'abord les informations d'identification puis vérifie le fichier HMAC.


Recommandations clés de gestion et de sécurité

  1. Stockage des clés : les touches HMAC ne doivent pas être codées en dur dans les entrepôts / codes. Utilisez des variables d'environnement, le stockage restreint des fichiers de configuration ou des systèmes de gestion de clés spécialisés (coffre-fort, kms cloud).

  2. Autorisations minimales : l'utilisation et les autorisations de la clé doivent être minimisées. Si différentes clés peuvent être affectées à différentes utilisations (la touche de signature de téléchargement est séparée des autres clés de service).

  3. Rotation régulière : élaborez des politiques de rotation des clés (par exemple tous les 90 jours) et prennent en charge la vérification à court terme des anciennes clés (les deux touches nouvelles / anciennes sont prises en charge pendant la rotation) pour éviter les interruptions de vérification.

  4. Audit : enregistrez la demande, la source IP et le temps d'échec des signatures, afin de faciliter la détection du comportement d'attaque par la suite.

  5. Utilisation de HTTPS / TLS : les signatures et les fichiers doivent être transférés via TLS pour éviter que les intermédiaires de écouter les signatures ou de voler des clés.


Utilisez Hash_Equals pour la comparaison pour éviter les attaques de synchronisation

N'utilisez pas == , == ou une épissage de chaîne pour comparer les signatures; Vous devez utiliser hash_equals () pour une comparaison de temps constante, par exemple:

 <span><span><span class="hljs-keyword">if</span></span><span> (!</span><span><span class="hljs-title function_ invoke__">hash_equals</span></span><span>(</span><span><span class="hljs-variable">$serverSig</span></span><span>, </span><span><span class="hljs-variable">$clientSig</span></span><span>)) {
    </span><span><span class="hljs-comment">// rejeter</span></span><span>
}
</span></span>

hash_equals () peut également empêcher la différence horaire d'être exploitée lorsqu'il y a différentes longueurs ou un décalage précoce.


Gérer d'autres problèmes de sécurité (type de fichier, autorisation)

La vérification HMAC n'est qu'une partie de l'intégrité et de l'authentification, et devrait également:

  • Vérifiez le type de fichier (Type MIME + signature d'en-tête de fichier) pour éviter d'exécuter des scripts malveillants.

  • Limiter la taille , limiter la fréquence de téléchargement et la liste blanche de la taille et du type d'utilisateurs.

  • Séparez le répertoire de téléchargement de l'utilisateur du répertoire de racine Web et définissez les autorisations de fichier appropriées (non exécutable).

  • Les fichiers exécutables sont effectués avec des analyses supplémentaires (analyse de virus / analyse sandbox) dans des scénarios à haut risque.


Scénario avancé: signature côté serveur (émission d'URL pré-signée)

Certaines architectures souhaitent que le serveur émet une "URL présidée" ou un jeton au client, afin que le client puisse le télécharger directement sur le stockage d'objets (S3, GCS, etc.). Dans ce scénario:

  • Lorsque le serveur génère une URL pré-signée, il peut intégrer le HMAC (ou utiliser le mécanisme d'auto-signature du stockage cloud) et informer le client qu'il doit apporter un en-tête spécifique (tel que X-Expection-Sha256 ) lors du téléchargement à l'avenir.

  • Lorsqu'un fichier est téléchargé sur le stockage cloud, le serveur peut vérifier le HMAC ou l'intégrité de l'objet de l'objet via des rappels ou des récupérations ultérieures (comme recalculer le HMAC pour les fichiers en stockage, ou le client de fournir des signatures et de retourner les métadonnées du stockage cloud lors du téléchargement). Les détails dépendent de la fonctionnalité du stockage cloud.


FAQ (court)

Q: Pourquoi avez-vous toujours besoin de HMAC, s'il y a HTTPS?
HTTPS protège la sécurité de la transmission, mais ne peut pas empêcher les détenteurs de clés juridiques de télécharger des fichiers malveillants ou de les télécharger du côté du serveur après avoir été falsifié. HMAC peut s'assurer que "la partie avec la clé génère une signature dans ce téléchargement et que le contenu du fichier n'est pas modifié avant et après transmission / stockage".

Q: Puis-je simplement utiliser hash_file () (pas de clé)?
hash_file () ne peut vérifier que l'intégrité (si elle a été falsifiée), mais n'importe qui peut forger la valeur de hachage. Si vous ne souhaitez pas exposer la valeur de hachage et que seul le serveur est responsable du calcul et de la comparaison, hash_file () est également utile; Mais si vous souhaitez vérifier l'initiateur de téléchargement, vous devez utiliser HMAC.


Résumé (points opérationnels)

  • Utilisez hash_hmac_file () (ou hash_init + hash_update ) pour calculer efficacement le fichier hmac sur le serveur; Le client signe avec la même clé et envoie la signature (ou les informations d'identification pré-signées sont émises par le serveur).

  • Utilisez hash_equals () pour les comparaisons de synchronisation.

  • Transférez toujours les signatures et les fichiers via HTTPS, les clés à l'aide de politiques de stockage et de rotation sécurisées.

  • Pour les téléchargements de fichiers / tranches importants, utilisez des schémas Streaming HMAC ou Signature Shard et maintenez la cohérence du côté serveur pendant la vérification de fusion finale ou par puce.

  • HMAC est utilisé dans le cadre de la vérification de la sténose et de la source, et combine la détection du type de fichier, le contrôle de l'autorisation et le balayage du virus pour former un système de protection complet.