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 visuale | n_pbt_crypto |
| Serve a | Verificare che un file non sia cambiato, custodire un segreto in un INI, firmare un ordine, autenticare una chiamata API |
| Principio | Una pagina nascosta fa il calcolo; ogni metodo è una normale chiamata PowerScript che restituisce il suo valore |
| Dipendenza | Il 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 #
- Hash e HMAC: esadecimale minuscolo, il testo letto in UTF-8 — Python, Java e
openssl dgstdanno lo stesso valore. of_encrypt: una sola stringa base64 che porta, in quest'ordine,PBK2(4 byte), il numero di round PBKDF2 (4 byte, big-endian), il sale (16), il nonce (12), il cifrato AES-256-GCM, poi il suo tag (16 byte, alla fine). La chiave è derivata dalla password (UTF-8) con PBKDF2-HMAC-SHA-256 con QUEI round —il_pbkdf2_roundsal momento della cifratura. Bastaof_decryptcon la stessa password, su qualsiasi postazione;of_encrypt_filescrive esattamente lo stesso contenitore, in byte.of_password_hash:pbkdf2-sha256$round$sale$hash, sale e hash in base64 standard. Questo formato è PROPRIO di PBToolboxAI: né passlib ($pbkdf2-sha256$…) né Django (pbkdf2_sha256$…) lo leggono così com'è.- Chiavi e firme: PEM, pubblica in SPKI (
BEGIN PUBLIC KEY), privata in PKCS#8 (BEGIN PRIVATE KEY); firme in base64, secondo la famiglia della chiave — RSA: RSASSA-PKCS1-v1_5 con SHA-256; ECDSA P-256: SHA-256, in DER (openssl dgst -sha256 -verify, JavaSHA256withECDSA); Ed25519: 64 byte.of_verifylegge una firma ECDSA in DER o in P1363 (r | s); il JWT ES256 resta in P1363, come richiede la RFC 7518. - RSA-OAEP (
of_rsa_encrypt): SHA-256, MGF1 con SHA-256, senza label. In Java:OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT); con openssl:-pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 -pkeyopt rsa_mgf1_md:sha256. Una chiave PKCS#1 (BEGIN RSA PRIVATE KEY) si converte conopenssl pkcs8 -topk8 -nocrypt. - MD5 è offerto in NATIVO (
HASH_MD5) per gli scambi datati che lo richiedono — mai per una password né per un'integrità di fronte a un avversario.
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à | Tipo | Predefinito | Ruolo |
|---|---|---|---|
is_last_error | string | "" | Perché l'ultima chiamata ha restituito una stringa vuota, false o un codice negativo: password errata, file illeggibile, algoritmo sconosciuto, limite demo |
il_timeout_ms | long | 20000 | Durata massima di una chiamata. Calcolare l'hash di un file grande o generare una chiave a 4096 bit può richiedere qualche secondo |
il_pbkdf2_rounds | long | 100000 | Round 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_rounds | long | 600000 | Round 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_owner | powerobject | null | L'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 #
| Metodo | Ruolo | |
|---|---|---|
of_open ( ) → long | Crea 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 ( ) → boolean | Vero appena la pagina nascosta esiste | |
of_hash (string as_algorithm, string as_text) → string | L'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) → string | of_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 }) → string | L'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 }) → boolean | Vero 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) → string | L'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) → string | Un 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) → string | Il 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) → string | I 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) → long | Scrive 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) → string | Cifra 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) → string | Il 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 ( ) → string | Un UUID casuale (versione 4) | |
of_random_hex (integer ai_bytes) → string | ai_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) → long | Una 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) → string | La 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) → boolean | Vero 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 (`r | s`, 64 byte: WebCrypto, .NET) |
of_generate_keypair (string as_kind, ref string as_public_pem, ref string as_private_pem) → long | Una 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) → string | La 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) → string | base64 sicuro per URL (JWT, query string) | |
of_base64url_decode (string as_base64url) → string | Il testo di ritorno; vuoto se non è base64url, o se i suoi byte non sono un testo | |
of_hex_encode (string as_text) → string | I byte UTF-8 di un testo in esadecimale | |
of_hex_decode (string as_hex) → string | Il 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) → string | Una 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) → boolean | Uguaglianza a tempo costante: confrontare un token o un codice senza tradirlo | |
of_password_hash (string as_password) → string | Ciò 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) → boolean | Vero 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 ( ) → string | Un segreto nuovo per i codici a due fattori (RFC 6238), in base32 | |
of_totp_code (string as_secret) → string | Il codice a sei cifre del momento, quello che mostra l'autenticatore | |
of_totp_verify (string as_secret, string as_code { , ref long al_step }) → boolean | Vero 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) → string | L'URI otpauth:// da mettere in un QR code per iscrivere l'utente | |
of_generate_key ( ) → string | Una chiave AES-256 nuova, 32 byte in esadecimale | |
of_encrypt_with_key (string as_key, string as_text) → string | AES-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) → string | Il testo di ritorno con la stessa chiave; vuoto e is_last_error altrimenti | |
of_rsa_encrypt (string as_public_pem, string as_text) → string | Un 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) → string | Il 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 } }) → string | Un 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 }) → string | I 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) → string | I claim SENZA verifica, per leggere chi il token nomina prima di scegliere la chiave | |
of_jwt_header (string as_token) → string | L'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 } }) → string | Un 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 }) → string | Il 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) → string | Il CRC32 di un testo, 8 cifre esadecimali — un controllo di integrità, non un hash | |
of_crc32_file (string as_path) → string | Il 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) → long | Un 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) → long | Il 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 #
- Un solo oggetto per finestra, aperto con lei (
of_open): la pagina nascosta costa qualche centinaio di millisecondi la prima volta, nulla dopo. - La password non si conserva:
of_encryptrestituisce tutto ciò che serve per decifrare, tranne lei — è il contratto. - La chiave privata non viaggia: firma a casa tua, pubblica la chiave pubblica.
of_verifychiede solo quella. - Leggi
is_last_errorquando un metodo restituisce una stringa vuota: il motivo è lì, e spesso dice «password errata» o «file illeggibile» — non un difetto del componente. - DPAPI protegge da un ALTRO ACCOUNT, non da un altro programma: tutto ciò che lo stesso utente avvia rilegge un
of_protect(qualsiasi account della postazione conab_machine). Passa un'entropia propria dell'applicazione per restringere questo cerchio. - Un JWT si verifica con il SUO algoritmo, scritto nel codice (
n_pbt_crypto.JWT_RS256), mai letto dal token né da un INI a cui può mancare: un algoritmo vuoto è rifiutato, ma è questa scelta che chiude la porta alla confusione HS/RS. Poi verificaaudeisssui claims restituiti.