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 #
n_pbt_crypto lnv_crypto
string ls_hash, ls_cipher
lnv_crypto = create n_pbt_crypto
// The fingerprint of a text, in hexadecimal
ls_hash = lnv_crypto.of_sha256("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("s3cret", "The safe code is 4152.")
MessageBox("Back", lnv_crypto.of_decrypt("s3cret", ls_cipher))
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 la sal (16 bytes), el nonce (12) y el cifrado AES-256-GCM; la clave se deriva de la contraseña con PBKDF2-SHA-256, 100 000 vueltas. Bastaof_decryptcon la misma contraseña, en cualquier equipo.- Claves: PEM, pública en SPKI (
BEGIN PUBLIC KEY), privada en PKCS#8 (BEGIN PRIVATE KEY); firmas RSASSA-PKCS1-v1_5 con SHA-256, en base64 — lo queopenssllee y verifica. - Sin MD5: la Web Crypto no lo ofrece, y nada debería pedirlo ya.
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, of_password_hash y of_encrypt_file: unos 100 ms. Más es más lento para todos, atacante incluido |
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.
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 un código negativo |
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. Vacío si falla |
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 | El HMAC de un texto bajo una clave secreta, en hexadecimal — lo que una API pide para autenticar una llamada |
of_hash_file (string as_algorithm, string as_path) → string | El hash de un archivo del equipo, de cualquier tipo y hasta 512 MB, sin cargarlo en PowerBuilder |
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; un valor que no es base64 devuelve una cadena vacía e is_last_error, nunca basura |
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 — hasta 512 MB, leídos por la DLL, nunca cargados en PowerBuilder |
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. Devuelve 0 una vez escrito, -5 con una ruta vacía o un valor que no es base64, -4 si el archivo no se puede escribir |
of_encrypt (string as_password, string as_text) → string | Cifra un texto con una contraseña: una sola cadena base64 que guardar. Vacío si falla |
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 basura |
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_*). Devuelve 0 cuando ambas claves están rellenas, -4 si falla |
of_sign (string as_private_pem, string as_text) → string | La firma de un texto por la clave privada, en base64. Vacío si falla |
of_verify (string as_public_pem, string as_text, string as_signature) → boolean | Verdadero si la firma se hizo exactamente sobre este texto con la clave privada correspondiente; falso — no un error — si el texto cambió |
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, -4 en caso de fallo |
of_key_kind (string as_pem) → string` | La familia de una clave PEM: rsa, ec-p256 o ed25519 |
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 |
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 |
of_random_password (integer ai_length, string as_charset) → string` | Una contraseña aleatoria (4 a 256 caracteres) sobre un juego CHARSET_*, sin sesgo. Vacía con una longitud incorrecta |
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_pbkdf2_rounds), rondas y sal en el valor. Nunca el mismo resultado dos veces |
of_password_verify (string as_password, string as_stored) → boolean` | Verdadero si la contraseña es la del valor guardado, comparada en tiempo constante |
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) → boolean` | Verdadero si el código es el del momento, del paso anterior o del siguiente |
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 (RSA-OAEP): solo la privada lo lee |
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` | Un JWT firmado: JWT_HS256 (secreto compartido), JWT_RS256/JWT_ES256/JWT_EDDSA (clave privada); iat añadido, exp si la duración es > 0. Vacío en caso de fallo |
of_jwt_verify (string as_algorithm, string as_key, string as_token) → string` | Los claims (JSON) de un token cuya firma, algoritmo y fechas son correctos; vacío e is_last_error si no |
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_protect (string as_text { , boolean ab_machine }) → 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 |
of_unprotect (string as_protected) → string` | El texto de vuelta, en la misma cuenta; 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 |
of_encrypt_file (string as_password, string as_source, string as_target) → long` | Un archivo cifrado por contraseña, en nativo (mismo contenedor que of_encrypt). Devuelve 0, -5 argumento vacío, -2 origen ilegible, -4 destino no escribible |
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), -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 #
string ls_expected, ls_actual
ls_expected = ProfileString("deploy.ini", "files", "orders.pbd", "")
ls_actual = lnv_crypto.of_hash_file(n_pbt_crypto.HASH_SHA256, "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("filename", "invoice_4152.pdf")
lnv_j.of_set_string("content", lnv_crypto.of_base64_encode_file("C:\invoices\4152.pdf"))
lnv_rest.of_post("https://api.example.com/documents", lnv_j.of_text())
// Receiving : the answer's attachment, back on disk
lnv_crypto.of_base64_decode_to_file(lnv_rest.of_json_value("content"), "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(ls_master, 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(ls_master, ProfileString("app.ini", "db", "password", ""))
if ls_db_password = "" then MessageBox("Login", lnv_crypto.is_last_error)
Firmar un pedido, verificarlo en otro sitio #
string ls_public, ls_private, ls_signature
lnv_crypto.of_generate_keypair(n_pbt_crypto.KEY_2048, ls_public, ls_private) // once ; keep ls_private
ls_signature = lnv_crypto.of_sign(ls_private, ls_order_json)
// The public key and the signature travel with the order ; anyone can check :
if not lnv_crypto.of_verify(ls_public, ls_order_json, 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(n_pbt_crypto.JWT_HS256, ls_api_secret, '{"sub":"guillaume","role":"admin"}', 3600)
lnv_rest.of_set_bearer(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(n_pbt_crypto.JWT_HS256, ls_api_secret, ls_token)
if ls_claims = "" then MessageBox("API", lnv_crypto.is_last_error)
ls_role = gnv_utils.of_json_get_str(ls_claims, "role")
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
SetProfileString("app.ini", "db", "password", lnv_crypto.of_protect(ls_db_password))
// At run time
ls_db_password = lnv_crypto.of_unprotect(ProfileString("app.ini", "db", "password", ""))
// 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(ls_secret, ls_login, "MyApp")
// Logging in : the stored hash, then the six digits
if lnv_crypto.of_password_verify(ls_typed, ls_stored_hash) and lnv_crypto.of_totp_verify(ls_secret, ls_six_digits) then ...
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.