# Stocker une clé API de LLM en sécurité plutôt que dans un champ ACF

> Snippet de stockage chiffré d'une clé API de LLM en base de données, à l'abri d'un champ personnalisé visible depuis l'éditeur. Sans traiter la rotation automatique de la clé.

- Auteur : WordPress Développement
- Publié le : 2023-07-15
- Mis à jour le : 2023-07-15
- Catégorie : IA &amp; MCP
- URL : https://www.wpmoderne.fr/ia-mcp/stocker-cle-api-llm-securisee-acf/

## L’essentiel

- Un champ ACF classique expose la clé à tout utilisateur avec accès à l'éditeur
- Le chiffrement avec OpenSSL protège la valeur stockée en base
- La clé en clair ne doit jamais transiter côté navigateur

`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

> L'essentiel à retenir : Un champ ACF classique expose la clé à tout utilisateur avec accès à l'éditeur ; Le chiffrement avec OpenSSL protège la valeur stockée en base ; La clé en clair ne doit jamais transiter côté navigateur

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 comme `manage_options`.
- Exclure `wp-config.php` du 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.
