PBToolboxAI v4 ← Site

crypto — n_pbt_crypto #

← Riferimento dei componenti · Sommario della guida

Hash, HMAC, cifratura con password, valori casuali e firme RSA — la Web Crypto API del motore WebView2, che il tuo PowerBuilder 10 non ha da nessun'altra parte. Nessuna DLL di terzi, nessun servizio: ciò che la postazione sa già fare, offerto a PowerScript.

▶ Vederlo dal vivo — Applicazione dimostrativa, riquadro Crypto: i risultati, il codice che li produce e questa pagina, fianco a fianco.


In breve #

Oggetto non visualen_pbt_crypto
Serve aVerificare che un file non sia cambiato, custodire un segreto in un INI, firmare un ordine, autenticare una chiamata API
PrincipioUna pagina nascosta fa il calcolo; ogni metodo è una normale chiamata PowerScript che restituisce il suo valore
DipendenzaIl runtime WebView2, già richiesto dalla libreria — nient'altro

Avvio rapido #

// 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

Ogni metodo è una chiamata che restituisce il suo valore: nessun evento, nessuna attesa da scrivere.


I formati, leggibili dagli altri strumenti #

Decifrare un valore di of_encrypt lato server, in Python (libreria 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")

Proprietà #

ProprietàTipoPredefinitoRuolo
is_last_errorstring""Perché l'ultima chiamata ha restituito una stringa vuota, false o un codice negativo: password errata, file illeggibile, algoritmo sconosciuto, limite demo
il_timeout_mslong20000Durata massima di una chiamata. Calcolare l'hash di un file grande o generare una chiave a 4096 bit può richiedere qualche secondo
il_pbkdf2_roundslong100000Round PBKDF2 di of_encrypt e of_encrypt_file: circa 100 ms, pagati a ogni lettura del valore. Da 1.000 a 10.000.000: un altro valore fa RIFIUTARE la chiamata (is_last_error lo dice, -5 per un file), mai sostituire
il_password_hash_roundslong600000Round PBKDF2 di of_password_hash: 600.000, la cifra OWASP per PBKDF2-HMAC-SHA-256 (circa 0,6 s). Un hash memorizzato è ciò che un attaccante forza offline se la tabella trapela; si verifica una sola volta per accesso. Stessi limiti
ipo_ownerpowerobjectnullL'oggetto visuale per cui lavora questo helper: la licenza si verifica sulla sua classe. Da impostare prima della prima chiamata. Necessario solo nell'applicazione dimostrativa; una chiave di sviluppo o di runtime sblocca l'helper senza di esso. Non sbloccato, è in modalità demo — testi di al più 2048 caratteri e file di al più 2048 byte, in ingresso come in uscita

Costanti: HASH_MD5, HASH_SHA1, HASH_SHA256, HASH_SHA384, HASH_SHA512 per l'algoritmo; KEY_2048, KEY_3072, KEY_4096 per la dimensione di una chiave RSA, KEY_RSA_2048, KEY_EC_P256, KEY_ED25519 per il tipo di coppia; JWT_HS256, JWT_RS256, JWT_ES256, JWT_EDDSA; CHARSET_ALL, CHARSET_ALPHANUM, CHARSET_LETTERS, CHARSET_DIGITS, CHARSET_HEX; KEYFORMAT_TEXT, KEYFORMAT_HEX, KEYFORMAT_BASE64 per la forma di una chiave HMAC.


Metodi #

MetodoRuolo
of_open ( ) → longCrea la pagina nascosta. Facoltativo — ogni metodo lo fa — ma chiamarlo all'apertura della finestra paga il costo una volta. Restituisce l'handle (> 0) o -2 se non è stato possibile crearla (is_last_error dice perché)
of_is_open ( ) → booleanVero appena la pagina nascosta esiste
of_hash (string as_algorithm, string as_text) → stringL'hash di un testo, in esadecimale; HASH_* per l'algoritmo, letto senza maiuscole, trattini o spazi (SHA-256 vale HASH_SHA256). NATIVO per ogni algoritmo: nessuna pagina si apre per un hash. Vuoto in caso di errore
of_sha256 (string as_text) → stringof_hash con HASH_SHA256 — quello che si usa di più
of_hmac (string as_algorithm, string as_key, string as_text { , string as_key_format }) → stringL'HMAC di un testo con una chiave segreta, in esadecimale — ciò che un'API chiede per autenticare una chiamata. La chiave è un TESTO per impostazione predefinita; KEYFORMAT_HEX o KEYFORMAT_BASE64 ne danno i BYTE: un segreto fornito codificato, o il risultato di un HMAC precedente (AWS SigV4 ne concatena quattro). Una chiave vuota è rifiutata, e anche una chiave PEM: non è mai un segreto HMAC
of_hmac_verify (string as_algorithm, string as_key, string as_text, string as_expected { , string as_key_format }) → booleanVero se as_expected è l'HMAC di as_text con as_key, confrontato in TEMPO COSTANTE — la verifica della firma di un webhook (Stripe, GitHub, Shopify) senza tradirla, mai if of_hmac(…) = intestazione. as_expected in esadecimale (maiuscole indifferenti) o in base64; togli prima un prefisso come sha256=. Falso altrimenti
of_hash_file (string as_algorithm, string as_path) → stringL'hash di un file della postazione, di qualsiasi tipo e dimensione (letto a pezzi), senza caricarlo in PowerBuilder. Un percorso RELATIVO si cerca nella cartella corrente, poi nella cartella in cui l'applicazione è partita, poi accanto all'EXE — la regola di tutti i file di questo componente. Vuoto in caso di errore; is_last_error distingue l'algoritmo sconosciuto dal file illeggibile
of_base64_encode (string as_text) → stringUn testo in base64 (i suoi byte UTF-8): ciò che un browser o Python scriverebbe. Vuoto in caso di errore
of_base64_decode (string as_base64) → stringIl TESTO di ritorno (UTF-8). Un valore che non è base64, o i cui byte non sono un testo (un PDF, un'immagine), rende una stringa vuota e is_last_error — mai rumore. I byte si scrivono in un file con of_base64_decode_to_file
of_base64_encode_file (string as_path) → stringI byte di un file della postazione in base64 — un allegato, un'immagine per una chiamata JSON — letti dalla DLL, mai caricati in PowerBuilder. Fino a 64 MB in un'applicazione a 32 bit (l'IDE PowerBuilder lo è), 512 MB a 64 bit. Vuoto in caso di errore; is_last_error dice quale: file troppo grande per questo processo, illeggibile o limite demo
of_base64_decode_to_file (string as_base64, string as_path) → longScrive i byte di un valore base64 in un file — l'allegato con cui un'API ha risposto. Un valore VUOTO scrive un file vuoto; un percorso RELATIVO si scrive nella cartella in cui l'applicazione è partita (la cartella corrente al caricamento della libreria, la stessa nell'IDE e compilata), dove of_hash_file lo ritrova. Rende 0 una volta scritto, -5 su un percorso vuoto o che dipende dalla cartella corrente di un'unità (\x, C:x), o un valore che non è base64, -4 se il file non può essere scritto o, in demo, oltre 2048 byte
of_encrypt (string as_password, string as_text) → stringCifra un testo con una password: una sola stringa base64 da memorizzare, che porta i suoi round PBKDF2 (vedi I formati). Una password VUOTA è rifiutata — chiunque rileggerebbe il valore. Vuoto in caso di errore
of_decrypt (string as_password, string as_cipher) → stringIl testo di ritorno, con la stessa password. Password errata o valore alterato: stringa vuota e is_last_error, mai rumore. Un valore il cui contenuto non è un testo (un file cifrato con of_encrypt_file) rende vuoto anch'esso: of_decrypt_file
of_uuid ( ) → stringUn UUID casuale (versione 4)
of_random_hex (integer ai_bytes) → stringai_bytes byte casuali (da 1 a 4096) in esadecimale: un sale, un token
of_generate_keypair (integer ai_bits, ref string as_public_pem, ref string as_private_pem) → longUna coppia RSA in PEM (KEY_2048, KEY_3072, KEY_4096). Restituisce 0 quando entrambe le chiavi sono riempite, -5 su qualsiasi altra dimensione (entrambe restano vuote), -4 in caso di errore
of_sign (string as_private_pem, string as_text) → stringLa firma di un testo con la chiave privata, in base64. La chiave dice la sua famiglia: RSA (RSASSA-PKCS1-v1_5, SHA-256), ECDSA P-256 (SHA-256, firma in DER — ciò che openssl dgst e Java SHA256withECDSA verificano) o Ed25519 (64 byte). Vuoto in caso di errore
of_verify (string as_public_pem, string as_text, string as_signature) → booleanVero se la firma è stata fatta esattamente su questo testo dalla chiave privata corrispondente; falso — non un errore — se il testo è cambiato. Una firma ECDSA si legge in DER (openssl, Java) o in P1363 (`rs`, 64 byte: WebCrypto, .NET)
of_generate_keypair (string as_kind, ref string as_public_pem, ref string as_private_pem) → longUna coppia per TIPO: KEY_RSA_2048/3072/4096, KEY_EC_P256 (firme corte) o KEY_ED25519. Rende 0, -5 su un tipo che non è nessuno di questi (mai una coppia RSA al suo posto), -4 in caso di errore
of_key_kind (string as_pem) → stringLa famiglia di una chiave PEM: rsa, ec-p256 o ed25519; si legge solo il PRIMO blocco del testo. Una chiave PKCS#1 (BEGIN RSA PRIVATE KEY), un certificato o una chiave cifrata è NOMINATA in is_last_error, con il comando openssl che la converte
of_base64url_encode (string as_text) → stringbase64 sicuro per URL (JWT, query string)
of_base64url_decode (string as_base64url) → stringIl testo di ritorno; vuoto se non è base64url, o se i suoi byte non sono un testo
of_hex_encode (string as_text) → stringI byte UTF-8 di un testo in esadecimale
of_hex_decode (string as_hex) → stringIl testo di ritorno; vuoto se non è esadecimale, o se i suoi byte non sono un testo
of_random_password (integer ai_length, string as_charset) → stringUna password casuale (4-256 caratteri) su un insieme CHARSET_* (vuoto = CHARSET_ALL), senza bias. Vuota su una lunghezza errata o un insieme sconosciuto
of_equals_constant_time (string as_a, string as_b) → booleanUguaglianza a tempo costante: confrontare un token o un codice senza tradirlo
of_password_hash (string as_password) → stringCiò che si MEMORIZZA per una password: PBKDF2 con sale (il_password_hash_rounds, 600.000 round), round e sale nel valore. Mai lo stesso risultato due volte. Una password vuota si hash (verificare una password vuota è legittimo). Il formato è PROPRIO di PBToolboxAI (vedi I formati)
of_password_verify (string as_password, string as_stored) → booleanVero se la password è quella del valore conservato, confrontata a tempo costante. Un valore i cui round escono da 1 000 - 10 000 000 è rifiutato: un valore ostile non fa girare la verifica per minuti
of_totp_secret ( ) → stringUn segreto nuovo per i codici a due fattori (RFC 6238), in base32
of_totp_code (string as_secret) → stringIl codice a sei cifre del momento, quello che mostra l'autenticatore
of_totp_verify (string as_secret, string as_code { , ref long al_step }) → booleanVero se il codice è quello dell'istante, del passo precedente o del successivo. Con al_step, il PASSO di 30 s per cui il codice è stato accettato (0 altrimenti): un codice non deve servire due volte (RFC 6238) — conserva l'ultimo passo accettato per l'utente e rifiuta un passo che non sia maggiore. Un segreto che contiene altro che A-Z, 2-7, spazi o = è rifiutato (uno 0 digitato per una O)
of_totp_uri (string as_secret, string as_account, string as_issuer) → stringL'URI otpauth:// da mettere in un QR code per iscrivere l'utente
of_generate_key ( ) → stringUna chiave AES-256 nuova, 32 byte in esadecimale
of_encrypt_with_key (string as_key, string as_text) → stringAES-256-GCM con una chiave ESPLICITA (hex o base64): base64 di nonce + cifrato + tag, ciò che openssl o Python decifrano
of_decrypt_with_key (string as_key, string as_cipher) → stringIl testo di ritorno con la stessa chiave; vuoto e is_last_error altrimenti
of_rsa_encrypt (string as_public_pem, string as_text) → stringUn segreto breve cifrato verso una chiave PUBBLICA: solo la privata lo legge. Le impostazioni ESATTE, per l'altro lato: RSA-OAEP, SHA-256, MGF1 con SHA-256, senza label — Java (OAEPWithSHA-256AndMGF1Padding) e openssl pkeyutl usano MGF1-SHA-1 per impostazione predefinita, vedi I formati. 190 byte al massimo con una chiave a 2048 bit; oltre, is_last_error lo dice — cifra i dati con of_encrypt_with_key e solo la loro chiave così. Una chiave PRIVATA al posto della pubblica è nominata
of_rsa_decrypt (string as_private_pem, string as_cipher) → stringIl segreto di ritorno, con la chiave privata
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 } }) → stringUn JWT firmato: JWT_HS256 (segreto condiviso), JWT_RS256/JWT_ES256/JWT_EDDSA (chiave privata); l'algoritmo è OBBLIGATORIO. iat aggiunto, exp se la durata è > 0 — un exp nei claims E una durata è rifiutato — e un exp/iat/nbf fornito deve essere un numero. L'intestazione nomina l'algoritmo come registrato (EdDSA). as_key_format: il segreto HS256 in KEYFORMAT_TEXT, KEYFORMAT_HEX o KEYFORMAT_BASE64; una chiave PEM non è mai un segreto HS256. as_header_json: membri AGGIUNTI all'intestazione, per esempio {"kid":"2026-09"} — la chiave che un'API sceglie (Apple, ogni fornitore che ruota le sue chiavi); alg e typ restano quelli della libreria. Vuoto in caso di errore
of_jwt_verify (string as_algorithm, string as_key, string as_token { , string as_key_format }) → stringI claims (JSON) di un token la cui firma, algoritmo e date sono corretti. L'algoritmo è OBBLIGATORIO e confrontato ESATTAMENTE con quello dell'intestazione: "" è rifiutato, e una chiave PEM non è mai un segreto HS256 — altrimenti una chiave PUBBLICA faceva da segreto e un token falsificato passava. Un exp o un nbf che non è un numero, e un'intestazione crit, sono rifiutati. Il pubblico (aud) e l'emittente (iss) restano da VERIFICARE da te sui claims restituiti. as_key_format come per of_jwt_sign. Vuoto e is_last_error altrimenti
of_jwt_claims (string as_token) → stringI claim SENZA verifica, per leggere chi il token nomina prima di scegliere la chiave
of_jwt_header (string as_token) → stringL'intestazione (JSON) di un token SENZA verifica: leggerne il kid per scegliere la chiave che lo verificherà. Non fidarsene mai da sola
of_protect (string as_text { , boolean ab_machine { , string as_entropy } }) → stringUn segreto protetto da Windows per questo utente (o questa macchina): DPAPI, nativo. Ciò che si mette nell'INI per una password di server. NON lo protegge da un altro programma avviato dallo stesso utente (né, con ab_machine, da un altro account della postazione): as_entropy, un segreto della tua applicazione, restringe questo cerchio — la stessa entropia serve per rileggerlo
of_unprotect (string as_protected { , string as_entropy }) → stringIl testo di ritorno, sullo stesso account e con la STESSA entropia se ce n'è una; vuoto e is_last_error altrove
of_crc32 (string as_text) → stringIl CRC32 di un testo, 8 cifre esadecimali — un controllo di integrità, non un hash
of_crc32_file (string as_path) → stringIl CRC32 di un file, letto dalla DLL fino in fondo: una lettura che fallisce a metà è un errore, mai il CRC dell'inizio
of_encrypt_file (string as_password, string as_source, string as_target) → longUn file cifrato con password, in nativo e a pezzi (stesso contenitore di of_encrypt), fino a 64 GB — il limite di un contenitore AES-GCM. Percorsi relativi: la sorgente si cerca come per of_hash_file, la destinazione si scrive nella cartella in cui l'applicazione è partita. Rende 0, -5 argomento vuoto o il_pbkdf2_rounds fuori limiti, -2 sorgente illeggibile, -4 destinazione non scrivibile o, in demo, sorgente oltre 2048 byte, -7 sorgente oltre 64 GB
of_decrypt_file (string as_password, string as_source, string as_target) → longIl file di ritorno. Rende 0, -3 password errata o file alterato (nulla è scritto: il testo è verificato in un file temporaneo nascosto che Windows cancella anche se l'applicazione muore), -5/-2/-4 come of_encrypt_file
of_close ( )Rilascia la pagina nascosta; fatto per te alla distruzione dell'oggetto
of_reset ( )Ritorno ai valori predefiniti

Esempi #

Verificare che un file non sia cambiato #

// 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.")

Un allegato in base64 per un'API #

// 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")

Custodire un segreto in un INI #

// 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.is_last_error)

Firmare un ordine, verificarlo altrove #

// Local variables
string ls_public, ls_private, ls_signature

// 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 token per un'API, e la sua verifica #

// 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.is_last_error)
ls_role = gnv_utils.of_json_get_str(/*json*/ ls_claims, /*key*/ "role")

Verificare la firma di un webhook #

// 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

Una password di server custodita da Windows, e un utente a due fattori #

// 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

Buone pratiche #

← Riferimento dei componenti · Sommario della guida