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 #
n_pbt_crypto lnv_crypto
string ls_hash, ls_cipher
lnv_crypto = create n_pbt_crypto
// The fingerprint of a text, in hexadecimal
ls_hash = lnv_crypto.of_sha256("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("s3cret", "The safe code is 4152.")
MessageBox("Back", lnv_crypto.of_decrypt("s3cret", ls_cipher))
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 le sel (16 octets), le nonce (12) et le chiffré AES-256-GCM ; la clé est dérivée du mot de passe par PBKDF2-SHA-256, 100 000 tours.of_decryptavec le même mot de passe suffit, sur n'importe quel poste.- Clés : PEM, publique en SPKI (
BEGIN PUBLIC KEY), privée en PKCS#8 (BEGIN PRIVATE KEY) ; signatures RSASSA-PKCS1-v1_5 avec SHA-256, en base64 — ce qu'openssllit et vérifie. - Pas de MD5 : la Web Crypto ne l'offre pas, et rien ne devrait plus le demander.
Propriétés #
| Propriété | Type | Défaut | Rôle |
|---|---|---|---|
is_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 |
il_timeout_ms | long | 20000 | Durée maximale d'un appel. Hacher un gros fichier ou générer une clé de 4096 bits peut prendre quelques secondes |
il_pbkdf2_rounds | long | 100000 | Tours PBKDF2 d'of_encrypt, of_password_hash et of_encrypt_file : environ 100 ms. Plus est plus lent pour tout le monde, l'attaquant compris |
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_EC_P256, KEY_ED25519 pour la sorte d'une paire ; JWT_HS256, JWT_RS256, JWT_ES256, JWT_EDDSA ; CHARSET_ALL, CHARSET_ALPHANUM, CHARSET_LETTERS, CHARSET_DIGITS, CHARSET_HEX.
Méthodes #
| Méthode | Rôle |
|---|---|
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 un code négatif |
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. 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 | Le HMAC d'un texte sous une clé secrète, en hexadécimal — ce qu'une API demande pour authentifier un appel |
of_hash_file (string as_algorithm, string as_path) → string | L'empreinte d'un fichier du poste, de n'importe quel type et jusqu'à 512 Mo, sans le charger dans PowerBuilder |
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 ; une valeur qui n'est pas du base64 rend une chaîne vide et is_last_error, jamais du bruit |
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 — jusqu'à 512 Mo, lus par la DLL, jamais chargés dans PowerBuilder |
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. Rend 0 une fois écrit, -5 sur un chemin vide ou une valeur qui n'est pas du base64, -4 si le fichier ne peut pas être écrit |
of_encrypt (string as_password, string as_text) → string | Chiffre un texte avec un mot de passe : une seule chaîne base64 à stocker. 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 is_last_error, jamais du bruit |
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_*). Rend 0 une fois les deux clés remplies, -4 en cas d'échec |
of_sign (string as_private_pem, string as_text) → string | La signature d'un texte par la clé privée, en base64. Vide en cas d'échec |
of_verify (string as_public_pem, string as_text, string as_signature) → 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é |
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 (signatures courtes) ou KEY_ED25519. Rend 0, -4 en cas d'échec |
of_key_kind (string as_pem) → string` | La famille d'une clé PEM : rsa, ec-p256 ou ed25519 |
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 |
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 |
of_random_password (integer ai_length, string as_charset) → string` | Un mot de passe aléatoire (4 à 256 caractères) sur un jeu CHARSET_*, sans biais. Vide sur une longueur fautive |
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_pbkdf2_rounds), tours et sel dans la valeur. Jamais le même résultat deux fois |
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 |
of_totp_secret ( ) → string` | Un secret neuf pour les codes à deux facteurs (RFC 6238), en base32 |
of_totp_code (string as_secret) → string` | Le code à six chiffres de l'instant, celui que montre l'authentificateur |
of_totp_verify (string as_secret, string as_code) → boolean` | Vrai si le code est celui de l'instant, du pas précédent ou du suivant |
of_totp_uri (string as_secret, string as_account, string as_issuer) → string` | L'URI otpauth:// à mettre dans un QR code pour enrôler l'utilisateur |
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` | AES-256-GCM avec une clé EXPLICITE (hex ou base64) : base64 de nonce + chiffré + tag, ce qu'openssl ou Python déchiffrent |
of_decrypt_with_key (string as_key, string as_cipher) → string` | Le texte de retour avec la même clé ; vide et is_last_error sinon |
of_rsa_encrypt (string as_public_pem, string as_text) → string` | Un secret court chiffré vers une clé PUBLIQUE (RSA-OAEP) : seule la privée le lit |
of_rsa_decrypt (string as_private_pem, string as_cipher) → string` | Le secret de retour, avec la clé privée |
of_jwt_sign (string as_algorithm, string as_key, string as_claims_json, long al_expires_seconds) → string` | Un JWT signé : JWT_HS256 (secret partagé), JWT_RS256/JWT_ES256/JWT_EDDSA (clé privée) ; iat ajouté, exp si la durée est > 0. Vide en cas d'échec |
of_jwt_verify (string as_algorithm, string as_key, string as_token) → string` | Les claims (JSON) d'un jeton dont la signature, l'algorithme et les dates sont bons ; vide et is_last_error sinon |
of_jwt_claims (string as_token) → string` | Les claims SANS vérification, pour lire qui le jeton nomme avant de choisir la clé |
of_protect (string as_text { , boolean ab_machine }) → 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 |
of_unprotect (string as_protected) → string` | Le texte de retour, sur le même compte ; vide et is_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 |
of_encrypt_file (string as_password, string as_source, string as_target) → long` | Un fichier chiffré par mot de passe, en natif (même conteneur qu'of_encrypt). Rend 0, -5 argument vide, -2 source illisible, -4 cible inscriptible |
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), -5/-2/-4 comme of_encrypt_file |
of_close ( ) | Libère la page cachée ; fait pour vous à la destruction de l'objet |
of_reset ( ) | Retour aux valeurs par défaut |
Exemples #
Vérifier qu'un fichier n'a pas changé #
string ls_expected, ls_actual
ls_expected = ProfileString("deploy.ini", "files", "orders.pbd", "")
ls_actual = lnv_crypto.of_hash_file(n_pbt_crypto.HASH_SHA256, "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 #
// Sending : the file's bytes in the JSON body
lnv_j.of_set_string("filename", "invoice_4152.pdf")
lnv_j.of_set_string("content", lnv_crypto.of_base64_encode_file("C:\invoices\4152.pdf"))
lnv_rest.of_post("https://api.example.com/documents", lnv_j.of_text())
// Receiving : the answer's attachment, back on disk
lnv_crypto.of_base64_decode_to_file(lnv_rest.of_json_value("content"), "C:\inbox\receipt.pdf")
Garder un secret dans un INI #
// At setup : encrypt once, store the string
SetProfileString("app.ini", "db", "password", lnv_crypto.of_encrypt(ls_master, 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(ls_master, ProfileString("app.ini", "db", "password", ""))
if ls_db_password = "" then MessageBox("Login", lnv_crypto.is_last_error)
Signer une commande, la vérifier ailleurs #
string ls_public, ls_private, ls_signature
lnv_crypto.of_generate_keypair(n_pbt_crypto.KEY_2048, ls_public, ls_private) // once ; keep ls_private
ls_signature = lnv_crypto.of_sign(ls_private, ls_order_json)
// The public key and the signature travel with the order ; anyone can check :
if not lnv_crypto.of_verify(ls_public, ls_order_json, ls_signature) then MessageBox("Order", "This order was altered.")
Un jeton pour une API, et sa vérification #
// The client : a token valid one hour, in the Authorization header
ls_token = lnv_crypto.of_jwt_sign(n_pbt_crypto.JWT_HS256, ls_api_secret, '{"sub":"guillaume","role":"admin"}', 3600)
lnv_rest.of_set_bearer(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(n_pbt_crypto.JWT_HS256, ls_api_secret, ls_token)
if ls_claims = "" then MessageBox("API", lnv_crypto.is_last_error)
ls_role = gnv_utils.of_json_get_str(ls_claims, "role")
Un mot de passe de serveur rangé par Windows, et un utilisateur à deux facteurs #
// At setup : the INI holds a value Windows protects for this account, never the password
SetProfileString("app.ini", "db", "password", lnv_crypto.of_protect(ls_db_password))
// At run time
ls_db_password = lnv_crypto.of_unprotect(ProfileString("app.ini", "db", "password", ""))
// 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(ls_secret, ls_login, "MyApp")
// Logging in : the stored hash, then the six digits
if lnv_crypto.of_password_verify(ls_typed, ls_stored_hash) and lnv_crypto.of_totp_verify(ls_secret, ls_six_digits) then ...
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
is_last_errorquand 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.