PBToolboxAI v5 ← 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
il_timeout_mslong20000Wie lange ein Aufruf der verborgenen Seite dauern darf: einen 4096-Bit-Schlüssel erzeugen oder einen Schlüssel mit Millionen PBKDF2-Runden ableiten kann einige Sekunden dauern. Hashes, Dateien und Blobs sind nativ: für sie gilt keine Zeitgrenze
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 (of_get_last_error() sagt es, -5 bei einer Datei), nie ersetzen Gilt auch für of_encrypt_blob. Ein Wert trägt seine Runden: sie zu ändern macht einen älteren Wert nie unlesbar
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 Erhöhen Sie sie, und of_password_needs_rehash sagt, welche gespeicherten Werte zu erneuern sind
il_jwt_leeway_secondslong60Toleranz von of_jwt_verify und of_jwt_verify_jwks bei exp und nbf, in Sekunden: die Uhren zweier Rechner driften. 0 bis 86 400; jeder andere Wert lässt die Prüfung ablehnen
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 Blobs folgen der Grenze der Dateien

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_RSA_3072, KEY_RSA_4096, KEY_EC_P256, KEY_EC_P384, KEY_ED25519 für die Art eines Paars; 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 für das Schema einer RSA-Signatur; 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_get_last_error ( ) → stringWarum der letzte Aufruf eine leere Zeichenkette, false oder einen negativen Code lieferte: falsches Passwort, unlesbare Datei, unbekannter Algorithmus, Demogrenze Ein NULL-Argument zählt als leeres: der Grund ist nie NULL.
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 (of_get_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 Auch base64url wird gelesen.
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 as_expected darf auch base64url sein.
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; of_get_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 of_get_last_error() zurück — nie Datenmüll. Bytes schreibt man mit of_base64_decode_to_file in eine Datei Oder of_base64_decode_blob, das sie in einem Blob zurückgibt.
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 32 MB in einer 32-Bit-Anwendung (die PowerBuilder-IDE ist eine), 512 MB in 64 Bit; darüber hinaus of_base64_encode_file_to_file. Leer bei Fehler; of_get_last_error() sagt welcher: eine für diesen Prozess zu große Datei, unlesbar, oder die Demo-Grenze
of_base64_encode_file_to_file (string as_source, string as_target) → longDas Base64 einer Datei, in EINE ANDERE Datei geschrieben (ASCII, ohne Zeilenumbruch), von der DLL, mit konstantem Speicher und beliebiger Größe: was ein großer Upload sendet. Pfade wie bei of_hash_file. Gibt 0 zurück, -5 bei leerem Pfad oder einem Ziel, das vom aktuellen Ordner eines Laufwerks abhängt (\x, C:x), -2 unlesbare Quelle, -4 Ziel nicht schreibbar oder im Demomodus Quelle über 2048 Bytes
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 of_get_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 oder of_decrypt_blob auf seinen Bytes.
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 { , string as_scheme, string as_hash }) → 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 as_scheme und as_hash: was die Gegenseite erwartet — SIGN_PSS für einen RSA-Schlüssel (Salt in Hash-Länge, MGF1 mit demselben Hash), HASH_SHA384 oder HASH_SHA512; ein leerer Text behält die des Schlüssels; ein EC-Schlüssel nimmt einen Hash, nie PSS; ein Ed25519-Schlüssel keines von beiden. Ein stattdessen übergebener ÖFFENTLICHER Schlüssel wird benannt
of_verify (string as_public_pem, string as_text, string as_signature { , string as_scheme, string as_hash }) → 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 Mit Schema und Hash des Unterzeichners (siehe of_sign`).
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 oder KEY_EC_P384 (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, ec-p384 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 of_get_last_error() BENANNT, mit dem openssl-Befehl, der ihn umwandelt
of_jwk_to_pem (string as_jwk_json) → stringEin als JWK veröffentlichter Schlüssel (RFC 7517: kty RSA, EC P-256 / P-384 oder OKP Ed25519) in PEM, was of_verify, of_jwt_verify und openssl nehmen: öffentlich in SPKI, privat in PKCS#8, wenn der JWK d enthält. Seine Verwendungsmitglieder (alg, use, key_ops, kid) gehören nicht zum Schlüssel. Leer bei Fehler
of_pem_to_jwk (string as_pem) → stringEin PEM-Schlüssel als JWK (JSON): was ein Schlüsselsatz veröffentlicht oder was eine API zur Registrierung verlangt. Ein privater PEM ergibt einen privaten JWK (mit d): behalten Sie ihn für sich. Leer bei Fehler
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_password_needs_rehash (string as_stored) → booleanWahr, wenn ein von of_password_hash gespeicherter Wert WENIGER Runden trägt als il_password_hash_rounds: direkt nach einem erfolgreichen of_password_verify, wenn das Passwort bekannt ist, erneut hashen und den neuen Wert speichern — der OWASP-Rat, wenn die Kosten steigen. Sonst falsch; falsch mit of_get_last_error(), wenn as_stored kein solcher Wert ist. Keine Seite
of_totp_secret ( ) → stringEin neues Geheimnis für Zwei-Faktor-Codes (RFC 6238), in Base32
of_totp_code (string as_secret { , string as_hash, integer ai_digits, integer ai_period }) → stringDer sechsstellige Code des Augenblicks, den der Authenticator zeigt Mit Einstellungen: HASH_SHA1, HASH_SHA256 oder HASH_SHA512, 6 bis 8 Ziffern, eine Periode von 1 bis 3 600 Sekunden — die, die of_totp_uri schreibt
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) Vom Benutzer getippte Leerzeichen („864 010“, wie die App ihn anzeigt) werden ignoriert.
of_totp_verify (string as_secret, string as_code, string as_hash, integer ai_digits, integer ai_period, ref long al_step) → booleanDieselbe Prüfung mit den Einstellungen von of_totp_code; al_step zählt Perioden von ai_period Sekunden
of_totp_uri (string as_secret, string as_account, string as_issuer { , string as_hash, integer ai_digits, integer ai_period }) → stringDie otpauth://-URI für einen QR-Code, um den Benutzer zu registrieren Das Geheimnis wird in Base32-Großbuchstaben geschrieben, ohne Leerzeichen und =; ein Geheimnis mit einem anderen Zeichen wird abgelehnt. Mit den Einstellungen von of_totp_code zeigt die App die Codes, die es damit berechnet
of_hotp_code (string as_secret, long al_counter) → stringDer sechsstellige Code eines ZÄHLERS (HOTP, RFC 4226, SHA-1): ein Hardware-Token, ein per E-Mail gesendeter Code — der Server führt den Zähler und erhöht ihn bei jedem akzeptierten Code. al_counter: 0 oder mehr. Leer bei Fehler
of_generate_key ( ) → stringEin neuer AES-256-Schlüssel, 32 Bytes in Hexadezimal
of_encrypt_with_key (string as_key, string as_text { , string as_aad }) → stringAES-256-GCM mit EXPLIZITEM Schlüssel (Hex oder Base64): Base64 aus Nonce + Chiffrat + Tag, was openssl oder Python entschlüsseln as_aad: ZUSATZDATEN, authentifiziert, aber nicht verschlüsselt — eine Bestellnummer, eine Benutzer-ID: der Wert entschlüsselt sich nur mit denselben. Der Schlüssel darf auch base64url sein
of_decrypt_with_key (string as_key, string as_cipher { , string as_aad }) → stringDer Text zurück mit demselben Schlüssel; sonst leer und of_get_last_error() Mit DENSELBEN Zusatzdaten, falls vorhanden.
of_rsa_encrypt (string as_public_pem, string as_text { , string as_hash }) → 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 of_get_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 as_hash: der Hash von OAEP und MGF1 — HASH_SHA1 verwenden openssl pkeyutl, .NET (OaepSHA1) und Java (OAEPPadding) standardmäßig
of_rsa_decrypt (string as_private_pem, string as_cipher { , string as_hash }) → stringDas Geheimnis zurück, mit dem privaten Schlüssel Mit dem Hash des Absenders (standardmäßig HASH_SHA256).
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 Auch JWT_HS384/HS512, JWT_RS384/RS512, JWT_PS256/PS384/PS512 und JWT_ES384 (Schlüssel KEY_EC_P384). Ein Schlüssel der falschen Familie oder seine ÖFFENTLICHE Hälfte wird benannt
of_jwt_verify (string as_algorithm, string as_key, string as_token { , string as_key_format { , string as_expected_claims_json } }) → stringDie Claims (JSON) eines Tokens, dessen Signatur, Algorithmus und Daten stimmen (il_jwt_leeway_seconds Toleranz, standardmäßig eine Minute). Der Algorithmus ist PFLICHT und wird EXAKT mit dem des Headers verglichen: "" wird abgelehnt, und ein PEM-Schlüssel ist nie ein HS-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. as_key_format wie bei of_jwt_sign. as_expected_claims_json: die Claims, die mit diesem Wert vorhanden sein müssen — der Aussteller (iss), die Zielgruppe (aud, auch in einer Liste gefunden); ein fehlender oder abweichender Claim wird abgelehnt und benannt. Sonst leer und of_get_last_error(); ein Claim wird mit n_pbt_json gelesen (of_load, dann of_get_string)
of_jwt_verify_jwks (string as_algorithm, string as_jwks_json, string as_token { , string as_expected_claims_json }) → stringDie Claims eines Tokens, geprüft gegen einen SCHLÜSSELSATZ (JWKS, RFC 7517) — was Azure AD, Google, Auth0 oder Keycloak unter ihrer jwks_uri veröffentlichen (mit dem restclient abrufen, einige Stunden aufbewahren): die kid des Tokens wählt den Schlüssel. Ein Token ohne kid, eine kid, die der Satz nicht oder doppelt enthält, ein Schlüssel für einen anderen Algorithmus oder eine andere Verwendung (use = enc): abgelehnt, of_get_last_error() sagt welche. Daten und erwartete Claims wie bei of_jwt_verify
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 of_get_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 Ein Ziel, das vom aktuellen Ordner eines Laufwerks abhängt (\x, C:x), gibt -5 zurück, und of_get_last_error() sagt es
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_base64_encode_blob (blob ab_data) → stringDie Bytes eines Blobs in Base64 — ein mit SelectBlob gelesenes Foto, ein empfangenes PDF — ohne Datei. Nativ. Bis 32 MB in 32 Bit, 512 MB in 64 Bit; im Demomodus 2048 Bytes. Leer bei Fehler
of_base64_decode_blob (string as_base64, ref blob ab_data) → longDie BYTES eines Base64-Werts (oder base64url) in einem Blob — ein empfangener Anhang, bereit für UpdateBlob. Nativ. Gibt 0 zurück, sobald ab_data gefüllt ist, -5, wenn der Wert kein Base64 ist, -4 im Demomodus über 2048 Bytes; ab_data ist dann leer
of_hash_blob (string as_algorithm, blob ab_data) → stringDer Hash der Bytes eines Blobs, mit den Algorithmen von of_hash — derselbe Wert wie of_hash_file auf denselben Bytes. Nativ. Leer bei Fehler
of_hmac_blob (string as_algorithm, string as_key, blob ab_data { , string as_key_format }) → stringDer HMAC der Bytes eines Blobs unter einem geheimen Schlüssel, der Schlüssel wie bei of_hmac: eine empfangene Datei, geprüft gegen die Signatur, die der Dienst mitgeschickt hat. Nativ. Leer bei Fehler
of_crc32_blob (blob ab_data) → stringDie CRC32 der Bytes eines Blobs, 8 Hexadezimalziffern. Nativ
of_encrypt_blob (string as_password, blob ab_data, ref blob ab_cipher) → longDie Bytes eines Blobs, per Passwort verschlüsselt: der Container von of_encrypt und of_encrypt_file, als Bytes (52 mehr als die Daten) — in Base64 gelesen, entschlüsselt er sich mit of_decrypt, und umgekehrt. Nativ, il_pbkdf2_rounds. Gibt 0 zurück, sobald ab_cipher gefüllt ist, -5 bei leerem Passwort oder Runden außerhalb der Grenzen, -4 bei Fehler oder im Demomodus über 2048 Bytes
of_decrypt_blob (string as_password, blob ab_cipher, ref blob ab_data) → longDie Bytes aus of_encrypt_blob (oder aus einem als Bytes gelesenen Wert von of_encrypt), mit demselben Passwort. Gibt 0 zurück, sobald ab_data gefüllt ist, -3 bei falschem Passwort oder veränderten Bytes (ab_data bleibt leer), -5 bei leerem Passwort oder Bytes, die kein solcher Wert sind, -4 im Demomodus über 2048 Bytes
of_close ( ) → longGibt die verborgene Seite frei; geschieht beim Zerstören des Objekts automatisch Liefert 0, sobald erledigt.
of_reset ( ) → longZurück zu den Standardwerten: il_timeout_ms, il_pbkdf2_rounds, il_password_hash_rounds, il_jwt_leeway_seconds, of_get_last_error(); ipo_owner bleibt Liefert 0, sobald erledigt.

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 #

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

Ein Geheimnis in einer INI aufbewahren #

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

Eine Bestellung signieren, anderswo prüfen #

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

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

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

Die Signatur eines Webhooks prüfen #

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

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

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

Ein Token von Azure AD, Google oder Auth0 prüfen (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())

Ein Foto aus der Datenbank, in 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

Bewährte Praxis #

← Komponentenreferenz · Inhalt des Handbuchs