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. - JWK (
of_jwk_to_pem,of_pem_to_jwk,of_jwt_verify_jwks): el JSON de la RFC 7517 —ktyRSA, EC (P-256, P-384) u OKP (Ed25519); un juego de claves (JWKS) es{"keys":[…]}, cada clave nombrada por sukid. - Blobs: los métodos
of_*_blobtrabajan sobre los BYTES, en nativo — los mismos valores que para un archivo que contuviera esos bytes;of_encrypt_blobescribe el contenedor deof_encrypt.
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 |
|---|---|---|---|
il_timeout_ms | long | 20000 | Cuánto puede durar una llamada a la página oculta: generar una clave de 4096 bits, o derivar una clave con millones de rondas PBKDF2, puede tardar unos segundos. Los hashes, los archivos y los blobs son nativos: no se les aplica ningún plazo |
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 (of_get_last_error() lo dice, -5 para un archivo), nunca sustituir Vale también para of_encrypt_blob. Un valor lleva sus rondas: cambiarlas nunca vuelve ilegible un valor más antiguo |
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 Auméntelas, y of_password_needs_rehash dice qué valores almacenados renovar |
il_jwt_leeway_seconds | long | 60 | Tolerancia de of_jwt_verify y of_jwt_verify_jwks sobre exp y nbf, en segundos: los relojes de dos máquinas derivan. De 0 a 86 400; cualquier otro valor hace rechazar la verificación |
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 Los blobs siguen el límite de los archivos |
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_RSA_3072, KEY_RSA_4096, KEY_EC_P256, KEY_EC_P384, KEY_ED25519 para el tipo de par; 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 para el esquema de una firma RSA; 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_get_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 Un argumento NULL cuenta como vacío: el motivo nunca es NULL. | |
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 (of_get_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 También se lee base64url. | |
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 as_expected también puede estar en base64url. | |
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; of_get_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 of_get_last_error() — nunca ruido. Los bytes se escriben en un archivo con of_base64_decode_to_file O of_base64_decode_blob, que los devuelve en un blob. | |
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 32 MB en una aplicación de 32 bits (el IDE de PowerBuilder lo es), 512 MB en 64 bits; más allá, of_base64_encode_file_to_file. Vacío en caso de fallo; of_get_last_error() dice cuál: un archivo demasiado grande para este proceso, ilegible, o el límite de demostración | |
of_base64_encode_file_to_file (string as_source, string as_target) → long | El base64 de un archivo escrito en OTRO archivo (ASCII, sin salto de línea), por la DLL, con memoria constante y sea cual sea el tamaño: lo que envía una subida grande. Rutas como en of_hash_file. Devuelve 0, -5 con una ruta vacía o un destino que depende de la carpeta actual de una unidad (\x, C:x), -2 origen ilegible, -4 destino imposible de escribir o, en demostración, origen de más de 2048 bytes | |
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 of_get_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 o of_decrypt_blob sobre sus bytes. | |
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 as_scheme, string as_hash }) → 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 as_scheme y as_hash: lo que espera la otra parte — SIGN_PSS para una clave RSA (sal de la longitud del hash, MGF1 con el mismo hash), HASH_SHA384 o HASH_SHA512; un texto vacío conserva los de la clave; una clave EC toma un hash, nunca PSS; una clave Ed25519 ni lo uno ni lo otro. Una clave PÚBLICA dada en su lugar se nombra | |
of_verify (string as_public_pem, string as_text, string as_signature { , string as_scheme, string as_hash }) → 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) Con el esquema y el hash del firmante (ver of_sign`). |
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 o KEY_EC_P384 (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, ec-p384 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 of_get_last_error(), con el comando openssl que la convierte | |
of_jwk_to_pem (string as_jwk_json) → string | Una clave publicada como JWK (RFC 7517: kty RSA, EC P-256 / P-384 u OKP Ed25519) en PEM, lo que toman of_verify, of_jwt_verify y openssl: pública en SPKI, privada en PKCS#8 cuando el JWK lleva d. Sus miembros de uso (alg, use, key_ops, kid) no forman parte de la clave. Vacío en caso de fallo | |
of_pem_to_jwk (string as_pem) → string | Una clave PEM como JWK (JSON): lo que publica un juego de claves, o lo que una API pide registrar. Un PEM privado da un JWK privado (con d): guárdelo para usted. Vacío en caso de fallo | |
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_password_needs_rehash (string as_stored) → boolean | Verdadero cuando un valor almacenado por of_password_hash lleva MENOS rondas que il_password_hash_rounds: justo después de un of_password_verify correcto, cuando se conoce la contraseña, vuelva a calcular su hash y almacene el nuevo valor — el consejo de OWASP cuando el coste sube. Falso en caso contrario; falso con of_get_last_error() cuando as_stored no es tal valor. Ninguna página | |
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 as_hash, integer ai_digits, integer ai_period }) → string | El código de seis dígitos del momento, el que muestra el autenticador Con ajustes: HASH_SHA1, HASH_SHA256 o HASH_SHA512, de 6 a 8 cifras, un periodo de 1 a 3 600 segundos — los que escribe of_totp_uri | |
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) Los espacios que teclea el usuario («864 010», como lo muestra la aplicación) se ignoran. | |
of_totp_verify (string as_secret, string as_code, string as_hash, integer ai_digits, integer ai_period, ref long al_step) → boolean | La misma verificación con los ajustes de of_totp_code; al_step cuenta periodos de ai_period segundos | |
of_totp_uri (string as_secret, string as_account, string as_issuer { , string as_hash, integer ai_digits, integer ai_period }) → string | La URI otpauth:// para poner en un código QR e inscribir al usuario El secreto se escribe en base32 en mayúsculas, sin espacios ni =; un secreto con otro carácter se rechaza. Con los ajustes de of_totp_code, la aplicación muestra los códigos que este calcula con ellos | |
of_hotp_code (string as_secret, long al_counter) → string | El código de seis cifras de un CONTADOR (HOTP, RFC 4226, SHA-1): un token físico, un código enviado por correo — el servidor guarda el contador y lo incrementa con cada código aceptado. al_counter: 0 o más. Vacío en caso de fallo | |
of_generate_key ( ) → string | Una clave AES-256 nueva, 32 bytes en hexadecimal | |
of_encrypt_with_key (string as_key, string as_text { , string as_aad }) → string | AES-256-GCM con una clave EXPLÍCITA (hex o base64): base64 de nonce + cifrado + tag, lo que openssl o Python descifran as_aad: DATOS ADICIONALES, autenticados pero no cifrados — un número de pedido, un identificador: el valor solo se descifra con los mismos. La clave también puede estar en base64url | |
of_decrypt_with_key (string as_key, string as_cipher { , string as_aad }) → string | El texto de vuelta con la misma clave; vacío e of_get_last_error() si no Con los MISMOS datos adicionales cuando los había. | |
of_rsa_encrypt (string as_public_pem, string as_text { , string as_hash }) → 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á, of_get_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 as_hash: el hash de OAEP y de MGF1 — HASH_SHA1 es lo que usan por defecto openssl pkeyutl, .NET (OaepSHA1) y Java (OAEPPadding) | |
of_rsa_decrypt (string as_private_pem, string as_cipher { , string as_hash }) → string | El secreto de vuelta, con la clave privada Con el hash del remitente (HASH_SHA256 por defecto). | |
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 También JWT_HS384/HS512, JWT_RS384/RS512, JWT_PS256/PS384/PS512 y JWT_ES384 (clave KEY_EC_P384). Una clave de la familia equivocada, o su mitad PÚBLICA, se nombra | |
of_jwt_verify (string as_algorithm, string as_key, string as_token { , string as_key_format { , string as_expected_claims_json } }) → string | Los claims (JSON) de un token cuya firma, algoritmo y fechas son correctos (il_jwt_leeway_seconds de tolerancia, un minuto por defecto). El algoritmo es OBLIGATORIO y se compara EXACTAMENTE con el de la cabecera: "" se rechaza, y una clave PEM nunca es un secreto HS — 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. as_key_format como en of_jwt_sign. as_expected_claims_json: los claims que deben estar con ese valor — el emisor (iss), la audiencia (aud, encontrada también en una lista); un claim ausente o distinto se rechaza y se nombra. Vacío e of_get_last_error() en caso contrario; un claim se lee con n_pbt_json (of_load, luego of_get_string) | |
of_jwt_verify_jwks (string as_algorithm, string as_jwks_json, string as_token { , string as_expected_claims_json }) → string | Los claims de un token verificado con un JUEGO DE CLAVES (JWKS, RFC 7517) — lo que publican Azure AD, Google, Auth0 o Keycloak en su jwks_uri (léalo con el restclient, guárdelo unas horas): el kid del token elige la clave. Un token sin kid, un kid ausente del juego o presente dos veces, una clave de otro algoritmo o de otro uso (use = enc): rechazados, of_get_last_error() dice cuál. Fechas y claims esperados como en of_jwt_verify | |
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 of_get_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 Un destino que depende de la carpeta actual de una unidad (\x, C:x) devuelve -5, e of_get_last_error() lo dice | |
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_base64_encode_blob (blob ab_data) → string | Los bytes de un blob en base64 — una foto leída con SelectBlob, un PDF recibido — sin archivo. Nativo. Hasta 32 MB en 32 bits, 512 MB en 64 bits; en demostración, 2048 bytes. Vacío en caso de fallo | |
of_base64_decode_blob (string as_base64, ref blob ab_data) → long | Los BYTES de un valor base64 (o base64url) en un blob — un adjunto recibido, listo para UpdateBlob. Nativo. Devuelve 0 una vez lleno ab_data, -5 cuando el valor no es base64, -4 en demostración por encima de 2048 bytes; ab_data queda vacío | |
of_hash_blob (string as_algorithm, blob ab_data) → string | El hash de los bytes de un blob, con los algoritmos de of_hash — el mismo valor que of_hash_file sobre los mismos bytes. Nativo. Vacío en caso de fallo | |
of_hmac_blob (string as_algorithm, string as_key, blob ab_data { , string as_key_format }) → string | El HMAC de los bytes de un blob con una clave secreta, la clave como en of_hmac: un archivo recibido, verificado con la firma que el servicio envió con él. Nativo. Vacío en caso de fallo | |
of_crc32_blob (blob ab_data) → string | El CRC32 de los bytes de un blob, 8 cifras hexadecimales. Nativo | |
of_encrypt_blob (string as_password, blob ab_data, ref blob ab_cipher) → long | Los bytes de un blob cifrados por contraseña: el contenedor de of_encrypt y de of_encrypt_file, en bytes (52 más que los datos) — leído en base64, se descifra con of_decrypt, y al revés. Nativo, il_pbkdf2_rounds. Devuelve 0 una vez lleno ab_cipher, -5 contraseña vacía o rondas fuera de límites, -4 en caso de fallo o, en demostración, por encima de 2048 bytes | |
of_decrypt_blob (string as_password, blob ab_cipher, ref blob ab_data) → long | Los bytes de of_encrypt_blob (o de un valor de of_encrypt leído en bytes), con la misma contraseña. Devuelve 0 una vez lleno ab_data, -3 contraseña incorrecta o bytes alterados (ab_data queda vacío), -5 contraseña vacía o bytes que no son tal valor, -4 en demostración por encima de 2048 bytes | |
of_close ( ) → long | Libera la página oculta; se hace por usted al destruir el objeto Devuelve 0 una vez hecho. | |
of_reset ( ) → long | Vuelve a los valores por defecto: il_timeout_ms, il_pbkdf2_rounds, il_password_hash_rounds, il_jwt_leeway_seconds, of_get_last_error(); ipo_owner se mantiene Devuelve 0 una vez hecho. |
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 #
// 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
Guardar un secreto en un INI #
// 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())
Firmar un pedido, verificarlo en otro sitio #
// 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.")
Un token para una API, y su verificación #
// 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
Verificar la firma de un webhook #
// 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
Una contraseña de servidor guardada por Windows, y un usuario con dos factores #
// 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
Verificar un token de Azure AD, Google o Auth0 (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())
Una foto de la base de datos, en 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
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
of_get_last_error()cuando 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. Indique después el emisor y la audiencia esperados (as_expected_claims_json): la verificación los rechaza si faltan.