crypto — n_pbt_crypto #
← Référence des composants · Sommaire du guide
Empreintes, HMAC, chiffrement par mot de passe, valeurs aléatoires et signatures RSA — la Web Crypto API du moteur WebView2, que votre PowerBuilder 10 n'a nulle part ailleurs. Aucune DLL tierce, aucun service : ce que le poste sait déjà faire, offert à PowerScript.
▶ Le voir en vrai — Application de démonstration, tuile Crypto : les résultats, le code qui les produit et cette page, côte à côte.
En bref #
| Objet non visuel | n_pbt_crypto |
| Sert à | Vérifier qu'un fichier n'a pas changé, garder un secret dans un INI, signer une commande, authentifier un appel d'API |
| Principe | Une page cachée fait le calcul ; chaque méthode est un appel PowerScript ordinaire qui rend sa valeur |
| Dépendance | Le runtime WebView2, déjà requis par la bibliothèque — rien d'autre |
Démarrage rapide #
// Local variables
n_pbt_crypto lnv_crypto
string ls_hash, ls_cipher
// Create the object once, free it when done
lnv_crypto = create n_pbt_crypto
// The fingerprint of a text, in hexadecimal
ls_hash = lnv_crypto.of_sha256(/*text*/ "Invoice 4152, 690.00 EUR")
// A secret kept with a password : one string to store, the password to keep
ls_cipher = lnv_crypto.of_encrypt(/*password*/ "s3cret", /*text*/ "The safe code is 4152.")
MessageBox("Back", lnv_crypto.of_decrypt(/*password*/ "s3cret", /*cipher*/ ls_cipher))
// Free the object
destroy lnv_crypto
Chaque méthode est un appel qui rend sa valeur : pas d'événement, pas d'attente à écrire.
Les formats, lisibles par les autres outils #
- Empreintes et HMAC : hexadécimal minuscule, le texte lu en UTF-8 — Python, Java et
openssl dgstdonnent la même valeur. of_encrypt: une seule chaîne base64 qui porte, dans l'ordre,PBK2(4 octets), le nombre de tours PBKDF2 (4 octets, gros-boutiste), le sel (16), le nonce (12), le chiffré AES-256-GCM, puis son tag (16 octets, à la fin). La clé est dérivée du mot de passe (UTF-8) par PBKDF2-HMAC-SHA-256 avec CES tours —il_pbkdf2_roundsau moment du chiffrement.of_decryptavec le même mot de passe suffit, sur n'importe quel poste ;of_encrypt_fileécrit exactement le même conteneur, en octets.of_password_hash:pbkdf2-sha256$tours$sel$empreinte, sel et empreinte en base64 standard. Ce format est PROPRE à PBToolboxAI : ni passlib ($pbkdf2-sha256$…) ni Django (pbkdf2_sha256$…) ne le lisent tel quel.- Clés et signatures : PEM, publique en SPKI (
BEGIN PUBLIC KEY), privée en PKCS#8 (BEGIN PRIVATE KEY) ; signatures en base64, selon la famille de la clé — RSA : RSASSA-PKCS1-v1_5 avec SHA-256 ; ECDSA P-256 : SHA-256, en DER (openssl dgst -sha256 -verify, JavaSHA256withECDSA) ; Ed25519 : 64 octets.of_verifylit une signature ECDSA en DER ou en P1363 (r | s) ; le JWT ES256 reste en P1363, comme la RFC 7518 l'exige. - RSA-OAEP (
of_rsa_encrypt) : SHA-256, MGF1 en SHA-256, sans label. En Java :OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT); avec openssl :-pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 -pkeyopt rsa_mgf1_md:sha256. Une clé PKCS#1 (BEGIN RSA PRIVATE KEY) se convertit paropenssl pkcs8 -topk8 -nocrypt. - MD5 est offert en NATIF (
HASH_MD5) pour les échanges anciens qui l'exigent — jamais pour un mot de passe ni pour une intégrité face à un adversaire. - JWK (
of_jwk_to_pem,of_pem_to_jwk,of_jwt_verify_jwks) : le JSON de la RFC 7517 —ktyRSA, EC (P-256, P-384) ou OKP (Ed25519) ; un jeu de clés (JWKS) est{"keys":[…]}, chaque clé nommée par sonkid. - Blobs : les méthodes
of_*_blobtravaillent sur les OCTETS, en natif — les mêmes valeurs que pour un fichier qui contiendrait ces octets ;of_encrypt_blobécrit le conteneur deof_encrypt.
Déchiffrer une valeur d'of_encrypt côté serveur, en Python (bibliothèque cryptography) :
import base64, struct
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
raw = base64.b64decode(value)
assert raw[:4] == b"PBK2"
rounds = struct.unpack(">I", raw[4:8])[0]
salt, nonce, data = raw[8:24], raw[24:36], raw[36:] # data = ciphertext + 16-byte tag
key = PBKDF2HMAC(hashes.SHA256(), 32, salt, rounds).derive(password.encode("utf-8"))
text = AESGCM(key).decrypt(nonce, data, None).decode("utf-8")
Propriétés #
| Propriété | Type | Défaut | Rôle |
|---|---|---|---|
il_timeout_ms | long | 20000 | Durée maximale d'un appel à la page cachée : générer une clé de 4096 bits, ou dériver une clé avec des millions de tours PBKDF2, peut prendre quelques secondes. Les empreintes, les fichiers et les blobs sont natifs : aucun délai ne s'y applique |
il_pbkdf2_rounds | long | 100000 | Tours PBKDF2 d'of_encrypt et d'of_encrypt_file : environ 100 ms, payés à chaque lecture de la valeur. De 1 000 à 10 000 000 : une autre valeur fait REFUSER l'appel (of_get_last_error() le dit, -5 pour un fichier), jamais remplacer Vaut aussi pour of_encrypt_blob. Une valeur porte ses tours : les changer ne rend jamais illisible une valeur plus ancienne |
il_password_hash_rounds | long | 600000 | Tours PBKDF2 d'of_password_hash : 600 000, le chiffre de l'OWASP pour PBKDF2-HMAC-SHA-256 (environ 0,6 s). Une empreinte stockée est ce qu'un attaquant force hors ligne si la table fuit ; elle ne se vérifie qu'une fois par connexion. Mêmes bornes Augmentez-les, et of_password_needs_rehash dit quelles valeurs stockées renouveler |
il_jwt_leeway_seconds | long | 60 | Tolérance de of_jwt_verify et of_jwt_verify_jwks sur exp et nbf, en secondes : les horloges de deux machines dérivent. 0 à 86 400 ; toute autre valeur fait refuser la vérification |
ipo_owner | powerobject | null | L'objet visuel pour lequel cet utilitaire travaille : la licence se vérifie sur sa classe. À poser avant le premier appel. Nécessaire seulement dans l'application de démonstration ; une clé de développement ou d'exécution débride l'utilitaire sans lui. Non débridé, il est en mode démo — textes de 2048 caractères et fichiers de 2048 octets au plus, à l'entrée comme à la sortie Les blobs suivent le plafond des fichiers |
Constantes : HASH_MD5, HASH_SHA1, HASH_SHA256, HASH_SHA384, HASH_SHA512 pour l'algorithme ; KEY_2048, KEY_3072, KEY_4096 pour la taille d'une clé RSA, KEY_RSA_2048, KEY_RSA_3072, KEY_RSA_4096, KEY_EC_P256, KEY_EC_P384, KEY_ED25519 pour la sorte d'une paire ; JWT_HS256, JWT_HS384, JWT_HS512, JWT_RS256, JWT_RS384, JWT_RS512, JWT_PS256, JWT_PS384, JWT_PS512, JWT_ES256, JWT_ES384, JWT_EDDSA ; SIGN_PKCS1, SIGN_PSS pour le schéma d'une signature RSA ; CHARSET_ALL, CHARSET_ALPHANUM, CHARSET_LETTERS, CHARSET_DIGITS, CHARSET_HEX ; KEYFORMAT_TEXT, KEYFORMAT_HEX, KEYFORMAT_BASE64 pour la forme d'une clé HMAC.
Méthodes #
| Méthode | Rôle | |
|---|---|---|
of_get_last_error ( ) → string | Pourquoi le dernier appel a rendu une chaîne vide, false ou un code négatif : mauvais mot de passe, fichier illisible, algorithme inconnu, limite démo Un argument NULL compte pour un argument vide : la raison n'est jamais NULL. | |
of_open ( ) → long | Crée la page cachée. Facultatif — chaque méthode le fait — mais l'appeler à l'ouverture de la fenêtre paie le coût une fois. Rend le handle (> 0) ou -2 si elle n'a pas pu être créée (of_get_last_error() dit pourquoi) | |
of_is_open ( ) → boolean | Vrai dès que la page cachée existe | |
of_hash (string as_algorithm, string as_text) → string | L'empreinte d'un texte, en hexadécimal ; HASH_* pour l'algorithme, lu sans casse, tirets ni espaces (SHA-256 vaut HASH_SHA256). NATIVE pour tous les algorithmes : aucune page n'est ouverte pour une empreinte. Vide en cas d'échec | |
of_sha256 (string as_text) → string | of_hash avec HASH_SHA256 — celui qu'on utilise le plus | |
of_hmac (string as_algorithm, string as_key, string as_text { , string as_key_format }) → string | Le HMAC d'un texte sous une clé secrète, en hexadécimal — ce qu'une API demande pour authentifier un appel. La clé est un TEXTE par défaut ; KEYFORMAT_HEX ou KEYFORMAT_BASE64 en donnent les OCTETS : un secret fourni encodé, ou le résultat d'un HMAC précédent (AWS SigV4 en enchaîne quatre). Une clé vide est refusée, et une clé PEM aussi : ce n'est jamais un secret HMAC Le base64url est lu aussi. | |
of_hmac_verify (string as_algorithm, string as_key, string as_text, string as_expected { , string as_key_format }) → boolean | Vrai si as_expected est le HMAC de as_text sous as_key, comparé en TEMPS CONSTANT — la vérification d'une signature de webhook (Stripe, GitHub, Shopify) sans la trahir, jamais if of_hmac(…) = en-tête. as_expected en hexadécimal (casse ignorée) ou en base64 ; ôtez d'abord un préfixe comme sha256=. Faux sinon as_expected peut aussi être en base64url. | |
of_hash_file (string as_algorithm, string as_path) → string | L'empreinte d'un fichier du poste, de n'importe quel type et de n'importe quelle taille (lu par morceaux), sans le charger dans PowerBuilder. Un chemin RELATIF se cherche dans le dossier courant, puis dans le dossier où l'application a démarré, puis à côté de l'EXE — la règle de tous les fichiers de ce composant. Vide en cas d'échec ; of_get_last_error() distingue l'algorithme inconnu du fichier illisible | |
of_base64_encode (string as_text) → string | Un texte en base64 (ses octets UTF-8) : ce qu'un navigateur ou Python écrirait. Vide en cas d'échec | |
of_base64_decode (string as_base64) → string | Le TEXTE de retour (UTF-8). Une valeur qui n'est pas du base64, ou dont les octets ne sont pas un texte (un PDF, une image), rend une chaîne vide et of_get_last_error() — jamais du bruit. Des octets s'écrivent dans un fichier par of_base64_decode_to_file Ou of_base64_decode_blob, qui les rend dans un blob. | |
of_base64_encode_file (string as_path) → string | Les octets d'un fichier du poste en base64 — une pièce jointe, une image pour un appel JSON — lus par la DLL, jamais chargés dans PowerBuilder. Jusqu'à 32 Mo dans une application 32 bits (l'IDE PowerBuilder en est une), 512 Mo en 64 bits ; au-delà, of_base64_encode_file_to_file. Vide en cas d'échec ; of_get_last_error() dit lequel : un fichier trop gros pour ce processus, illisible, ou la limite de démonstration | |
of_base64_encode_file_to_file (string as_source, string as_target) → long | Le base64 d'un fichier écrit dans UN AUTRE fichier (ASCII, sans retour à la ligne), par la DLL, en mémoire constante et quelle que soit la taille : ce qu'envoie un gros dépôt. Chemins comme of_hash_file. Rend 0, -5 sur un chemin vide ou une cible qui dépend du dossier courant d'un lecteur (\x, C:x), -2 source illisible, -4 cible impossible à écrire ou, en démonstration, source de plus de 2048 octets | |
of_base64_decode_to_file (string as_base64, string as_path) → long | Écrit les octets d'une valeur base64 dans un fichier — la pièce jointe qu'une API a répondue. Une valeur VIDE écrit un fichier vide ; un chemin RELATIF s'écrit dans le dossier où l'application a démarré (le dossier courant au chargement de la bibliothèque, le même dans l'IDE et compilé), là où of_hash_file le retrouve. Rend 0 une fois écrit, -5 sur un chemin vide ou qui dépend du dossier courant d'un lecteur (\x, C:x), ou une valeur qui n'est pas du base64, -4 si le fichier ne peut pas être écrit ou, en démo, au-delà de 2048 octets | |
of_encrypt (string as_password, string as_text) → string | Chiffre un texte avec un mot de passe : une seule chaîne base64 à stocker, qui porte ses tours PBKDF2 (voir Les formats). Un mot de passe VIDE est refusé — n'importe qui relirait la valeur. Vide en cas d'échec | |
of_decrypt (string as_password, string as_cipher) → string | Le texte de retour, avec le même mot de passe. Mauvais mot de passe ou valeur altérée : chaîne vide et of_get_last_error(), jamais du bruit. Une valeur dont le contenu n'est pas un texte (un fichier chiffré par of_encrypt_file) rend vide aussi : of_decrypt_file ou of_decrypt_blob sur ses octets. | |
of_uuid ( ) → string | Un UUID aléatoire (version 4) | |
of_random_hex (integer ai_bytes) → string | ai_bytes octets aléatoires (1 à 4096) en hexadécimal : un sel, un jeton | |
of_generate_keypair (integer ai_bits, ref string as_public_pem, ref string as_private_pem) → long | Une paire RSA en PEM (KEY_2048, KEY_3072, KEY_4096). Rend 0 une fois les deux clés remplies, -5 sur toute autre taille (les deux restent vides), -4 en cas d'échec | |
of_sign (string as_private_pem, string as_text { , string as_scheme, string as_hash }) → string | La signature d'un texte par la clé privée, en base64. La clé dit sa famille : RSA (RSASSA-PKCS1-v1_5, SHA-256), ECDSA P-256 (SHA-256, signature en DER — ce que openssl dgst et Java SHA256withECDSA vérifient) ou Ed25519 (64 octets). Vide en cas d'échec as_scheme et as_hash : ce qu'attend l'autre partie — SIGN_PSS pour une clé RSA (sel de la longueur du hachage, MGF1 du même hachage), HASH_SHA384 ou HASH_SHA512 ; un texte vide garde ceux de la clé ; une clé EC prend un hachage, jamais PSS ; une clé Ed25519 ni l'un ni l'autre. Une clé PUBLIQUE donnée à la place est nommée | |
of_verify (string as_public_pem, string as_text, string as_signature { , string as_scheme, string as_hash }) → boolean | Vrai si la signature a été faite sur exactement ce texte par la clé privée correspondante ; faux — pas une erreur — si le texte a changé. Une signature ECDSA se lit en DER (openssl, Java) ou en P1363 (`r | s, 64 octets : WebCrypto, .NET) Avec le schéma et le hachage du signataire (voir of_sign`). |
of_generate_keypair (string as_kind, ref string as_public_pem, ref string as_private_pem) → long | Une paire par SORTE : KEY_RSA_2048/3072/4096, KEY_EC_P256 ou KEY_EC_P384 (signatures courtes) ou KEY_ED25519. Rend 0, -5 sur une sorte qui n'est aucune de celles-ci (jamais une paire RSA à la place), -4 en cas d'échec | |
of_key_kind (string as_pem) → string | La famille d'une clé PEM : rsa, ec-p256, ec-p384 ou ed25519 ; seul le PREMIER bloc du texte est lu. Une clé PKCS#1 (BEGIN RSA PRIVATE KEY), un certificat ou une clé chiffrée est NOMMÉ dans of_get_last_error(), avec la commande openssl qui la convertit | |
of_jwk_to_pem (string as_jwk_json) → string | Une clé publiée en JWK (RFC 7517 : kty RSA, EC P-256 / P-384 ou OKP Ed25519) en PEM, ce que prennent of_verify, of_jwt_verify et openssl : publique en SPKI, privée en PKCS#8 quand le JWK porte d. Ses membres d'usage (alg, use, key_ops, kid) ne font pas partie de la clé. Vide en cas d'échec | |
of_pem_to_jwk (string as_pem) → string | Une clé PEM en JWK (JSON) : ce que publie un jeu de clés, ou ce qu'une API demande d'enregistrer. Un PEM privé donne un JWK privé (avec d) : gardez-le pour vous. Vide en cas d'échec | |
of_base64url_encode (string as_text) → string | base64 sûr pour une URL (JWT, chaîne de requête) | |
of_base64url_decode (string as_base64url) → string | Le texte de retour ; vide si ce n'est pas du base64url, ou si ses octets ne sont pas un texte | |
of_hex_encode (string as_text) → string | Les octets UTF-8 d'un texte en hexadécimal | |
of_hex_decode (string as_hex) → string | Le texte de retour ; vide si ce n'est pas de l'hexadécimal, ou si ses octets ne sont pas un texte | |
of_random_password (integer ai_length, string as_charset) → string | Un mot de passe aléatoire (4 à 256 caractères) sur un jeu CHARSET_* (vide = CHARSET_ALL), sans biais. Vide sur une longueur fautive ou un jeu inconnu | |
of_equals_constant_time (string as_a, string as_b) → boolean | Égalité en temps constant : comparer un jeton ou un code sans le trahir | |
of_password_hash (string as_password) → string | Ce qu'on STOCKE pour un mot de passe : PBKDF2 salé (il_password_hash_rounds, 600 000 tours), tours et sel dans la valeur. Jamais le même résultat deux fois. Un mot de passe vide se hache (vérifier un mot de passe vide est légitime). Le format est PROPRE à PBToolboxAI (voir Les formats) | |
of_password_verify (string as_password, string as_stored) → boolean | Vrai si le mot de passe est celui de la valeur stockée, comparé en temps constant. Une valeur dont les tours sortent de 1 000 à 10 000 000 est refusée : une valeur hostile ne fait pas tourner la vérification des minutes | |
of_password_needs_rehash (string as_stored) → boolean | Vrai quand une valeur stockée par of_password_hash porte MOINS de tours que il_password_hash_rounds : juste après un of_password_verify réussi, quand le mot de passe est connu, hachez-le à nouveau et stockez la nouvelle valeur — le conseil de l'OWASP quand le coût monte. Faux sinon ; faux avec of_get_last_error() quand as_stored n'est pas une telle valeur. Aucune page | |
of_totp_secret ( ) → string | Un secret neuf pour les codes à deux facteurs (RFC 6238), en base32 | |
of_totp_code (string as_secret { , string as_hash, integer ai_digits, integer ai_period }) → string | Le code à six chiffres de l'instant, celui que montre l'authentificateur Avec des réglages : HASH_SHA1, HASH_SHA256 ou HASH_SHA512, 6 à 8 chiffres, une période de 1 à 3 600 secondes — ceux qu'écrit of_totp_uri | |
of_totp_verify (string as_secret, string as_code { , ref long al_step }) → boolean | Vrai si le code est celui de l'instant, du pas précédent ou du suivant. Avec al_step, le PAS de 30 s pour lequel le code a été accepté (0 sinon) : un code ne doit pas servir deux fois (RFC 6238) — gardez le dernier pas accepté pour l'utilisateur et refusez un pas qui n'est pas plus grand. Un secret qui contient autre chose que A-Z, 2-7, des espaces ou des = est refusé (un 0 tapé pour un O) Les espaces tapés par l'utilisateur (« 864 010 », comme l'application l'affiche) sont ignorés. | |
of_totp_verify (string as_secret, string as_code, string as_hash, integer ai_digits, integer ai_period, ref long al_step) → boolean | La même vérification avec les réglages de of_totp_code ; al_step compte des périodes de ai_period secondes | |
of_totp_uri (string as_secret, string as_account, string as_issuer { , string as_hash, integer ai_digits, integer ai_period }) → string | L'URI otpauth:// à mettre dans un QR code pour enrôler l'utilisateur Le secret s'y écrit en base32 majuscule, sans espaces ni = ; un secret qui porte un autre caractère est refusé. Avec les réglages de of_totp_code, l'application affiche les codes qu'il calcule avec eux | |
of_hotp_code (string as_secret, long al_counter) → string | Le code à six chiffres d'un COMPTEUR (HOTP, RFC 4226, SHA-1) : un jeton matériel, un code envoyé par courriel — le serveur garde le compteur et l'avance à chaque code accepté. al_counter : 0 ou plus. Vide en cas d'échec | |
of_generate_key ( ) → string | Une clé AES-256 neuve, 32 octets en hexadécimal | |
of_encrypt_with_key (string as_key, string as_text { , string as_aad }) → string | AES-256-GCM avec une clé EXPLICITE (hex ou base64) : base64 de nonce + chiffré + tag, ce qu'openssl ou Python déchiffrent as_aad : des DONNÉES ASSOCIÉES, authentifiées mais pas chiffrées — un numéro de commande, un identifiant : la valeur ne se déchiffre qu'avec les mêmes. La clé peut aussi être en base64url | |
of_decrypt_with_key (string as_key, string as_cipher { , string as_aad }) → string | Le texte de retour avec la même clé ; vide et of_get_last_error() sinon Avec les MÊMES données associées quand il y en avait. | |
of_rsa_encrypt (string as_public_pem, string as_text { , string as_hash }) → string | Un secret court chiffré vers une clé PUBLIQUE : seule la privée le lit. Le paramétrage EXACT, pour l'autre côté : RSA-OAEP, SHA-256, MGF1 en SHA-256, sans label — Java (OAEPWithSHA-256AndMGF1Padding) et openssl pkeyutl prennent MGF1-SHA-1 par défaut, voir Les formats. 190 octets au plus avec une clé de 2048 bits ; au-delà, of_get_last_error() le dit — chiffrez les données avec of_encrypt_with_key et seulement leur clé ainsi. Une clé PRIVÉE à la place de la publique est nommée as_hash : le hachage d'OAEP et de MGF1 — HASH_SHA1 est ce qu'utilisent par défaut openssl pkeyutl, .NET (OaepSHA1) et Java (OAEPPadding) | |
of_rsa_decrypt (string as_private_pem, string as_cipher { , string as_hash }) → string | Le secret de retour, avec la clé privée Avec le hachage de l'expéditeur (HASH_SHA256 par défaut). | |
of_jwt_sign (string as_algorithm, string as_key, string as_claims_json, long al_expires_seconds { , string as_key_format { , string as_header_json } }) → string | Un JWT signé : JWT_HS256 (secret partagé), JWT_RS256/JWT_ES256/JWT_EDDSA (clé privée) ; l'algorithme est OBLIGATOIRE. iat ajouté, exp si la durée est > 0 — un exp dans les claims ET une durée est refusé — et un exp/iat/nbf fourni doit être un nombre. L'en-tête nomme l'algorithme comme il est enregistré (EdDSA). as_key_format : le secret HS256 en KEYFORMAT_TEXT, KEYFORMAT_HEX ou KEYFORMAT_BASE64 ; une clé PEM n'est jamais un secret HS256. as_header_json : des membres AJOUTÉS à l'en-tête, par exemple {"kid":"2026-09"} — la clé qu'une API choisit (Apple, tout fournisseur qui fait tourner ses clés) ; alg et typ restent ceux de la bibliothèque. Vide en cas d'échec Aussi JWT_HS384/HS512, JWT_RS384/RS512, JWT_PS256/PS384/PS512 et JWT_ES384 (clé KEY_EC_P384). Une clé de la mauvaise famille, ou sa moitié PUBLIQUE, est nommée | |
of_jwt_verify (string as_algorithm, string as_key, string as_token { , string as_key_format { , string as_expected_claims_json } }) → string | Les claims (JSON) d'un jeton dont la signature, l'algorithme et les dates sont justes (il_jwt_leeway_seconds de tolérance, une minute par défaut). L'algorithme est OBLIGATOIRE et comparé EXACTEMENT à celui de l'en-tête : "" est refusé, et une clé PEM n'est jamais un secret HS — sinon une clé PUBLIQUE servait de secret et un jeton forgé passait. Un exp ou un nbf qui n'est pas un nombre, et un en-tête crit, sont refusés. as_key_format comme pour of_jwt_sign. as_expected_claims_json : les claims qui doivent y être avec cette valeur — l'émetteur (iss), l'audience (aud, trouvée aussi dans une liste) ; une claim absente ou différente est refusée et nommée. Vide et of_get_last_error() sinon ; une claim se lit avec n_pbt_json (of_load, puis of_get_string) | |
of_jwt_verify_jwks (string as_algorithm, string as_jwks_json, string as_token { , string as_expected_claims_json }) → string | Les claims d'un jeton vérifié par un JEU DE CLÉS (JWKS, RFC 7517) — ce que publient Azure AD, Google, Auth0 ou Keycloak à leur jwks_uri (à lire avec le restclient, à garder quelques heures) : le kid du jeton choisit la clé. Un jeton sans kid, un kid absent du jeu ou présent deux fois, une clé d'un autre algorithme ou d'un autre usage (use = enc) : refusés, of_get_last_error() dit lequel. Dates et claims attendues comme pour of_jwt_verify | |
of_jwt_claims (string as_token) → string | Les claims SANS vérification, pour lire qui le jeton nomme avant de choisir la clé | |
of_jwt_header (string as_token) → string | L'en-tête (JSON) d'un jeton SANS vérification : lire son kid pour choisir la clé qui le vérifiera. Ne jamais s'y fier seul | |
of_protect (string as_text { , boolean ab_machine { , string as_entropy } }) → string | Un secret protégé par Windows pour cet utilisateur (ou cette machine) : DPAPI, natif. Ce qu'on range dans l'INI pour un mot de passe de serveur. Il ne le protège PAS d'un autre programme lancé par le même utilisateur (ni, avec ab_machine, d'un autre compte du poste) : as_entropy, un secret de votre application, réduit ce cercle — la même entropie est exigée pour relire | |
of_unprotect (string as_protected { , string as_entropy }) → string | Le texte de retour, sur le même compte et avec la MÊME entropie s'il y en a une ; vide et of_get_last_error() ailleurs | |
of_crc32 (string as_text) → string | Le CRC32 d'un texte, 8 chiffres hexadécimaux — un contrôle d'intégrité, pas une empreinte | |
of_crc32_file (string as_path) → string | Le CRC32 d'un fichier, lu par la DLL jusqu'au bout : une lecture qui échoue en route est une erreur, jamais le CRC du début | |
of_encrypt_file (string as_password, string as_source, string as_target) → long | Un fichier chiffré par mot de passe, en natif et par morceaux (même conteneur qu'of_encrypt), jusqu'à 64 Go — la limite d'un conteneur AES-GCM. Chemins relatifs : la source se cherche comme pour of_hash_file, la cible s'écrit dans le dossier où l'application a démarré. Rend 0, -5 argument vide ou il_pbkdf2_rounds hors bornes, -2 source illisible, -4 cible inscriptible impossible ou, en démo, source au-delà de 2048 octets, -7 source au-delà de 64 Go Une cible qui dépend du dossier courant d'un lecteur (\x, C:x) rend -5, et of_get_last_error() le dit | |
of_decrypt_file (string as_password, string as_source, string as_target) → long | Le fichier de retour. Rend 0, -3 mauvais mot de passe ou fichier altéré (rien n'est écrit : le texte est vérifié dans un fichier temporaire caché que Windows efface même si l'application meurt), -5/-2/-4 comme of_encrypt_file | |
of_base64_encode_blob (blob ab_data) → string | Les octets d'un blob en base64 — une photo lue par SelectBlob, un PDF reçu — sans fichier. Natif. Jusqu'à 32 Mo en 32 bits, 512 Mo en 64 bits ; en démonstration, 2048 octets. Vide en cas d'échec | |
of_base64_decode_blob (string as_base64, ref blob ab_data) → long | Les OCTETS d'une valeur base64 (ou base64url) dans un blob — une pièce jointe reçue, prête pour UpdateBlob. Natif. Rend 0 une fois ab_data rempli, -5 quand la valeur n'est pas du base64, -4 en démonstration au-delà de 2048 octets ; ab_data est alors vide | |
of_hash_blob (string as_algorithm, blob ab_data) → string | L'empreinte des octets d'un blob, avec les algorithmes de of_hash — la même valeur que of_hash_file sur les mêmes octets. Natif. Vide en cas d'échec | |
of_hmac_blob (string as_algorithm, string as_key, blob ab_data { , string as_key_format }) → string | Le HMAC des octets d'un blob sous une clé secrète, la clé comme pour of_hmac : un fichier reçu, vérifié par la signature que le service a envoyée avec lui. Natif. Vide en cas d'échec | |
of_crc32_blob (blob ab_data) → string | Le CRC32 des octets d'un blob, 8 chiffres hexadécimaux. Natif | |
of_encrypt_blob (string as_password, blob ab_data, ref blob ab_cipher) → long | Les octets d'un blob chiffrés par mot de passe : le conteneur de of_encrypt et de of_encrypt_file, en octets (52 de plus que les données) — lu en base64, il se déchiffre par of_decrypt, et l'inverse. Natif, il_pbkdf2_rounds. Rend 0 une fois ab_cipher rempli, -5 mot de passe vide ou tours hors bornes, -4 en cas d'échec ou, en démonstration, au-delà de 2048 octets | |
of_decrypt_blob (string as_password, blob ab_cipher, ref blob ab_data) → long | Les octets de of_encrypt_blob (ou d'une valeur de of_encrypt lue en octets), avec le même mot de passe. Rend 0 une fois ab_data rempli, -3 mauvais mot de passe ou octets altérés (ab_data reste vide), -5 mot de passe vide ou octets qui ne sont pas une telle valeur, -4 en démonstration au-delà de 2048 octets | |
of_close ( ) → long | Libère la page cachée ; fait pour vous à la destruction de l'objet Rend 0 une fois fait. | |
of_reset ( ) → long | Revient aux valeurs par défaut : il_timeout_ms, il_pbkdf2_rounds, il_password_hash_rounds, il_jwt_leeway_seconds, of_get_last_error() ; ipo_owner reste Rend 0 une fois fait. |
Exemples #
Vérifier qu'un fichier n'a pas changé #
// Local variables
string ls_expected, ls_actual
// The fingerprint recorded at deploy time, and the one of the file on this workstation
ls_expected = ProfileString("deploy.ini", "files", "orders.pbd", "")
ls_actual = lnv_crypto.of_hash_file(/*algorithm*/ n_pbt_crypto.HASH_SHA256, /*path*/ "C:\MyApp\orders.pbd")
if ls_actual <> ls_expected then MessageBox("Deploy", "orders.pbd is not the file that was tested.")
Une pièce jointe en base64 pour une API #
// Local variables
n_pbt_json lnv_j
n_pbt_restclient lnv_rest
// The REST client of the window
lnv_rest = create n_pbt_restclient
// Sending : the file's bytes in the JSON body
lnv_j.of_set_string(/*path*/ "filename", /*value*/ "invoice_4152.pdf")
lnv_j.of_set_string(/*path*/ "content", /*value*/ lnv_crypto.of_base64_encode_file(/*path*/ "C:\invoices\4152.pdf"))
lnv_rest.of_post(/*url*/ "https://api.example.com/documents", /*body*/ lnv_j.of_text())
// Receiving : the answer's attachment, back on disk
lnv_crypto.of_base64_decode_to_file(/*base64*/ lnv_rest.of_json_value(/*path*/ "content"), /*path*/ "C:\inbox\receipt.pdf")
// Free the client
destroy lnv_rest
Garder un secret dans un INI #
// Local variables
string ls_master, ls_db_password
// At setup : encrypt once, store the string
SetProfileString("app.ini", "db", "password", lnv_crypto.of_encrypt(/*password*/ ls_master, /*text*/ ls_db_password))
// At run time : the master password comes from the user, the INI gives the rest
ls_db_password = lnv_crypto.of_decrypt(/*password*/ ls_master, /*cipher*/ ProfileString("app.ini", "db", "password", ""))
if ls_db_password = "" then MessageBox("Login", lnv_crypto.of_get_last_error())
Signer une commande, la vérifier ailleurs #
// Local variables
string ls_public, ls_private, ls_signature, ls_order_json
// The order to sign, as the application writes it
ls_order_json = '{"order":4152,"amount":690.00}'
// Sign the order with the private key
lnv_crypto.of_generate_keypair(/*bits*/ n_pbt_crypto.KEY_2048, /*public_pem*/ ls_public, /*private_pem*/ ls_private) // once ; keep ls_private
ls_signature = lnv_crypto.of_sign(/*private_pem*/ ls_private, /*text*/ ls_order_json)
// The public key and the signature travel with the order ; anyone can check :
if not lnv_crypto.of_verify(/*public_pem*/ ls_public, /*text*/ ls_order_json, /*signature*/ ls_signature) then MessageBox("Order", "This order was altered.")
Un jeton pour une API, et sa vérification #
// Local variables
string ls_api_secret, ls_token, ls_claims, ls_role
n_pbt_restclient lnv_rest
n_pbt_json lnv_json
// The REST client of the window, and the secret the API shares with you
lnv_rest = create n_pbt_restclient
ls_api_secret = ProfileString("app.ini", "api", "secret", "")
// The client : a token valid one hour, in the Authorization header
ls_token = lnv_crypto.of_jwt_sign(/*algorithm*/ n_pbt_crypto.JWT_HS256, /*key*/ ls_api_secret, /*claims_json*/ '{"sub":"jdoe","role":"admin"}', /*expires_seconds*/ 3600)
lnv_rest.of_set_bearer(/*token*/ ls_token)
// The server side (or a check of what came back) : the claims, once the signature and the expiry are right
ls_claims = lnv_crypto.of_jwt_verify(/*algorithm*/ n_pbt_crypto.JWT_HS256, /*key*/ ls_api_secret, /*token*/ ls_token)
if ls_claims = "" then MessageBox("API", lnv_crypto.of_get_last_error())
// One claim, read with n_pbt_json
lnv_json.of_load(/*json*/ ls_claims)
ls_role = lnv_json.of_get_string(/*path*/ "role")
destroy lnv_rest
Vérifier la signature d'un webhook #
// Local variables
string ls_header, ls_signature, ls_raw_body, ls_webhook_secret
// ls_raw_body and ls_header : the body and the signature header your web service received ;
// the secret, the one the service shares with you
ls_webhook_secret = ProfileString("app.ini", "webhook", "secret", "")
// The service signs the raw body with the shared secret and sends the HMAC in a header
// (GitHub : "sha256=<hex>", Stripe : hex, Shopify : base64). Compare in CONSTANT time.
ls_signature = ls_header
if Left(ls_signature, 7) = "sha256=" then ls_signature = Mid(ls_signature, 8)
if not lnv_crypto.of_hmac_verify(/*algorithm*/ n_pbt_crypto.HASH_SHA256, /*key*/ ls_webhook_secret, /*text*/ ls_raw_body, /*expected*/ ls_signature) then
MessageBox("Webhook", "This call was not sent by the service.")
return
end if
Un mot de passe de serveur rangé par Windows, et un utilisateur à deux facteurs #
// Local variables
string ls_db_password, ls_secret, ls_login, ls_typed, ls_stored_hash, ls_six_digits
long ll_step, ll_last_step
// ls_login, ls_typed and ls_six_digits come from the login window ; ls_secret,
// ls_stored_hash and ll_last_step from the record of the user. At setup : the INI holds a value Windows protects for this account, never the password ;
// the entropy, a secret of the application, keeps other programs of the same user out
SetProfileString("app.ini", "db", "password", lnv_crypto.of_protect(/*text*/ ls_db_password, /*machine*/ false, /*entropy*/ "MyApp-7f3a"))
// At run time : the same entropy
ls_db_password = lnv_crypto.of_unprotect(/*protected*/ ProfileString("app.ini", "db", "password", ""), /*entropy*/ "MyApp-7f3a")
// Enrolling a user : a secret in the database, the QR on screen
ls_secret = lnv_crypto.of_totp_secret()
uo_qr.is_data = lnv_crypto.of_totp_uri(/*secret*/ ls_secret, /*account*/ ls_login, /*issuer*/ "MyApp")
// Logging in : the stored hash, then the six digits - and never the same code twice (RFC 6238)
if lnv_crypto.of_password_verify(/*password*/ ls_typed, /*stored*/ ls_stored_hash) and lnv_crypto.of_totp_verify(/*secret*/ ls_secret, /*code*/ ls_six_digits, /*step*/ ll_step) then
if ll_step > ll_last_step then ll_last_step = ll_step // store it with the user ; accept the login
end if
Vérifier un jeton d'Azure AD, de Google ou d'Auth0 (JWKS) #
// Local variables
n_pbt_restclient lnv_rest
string ls_jwks, ls_token, ls_claims
// ls_token : the token the user brought (the Authorization header). The key set of the identity provider : fetched once, kept a few hours
lnv_rest = create n_pbt_restclient
if lnv_rest.of_get(/*url*/ "https://login.microsoftonline.com/common/discovery/v2.0/keys") = 200 then ls_jwks = lnv_rest.of_response_text()
destroy lnv_rest
// The kid of the token picks the key ; the issuer and the audience must be yours
ls_claims = lnv_crypto.of_jwt_verify_jwks(/*algorithm*/ n_pbt_crypto.JWT_RS256, /*jwks_json*/ ls_jwks, /*token*/ ls_token, /*expected_claims_json*/ '{"iss":"https://login.microsoftonline.com/my-tenant-id/v2.0","aud":"api://orders"}')
if ls_claims = "" then MessageBox("Login", lnv_crypto.of_get_last_error())
Une photo de la base de données, en base64 (blob) #
// Local variables
blob lb_photo
string ls_answer
n_pbt_json lnv_j
// ls_answer : the base64 an API answered. The photo of the employee, read from the database
SELECTBLOB photo INTO :lb_photo FROM employee WHERE emp_id = 4152 USING SQLCA;
// Its bytes in base64 in the JSON body of a call, with its fingerprint
lnv_j.of_set_string(/*path*/ "photo", /*value*/ lnv_crypto.of_base64_encode_blob(/*data*/ lb_photo))
lnv_j.of_set_string(/*path*/ "sha256", /*value*/ lnv_crypto.of_hash_blob(/*algorithm*/ n_pbt_crypto.HASH_SHA256, /*data*/ lb_photo))
// The other way : the bytes an API answered, back into the database
if lnv_crypto.of_base64_decode_blob(/*base64*/ ls_answer, /*data*/ lb_photo) = 0 then
UPDATEBLOB employee SET photo = :lb_photo WHERE emp_id = 4152 USING SQLCA;
end if
Bonnes pratiques #
- Un seul objet par fenêtre, ouvert avec elle (
of_open) : la page cachée coûte quelques centaines de millisecondes la première fois, rien ensuite. - Le mot de passe ne se stocke pas :
of_encryptrend tout ce qu'il faut pour déchiffrer, sauf lui — c'est le contrat. - La clé privée ne voyage pas : signez chez vous, publiez la clé publique.
of_verifyne demande que celle-là. - Lisez
of_get_last_error()quand une méthode rend une chaîne vide : la raison y est, et elle dit souvent « mauvais mot de passe » ou « fichier illisible » — pas un défaut du composant. - DPAPI protège d'un AUTRE COMPTE, pas d'un autre programme : tout ce que lance le même utilisateur relit un
of_protect(tout compte du poste avecab_machine). Passez une entropie propre à l'application pour réduire ce cercle. - Un JWT se vérifie avec SON algorithme, écrit en dur (
n_pbt_crypto.JWT_RS256), jamais lu dans le jeton ni dans un INI qui peut manquer : un algorithme vide est refusé, mais c'est ce choix qui ferme la porte à la confusion HS/RS. Nommez ensuite l'émetteur et l'audience attendus (as_expected_claims_json) : la vérification les refuse s'ils manquent.