Sur cette page
- Le problème : tout repose sur un seul secret
- Créer une clé : du hasard, pas un mot de passe
- Qui génère la clé
- Où une clé peut se trouver
- Signature locale et signature à distance
- Le modèle PKCS#11
- Les attributs qui retiennent une clé
- Signer via PKCS#11
- HSM ≠ autorisation
- Cycle de vie : expiration, compromission et reprise
- Un mot sur FIPS 140-3
- La clé de Mariama, le contrôle d’Ibrahima
- À vous : une clé qui ne quitte pas son jeton
- Installer et inspecter le jeton
- Créer le jeton et y générer la clé
- Signer sur le jeton
- Vérifier : d’abord le mauvais format, puis le bon
- Falsifier le contrat
- Tenter d’exporter la clé privée
- La place de SEDEYA
- Risques et idées reçues
- En résumé
- Vérifiez votre compréhension
- Prochaine étape
Pour qui
Ingénieurs logiciels et décideurs techniques qui ont lu les articles 1 et 2 et savent ce que sont une paire de clés, une signature et un certificat.
À lire avant
Vous apprendrez
- Expliquer comment une clé de signature doit être générée, et pourquoi elle ne doit jamais être dérivée d'un mot de passe
- Décrire le modèle PKCS#11 : slots, jetons, objets, rôles de SO et d'utilisateur, sessions et connexion
- Lire les attributs qui gardent une clé privée dans son dispositif, et ceux qui n'en donnent que l'illusion
- Distinguer la signature locale de la signature à distance, et dire qui contrôle la clé dans chaque cas
- Générer une clé dans SoftHSM, signer avec pkcs11-tool, vérifier avec OpenSSL, et lire les attributs qui empêchent l'export de la clé
- Expliquer pourquoi une clé conservée dans un HSM ne montre pas, à elle seule, qu'une signature a été autorisée
Le problème : tout repose sur un seul secret
Dans l’article 1, Mariama a signé un contrat de prêt avec sa clé privée, et Ibrahima l’a vérifié avec sa clé publique. Dans l’article 2, un certificat a lié cette clé publique à son nom.
Les deux articles supposaient que Mariama est la seule à pouvoir utiliser sa clé privée. Si cette clé est un fichier sur son ordinateur portable, quiconque copie le fichier peut signer à sa place : les signatures se vérifieront, et le certificat s’en portera garant.
FIPS 186-5 pose l’exigence sans détour : « Une clé privée doit être protégée contre tout accès, toute divulgation et toute modification non autorisés », et elle « doit rester secrète » (FIPS 186-5, §5.2). Le SP 800-57 du NIST ajoute que les clés de signature ne permettent la non-répudiation que « si elles sont correctement gérées » (SP 800-57, §5.1.1). Cet article porte sur cette gestion, et sur ce qu’un dispositif sécurisé ne peut pas faire à votre place.
Les normes de l’OASIS et du NIST, comme les autres sources techniques citées ici, n’existent qu’en anglais : leurs citations sont traduites par nos soins.
Créer une clé : du hasard, pas un mot de passe
Une clé de signature doit être imprévisible. FIPS 186-5 fait générer les paires de clés ECDSA par un générateur de bits aléatoires approuvé, spécifié dans le SP 800-90A, au niveau de sécurité de la courbe (§6.2.1, annexe A.2), et exige la même chose pour RSA (§5.4). Concrètement : un générateur de nombres aléatoires cryptographiquement sûr (CSPRNG) (CSPRNG), jamais rand(), et jamais une valeur choisie par une personne.
ECDSA sur P-256 donne des clés courtes et des signatures de 64 octets sous forme brute ; RSA exige un module d’au moins 2048 bits. Une clé ECDSA « ne doit servir » qu’à des signatures ECDSA (FIPS 186-5, §6.2, §6.2.2).
Ne dérivez jamais une clé de signature d’un mot de passe. Le SP 800-132 du NIST indique que les mots de passe choisis par les utilisateurs « ont une faible entropie et de faibles propriétés d’aléa » et « ne doivent pas être utilisés directement comme clés cryptographiques » (SP 800-132, §5). La dérivation à partir d’un mot de passe qu’il décrit vise la « protection de données stockées électroniquement » (§1), pas la signature (notre lecture). Un mot de passe peut déverrouiller une clé. Il ne doit jamais être la clé.
Qui génère la clé
Le SP 800-57 est précis au sujet des clés de signature : le propriétaire de la paire de clés « devrait générer le matériel de clé plutôt que toute autre entité », car « cela facilitera la non-répudiation ». La clé privée « ne doit pas être distribuée à d’autres entités » (SP 800-57, §8.1.5.1).
C’est l’origine des clés individuelles (per-user keys). Comme l’a montré l’article 1, une clé unique pour toute l’entreprise montre seulement que la clé de l’entreprise a signé. Une clé générée pour une personne, et que seule cette personne peut utiliser, permet à une signature de désigner un individu.
Où une clé peut se trouver
- Un magasin de clés logiciel. Un fichier sur un disque, souvent chiffré par un mot de passe. Facile à créer, facile à copier.
- Une carte à puce ou un jeton USB que le signataire garde sur lui.
- Un module matériel de sécurité (hardware security module) (HSM), un appareil conçu pour conserver les clés de nombreux utilisateurs ou services.
Pour une application, les deux derniers fonctionnent de la même façon : elle demande à l’appareil d’effectuer l’opération, « sans que des informations sensibles comme les clés privées soient jamais révélées » (guide d’utilisation PKCS #11, §2.1).
Signature locale et signature à distance
C’est l’emplacement de l’appareil qui décide de qui le contrôle. Ce découpage est le nôtre, pas un terme que définissent les normes.
En signature locale, la clé se trouve sur un appareil que détient le signataire, « sous le contrôle d’un seul utilisateur » (§2.1).
En signature à distance, la clé se trouve dans le HSM d’un service. L’API CSC du Cloud Signature Consortium, une spécification de signature à distance répandue, décrit un prestataire « qui gère un ensemble d’identifiants de signature pour le compte de plusieurs utilisateurs », et qui « exploite généralement un HSM (ou un dispositif sécurisé multi-utilisateur fonctionnellement équivalent) et un service d’authentification » (CSC API v2.0, §4.1). Le signataire prouve son identité au service, et le service demande au HSM de signer. La signature à distance appelle contrôle exclusif (sole control) la propriété d’une clé que seul son signataire peut utiliser.
Le compromis oppose le contrôle à la commodité. Un appareil local reste chez le signataire, mais il peut se perdre et exige un lecteur. Un HSM distant fonctionne depuis n’importe quel téléphone, mais le signataire dépend du service pour que sa clé ne soit utilisée qu’à sa demande.
Pour comparaison : comment eIDAS nomme ces dispositifs
eIDAS définit un « dispositif de création de signature électronique » comme « un dispositif logiciel ou matériel configuré servant à créer une signature électronique » (art. 3, point 22), et sa variante qualifiée à distance comme un dispositif « géré par un prestataire de services de confiance qualifié … pour le compte d’un signataire » (art. 3, point 23 bis). Ces définitions de l’Union européenne ne décrivent ni le droit guinéen ni un prestataire en particulier. Source : eIDAS, version consolidée de 2024.
Le modèle PKCS#11
PKCS#11 (PKCS#11), une norme OASIS aussi appelée Cryptoki, donne une interface commune à tous les HSM, cartes et jetons. Sa version 3.2 a été publiée le 3 juin 2026 (PKCS #11 v3.2). Elle « ne spécifie que l’interface avec la bibliothèque, pas ses fonctionnalités » : un appareil n’a pas à prendre en charge tous les mécanismes (guide d’utilisation, §2.2). Voici les éléments, de l’extérieur vers l’intérieur :
- Bibliothèque. L’application charge le fichier
.soou.dlld’un fabricant, que les outils appellent aussi « module », et appelle des fonctions commeC_LoginetC_Sign(§2.5). - Slot et jeton. La bibliothèque expose des « slots » ; un slot peut contenir un « jeton » (token), l’appareil lui-même, qui « peut être entièrement implémenté en logiciel … aucun matériel particulier n’est nécessaire » (§2.2).
- Objets. Objets de données, certificats et clés. Les objets de jeton (token object) persistent ; les objets de session (session object) disparaissent avec la session qui les a créés (§2.3).
- Utilisateurs. Le responsable de la sécurité (Security Officer) (SO) initialise le jeton et définit le PIN utilisateur. Seul l’utilisateur normal peut accéder aux objets privés (§2.4).
- Sessions. Une connexion en lecture seule ou en lecture-écriture entre une application et un jeton (§2.6.1).
Une règle surprend la plupart des développeurs : la connexion vaut pour l’application, pas pour la session. « Lorsqu’une session d’une application se connecte à un jeton, toutes les sessions de cette application avec ce jeton deviennent connectées », y compris celles ouvertes plus tard (§2.6.5).
L’application trouve une clé par son libellé ou par son CKA_ID. Une paire de clés et son certificat « devraient » partager le même CKA_ID, mais « Cryptoki n’impose pas ces associations » (PKCS #11 v3.2, §4.8).
Les attributs qui retiennent une clé
Chaque objet PKCS#11 porte des attributs. Cinq d’entre eux indiquent où se trouve une clé privée et si elle peut en sortir. Seuls CKA_SENSITIVE et CKA_EXTRACTABLE la retiennent réellement (PKCS #11 v3.2, §4.4, §4.8, §4.10).
| Attribut | Signification | Modifiable ? |
|---|---|---|
CKA_TOKEN |
TRUE pour un objet de jeton persistant. FALSE par défaut. | — |
CKA_PRIVATE |
TRUE signifie qu’un utilisateur « ne peut pas accéder à l’objet tant qu’il ne s’est pas authentifié auprès du jeton ». | — |
CKA_SENSITIVE |
TRUE signifie que la valeur secrète de la clé ne peut pas être révélée en clair hors du jeton. | Une fois à TRUE, ne repasse jamais à FALSE |
CKA_EXTRACTABLE |
TRUE signifie que la clé « est extractible et peut être encapsulée » (exportée, chiffrée sous une autre clé). | Une fois à FALSE, ne repasse jamais à TRUE |
CKA_LOCAL |
TRUE seulement si la clé a été générée sur le jeton, ou copiée à partir d’une telle clé. | Fixé par le jeton |
Ces changements sont à sens unique, si bien que la protection ne peut pas être désactivée après coup : tout se décide au moment de la génération. CKA_ALWAYS_SENSITIVE et CKA_NEVER_EXTRACTABLE enregistrent si la clé a déjà été exposée, et seul CKA_LOCAL indique qu’elle est née sur le jeton.
Si CKA_SENSITIVE vaut TRUE ou si CKA_EXTRACTABLE vaut FALSE, la valeur secrète (pour une clé EC, CKA_VALUE, l’entier privé d) « ne peut pas être révélée en clair hors du jeton » (§4.10). C_GetAttributeValue renvoie alors CK_UNAVAILABLE_INFORMATION pour cet attribut et « devrait renvoyer » CKR_ATTRIBUTE_SENSITIVE (§5.7.5). L’encapsulation d’une clé non extractible échoue avec CKR_KEY_UNEXTRACTABLE (§5.18.3). Mais les clés non extractibles « peuvent toujours servir de clés » (guide d’utilisation, §3.1) : le caractère non extractible empêche la copie, pas la signature.
CKA_ALWAYS_AUTHENTICATE va plus loin : l’utilisateur doit saisir à nouveau le PIN pour chaque signature, et cet attribut n’est permis que si CKA_PRIVATE vaut TRUE (§4.10).
Signer via PKCS#11
Pour signer, l’application choisit un mécanisme (mechanism). Deux comptent pour ECDSA (PKCS #11 v3.2, §6.3.12, §6.3.13) :
CKM_ECDSA_SHA256« calcule l’intégralité de la spécification ECDSA, hachage compris » : vous passez le document.CKM_ECDSAprend une empreinte que vous avez déjà calculée.
Le format de sortie piège beaucoup de monde. Une signature ECDSA PKCS#11 est la concaténation des deux entiers r et s, chacun complété à la même longueur : toujours 64 octets pour P-256 (§6.3.1). OpenSSL, comme la plupart des autres outils, attend à la place la structure DER SEQUENCE { r, s }. Donnez-lui les octets bruts et la vérification échoue pour une raison de format, pas de cryptographie, comme le montre la partie pratique.
HSM ≠ autorisation
Un HSM protège le secret de la clé. Il ne décide pas si une signature doit avoir lieu. Le guide d’utilisation de PKCS#11 est explicite : « Une fois qu’un utilisateur normal s’est authentifié auprès du jeton, Cryptoki ne restreint pas les opérations cryptographiques qu’il peut effectuer ; l’utilisateur peut effectuer toute opération prise en charge par le jeton » (guide d’utilisation, §3.1).
Ajoutez-y la connexion par application (notre synthèse) : après un seul C_Login, chaque session de cette application peut utiliser chaque clé privée à laquelle l’utilisateur a accès. Le jeton voit une référence de clé et quelques octets, ni le document, ni la personne, ni la transaction. Le guide prévient aussi que « des applications et des appareils malveillants peuvent également modifier les commandes envoyées au dispositif cryptographique » (§3.1). Un serveur de signature compromis qui dispose d’une session connectée peut tout signer, avec des clés qui ne quittent jamais le HSM.
Le lien entre cette personne, ce document et cette signature doit donc être établi hors du HSM. Dans l’API CSC, « l’accès à un identifiant de signature à distance exige une autorisation de l’utilisateur qui possède la clé de signature ». Cette autorisation produit des données d’activation de signature (Signature Activation Data) (SAD), « utilisées pour contrôler une opération de signature donnée ». Un module d’activation de signature (signature activation module) utilise les SAD « afin que les clés de signature soient utilisées sous le contrôle exclusif du signataire » (CSC API v2.0, §4.1, §8.2). Au niveau que la CSC appelle SCAL2, défini dans la norme CEN EN 419 241-1, les SAD sont « liées au document ou aux documents à signer », et « une autorisation à deux facteurs est nécessaire » (§8.2).
« La clé est dans un HSM, donc chaque signature est autorisée. »
Un HSM empêche la copie de la clé. Une fois l’utilisateur connecté, PKCS#11 « ne restreint pas les
opérations cryptographiques qu’il peut effectuer » (guide d’utilisation, §3.1), et le jeton ne
peut pas savoir à quel document ni à quel signataire correspond un appel C_Sign. L’autorisation
est un contrôle distinct, lié à la transaction hors du HSM : les SAD dans le modèle CSC, ou
l’autorisation liée à la transaction que construit l’article
5.
Cycle de vie : expiration, compromission et reprise
Comme l’a noté l’article 1, le SP 800-57 suggère une cryptopériode d’environ un à trois ans pour une clé privée de signature, au terme de laquelle elle « doit être détruite » (SP 800-57, §5.3.6) ; la clé publique continue de vérifier les anciennes signatures. Si la clé privée est divulguée, « l’intégrité et la non-répudiation de toutes les données signées avec cette clé deviennent douteuses », même si des horodatages peuvent préserver les signatures produites avant la compromission (§5.5). L’article 4 traite de la révocation, l’article 7 des horodatages.
Une sauvegarde ? Pour une clé privée de signature, le SP 800-57 répond : « Non (en général) ; la non-répudiation serait remise en cause » (§8.2.2.1, tableau 7), avec des exceptions comme la propre clé d’une AC. Un signataire qui perd son jeton reçoit une nouvelle paire de clés et un nouveau certificat, pas une copie restaurée.
Un mot sur FIPS 140-3
Les HSM sont souvent vendus comme « validés FIPS 140-3 » : le module atteint l’un des « quatre niveaux de sécurité qualitatifs croissants » (FIPS 140-3, point 3), selon la validation du Cryptographic Module Validation Program (CMVP). Après le 21 septembre 2026, le CMVP transfère les modules validés uniquement selon FIPS 140-2 vers sa liste historique, où les agences fédérales américaines ne peuvent les utiliser que pour des « systèmes existants uniquement » (CMVP). Une validation porte sur un module, ni sur la personne qui a autorisé une signature, ni sur son effet juridique.
La clé de Mariama, le contrôle d’Ibrahima
Revenons au prêt. Le jeton de Mariama est protégé par un PIN utilisateur qu’elle seule connaît, et une paire de clés P-256 est générée à l’intérieur, non extractible dès le départ. Son application se connecte et demande au jeton de signer le contrat, puis en lit la clé publique pour Ibrahima (en pratique, dans son certificat). Ibrahima vérifie : « Verified OK », et une copie falsifiée échoue. Quelqu’un qui a accès à son ordinateur tente d’exporter la clé privée. Le jeton la marque comme jamais extractible, et rien ne sort.
C’est cette dernière étape qui change par rapport à l’article 1. L’étape de signature, elle, sert d’avertissement : quiconque détient son PIN et son jeton peut faire exactement ce qu’elle a fait.
À vous : une clé qui ne quitte pas son jeton
Cette partie pratique utilise SoftHSM, « une implémentation logicielle d’un dispositif cryptographique générique doté d’une interface PKCS#11 » (README de SoftHSM). Il parle un vrai PKCS#11 : les mêmes commandes fonctionnent avec un HSM physique, à condition de lui donner le chemin de sa bibliothèque et de s’en tenir aux mécanismes qu’il prend en charge. Ce n’est pas une frontière de sécurité : ses jetons sont des fichiers ordinaires, et « la sauvegarde peut donc se faire par une simple copie de fichier ». Servez-vous-en pour apprendre l’API, jamais pour protéger une vraie clé.
La clé et le PIN 1234 sont jetables. N’utilisez jamais ces commandes avec une vraie clé ou un vrai PIN.
Exemple pédagogique exécutable · Docker · alpine:edge · SoftHSM 2.7.0-r0 · OpenSC 0.27.1-r0 (pkcs11-tool) · OpenSSL 3.5.8 25 Aug 2026 · Python 3.14.7-r0
Enregistrez ce script, celui-là même qui a produit la sortie ci-dessous, sous le nom run.sh.
#!/bin/sh
set -e
echo "### apk add --no-cache softhsm=2.7.0-r0 opensc=0.27.1-r0 openssl python3 ###"
apk add --no-cache softhsm=2.7.0-r0 opensc=0.27.1-r0 openssl python3
echo
echo "### softhsm2-util --version ###"
softhsm2-util --version
echo
echo "### pkcs11-tool -I --module /usr/lib/softhsm/libsofthsm2.so ###"
pkcs11-tool -I --module /usr/lib/softhsm/libsofthsm2.so
echo
echo "### softhsm2-util --init-token --slot 0 --label mariama-token --pin 1234 --so-pin 5678 ###"
softhsm2-util --init-token --slot 0 --label mariama-token --pin 1234 --so-pin 5678
echo
echo "### pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so --keypairgen --key-type EC:prime256v1 --id 01 --label mariama-sign --usage-sign --login --pin 1234 ###"
pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so \
--keypairgen --key-type EC:prime256v1 --id 01 --label mariama-sign \
--usage-sign --login --pin 1234
echo
echo "### printf 'Contrat de pret 2026-001 : montant 5000000 GNF' > contrat.txt ###"
printf 'Contrat de pret 2026-001 : montant 5000000 GNF\n' > contrat.txt
cat contrat.txt
openssl dgst -sha256 contrat.txt
echo
echo "### pkcs11-tool --sign -m ECDSA-SHA256 --id 01 --login --pin 1234 -i contrat.txt -o contrat.sig.raw (default 'rs' format = raw r||s) ###"
pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so \
--sign -m ECDSA-SHA256 --id 01 --login --pin 1234 \
-i contrat.txt -o contrat.sig.raw
echo "raw signature: $(wc -c < contrat.sig.raw) bytes"
od -An -tx1 contrat.sig.raw
echo
echo "### export the public key (needed to verify) and try openssl verify against the RAW signature ###"
pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so \
--read-object --id 01 --type pubkey --public-key-info -o mariama_pub.der
openssl pkey -pubin -inform DER -in mariama_pub.der -pubout -out mariama_pub.pem
cat mariama_pub.pem
set +e
openssl dgst -sha256 -verify mariama_pub.pem -signature contrat.sig.raw contrat.txt
echo "exit=$? (expected: openssl cannot parse a raw r||s blob as an ASN.1 ECDSA-Sig-Value)"
set -e
echo
echo "### convert raw r||s (32+32 bytes for P-256) to a DER ECDSA-Sig-Value ###"
cat > raw_to_der.py <<'PYEOF'
with open("contrat.sig.raw", "rb") as f:
raw = f.read()
assert len(raw) == 64, f"expected 64 raw bytes (r||s for P-256), got {len(raw)}"
r, s = raw[:32], raw[32:]
def encode_int(b):
b = b.lstrip(b"\x00") or b"\x00"
if b[0] & 0x80: # DER INTEGER: prepend 0x00 if the top bit would look negative
b = b"\x00" + b
return b"\x02" + bytes([len(b)]) + b
body = encode_int(r) + encode_int(s)
der = b"\x30" + bytes([len(body)]) + body
with open("contrat.sig.der", "wb") as f:
f.write(der)
print("r =", r.hex())
print("s =", s.hex())
print("DER =", der.hex())
PYEOF
python3 raw_to_der.py
echo
echo "### openssl dgst -sha256 -verify against the DER-converted signature ###"
openssl dgst -sha256 -verify mariama_pub.pem -signature contrat.sig.der contrat.txt
echo
echo "### cross-check: pkcs11-tool can emit DER directly with --signature-format sequence ###"
pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so \
--sign -m ECDSA-SHA256 --id 01 --login --pin 1234 \
--signature-format sequence \
-i contrat.txt -o contrat.sig.sequence.der
openssl dgst -sha256 -verify mariama_pub.pem -signature contrat.sig.sequence.der contrat.txt
echo "note: contrat.sig.der and contrat.sig.sequence.der differ byte-for-byte (fresh ECDSA nonce each signing call); both verify OK"
echo
echo "### tamper check: verify the DER signature against a modified contract (expected: fails) ###"
sed 's/5000000/6000000/' contrat.txt > contrat_tampered.txt
diff contrat.txt contrat_tampered.txt || true
set +e
openssl dgst -sha256 -verify mariama_pub.pem -signature contrat.sig.der contrat_tampered.txt
echo "exit=$?"
set -e
echo
echo "### attempt to export the private key ###"
echo "### pkcs11-tool --read-object --id 01 --type privkey --login --pin 1234 -o priv.der ###"
set +e
pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so \
--read-object --id 01 --type privkey --login --pin 1234 -o priv.der
echo "exit=$?"
set -e
if [ -f priv.der ]; then
echo "priv.der exists, size: $(wc -c < priv.der) bytes"
else
echo "priv.der was NOT created -- no private key material left the token"
fi
echo
echo "### pkcs11-tool --list-objects --login --pin 1234 (attributes explaining the refusal) ###"
pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so --list-objects --login --pin 1234
echo
echo "### done ###"
Lancez-le ensuite dans un conteneur jetable. Le script y est monté en lecture seule, et chaque fichier qu’il écrit disparaît avec le conteneur :
docker run --rm --mount type=bind,src="$PWD/run.sh",dst=/w/run.sh,readonly -w /w alpine:edge sh /w/run.sh
Chaque ligne ### reprend l’étape qui la suit. Les lignes de téléchargement de l’image Docker, s’il y en a, s’affichent en premier.
Installer et inspecter le jeton
Résultat attendu
### apk add --no-cache softhsm=2.7.0-r0 opensc=0.27.1-r0 openssl python3 ###
( 1/24) Upgrading libcrypto3 (3.5.7-r0 -> 3.5.8-r0)
( 2/24) Upgrading libssl3 (3.5.7-r0 -> 3.5.8-r0)
( 3/24) Installing eudev-libs (3.2.14-r8)
( 4/24) Installing pcsc-lite (2.5.2-r0)
Executing pcsc-lite-2.5.2-r0.pre-install
( 5/24) Installing ncurses-terminfo-base (6.6_p20260822-r0)
( 6/24) Installing libncursesw (6.6_p20260822-r0)
( 7/24) Installing readline (8.3.3-r1)
( 8/24) Installing opensc (0.27.1-r0)
( 9/24) Installing openssl (3.5.8-r0)
(10/24) Installing libexpat (2.8.4-r0)
(11/24) Installing libbz2 (1.0.8-r6)
(12/24) Installing libffi (3.8.0-r0)
(13/24) Installing xz-libs (5.8.4-r0)
(14/24) Installing libgcc (15.2.0-r9)
(15/24) Installing libstdc++ (15.2.0-r9)
(16/24) Installing mpdecimal (4.0.1-r0)
(17/24) Installing libpanelw (6.6_p20260822-r0)
(18/24) Installing sqlite-libs (3.53.4-r0)
(19/24) Installing python3 (3.14.7-r0)
(20/24) Installing python3-pycache-pyc0 (3.14.7-r0)
(21/24) Installing pyc (3.14.7-r0)
(22/24) Installing python3-pyc (3.14.7-r0)
(23/24) Installing sqlite (3.53.4-r0)
(24/24) Installing softhsm (2.7.0-r0)
Executing busybox-1.38.0-r4.trigger
OK: 58.9 MiB in 38 packages
### softhsm2-util --version ###
2.7.0
### pkcs11-tool -I --module /usr/lib/softhsm/libsofthsm2.so ###
Using slot 0 with a present token (0x0)
Cryptoki version 3.2
Manufacturer SoftHSM
Library Implementation of PKCS11 (ver 2.7)alpine:edge était, au moment de la rédaction, la seule branche d’Alpine à fournir à la fois SoftHSM 2.7.0 et OpenSC 0.27.1. Edge évolue : la liste d’installation changera, et si edge abandonne ces versions précises, la commande apk add épinglée échouera. La bibliothèque annonce Cryptoki version 3.2.
Créer le jeton et y générer la clé
Résultat attendu
### softhsm2-util --init-token --slot 0 --label mariama-token --pin 1234 --so-pin 5678 ###
The token has been initialized and is reassigned to slot 1758845579
### pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so --keypairgen --key-type EC:prime256v1 --id 01 --label mariama-sign --usage-sign --login --pin 1234 ###
Using slot 0 with a present token (0x68d5da8b)
Key pair generated:
Private Key Object; EC
label: mariama-sign
ID: 1 (0x01)
Usage: decrypt, sign, signRecover, unwrap
Access: sensitive, always sensitive, never extractable, local
uri: pkcs11:model=SoftHSM%20v2;manufacturer=SoftHSM%20project;serial=fde4458568d5da8b;token=mariama-token;id=%01;object=mariama-sign;type=private
Public Key Object; EC EC_POINT 256 bits
EC Point: 0441044d8e5e8c3cc030f0f888f728b873d5cea99dcaba7f947c1e1f2ef64b0a
c1f1c5c87a10543c15eca778b200af3e31c7016146439030a821b4dae7416d18
30f695
EC Params: 06:08:2a:86:48:ce:3d:03:01:07 ("prime256v1" OID:"1.2.840.10045.3.1.7")
label: mariama-sign
ID: 1 (0x01)
Usage: encrypt, verify, verifyRecover, wrap
Access: local
uri: pkcs11:model=SoftHSM%20v2;manufacturer=SoftHSM%20project;serial=fde4458568d5da8b;token=mariama-token;id=%01;object=mariama-sign;type=public--init-token joue le rôle du SO et définit les deux PIN. prime256v1 désigne P-256.
Lisez la ligne Access: de la clé privée : sensitive, always sensitive, never extractable, local. pkcs11-tool --keypairgen demande toujours une clé sensible et privée, et ne demande une clé extractible que si vous passez --extractable ; sans cette option, c’est la valeur par défaut de SoftHSM, non extractible, qui s’applique (code source de pkcs11-tool ; code source de SoftHSM). Sur un autre jeton, vérifiez la ligne Access: au lieu de le supposer.
Le numéro de slot, l’identifiant de jeton 0x68d5da8b, le numéro de série et le point EC changent à chaque exécution, puisque chaque exécution crée un nouveau jeton et une nouvelle clé.
Signer sur le jeton
Résultat attendu
### printf 'Contrat de pret 2026-001 : montant 5000000 GNF' > contrat.txt ###
Contrat de pret 2026-001 : montant 5000000 GNF
SHA2-256(contrat.txt)= 0c53f84a6928871a965328cc983d0a20d07f16dd69a3b273847aa2a9f54cff4c
### pkcs11-tool --sign -m ECDSA-SHA256 --id 01 --login --pin 1234 -i contrat.txt -o contrat.sig.raw (default 'rs' format = raw r||s) ###
Using slot 0 with a present token (0x68d5da8b)
Using signature algorithm ECDSA-SHA256
raw signature: 64 bytes
1b 26 96 ec 43 e4 42 4c 97 f8 5e ad b5 87 1d be
de aa 1e 42 d1 ce 80 ee db 8a e9 cd 70 8e 72 8d
1e 51 b4 e2 de 2e 7b 1d e8 69 cb e0 4d f8 80 63
1f 00 ea 55 4f b4 34 bb 3d 21 c2 55 40 24 55 6fLe contrat et son empreinte sont identiques, octet pour octet, à ceux de l’article 1. -m ECDSA-SHA256 sélectionne CKM_ECDSA_SHA256 : c’est donc le jeton qui hache, un mécanisme que SoftHSM a ajouté dans sa version 2.7.0 (NEWS de SoftHSM). La signature fait 64 octets, r puis s, et change à chaque exécution, car ECDSA utilise chaque fois un nouveau nombre aléatoire.
Vérifier : d’abord le mauvais format, puis le bon
Résultat attendu
### export the public key (needed to verify) and try openssl verify against the RAW signature ###
Using slot 0 with a present token (0x68d5da8b)
warning: PKCS11 function getPUBLIC_KEY_INFO returned a value of length 0 failed: rv = CKR_OK (0x0)
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAETY5ejDzAMPD4iPcouHPVzqmdyrp/
lHweHy72SwrB8cXIehBUPBXsp3iyAK8+MccBYUZDkDCoIbTa50FtGDD2lQ==
-----END PUBLIC KEY-----
Error verifying data
exit=1 (expected: openssl cannot parse a raw r||s blob as an ASN.1 ECDSA-Sig-Value)
### convert raw r||s (32+32 bytes for P-256) to a DER ECDSA-Sig-Value ###
r = 1b2696ec43e4424c97f85eadb5871dbedeaa1e42d1ce80eedb8ae9cd708e728d
s = 1e51b4e2de2e7b1de869cbe04df880631f00ea554fb434bb3d21c2554024556f
DER = 304402201b2696ec43e4424c97f85eadb5871dbedeaa1e42d1ce80eedb8ae9cd708e728d02201e51b4e2de2e7b1de869cbe04df880631f00ea554fb434bb3d21c2554024556f
### openssl dgst -sha256 -verify against the DER-converted signature ###
Verified OK
### cross-check: pkcs11-tool can emit DER directly with --signature-format sequence ###
Using slot 0 with a present token (0x68d5da8b)
Using signature algorithm ECDSA-SHA256
Verified OK
note: contrat.sig.der and contrat.sig.sequence.der differ byte-for-byte (fresh ECDSA nonce each signing call); both verify OKLa ligne warning: est une bizarrerie de cette association entre OpenSC et SoftHSM : la clé publique écrite est correcte.
La première vérification échoue avec « Error verifying data », car OpenSSL ne sait pas analyser un r‖s brut. Le script Python enveloppe les deux mêmes entiers en DER, SEQUENCE { INTEGER r, INTEGER s }, en ajoutant un octet nul en tête quand le bit de poids fort d’un entier est à 1, pour qu’il ne soit pas lu comme négatif, et les mêmes octets donnent « Verified OK ». En pratique, demandez directement du DER à pkcs11-tool avec --signature-format sequence (ou signez avec openssl), comme le fait la vérification croisée. La clé, r, s et le DER changent à chaque exécution.
Falsifier le contrat
Résultat attendu
### tamper check: verify the DER signature against a modified contract (expected: fails) ###
--- contrat.txt
+++ contrat_tampered.txt
@@ -1 +1 @@
-Contrat de pret 2026-001 : montant 5000000 GNF
+Contrat de pret 2026-001 : montant 6000000 GNF
Verification failure
287DF581FFFF0000:error:030000EA:digital envelope routines:EVP_DigestVerifyFinal:provider signature failure:crypto/evp/m_sigver.c:703:ECDSA digest_verify_final:
exit=1Une fois sortie du jeton, la signature est une signature ECDSA ordinaire : changez un chiffre et elle échoue, comme dans l’article 1. Le préfixe de la ligne d’erreur (287DF581FFFF0000) change à chaque exécution, tout comme la place de la ligne error: parmi les lignes qui l’entourent.
Tenter d’exporter la clé privée
Résultat attendu
### attempt to export the private key ###
### pkcs11-tool --read-object --id 01 --type privkey --login --pin 1234 -o priv.der ###
Using slot 0 with a present token (0x68d5da8b)
sorry, reading private keys not (yet) supported
exit=0
priv.der was NOT created -- no private key material left the token
### pkcs11-tool --list-objects --login --pin 1234 (attributes explaining the refusal) ###
Using slot 0 with a present token (0x68d5da8b)
Public Key Object; EC EC_POINT 256 bits
EC Point: 0441044d8e5e8c3cc030f0f888f728b873d5cea99dcaba7f947c1e1f2ef64b0a
c1f1c5c87a10543c15eca778b200af3e31c7016146439030a821b4dae7416d18
30f695
EC Params: 06:08:2a:86:48:ce:3d:03:01:07 ("prime256v1" OID:"1.2.840.10045.3.1.7")
label: mariama-sign
ID: 1 (0x01)
Usage: encrypt, verify, verifyRecover, wrap
Access: local
uri: pkcs11:model=SoftHSM%20v2;manufacturer=SoftHSM%20project;serial=fde4458568d5da8b;token=mariama-token;id=%01;object=mariama-sign;type=public
Private Key Object; EC
label: mariama-sign
ID: 1 (0x01)
Usage: decrypt, sign, signRecover, unwrap
Access: sensitive, always sensitive, never extractable, local
uri: pkcs11:model=SoftHSM%20v2;manufacturer=SoftHSM%20project;serial=fde4458568d5da8b;token=mariama-token;id=%01;object=mariama-sign;type=private
### done ###Le message « sorry, reading private keys not (yet) supported » est produit côté client par pkcs11-tool : l’outil refuse de lire toute clé privée, quels que soient ses attributs, et n’interroge jamais le jeton. Il afficherait la même ligne pour une clé extractible. Ce n’est pas une erreur CKR_ATTRIBUTE_SENSITIVE renvoyée en direct par le jeton. L’outil se termine même avec le code 0 : un script qui ne testerait que le code de sortie ne verrait pas le refus. Le contrôle qui compte, c’est qu’aucun fichier priv.der n’a été écrit.
La preuve que c’est le jeton qui protège la clé vient de la seconde commande : SoftHSM indique sensitive, always sensitive, never extractable, local. Selon PKCS#11, un jeton ne doit pas révéler une telle clé en clair, et doit refuser de l’encapsuler en renvoyant CKR_KEY_UNEXTRACTABLE (PKCS #11 v3.2, §4.10, §5.18.3). Cette exécution n’a pas tenté d’encapsulation. Et la ligne Usage: mentionne toujours sign : le caractère non extractible empêche la copie, pas l’usage. L’ordre des deux objets dans --list-objects peut aussi varier d’une exécution à l’autre.
La place de SEDEYA
Les clés de signature de SEDEYA sont conservées dans un module matériel de sécurité, auquel on accède via PKCS#11, ou dans un KMS (service de gestion de clés). Les signatures produites sont au format PAdES, jusqu’au niveau B-LTA, et n’importe qui peut contrôler un PDF signé sur la page de vérification publique de SEDEYA, en le déposant ou en scannant son QR code. L’endroit où se trouve une clé répond à une question ; la façon dont chaque appel de signature est autorisé en pose une autre, traitée dans l’article 5. Les prochains articles de cette série traitent de la construction de la PKI qui se trouve derrière ces clés, et du lien entre chaque signature et une seule transaction.
Risques et idées reçues
« SoftHSM est un HSM. »
SoftHSM implémente l’interface PKCS#11 en logiciel, et ses jetons peuvent être sauvegardés « par une simple copie de fichier » (README de SoftHSM). L’API est réelle ; la frontière de sécurité ne l’est pas.
« CKA_PRIVATE signifie que la clé est secrète. »
CKA_PRIVATE règle la visibilité de l’objet avant la connexion (PKCS #11 v3.2, §4.4). Le secret
vient de CKA_SENSITIVE et de CKA_EXTRACTABLE ; un objet privé peut très bien être extractible.
« Le SO et l'utilisateur sont toujours deux personnes différentes. »
Ils « peuvent être la même personne ou des personnes différentes » (guide d’utilisation, §2.4). La séparation des tâches est une politique qu’il vous revient de concevoir.
En résumé
- Une clé de signature doit provenir d’un générateur aléatoire approuvé. Un mot de passe peut déverrouiller une clé, mais ne doit jamais en être une.
- Le propriétaire devrait générer sa propre clé. Les clés individuelles permettent à une signature de désigner une seule personne.
- Cartes, jetons et HSM signent sur demande, derrière PKCS#11, sans jamais livrer la clé.
CKA_SENSITIVEetCKA_EXTRACTABLEgardent la valeur de la clé dans le jeton ;CKA_PRIVATEne fait que masquer l’objet avant la connexion.- Les signatures ECDSA PKCS#11 sont des
r‖sbruts. Convertissez-les en DER, ou demandez du DER, avant de vérifier avec OpenSSL. - Dans la partie pratique, le refus d’export venait de
pkcs11-toollui-même ; la vraie protection, c’est l’attributnever extractabledu jeton. - Un HSM protège le secret de la clé, pas la décision de signer. L’autorisation doit être liée à chaque transaction, hors du HSM.
Vérifiez votre compréhension
Vérifiez votre compréhension
Votre équipe conserve une clé de signature dans un HSM, avec CKA_EXTRACTABLE à FALSE. Un attaquant obtient un accès shell au serveur de signature pendant que l'application est connectée. Que peut-il faire ?
Afficher la réponse
Tout signer, avec n’importe quelle clé accessible à l’utilisateur connecté, en pilotant l’application connectée ou en réutilisant le PIN qu’elle stocke. L’attaquant ne peut pas copier la clé, mais PKCS#11 ne restreint pas les opérations qu’effectue une application connectée, et cette connexion couvre toutes ses sessions. C’est pourquoi l’autorisation doit être liée à chaque document, hors du HSM.
Vérifiez votre compréhension
pkcs11-tool signe un fichier sur un jeton, et openssl dgst -verify affiche « Error verifying data » avec la bonne clé publique. Quelle est la cause la plus probable ?
Afficher la réponse
Un problème de format, pas une mauvaise signature. PKCS#11 renvoie les signatures ECDSA sous forme
de r‖s brut, alors qu’OpenSSL attend une SEQUENCE DER. Signez avec --signature-format sequence ou avec openssl, ou convertissez les octets bruts en DER.
Vérifiez votre compréhension
Une signataire perd sa carte à puce. Le prestataire doit-il restaurer sa clé de signature à partir d'une sauvegarde ?
Afficher la réponse
En général, non. Le SP 800-57 indique que les clés privées de signature ne sont pas sauvegardées, car la non-répudiation serait remise en cause. Elle reçoit une nouvelle paire de clés et un nouveau certificat ; ses anciennes signatures se vérifient toujours avec l’ancienne clé publique.
Prochaine étape
L’article 4 traite de la construction de la PKI derrière un certificat : AC racine et émettrice, CRL et OCSP. D’ici là, envie de voir comment n’importe qui peut contrôler un PDF signé, sans outil particulier ? Parlez-nous : nous vous présenterons une signature SEDEYA, de la signature jusqu’à la vérification publique.
Références
- PKCS #11 Specification Version 3.2, OASIS, OASIS Standard, 3 June 2026 (§3.3, §4.2, §4.4, §4.8, §4.10, §5.7.5, §5.18.3, §6.3.1, §6.3.12, §6.3.13)
- PKCS #11 Cryptographic Token Interface Usage Guide Version 3.2, OASIS, Committee Note 01, 15 April 2025 (non-normative) (§2.1–2.6, §3.1)
- SoftHSMv2 2.7.0: README and NEWS, OpenDNSSEC / SoftHSM project, 2.7.0, 20 January 2026 (Introduction, Backup; NEWS 2.7.0)
- pkcs11-tool(1), OpenSC 0.27.1, OpenSC project, 0.27.1, 31 March 2026 (--keypairgen, --sign, --signature-format, --read-object, --list-objects)
- Digital Signature Standard (DSS), FIPS 186-5, NIST, 3 February 2023 (§5.2, §5.4, §6.2, §6.2.1, §6.2.2; Appendix A.2)
- Recommendation for Key Management: Part 1 – General, SP 800-57 Part 1 Rev. 5, NIST, May 2020 (§5.1.1, §5.3.6, §5.5, §8.1.5.1, §8.2.2.1 (Table 7))
- Recommendation for Password-Based Key Derivation, Part 1: Storage Applications, SP 800-132, NIST, December 2010 (§1, §5)
- Security Requirements for Cryptographic Modules, FIPS 140-3, NIST, 22 March 2019 (Announcement items 3 and 7; §3.3)
- Cryptographic Module Validation Program, NIST CSRC, Read 23 September 2026 (Applicability of Validated Modules)
- Architectures and protocols for remote signature applications (CSC API) v2.0.0.2, Cloud Signature Consortium, v2.0.0.2 (v2.2 is current; this article quotes v2.0.0.2) (§4.1, §8.2)
- Regulation (EU) No 910/2014 (eIDAS), consolidated text, Publications Office of the European Union, Consolidated 18 October 2024 (Art. 3(22), art. 3(23a))

