crypto — n_pbt_crypto #
← Referencia de componentes · Índice de la guía
Hashes, HMAC, cifrado por contraseña, valores aleatorios y firmas RSA — la Web Crypto API del motor WebView2, que su PowerBuilder 10 no tiene en ningún otro sitio. Sin DLL de terceros, sin servicio: lo que el equipo ya sabe hacer, ofrecido a PowerScript.
▶ Verlo en vivo — Aplicación de demostración, mosaico Crypto: los resultados, el código que los produce y esta página, lado a lado.
En resumen #
| Objeto no visual | n_pbt_crypto |
| Sirve para | Comprobar que un archivo no ha cambiado, guardar un secreto en un INI, firmar un pedido, autenticar una llamada a una API |
| Principio | Una página oculta hace el cálculo; cada método es una llamada PowerScript normal que devuelve su valor |
| Dependencia | El runtime WebView2, que la biblioteca ya requiere — nada más |
Inicio rápido #
// 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
Cada método es una llamada que devuelve su valor: sin evento, sin espera que escribir.
Los formatos, legibles por otras herramientas #
- Hashes y HMAC: hexadecimal en minúsculas, el texto leído en UTF-8 — Python, Java y
openssl dgstdan el mismo valor. of_encrypt: una sola cadena base64 que lleva, en este orden,PBK2(4 bytes), el número de rondas PBKDF2 (4 bytes, big-endian), la sal (16), el nonce (12), el cifrado AES-256-GCM y luego su tag (16 bytes, al final). La clave se deriva de la contraseña (UTF-8) con PBKDF2-HMAC-SHA-256 con ESAS rondas —il_pbkdf2_roundsen el momento del cifrado. Bastaof_decryptcon la misma contraseña, en cualquier equipo;of_encrypt_fileescribe exactamente el mismo contenedor, en bytes.of_password_hash:pbkdf2-sha256$rondas$sal$hash, sal y hash en base64 estándar. Este formato es PROPIO de PBToolboxAI: ni passlib ($pbkdf2-sha256$…) ni Django (pbkdf2_sha256$…) lo leen tal cual.- Claves y firmas: PEM, pública en SPKI (
BEGIN PUBLIC KEY), privada en PKCS#8 (BEGIN PRIVATE KEY); firmas en base64, según la familia de la clave — RSA: RSASSA-PKCS1-v1_5 con SHA-256; ECDSA P-256: SHA-256, en DER (openssl dgst -sha256 -verify, JavaSHA256withECDSA); Ed25519: 64 bytes.of_verifylee una firma ECDSA en DER o en P1363 (r | s); el JWT ES256 sigue en P1363, como exige la RFC 7518. - RSA-OAEP (
of_rsa_encrypt): SHA-256, MGF1 con SHA-256, sin etiqueta. En 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 clave PKCS#1 (BEGIN RSA PRIVATE KEY) se convierte conopenssl pkcs8 -topk8 -nocrypt. - MD5 se ofrece en NATIVO (
HASH_MD5) para los intercambios antiguos que lo exigen — nunca para una contraseña ni para una integridad frente a un adversario.
Descifrar un valor de of_encrypt en el servidor, en Python (biblioteca 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")
Propiedades #
| Propiedad | Tipo | Predeterminado | Función |
|---|---|---|---|
is_last_error | string | "" | Por qué la última llamada devolvió una cadena vacía, false o un código negativo: contraseña errónea, archivo ilegible, algoritmo desconocido, límite demo |
il_timeout_ms | long | 20000 | Duración máxima de una llamada. Calcular el hash de un archivo grande o generar una clave de 4096 bits puede tardar unos segundos |
il_pbkdf2_rounds | long | 100000 | Rondas PBKDF2 de of_encrypt y of_encrypt_file: unos 100 ms, pagados en cada lectura del valor. De 1.000 a 10.000.000: cualquier otro valor hace RECHAZAR la llamada (is_last_error lo dice, -5 para un archivo), nunca sustituir |
il_password_hash_rounds | long | 600000 | Rondas PBKDF2 de of_password_hash: 600.000, la cifra de OWASP para PBKDF2-HMAC-SHA-256 (unos 0,6 s). Un hash almacenado es lo que un atacante fuerza sin conexión si la tabla se filtra; se verifica una sola vez por inicio de sesión. Mismos límites |
ipo_owner | powerobject | null | El objeto visual para el que trabaja esta utilidad: la licencia se comprueba en su clase. Se asigna antes de la primera llamada. Necesario solo en la aplicación de demostración; una clave de desarrollo o de ejecución desbloquea la utilidad sin él. Sin desbloquear, está en modo demo — textos de 2048 caracteres y archivos de 2048 bytes como máximo, tanto a la entrada como a la salida |
Constantes: HASH_MD5, HASH_SHA1, HASH_SHA256, HASH_SHA384, HASH_SHA512 para el algoritmo; KEY_2048, KEY_3072, KEY_4096 para el tamaño de una clave RSA, KEY_RSA_2048, KEY_EC_P256, KEY_ED25519 para el tipo de par; JWT_HS256, JWT_RS256, JWT_ES256, JWT_EDDSA; CHARSET_ALL, CHARSET_ALPHANUM, CHARSET_LETTERS, CHARSET_DIGITS, CHARSET_HEX; KEYFORMAT_TEXT, KEYFORMAT_HEX, KEYFORMAT_BASE64 para la forma de una clave HMAC.
Métodos #
| Método | Función | |
|---|---|---|
of_open ( ) → long | Crea la página oculta. Opcional — cada método lo hace — pero llamarlo al abrir la ventana paga el coste una vez. Devuelve el handle (> 0) o -2 si no se pudo crear (is_last_error dice por qué) | |
of_is_open ( ) → boolean | Verdadero en cuanto existe la página oculta | |
of_hash (string as_algorithm, string as_text) → string | El hash de un texto, en hexadecimal; HASH_* para el algoritmo, leído sin mayúsculas, guiones ni espacios (SHA-256 vale HASH_SHA256). NATIVO para todos los algoritmos: no se abre ninguna página para un hash. Vacío en caso de error | |
of_sha256 (string as_text) → string | of_hash con HASH_SHA256 — el que más se usa | |
of_hmac (string as_algorithm, string as_key, string as_text { , string as_key_format }) → string | El HMAC de un texto bajo una clave secreta, en hexadecimal — lo que una API pide para autenticar una llamada. La clave es un TEXTO por defecto; KEYFORMAT_HEX o KEYFORMAT_BASE64 dan sus BYTES: un secreto entregado codificado, o el resultado de un HMAC anterior (AWS SigV4 encadena cuatro). Una clave vacía se rechaza, y también una clave PEM: nunca es un secreto HMAC | |
of_hmac_verify (string as_algorithm, string as_key, string as_text, string as_expected { , string as_key_format }) → boolean | Verdadero si as_expected es el HMAC de as_text bajo as_key, comparado en TIEMPO CONSTANTE — la verificación de la firma de un webhook (Stripe, GitHub, Shopify) sin delatarla, nunca if of_hmac(…) = cabecera. as_expected en hexadecimal (sin importar mayúsculas) o en base64; quite antes un prefijo como sha256=. Falso en otro caso | |
of_hash_file (string as_algorithm, string as_path) → string | El hash de un archivo del equipo, de cualquier tipo y tamaño (leído por trozos), sin cargarlo en PowerBuilder. Una ruta RELATIVA se busca en la carpeta actual, luego en la carpeta en la que arrancó la aplicación, luego junto al EXE — la regla de todos los archivos de este componente. Vacío en caso de error; is_last_error distingue el algoritmo desconocido del archivo ilegible | |
of_base64_encode (string as_text) → string | Un texto en base64 (sus bytes UTF-8): lo que un navegador o Python escribiría. Vacío en caso de fallo | |
of_base64_decode (string as_base64) → string | El TEXTO de vuelta (UTF-8). Un valor que no es base64, o cuyos bytes no son un texto (un PDF, una imagen), devuelve una cadena vacía e is_last_error — nunca ruido. Los bytes se escriben en un archivo con of_base64_decode_to_file | |
of_base64_encode_file (string as_path) → string | Los bytes de un archivo del equipo en base64 — un adjunto, una imagen para una llamada JSON — leídos por la DLL, nunca cargados en PowerBuilder. Hasta 64 MB en una aplicación de 32 bits (el IDE de PowerBuilder lo es), 512 MB en 64 bits. Vacío si falla; is_last_error dice cuál: archivo demasiado grande para este proceso, ilegible o límite de demo | |
of_base64_decode_to_file (string as_base64, string as_path) → long | Escribe los bytes de un valor base64 en un archivo — el adjunto con el que respondió una API. Un valor VACÍO escribe un archivo vacío; una ruta RELATIVA se escribe en la carpeta en la que arrancó la aplicación (la carpeta actual al cargar la biblioteca, la misma en el IDE y compilada), donde of_hash_file la vuelve a encontrar. Devuelve 0 una vez escrito, -5 con una ruta vacía o que depende de la carpeta actual de una unidad (\x, C:x), o un valor que no es base64, -4 si el archivo no se puede escribir o, en demo, más allá de 2048 bytes | |
of_encrypt (string as_password, string as_text) → string | Cifra un texto con una contraseña: una sola cadena base64 que guardar, que lleva sus rondas PBKDF2 (ver Los formatos). Una contraseña VACÍA se rechaza — cualquiera releería el valor. Vacío en caso de error | |
of_decrypt (string as_password, string as_cipher) → string | El texto de vuelta, con la misma contraseña. Contraseña errónea o valor alterado: cadena vacía e is_last_error, nunca ruido. Un valor cuyo contenido no es un texto (un archivo cifrado con of_encrypt_file) devuelve vacío también: of_decrypt_file | |
of_uuid ( ) → string | Un UUID aleatorio (versión 4) | |
of_random_hex (integer ai_bytes) → string | ai_bytes bytes aleatorios (1 a 4096) en hexadecimal: una sal, un token | |
of_generate_keypair (integer ai_bits, ref string as_public_pem, ref string as_private_pem) → long | Un par RSA en PEM (KEY_2048, KEY_3072, KEY_4096). Devuelve 0 cuando ambas claves están rellenas, -5 con cualquier otro tamaño (ambas quedan vacías), -4 si falla | |
of_sign (string as_private_pem, string as_text) → string | La firma de un texto con la clave privada, en base64. La clave dice su familia: RSA (RSASSA-PKCS1-v1_5, SHA-256), ECDSA P-256 (SHA-256, firma en DER — lo que openssl dgst y Java SHA256withECDSA verifican) o Ed25519 (64 bytes). Vacío en caso de error | |
of_verify (string as_public_pem, string as_text, string as_signature) → boolean | Verdadero si la firma se hizo sobre exactamente este texto con la clave privada correspondiente; falso — no un error — si el texto cambió. Una firma ECDSA se lee en DER (openssl, Java) o en P1363 (`r | s`, 64 bytes: WebCrypto, .NET) |
of_generate_keypair (string as_kind, ref string as_public_pem, ref string as_private_pem) → long | Un par por TIPO: KEY_RSA_2048/3072/4096, KEY_EC_P256 (firmas cortas) o KEY_ED25519. Devuelve 0, -5 con un tipo que no es ninguno de estos (nunca un par RSA en su lugar), -4 en caso de fallo | |
of_key_kind (string as_pem) → string | La familia de una clave PEM: rsa, ec-p256 o ed25519; solo se lee el PRIMER bloque del texto. Una clave PKCS#1 (BEGIN RSA PRIVATE KEY), un certificado o una clave cifrada se NOMBRA en is_last_error, con el comando openssl que la convierte | |
of_base64url_encode (string as_text) → string | base64 seguro para URL (JWT, cadena de consulta) | |
of_base64url_decode (string as_base64url) → string | El texto de vuelta; vacío si no es base64url, o si sus bytes no son un texto | |
of_hex_encode (string as_text) → string | Los bytes UTF-8 de un texto en hexadecimal | |
of_hex_decode (string as_hex) → string | El texto de vuelta; vacío si no es hexadecimal, o si sus bytes no son un texto | |
of_random_password (integer ai_length, string as_charset) → string | Una contraseña aleatoria (4 a 256 caracteres) sobre un juego CHARSET_* (vacío = CHARSET_ALL), sin sesgo. Vacía con una longitud incorrecta o un juego desconocido | |
of_equals_constant_time (string as_a, string as_b) → boolean | Igualdad en tiempo constante: comparar un token o un código sin delatarlo | |
of_password_hash (string as_password) → string | Lo que se GUARDA para una contraseña: PBKDF2 con sal (il_password_hash_rounds, 600.000 rondas), rondas y sal en el valor. Nunca el mismo resultado dos veces. Una contraseña vacía se hashea (verificar una contraseña vacía es legítimo). El formato es PROPIO de PBToolboxAI (ver Los formatos) | |
of_password_verify (string as_password, string as_stored) → boolean | Verdadero si la contraseña es la del valor guardado, comparada en tiempo constante. Un valor cuyas rondas salen de 1 000 a 10 000 000 se rechaza: un valor hostil no hace girar la verificación durante minutos | |
of_totp_secret ( ) → string | Un secreto nuevo para los códigos de dos factores (RFC 6238), en base32 | |
of_totp_code (string as_secret) → string | El código de seis dígitos del momento, el que muestra el autenticador | |
of_totp_verify (string as_secret, string as_code { , ref long al_step }) → boolean | Verdadero si el código es el del instante, del paso anterior o del siguiente. Con al_step, el PASO de 30 s para el que se aceptó el código (0 en otro caso): un código no debe servir dos veces (RFC 6238) — guarde el último paso aceptado para el usuario y rechace un paso que no sea mayor. Un secreto que contiene algo distinto de A-Z, 2-7, espacios o = se rechaza (un 0 tecleado por una O) | |
of_totp_uri (string as_secret, string as_account, string as_issuer) → string | La URI otpauth:// para poner en un código QR e inscribir al usuario | |
of_generate_key ( ) → string | Una clave AES-256 nueva, 32 bytes en hexadecimal | |
of_encrypt_with_key (string as_key, string as_text) → string | AES-256-GCM con una clave EXPLÍCITA (hex o base64): base64 de nonce + cifrado + tag, lo que openssl o Python descifran | |
of_decrypt_with_key (string as_key, string as_cipher) → string | El texto de vuelta con la misma clave; vacío e is_last_error si no | |
of_rsa_encrypt (string as_public_pem, string as_text) → string | Un secreto corto cifrado hacia una clave PÚBLICA: solo la privada lo lee. La configuración EXACTA, para el otro lado: RSA-OAEP, SHA-256, MGF1 con SHA-256, sin etiqueta — Java (OAEPWithSHA-256AndMGF1Padding) y openssl pkeyutl toman MGF1-SHA-1 por defecto, ver Los formatos. 190 bytes como máximo con una clave de 2048 bits; más allá, is_last_error lo dice — cifre los datos con of_encrypt_with_key y solo su clave así. Una clave PRIVADA en lugar de la pública se nombra | |
of_rsa_decrypt (string as_private_pem, string as_cipher) → string | El secreto de vuelta, con la clave privada | |
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 firmado: JWT_HS256 (secreto compartido), JWT_RS256/JWT_ES256/JWT_EDDSA (clave privada); el algoritmo es OBLIGATORIO. iat añadido, exp si la duración es > 0 — un exp en los claims Y una duración se rechaza — y un exp/iat/nbf dado debe ser un número. La cabecera nombra el algoritmo como está registrado (EdDSA). as_key_format: el secreto HS256 en KEYFORMAT_TEXT, KEYFORMAT_HEX o KEYFORMAT_BASE64; una clave PEM nunca es un secreto HS256. as_header_json: miembros AÑADIDOS a la cabecera, por ejemplo {"kid":"2026-09"} — la clave que elige una API (Apple, cualquier proveedor que rota sus claves); alg y typ siguen siendo los de la biblioteca. Vacío en caso de error | |
of_jwt_verify (string as_algorithm, string as_key, string as_token { , string as_key_format }) → string | Los claims (JSON) de un token cuya firma, algoritmo y fechas son correctos. El algoritmo es OBLIGATORIO y se compara EXACTAMENTE con el de la cabecera: "" se rechaza, y una clave PEM nunca es un secreto HS256 — si no, una clave PÚBLICA servía de secreto y un token falsificado pasaba. Un exp o un nbf que no es un número, y una cabecera crit, se rechazan. La audiencia (aud) y el emisor (iss) quedan por VERIFICAR por usted en los claims devueltos. as_key_format como para of_jwt_sign. Vacío e is_last_error en otro caso | |
of_jwt_claims (string as_token) → string | Los claims SIN verificación, para leer a quién nombra el token antes de elegir la clave | |
of_jwt_header (string as_token) → string | La cabecera (JSON) de un token SIN verificación: leer su kid para elegir la clave que lo verificará. No fiarse nunca solo de ella | |
of_protect (string as_text { , boolean ab_machine { , string as_entropy } }) → string | Un secreto protegido por Windows para este usuario (o esta máquina): DPAPI, nativo. Lo que va en el INI para una contraseña de servidor. NO lo protege de otro programa lanzado por el mismo usuario (ni, con ab_machine, de otra cuenta del equipo): as_entropy, un secreto de su aplicación, reduce ese círculo — se exige la misma entropía para releerlo | |
of_unprotect (string as_protected { , string as_entropy }) → string | El texto de vuelta, en la misma cuenta y con la MISMA entropía si se dio una; vacío e is_last_error en otra | |
of_crc32 (string as_text) → string | El CRC32 de un texto, 8 dígitos hexadecimales — un control de integridad, no un hash | |
of_crc32_file (string as_path) → string | El CRC32 de un archivo, leído por la DLL hasta el final: una lectura que falla a mitad es un error, nunca el CRC del principio | |
of_encrypt_file (string as_password, string as_source, string as_target) → long | Un archivo cifrado por contraseña, en nativo y por trozos (el mismo contenedor que of_encrypt), hasta 64 GB — el límite de un contenedor AES-GCM. Rutas relativas: el origen se busca como para of_hash_file, el destino se escribe en la carpeta en la que arrancó la aplicación. Devuelve 0, -5 argumento vacío o il_pbkdf2_rounds fuera de límites, -2 origen ilegible, -4 destino no escribible o, en demo, origen de más de 2048 bytes, -7 origen de más de 64 GB | |
of_decrypt_file (string as_password, string as_source, string as_target) → long | El archivo de vuelta. Devuelve 0, -3 contraseña incorrecta o archivo alterado (no se escribe nada: el texto se comprueba en un archivo temporal oculto que Windows borra aunque la aplicación muera), -5/-2/-4 como of_encrypt_file | |
of_close ( ) | Libera la página oculta; se hace por usted al destruir el objeto | |
of_reset ( ) | Vuelta a los valores por defecto |
Ejemplos #
Comprobar que un archivo no ha cambiado #
// 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 adjunto en base64 para una 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")
Guardar un secreto en 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)
Firmar un pedido, verificarlo en otro sitio #
// 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 para una API, y su verificación #
// 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")
Verificar la firma de 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 contraseña de servidor guardada por Windows, y un usuario con dos factores #
// 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
Buenas prácticas #
- Un solo objeto por ventana, abierto con ella (
of_open): la página oculta cuesta unos cientos de milisegundos la primera vez, nada después. - La contraseña no se guarda:
of_encryptdevuelve todo lo necesario para descifrar, salvo ella — ese es el contrato. - La clave privada no viaja: firme en casa, publique la clave pública.
of_verifysolo necesita esa. - Lea
is_last_errorcuando un método devuelva una cadena vacía: la razón está ahí, y a menudo dice «contraseña errónea» o «archivo ilegible» — no un defecto del componente. - DPAPI protege de OTRA CUENTA, no de otro programa: todo lo que lanza el mismo usuario relee un
of_protect(cualquier cuenta del equipo conab_machine). Pase una entropía propia de la aplicación para reducir ese círculo. - Un JWT se verifica con SU algoritmo, escrito en el código (
n_pbt_crypto.JWT_RS256), nunca leído del token ni de un INI al que le puede faltar: un algoritmo vacío se rechaza, pero es esta elección la que cierra la puerta a la confusión HS/RS. Después verifiqueaudeissen los claims devueltos.