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 Objekt | n_pbt_crypto |
| Wofür | Prüfen, dass eine Datei unverändert ist, ein Geheimnis in einer INI aufbewahren, eine Bestellung signieren, einen API-Aufruf authentifizieren |
| Prinzip | Eine verborgene Seite rechnet; jede Methode ist ein gewöhnlicher PowerScript-Aufruf, der seinen Wert liefert |
| Abhängigkeit | Die 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 #
- Hashes und HMAC: Kleinbuchstaben-Hexadezimal, der Text als UTF-8 gelesen — Python, Java und
openssl dgstliefern denselben Wert. of_encrypt: eine einzige Base64-Zeichenkette, die in dieser Reihenfolge trägt:PBK2(4 Bytes), die Anzahl der PBKDF2-Runden (4 Bytes, Big-Endian), das Salz (16), die Nonce (12), das AES-256-GCM-Chiffrat, dann sein Tag (16 Bytes, am Ende). Der Schlüssel wird aus dem Passwort (UTF-8) per PBKDF2-HMAC-SHA-256 mit DIESEN Runden abgeleitet —il_pbkdf2_roundszum Zeitpunkt der Verschlüsselung.of_decryptmit demselben Passwort genügt, auf jedem Arbeitsplatz;of_encrypt_fileschreibt genau denselben Container, als Bytes.of_password_hash:pbkdf2-sha256$Runden$Salz$Hash, Salz und Hash in Standard-Base64. Dieses Format ist PBToolboxAI-EIGEN: weder passlib ($pbkdf2-sha256$…) noch Django (pbkdf2_sha256$…) lesen es unverändert.- Schlüssel und Signaturen: PEM, öffentlich als SPKI (
BEGIN PUBLIC KEY), privat als PKCS#8 (BEGIN PRIVATE KEY); Signaturen in Base64, je nach Familie des Schlüssels — RSA: RSASSA-PKCS1-v1_5 mit SHA-256; ECDSA P-256: SHA-256, in DER (openssl dgst -sha256 -verify, JavaSHA256withECDSA); Ed25519: 64 Bytes.of_verifyliest eine ECDSA-Signatur in DER oder in P1363 (r | s); das ES256-JWT bleibt in P1363, wie RFC 7518 es verlangt. - RSA-OAEP (
of_rsa_encrypt): SHA-256, MGF1 mit SHA-256, ohne Label. In Java:OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT); mit openssl:-pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 -pkeyopt rsa_mgf1_md:sha256. Ein PKCS#1-Schlüssel (BEGIN RSA PRIVATE KEY) wird mitopenssl pkcs8 -topk8 -nocryptumgewandelt. - MD5 wird NATIV angeboten (
HASH_MD5) für alte Austausche, die es verlangen — nie für ein Passwort oder für Integrität gegenüber einem Angreifer.
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 #
| Eigenschaft | Typ | Standard | Rolle |
|---|---|---|---|
is_last_error | string | "" | Warum der letzte Aufruf eine leere Zeichenkette, false oder einen negativen Code lieferte: falsches Passwort, unlesbare Datei, unbekannter Algorithmus, Demogrenze |
il_timeout_ms | long | 20000 | Maximale Dauer eines Aufrufs. Eine große Datei zu hashen oder einen 4096-Bit-Schlüssel zu erzeugen kann einige Sekunden dauern |
il_pbkdf2_rounds | long | 100000 | PBKDF2-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_rounds | long | 600000 | PBKDF2-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_owner | powerobject | null | Das 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 #
| Methode | Rolle | |
|---|---|---|
of_open ( ) → long | Erzeugt 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 ( ) → boolean | Wahr, sobald die verborgene Seite existiert | |
of_hash (string as_algorithm, string as_text) → string | Der 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) → string | of_hash mit HASH_SHA256 — der meistgebrauchte | |
of_hmac (string as_algorithm, string as_key, string as_text { , string as_key_format }) → string | Der 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 }) → boolean | Wahr, 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) → string | Der 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) → string | Ein Text in Base64 (seine UTF-8-Bytes): was ein Browser oder Python schreiben würde. Leer bei Fehler | |
of_base64_decode (string as_base64) → string | Der 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) → string | Die 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) → long | Schreibt 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) → string | Verschlü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) → string | Der 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 ( ) → string | Eine zufällige UUID (Version 4) | |
of_random_hex (integer ai_bytes) → string | ai_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) → long | Ein 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 | Die 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) → boolean | Wahr, 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 (`r | s`, 64 Bytes: WebCrypto, .NET) gelesen |
of_generate_keypair (string as_kind, ref string as_public_pem, ref string as_private_pem) → long | Ein 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) → string | Die 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) → string | URL-sicheres Base64 (JWT, Query-String) | |
of_base64url_decode (string as_base64url) → string | Der Text zurück; leer, wenn es kein Base64url ist oder seine Bytes kein Text sind | |
of_hex_encode (string as_text) → string | Die UTF-8-Bytes eines Texts in Hexadezimal | |
of_hex_decode (string as_hex) → string | Der Text zurück; leer, wenn es kein Hexadezimal ist oder seine Bytes kein Text sind | |
of_random_password (integer ai_length, string as_charset) → string | Ein 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) → boolean | Gleichheit in konstanter Zeit: ein Token oder einen Code vergleichen, ohne ihn preiszugeben | |
of_password_hash (string as_password) → string | Was 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) → boolean | Wahr, 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 ( ) → string | Ein neues Geheimnis für Zwei-Faktor-Codes (RFC 6238), in Base32 | |
of_totp_code (string as_secret) → string | Der sechsstellige Code des Augenblicks, den der Authenticator zeigt | |
of_totp_verify (string as_secret, string as_code { , ref long al_step }) → boolean | Wahr, 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) → string | Die otpauth://-URI für einen QR-Code, um den Benutzer zu registrieren | |
of_generate_key ( ) → string | Ein neuer AES-256-Schlüssel, 32 Bytes in Hexadezimal | |
of_encrypt_with_key (string as_key, string as_text) → string | AES-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) → string | Der Text zurück mit demselben Schlüssel; sonst leer und is_last_error | |
of_rsa_encrypt (string as_public_pem, string as_text) → string | Ein 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) → string | Das 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 } }) → string | Ein 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 }) → string | Die 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) → string | Die Claims OHNE Prüfung, um zu lesen, wen das Token nennt, bevor der Schlüssel gewählt wird | |
of_jwt_header (string as_token) → string | Der 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 } }) → string | Ein 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 }) → string | Der 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) → string | Der CRC32 eines Texts, 8 Hexziffern — eine Integritätsprüfung, kein Hash | |
of_crc32_file (string as_path) → string | Der 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) → long | Eine 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) → long | Die 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 #
- Ein Objekt pro Fenster, mit ihm geöffnet (
of_open): die verborgene Seite kostet beim ersten Mal einige hundert Millisekunden, danach nichts. - Das Passwort wird nicht gespeichert:
of_encryptliefert alles zum Entschlüsseln — außer ihm; das ist der Vertrag. - Der private Schlüssel reist nicht: zu Hause signieren, den öffentlichen Schlüssel veröffentlichen.
of_verifybraucht nur diesen. is_last_errorlesen, wenn eine Methode eine leere Zeichenkette liefert: der Grund steht dort, und oft heißt er „falsches Passwort“ oder „unlesbare Datei“ — kein Fehler der Komponente.- DPAPI schützt vor einem ANDEREN KONTO, nicht vor einem anderen Programm: alles, was derselbe Benutzer startet, liest ein
of_protectzurück (jedes Konto des Arbeitsplatzes mitab_machine). Übergeben Sie eine anwendungseigene Entropie, um diesen Kreis zu verkleinern. - Ein JWT wird mit SEINEM Algorithmus geprüft, fest im Code (
n_pbt_crypto.JWT_RS256), nie aus dem Token oder einer INI gelesen, der er fehlen kann: ein leerer Algorithmus wird abgelehnt, aber erst diese Wahl schließt die Tür zur HS/RS-Verwechslung. Danachaudundissan den zurückgegebenen Claims prüfen.