openssl_encrypt() : cette seule fonction PHP change la donne pour qui stocke une clé API de fournisseur de LLM dans WordPress. Trop de projets la placent encore dans un champ ACF de type texte, visible en clair dans l’éditeur par n’importe quel rédacteur ayant accès à la page de réglages, et parfois même exporté sans y penser dans un export JSON de contenu.
Ce billet propose un snippet concret pour chiffrer la clé avant stockage en base et la déchiffrer uniquement au moment de l’appel serveur. La rotation automatique de cette clé, sujet à part entière, n’est pas traitée ici.
Le problème du champ ACF en clair
Un champ personnalisé standard, qu’il soit ACF ou un simple add_option(), stocke sa valeur telle quelle dans la table wp_options ou wp_postmeta. N’importe quel utilisateur avec un accès administrateur, ou un plugin mal intentionné doté des bonnes capacités, peut lire cette valeur directement en base ou via l’API REST si le champ est exposé par erreur. Une clé API de LLM qui fuite de cette manière expose directement le budget de génération à un usage frauduleux.
Le risque ne se limite pas à une lecture malveillante : un export de contenu au format JSON, une sauvegarde de base non chiffrée transmise à un prestataire, ou une capture d’écran de l’éditeur suffisent à divulguer la clé sans qu’aucune intrusion ne soit nécessaire.
Le principe du chiffrement au repos

La clé n’est jamais stockée en clair. Elle est chiffrée avec openssl_encrypt() en AES-256-CBC, à l’aide d’une clé de chiffrement dérivée d’une constante définie dans wp-config.php — jamais dans la base de données elle-même. Un vecteur d’initialisation de 16 octets, différent à chaque chiffrement, accompagne la valeur stockée.
// Dans wp-config.php, hors du dépôt versionné
define( 'LLM_KEY_SECRET', 'une-chaine-aleatoire-de-64-caracteres' );
function chiffrer_cle_llm( $cle_en_clair ) {
$iv = openssl_random_pseudo_bytes( 16 );
$chiffre = openssl_encrypt(
$cle_en_clair,
'aes-256-cbc',
LLM_KEY_SECRET,
0,
$iv
);
return base64_encode( $iv . '::' . $chiffre );
}
function dechiffrer_cle_llm( $valeur_stockee ) {
list( $iv, $chiffre ) = explode( '::', base64_decode( $valeur_stockee ), 2 );
return openssl_decrypt( $chiffre, 'aes-256-cbc', LLM_KEY_SECRET, 0, $iv );
}
La clé chiffrée est ensuite stockée via update_option( 'llm_api_key_chiffree', chiffrer_cle_llm( $cle ) ), jamais dans un champ visible dans l’éditeur de contenu.
Le déchiffrement uniquement côté serveur
Le déchiffrement ne doit avoir lieu que dans le contexte d’un appel serveur vers le fournisseur de LLM, jamais pour affichage dans une page d’administration :
function appeler_llm( $prompt ) {
$cle = dechiffrer_cle_llm( get_option( 'llm_api_key_chiffree' ) );
return wp_remote_post( 'https://api.fournisseur-llm.example/v1/completions', array(
'headers' => array(
'Authorization' => 'Bearer ' . $cle,
'Content-Type' => 'application/json',
),
'body' => wp_json_encode( array( 'prompt' => $prompt ) ),
'timeout' => 20,
) );
}
Les précautions complémentaires
- Ne jamais journaliser la clé en clair, même dans des logs de debug temporaires.
- Restreindre l’accès à l’option chiffrée via
register_setting()avec une capacité stricte commemanage_options. - Exclure
wp-config.phpdu dépôt Git et de toute sauvegarde transmise sans chiffrement.
Ce que ce snippet ne résout pas
Le chiffrement au repos protège contre une lecture directe en base, mais pas contre un serveur compromis où un attaquant a déjà accès au code source et donc à wp-config.php. Dans ce scénario, seule une rotation régulière de la clé, couplée à une surveillance de l’usage via le tableau de bord du fournisseur, limite réellement les dégâts.
Chiffrer une clé qu’on ne fait jamais tourner ne fait que retarder le problème : le chiffrement protège le stockage, pas l’usage qui en est fait une fois la clé compromise ailleurs.
En résumé
Un champ personnalisé, aussi pratique soit-il pour un rédacteur, n’a jamais été conçu pour protéger un secret. Le couple openssl_encrypt() / openssl_decrypt(), associé à une clé de chiffrement hors base de données, reste la méthode la plus simple pour retirer une clé API de LLM du champ de vision de qui n’en a pas besoin.