PBToolboxAI v4 ← Site

crypto — n_pbt_crypto #

← Komponentenreferenz · Inhalt des Handbuchs

Hashes, HMAC, Verschlüsselung per Passwort, Zufallswerte und RSA-Signaturen — die Web Crypto API der WebView2-Engine, die Ihr PowerBuilder 10 sonst nirgends hat. Keine Dritt-DLL, kein Dienst: was der Arbeitsplatz bereits kann, für PowerScript.

▶ Live ansehen — Demoanwendung, Kachel Crypto: die Ergebnisse, der zugehörige Code und diese Seite nebeneinander.


Kurzüberblick #

Nichtvisuelles Objektn_pbt_crypto
WofürPrüfen, dass eine Datei unverändert ist, ein Geheimnis in einer INI aufbewahren, eine Bestellung signieren, einen API-Aufruf authentifizieren
PrinzipEine verborgene Seite rechnet; jede Methode ist ein gewöhnlicher PowerScript-Aufruf, der seinen Wert liefert
AbhängigkeitDie WebView2-Runtime, die die Bibliothek ohnehin braucht — sonst nichts

Schnellstart #

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

Jede Methode ist ein Aufruf, der seinen Wert liefert: kein Ereignis, kein Warten.


Die Formate, für andere Werkzeuge lesbar #

Einen Wert von of_encrypt serverseitig in Python entschlüsseln (Bibliothek 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")

Eigenschaften #

EigenschaftTypStandardRolle
is_last_errorstring""Warum der letzte Aufruf eine leere Zeichenkette, false oder einen negativen Code lieferte: falsches Passwort, unlesbare Datei, unbekannter Algorithmus, Demogrenze
il_timeout_mslong20000Maximale Dauer eines Aufrufs. Eine große Datei zu hashen oder einen 4096-Bit-Schlüssel zu erzeugen kann einige Sekunden dauern
il_pbkdf2_roundslong100000PBKDF2-Runden von of_encrypt und of_encrypt_file: etwa 100 ms, bei jedem Lesen des Werts bezahlt. 1.000 bis 10.000.000: jeder andere Wert lässt den Aufruf ABLEHNEN (is_last_error sagt es, -5 bei einer Datei), nie ersetzen
il_password_hash_roundslong600000PBKDF2-Runden von of_password_hash: 600.000, die OWASP-Zahl für PBKDF2-HMAC-SHA-256 (etwa 0,6 s). Ein gespeicherter Hash ist, was ein Angreifer offline erzwingt, wenn die Tabelle abfließt; er wird nur einmal pro Anmeldung geprüft. Gleiche Grenzen
ipo_ownerpowerobjectnullDas visuelle Objekt, für das dieses Hilfsobjekt arbeitet: die Lizenz wird an seiner Klasse geprüft. Vor dem ersten Aufruf setzen. Nur in der Demoanwendung nötig; ein Entwicklungs- oder Laufzeitschlüssel schaltet das Hilfsobjekt ohne es frei. Nicht freigeschaltet läuft es im Demomodus — Texte mit höchstens 2048 Zeichen und Dateien mit höchstens 2048 Bytes, bei der Eingabe wie bei der Ausgabe

Konstanten: HASH_MD5, HASH_SHA1, HASH_SHA256, HASH_SHA384, HASH_SHA512 für den Algorithmus; KEY_2048, KEY_3072, KEY_4096 für die Größe eines RSA-Schlüssels, KEY_RSA_2048, KEY_EC_P256, KEY_ED25519 für die Art eines Paars; JWT_HS256, JWT_RS256, JWT_ES256, JWT_EDDSA; CHARSET_ALL, CHARSET_ALPHANUM, CHARSET_LETTERS, CHARSET_DIGITS, CHARSET_HEX; KEYFORMAT_TEXT, KEYFORMAT_HEX, KEYFORMAT_BASE64 für die Form eines HMAC-Schlüssels.


Methoden #

MethodeRolle
of_open ( ) → longErzeugt die verborgene Seite. Optional — jede Methode tut es — aber beim Öffnen des Fensters aufgerufen, fällt der Aufwand einmal an. Liefert das Handle (> 0) oder -2, wenn sie nicht erzeugt werden konnte (is_last_error sagt, warum)
of_is_open ( ) → booleanWahr, sobald die verborgene Seite existiert
of_hash (string as_algorithm, string as_text) → stringDer Hash eines Textes, hexadezimal; HASH_* für den Algorithmus, ohne Groß-/Kleinschreibung, Bindestriche oder Leerzeichen gelesen (SHA-256 ist HASH_SHA256). NATIV für jeden Algorithmus: für einen Hash wird keine Seite geöffnet. Leer bei Fehler
of_sha256 (string as_text) → stringof_hash mit HASH_SHA256 — der meistgebrauchte
of_hmac (string as_algorithm, string as_key, string as_text { , string as_key_format }) → stringDer HMAC eines Textes unter einem geheimen Schlüssel, hexadezimal — was eine API zur Authentifizierung eines Aufrufs verlangt. Der Schlüssel ist standardmäßig TEXT; KEYFORMAT_HEX oder KEYFORMAT_BASE64 geben seine BYTES: ein kodiert ausgegebenes Geheimnis oder das Ergebnis eines vorherigen HMAC (AWS SigV4 verkettet vier). Ein leerer Schlüssel wird abgelehnt, ebenso ein PEM-Schlüssel: er ist nie ein HMAC-Geheimnis
of_hmac_verify (string as_algorithm, string as_key, string as_text, string as_expected { , string as_key_format }) → booleanWahr, wenn as_expected der HMAC von as_text unter as_key ist, in KONSTANTER ZEIT verglichen — die Prüfung einer Webhook-Signatur (Stripe, GitHub, Shopify), ohne sie zu verraten, nie if of_hmac(…) = Header. as_expected hexadezimal (beliebige Schreibung) oder Base64; ein Präfix wie sha256= vorher entfernen. Sonst falsch
of_hash_file (string as_algorithm, string as_path) → stringDer Hash einer Datei des Arbeitsplatzes, beliebigen Typs und beliebiger Größe (stückweise gelesen), ohne sie in PowerBuilder zu laden. Ein RELATIVER Pfad wird im aktuellen Ordner gesucht, dann im Ordner, in dem die Anwendung gestartet ist, dann neben der EXE — die Regel für alle Dateien dieser Komponente. Leer bei Fehler; is_last_error unterscheidet einen unbekannten Algorithmus von einer unlesbaren Datei
of_base64_encode (string as_text) → stringEin Text in Base64 (seine UTF-8-Bytes): was ein Browser oder Python schreiben würde. Leer bei Fehler
of_base64_decode (string as_base64) → stringDer TEXT zurück (UTF-8). Ein Wert, der kein Base64 ist oder dessen Bytes kein Text sind (ein PDF, ein Bild), gibt eine leere Zeichenkette und is_last_error zurück — nie Datenmüll. Bytes schreibt man mit of_base64_decode_to_file in eine Datei
of_base64_encode_file (string as_path) → stringDie Bytes einer Datei des Arbeitsplatzes in Base64 — ein Anhang, ein Bild für einen JSON-Aufruf — von der DLL gelesen, nie in PowerBuilder geladen. Bis 64 MB in einer 32-Bit-Anwendung (die PowerBuilder-IDE ist eine), 512 MB in 64 Bit. Leer bei Fehler; is_last_error sagt, welcher: Datei zu groß für diesen Prozess, unlesbar oder Demo-Grenze
of_base64_decode_to_file (string as_base64, string as_path) → longSchreibt die Bytes eines Base64-Werts in eine Datei — den Anhang, mit dem eine API geantwortet hat. Ein LEERER Wert schreibt eine leere Datei; ein RELATIVER Pfad wird in den Ordner geschrieben, in dem die Anwendung gestartet ist (der aktuelle Ordner beim Laden der Bibliothek, in der IDE wie kompiliert derselbe), wo of_hash_file ihn wiederfindet. Gibt 0 nach dem Schreiben zurück, -5 bei leerem Pfad oder einem, der vom aktuellen Ordner eines Laufwerks abhängt (\x, C:x), oder bei einem Wert, der kein Base64 ist, -4 wenn die Datei nicht geschrieben werden kann oder, in der Demo, über 2048 Bytes
of_encrypt (string as_password, string as_text) → stringVerschlüsselt einen Text mit einem Passwort: eine einzige Base64-Zeichenkette zum Speichern, die ihre PBKDF2-Runden trägt (siehe Die Formate). Ein LEERES Passwort wird abgelehnt — jeder könnte den Wert zurücklesen. Leer bei Fehler
of_decrypt (string as_password, string as_cipher) → stringDer Text zurück, mit demselben Passwort. Falsches Passwort oder veränderter Wert: leere Zeichenkette und is_last_error, nie Datenmüll. Ein Wert, dessen Inhalt kein Text ist (eine mit of_encrypt_file verschlüsselte Datei), gibt ebenfalls leer zurück: of_decrypt_file
of_uuid ( ) → stringEine zufällige UUID (Version 4)
of_random_hex (integer ai_bytes) → stringai_bytes Zufallsbytes (1 bis 4096) hexadezimal: ein Salt, ein Token
of_generate_keypair (integer ai_bits, ref string as_public_pem, ref string as_private_pem) → longEin RSA-Schlüsselpaar in PEM (KEY_2048, KEY_3072, KEY_4096). Liefert 0, sobald beide Schlüssel gefüllt sind, -5 bei jeder anderen Größe (beide bleiben leer), -4 bei Fehler
of_sign (string as_private_pem, string as_text) → stringDie Signatur eines Textes mit dem privaten Schlüssel, Base64. Der Schlüssel nennt seine Familie: RSA (RSASSA-PKCS1-v1_5, SHA-256), ECDSA P-256 (SHA-256, Signatur in DER — was openssl dgst und Java SHA256withECDSA prüfen) oder Ed25519 (64 Bytes). Leer bei Fehler
of_verify (string as_public_pem, string as_text, string as_signature) → booleanWahr, wenn die Signatur genau auf diesem Text mit dem passenden privaten Schlüssel erstellt wurde; falsch — kein Fehler — wenn sich der Text geändert hat. Eine ECDSA-Signatur wird in DER (openssl, Java) oder in P1363 (`rs`, 64 Bytes: WebCrypto, .NET) gelesen
of_generate_keypair (string as_kind, ref string as_public_pem, ref string as_private_pem) → longEin Schlüsselpaar nach ART: KEY_RSA_2048/3072/4096, KEY_EC_P256 (kurze Signaturen) oder KEY_ED25519. Gibt 0 zurück, -5 bei einer Art, die keine davon ist (nie ersatzweise ein RSA-Paar), -4 bei Fehler
of_key_kind (string as_pem) → stringDie Familie eines PEM-Schlüssels: rsa, ec-p256 oder ed25519; nur der ERSTE Block des Textes wird gelesen. Ein PKCS#1-Schlüssel (BEGIN RSA PRIVATE KEY), ein Zertifikat oder ein verschlüsselter Schlüssel wird in is_last_error BENANNT, mit dem openssl-Befehl, der ihn umwandelt
of_base64url_encode (string as_text) → stringURL-sicheres Base64 (JWT, Query-String)
of_base64url_decode (string as_base64url) → stringDer Text zurück; leer, wenn es kein Base64url ist oder seine Bytes kein Text sind
of_hex_encode (string as_text) → stringDie UTF-8-Bytes eines Texts in Hexadezimal
of_hex_decode (string as_hex) → stringDer Text zurück; leer, wenn es kein Hexadezimal ist oder seine Bytes kein Text sind
of_random_password (integer ai_length, string as_charset) → stringEin zufälliges Passwort (4 bis 256 Zeichen) aus einem CHARSET_*-Satz (leer = CHARSET_ALL), ohne Verzerrung. Leer bei falscher Länge oder unbekanntem Satz
of_equals_constant_time (string as_a, string as_b) → booleanGleichheit in konstanter Zeit: ein Token oder einen Code vergleichen, ohne ihn preiszugeben
of_password_hash (string as_password) → stringWas man für ein Passwort SPEICHERT: gesalzenes PBKDF2 (il_password_hash_rounds, 600.000 Runden), Runden und Salz im Wert. Nie zweimal dasselbe Ergebnis. Ein leeres Passwort wird gehasht (ein leeres Passwort zu prüfen ist legitim). Das Format ist PBToolboxAI-EIGEN (siehe Die Formate)
of_password_verify (string as_password, string as_stored) → booleanWahr, wenn das Passwort das des gespeicherten Werts ist, in konstanter Zeit verglichen. Ein Wert mit Runden außerhalb von 1 000 bis 10 000 000 wird abgelehnt: ein feindlicher Wert kann die Prüfung nicht minutenlang laufen lassen
of_totp_secret ( ) → stringEin neues Geheimnis für Zwei-Faktor-Codes (RFC 6238), in Base32
of_totp_code (string as_secret) → stringDer sechsstellige Code des Augenblicks, den der Authenticator zeigt
of_totp_verify (string as_secret, string as_code { , ref long al_step }) → booleanWahr, wenn der Code der des Augenblicks, des vorherigen oder des nächsten Schritts ist. Mit al_step der 30-Sekunden-SCHRITT, für den der Code angenommen wurde (sonst 0): ein Code darf nicht zweimal dienen (RFC 6238) — den zuletzt angenommenen Schritt des Benutzers aufbewahren und einen nicht größeren Schritt ablehnen. Ein Geheimnis mit anderem als A-Z, 2-7, Leerzeichen oder = wird abgelehnt (eine 0 statt eines O getippt)
of_totp_uri (string as_secret, string as_account, string as_issuer) → stringDie otpauth://-URI für einen QR-Code, um den Benutzer zu registrieren
of_generate_key ( ) → stringEin neuer AES-256-Schlüssel, 32 Bytes in Hexadezimal
of_encrypt_with_key (string as_key, string as_text) → stringAES-256-GCM mit EXPLIZITEM Schlüssel (Hex oder Base64): Base64 aus Nonce + Chiffrat + Tag, was openssl oder Python entschlüsseln
of_decrypt_with_key (string as_key, string as_cipher) → stringDer Text zurück mit demselben Schlüssel; sonst leer und is_last_error
of_rsa_encrypt (string as_public_pem, string as_text) → stringEin kurzes Geheimnis, verschlüsselt an einen ÖFFENTLICHEN Schlüssel: nur der private liest es. Die GENAUEN Einstellungen für die andere Seite: RSA-OAEP, SHA-256, MGF1 mit SHA-256, ohne Label — Java (OAEPWithSHA-256AndMGF1Padding) und openssl pkeyutl nehmen standardmäßig MGF1-SHA-1, siehe Die Formate. Höchstens 190 Bytes mit einem 2048-Bit-Schlüssel; darüber sagt es is_last_error — die Daten mit of_encrypt_with_key verschlüsseln und nur ihren Schlüssel so. Ein PRIVATER Schlüssel statt des öffentlichen wird benannt
of_rsa_decrypt (string as_private_pem, string as_cipher) → stringDas Geheimnis zurück, mit dem privaten Schlüssel
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 } }) → stringEin signiertes JWT: JWT_HS256 (geteiltes Geheimnis), JWT_RS256/JWT_ES256/JWT_EDDSA (privater Schlüssel); der Algorithmus ist PFLICHT. iat hinzugefügt, exp bei Dauer > 0 — ein exp in den Claims UND eine Dauer wird abgelehnt — und ein angegebenes exp/iat/nbf muss eine Zahl sein. Der Header nennt den Algorithmus wie registriert (EdDSA). as_key_format: das HS256-Geheimnis als KEYFORMAT_TEXT, KEYFORMAT_HEX oder KEYFORMAT_BASE64; ein PEM-Schlüssel ist nie ein HS256-Geheimnis. as_header_json: dem Header HINZUGEFÜGTE Mitglieder, zum Beispiel {"kid":"2026-09"} — der Schlüssel, den eine API wählt (Apple, jeder Anbieter, der seine Schlüssel rotiert); alg und typ bleiben die der Bibliothek. Leer bei Fehler
of_jwt_verify (string as_algorithm, string as_key, string as_token { , string as_key_format }) → stringDie Claims (JSON) eines Tokens, dessen Signatur, Algorithmus und Daten stimmen. Der Algorithmus ist PFLICHT und wird GENAU mit dem des Headers verglichen: "" wird abgelehnt, und ein PEM-Schlüssel ist nie ein HS256-Geheimnis — sonst diente ein ÖFFENTLICHER Schlüssel als Geheimnis und ein gefälschtes Token ging durch. Ein exp oder nbf, das keine Zahl ist, und ein crit-Header werden abgelehnt. Zielgruppe (aud) und Aussteller (iss) prüfen SIE an den zurückgegebenen Claims. as_key_format wie bei of_jwt_sign. Sonst leer und is_last_error
of_jwt_claims (string as_token) → stringDie Claims OHNE Prüfung, um zu lesen, wen das Token nennt, bevor der Schlüssel gewählt wird
of_jwt_header (string as_token) → stringDer Header (JSON) eines Tokens OHNE Prüfung: sein kid lesen, um den prüfenden Schlüssel zu wählen. Nie allein darauf verlassen
of_protect (string as_text { , boolean ab_machine { , string as_entropy } }) → stringEin von Windows für diesen Benutzer (oder diese Maschine) geschütztes Geheimnis: DPAPI, nativ. Was in die INI für ein Serverpasswort kommt. Es schützt NICHT vor einem anderen Programm desselben Benutzers (noch, mit ab_machine, vor einem anderen Konto des Arbeitsplatzes): as_entropy, ein Geheimnis Ihrer Anwendung, verkleinert diesen Kreis — dieselbe Entropie ist zum Zurücklesen nötig
of_unprotect (string as_protected { , string as_entropy }) → stringDer Text zurück, auf demselben Konto und mit DERSELBEN Entropie, falls eine angegeben wurde; anderswo leer und is_last_error
of_crc32 (string as_text) → stringDer CRC32 eines Texts, 8 Hexziffern — eine Integritätsprüfung, kein Hash
of_crc32_file (string as_path) → stringDer CRC32 einer Datei, von der DLL bis zum Ende gelesen: ein Lesefehler unterwegs ist ein Fehler, nie der CRC des Anfangs
of_encrypt_file (string as_password, string as_source, string as_target) → longEine per Passwort verschlüsselte Datei, nativ und stückweise (derselbe Container wie of_encrypt), bis 64 GB — die Grenze eines AES-GCM-Containers. Relative Pfade: die Quelle wird wie bei of_hash_file gesucht, das Ziel in den Ordner geschrieben, in dem die Anwendung gestartet ist. Gibt 0 zurück, -5 leeres Argument oder il_pbkdf2_rounds außerhalb der Grenzen, -2 unlesbare Quelle, -4 Ziel nicht schreibbar oder, in der Demo, Quelle über 2048 Bytes, -7 Quelle über 64 GB
of_decrypt_file (string as_password, string as_source, string as_target) → longDie Datei zurück. Gibt 0 zurück, -3 falsches Passwort oder veränderte Datei (nichts wird geschrieben: der Text wird in einer versteckten temporären Datei geprüft, die Windows löscht, selbst wenn die Anwendung abstürzt), -5/-2/-4 wie of_encrypt_file
of_close ( )Gibt die verborgene Seite frei; geschieht beim Zerstören des Objekts automatisch
of_reset ( )Zurück zu den Standardwerten

Beispiele #

Prüfen, dass eine Datei unverändert ist #

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

Ein Anhang in Base64 für eine 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")

Ein Geheimnis in einer INI aufbewahren #

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

Eine Bestellung signieren, anderswo prüfen #

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

Ein Token für eine API und seine Prüfung #

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

Die Signatur eines Webhooks prüfen #

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

Ein von Windows verwahrtes Serverpasswort und ein Zwei-Faktor-Benutzer #

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

Bewährte Praxis #

← Komponentenreferenz · Inhalt des Handbuchs