Chaos Scripting — Guide d'implémentation HMAC¶
NeoLoad (VuGen C), NeoLoad JavaScript, JMeter¶
1. Ce qu'il faut calculer¶
Niveau 3 — Expert¶
Clé = "perfshop-chaos-expert-secret-2025" ← statique, toujours la même
Payload = sessionToken + ":" + csrfToken + ":" + stepToken
Signature = HMAC-SHA256(payload, clé) → Base64 URL-safe sans padding
Niveau 4 — Maestro¶
CléDerivée = HMAC-SHA256(sessionToken, "perfshop-chaos-expert-secret-2025")
Payload = sessionToken + ":" + csrfToken + ":" + stepToken
Signature = HMAC-SHA256(payload, cléDerivée) → Base64 URL-safe sans padding
Base64 URL-safe = remplacer + par -, / par _, supprimer les = de fin.
Séquence des étapes¶
| Étape | Endpoint | Step envoyé | Step après rotation |
|---|---|---|---|
| 1 | POST /api/checkout/address | step1 | step2 |
| 2 | POST /api/checkout/shipping | step2 | step3 |
| 3 | POST /api/checkout/payment | step3 | step4 |
| 4 | POST /api/orders | step4 | (fin) |
2. JMeter — JSR223 (Groovy, aucune dépendance)¶
Groovy est natif dans JMeter 3.1+. Pas d'import, pas de JAR supplémentaire.
Fonction HMAC réutilisable (à mettre dans un JSR223 Sampler ou PreProcessor)¶
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import java.util.Base64
// ── Fonction signature ──────────────────────────────────────────────
def hmacSHA256Base64Url(String payload, String key) {
Mac mac = Mac.getInstance("HmacSHA256")
mac.init(new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256"))
byte[] raw = mac.doFinal(payload.getBytes("UTF-8"))
return Base64.getUrlEncoder().withoutPadding().encodeToString(raw)
}
// ── Constante ───────────────────────────────────────────────────────
def HMAC_SECRET = "perfshop-chaos-expert-secret-2025"
// ── Lire les tokens extraits par les extracteurs JSON précédents ────
def session = vars.get("sessionToken") // extrait du login
def action = vars.get("actionToken") // extrait du login
def csrf = vars.get("csrfToken") // extrait du login (puis de chaque réponse)
def step = vars.get("stepToken") // extrait du login (puis de chaque réponse)
// ── Calcul signature ────────────────────────────────────────────────
// Niveau 3 Expert :
String sigKey = HMAC_SECRET
// Niveau 4 Maestro (décommenter) :
// String sigKey = hmacSHA256Base64Url(session, HMAC_SECRET)
String payload = session + ":" + csrf + ":" + step
String signature = hmacSHA256Base64Url(payload, sigKey)
// ── Stocker dans des variables JMeter ───────────────────────────────
vars.put("signature", signature)
log.info("Step=${step} CSRF=${csrf.take(8)}... Sig=${signature.take(8)}...")
Plan JMeter complet — niveau 3 Expert¶
Étape 1 — Login
Requête : POST /api/auth/login
Extracteurs JSON sur la réponse (Header Extractor) :
x-session-token → sessionToken
x-action-token → actionToken
x-csrf-token → csrfToken
x-step-token → stepToken
JSR223 PreProcessor avant chaque étape checkout :
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import java.util.Base64
def hmac(String payload, String key) {
Mac mac = Mac.getInstance("HmacSHA256")
mac.init(new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256"))
return Base64.getUrlEncoder().withoutPadding()
.encodeToString(mac.doFinal(payload.getBytes("UTF-8")))
}
def HMAC_SECRET = "perfshop-chaos-expert-secret-2025"
def session = vars.get("sessionToken")
def csrf = vars.get("csrfToken")
def step = vars.get("stepToken")
// Niveau 4 : sigKey = hmac(session, HMAC_SECRET)
def sigKey = HMAC_SECRET
vars.put("signature", hmac(session + ":" + csrf + ":" + step, sigKey))
Headers sur chaque étape checkout :
X-Session-Token : ${sessionToken}
X-Action-Token : ${actionToken}
X-CSRF-Token : ${csrfToken}
X-Step-Token : ${stepToken}
X-Signature : ${signature}
X-Request-ID : ${__UUID()}
Extracteurs JSON sur chaque réponse checkout (mettre à jour csrf, step, sig) :
Header : x-csrf-token → csrfToken
Header : x-step-token → stepToken
Header : x-signature → signature ← optionnel (recalculé de toute façon)
3. NeoLoad — Script JavaScript (moteur V8 intégré)¶
NeoLoad supporte JavaScript natif dans les "Advanced Actions". Le moteur V8 n'a pas de crypto natif, mais NeoLoad expose java.* directement.
Fichier : hmac_utils.js — à inclure une fois au début du scénario¶
// ── HMAC-SHA256 Base64 URL-safe via Java (disponible dans NeoLoad V8) ──
function hmacSHA256(payload, key) {
var keyBytes = new java.lang.String(key).getBytes("UTF-8");
var payloadBytes = new java.lang.String(payload).getBytes("UTF-8");
var mac = javax.crypto.Mac.getInstance("HmacSHA256");
mac.init(new javax.crypto.spec.SecretKeySpec(keyBytes, "HmacSHA256"));
var raw = mac.doFinal(payloadBytes);
var b64 = java.util.Base64.getUrlEncoder().withoutPadding().encodeToString(raw);
return String(b64); // convertir java.lang.String → JS String
}
// ── Constante ──
var HMAC_SECRET = "perfshop-chaos-expert-secret-2025";
Action "Checkout Address" (level 3)¶
// Récupérer les tokens depuis les variables NeoLoad
var session = context.variableManager.getValue("sessionToken");
var action = context.variableManager.getValue("actionToken");
var csrf = context.variableManager.getValue("csrfToken");
var step = context.variableManager.getValue("stepToken");
// Niveau 3 Expert
var sigKey = HMAC_SECRET;
// Niveau 4 Maestro (décommenter) :
// var sigKey = hmacSHA256(session, HMAC_SECRET);
var payload = session + ":" + csrf + ":" + step;
var signature = hmacSHA256(payload, sigKey);
// Injecter dans les headers de la requête suivante
context.variableManager.setValue("signature", signature);
// Log pour debug
context.logger.info("Étape: " + step + " | Sig: " + signature.substring(0, 8) + "...");
Variables NeoLoad à configurer¶
Dans "Variable Extractors" → "From Header" sur chaque réponse :
Login response headers → sessionToken, actionToken, csrfToken, stepToken
Address response headers → csrfToken, stepToken (écrasent les précédentes)
Shipping response headers → csrfToken, stepToken
Payment response headers → csrfToken, stepToken
4. VuGen — C (LoadRunner / NeoLoad VuGen)¶
VuGen C supporte lr_hmac_sha256() depuis LR 12.55. Si tu es sur une version antérieure ou NeoLoad VuGen, utilise l'approche web_custom_request + script externe, ou la méthode OpenSSL ci-dessous.
Option A — lr_hmac_sha256() natif (LR 12.55+)¶
#include "lrun.h"
/* Calcule HMAC-SHA256 et encode en Base64 URL-safe */
static char* compute_signature(const char* session, const char* csrf, const char* step) {
static char payload[1024];
static char sig_b64[256];
static char sig_urlsafe[256];
int i;
char raw_sig[32];
int raw_len = 32;
/* Payload */
sprintf(payload, "%s:%s:%s", session, csrf, step);
/* HMAC-SHA256 */
lr_hmac_sha256(
(unsigned char*)"perfshop-chaos-expert-secret-2025", 36,
(unsigned char*)payload, strlen(payload),
(unsigned char*)raw_sig, &raw_len
);
/* Base64 standard */
lr_base64_encode((unsigned char*)raw_sig, raw_len, sig_b64);
/* Convertir en URL-safe : + → -, / → _, supprimer = */
for (i = 0; sig_b64[i]; i++) {
if (sig_b64[i] == '+') sig_urlsafe[i] = '-';
else if (sig_b64[i] == '/') sig_urlsafe[i] = '_';
else if (sig_b64[i] == '=') { sig_urlsafe[i] = '\0'; break; }
else sig_urlsafe[i] = sig_b64[i];
}
sig_urlsafe[i] = '\0';
lr_save_string(sig_urlsafe, "signature");
return sig_urlsafe;
}
/* Dans ton Action() : */
Action() {
char* session = lr_eval_string("{sessionToken}");
char* csrf = lr_eval_string("{csrfToken}");
char* step = lr_eval_string("{stepToken}");
compute_signature(session, csrf, step);
web_custom_request("checkout_address",
"URL=https://perfshop-api.perfshop.io/api/checkout/address",
"Method=POST",
"TargetFrame=",
"Resource=0",
"RecContentType=application/json",
"Referer=",
"Snapshot=",
"Mode=HTTP",
"EXTRARES",
"Header=X-Session-Token: {sessionToken}",
"Header=X-Action-Token: {actionToken}",
"Header=X-CSRF-Token: {csrfToken}",
"Header=X-Step-Token: {stepToken}",
"Header=X-Signature: {signature}",
"Header=X-Request-ID: {NewUUID}",
"Body={\"street\":\"12 rue de la Paix\",\"city\":\"Paris\",\"zip\":\"75001\"}",
LAST);
/* Extraire les tokens rotatifs de la réponse */
web_reg_save_param_ex("ParamName=csrfToken",
"LB=x-csrf-token: ", "RB=\r\n",
SEARCH_FILTERS, "Scope=Headers", LAST);
web_reg_save_param_ex("ParamName=stepToken",
"LB=x-step-token: ", "RB=\r\n",
SEARCH_FILTERS, "Scope=Headers", LAST);
return 0;
}
Option B — Script PowerShell appelé par VuGen (sans dépendance LR)¶
Si lr_hmac_sha256 n'est pas disponible, déléguer à PowerShell via system() :
/* Génère la signature via PowerShell et lit le résultat */
static void compute_signature_ps(const char* session, const char* csrf, const char* step) {
char cmd[2048];
char ps_script[1024];
sprintf(ps_script,
"$k=[System.Text.Encoding]::UTF8.GetBytes('perfshop-chaos-expert-secret-2025');"
"$p=[System.Text.Encoding]::UTF8.GetBytes('%s:%s:%s');"
"$m=New-Object System.Security.Cryptography.HMACSHA256;"
"$m.Key=$k;"
"$r=$m.ComputeHash($p);"
"$b=[Convert]::ToBase64String($r);"
"$u=$b.Replace('+','-').Replace('/','_').TrimEnd('=');"
"Set-Content -Path C:\\Temp\\sig.txt -Value $u",
session, csrf, step);
sprintf(cmd, "powershell -Command \"%s\"", ps_script);
system(cmd);
lr_load_string("C:\\Temp\\sig.txt", "signature");
}
5. Niveau 4 Maestro — différence par rapport à Expert¶
JMeter (Groovy)¶
// Remplacer une ligne dans le PreProcessor :
// AVANT (Expert) :
def sigKey = HMAC_SECRET
// APRÈS (Maestro) :
def sigKey = hmac(session, HMAC_SECRET) // dérivation de la clé
NeoLoad JS¶
// AVANT (Expert) :
var sigKey = HMAC_SECRET;
// APRÈS (Maestro) :
var sigKey = hmacSHA256(session, HMAC_SECRET);
VuGen C¶
/* Dériver la clé d'abord, puis recalculer */
static char derived_key_b64[256];
void derive_hmac_key(const char* session_token) {
char raw_key[32];
int raw_len = 32;
lr_hmac_sha256(
(unsigned char*)"perfshop-chaos-expert-secret-2025", 36,
(unsigned char*)session_token, strlen(session_token),
(unsigned char*)raw_key, &raw_len
);
/* Stocker la clé brute pour la réutiliser */
memcpy(g_derived_key, raw_key, 32); /* variable globale */
g_derived_key_len = 32;
}
/* Puis dans compute_signature : utiliser g_derived_key au lieu de HMAC_SECRET */
6. Récapitulatif — checklist par requête¶
Login¶
- [x]
POST /api/auth/loginavecemail+password - [x] Extraire des headers de réponse :
X-Session-Token,X-Action-Token,X-CSRF-Token,X-Step-Token - [x] Maestro uniquement : dériver
sigKey = HMAC(sessionToken, SECRET)
Chaque étape checkout (address / shipping / payment / orders)¶
- [x] Calculer
signature = HMAC(session+":"+csrf+":"+step, sigKey) - [x] Envoyer les 6 headers : Session, Action, CSRF, Step, Signature, RequestID
- [x] Extraire
X-CSRF-TokenetX-Step-Tokende la réponse (ils ont changé) - [x] Recalculer la signature avant la prochaine requête
Codes d'erreur à gérer¶
| Code | Signification | Action corrective |
|---|---|---|
E-TKN-99 |
Pas de bundle → re-login | Rejouer depuis le login |
E-CSRF-01 |
CSRF invalide | Vérifier l'extraction de la réponse précédente |
E-STEP-04 |
Mauvais step | Vérifier la séquence step1→2→3→4 |
E-SIG-07 |
Signature invalide (Expert) | Vérifier payload et clé HMAC |
E-HMAC-03 |
Signature invalide (Maestro) | Vérifier la dérivation de clé |