PBToolboxAI v4 ← Site

crypto — n_pbt_crypto #

← Referência dos componentes · Índice do guia

Hashes, HMAC, cifra por palavra-passe, valores aleatórios e assinaturas RSA — a Web Crypto API do motor WebView2, que o seu PowerBuilder 10 não tem em mais lado nenhum. Sem DLL de terceiros, sem serviço: o que o posto já sabe fazer, oferecido ao PowerScript.

▶ Ver ao vivo — Aplicação de demonstração, mosaico Crypto: os resultados, o código que os produz e esta página, lado a lado.


Em resumo #

Objeto não visualn_pbt_crypto
Serve paraVerificar que um ficheiro não mudou, guardar um segredo num INI, assinar uma encomenda, autenticar uma chamada a uma API
PrincípioUma página oculta faz o cálculo; cada método é uma chamada PowerScript normal que devolve o seu valor
DependênciaO runtime WebView2, já exigido pela biblioteca — mais nada

Início 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 é uma chamada que devolve o seu valor: sem evento, sem espera a escrever.


Os formatos, legíveis pelas outras ferramentas #

Decifrar um valor de of_encrypt do lado do servidor, em 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")

Propriedades #

PropriedadeTipoPredefiniçãoFunção
is_last_errorstring""Porque a última chamada devolveu uma cadeia vazia, false ou um código negativo: palavra-passe errada, ficheiro ilegível, algoritmo desconhecido, limite demo
il_timeout_mslong20000Duração máxima de uma chamada. Calcular o hash de um ficheiro grande ou gerar uma chave de 4096 bits pode demorar alguns segundos
il_pbkdf2_roundslong100000Rondas PBKDF2 de of_encrypt e of_encrypt_file: cerca de 100 ms, pagos em cada leitura do valor. De 1.000 a 10.000.000: qualquer outro valor faz RECUSAR a chamada (is_last_error di-lo, -5 para um ficheiro), nunca substituir
il_password_hash_roundslong600000Rondas PBKDF2 de of_password_hash: 600.000, o número da OWASP para PBKDF2-HMAC-SHA-256 (cerca de 0,6 s). Um hash guardado é o que um atacante força offline se a tabela vazar; verifica-se uma só vez por início de sessão. Mesmos limites
ipo_ownerpowerobjectnullO objeto visual para o qual este utilitário trabalha: a licença verifica-se na sua classe. Define-se antes da primeira chamada. Necessário apenas na aplicação de demonstração; uma chave de desenvolvimento ou de execução desbloqueia o utilitário sem ele. Não desbloqueado, está em modo demo — textos de 2048 caracteres e ficheiros de 2048 bytes no máximo, tanto à entrada como à saída

Constantes: HASH_MD5, HASH_SHA1, HASH_SHA256, HASH_SHA384, HASH_SHA512 para o algoritmo; KEY_2048, KEY_3072, KEY_4096 para o tamanho de uma chave RSA, KEY_RSA_2048, KEY_EC_P256, KEY_ED25519 para o 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 a forma de uma chave HMAC.


Métodos #

MétodoFunção
of_open ( ) → longCria a página oculta. Facultativo — cada método fá-lo — mas chamá-lo ao abrir a janela paga o custo uma vez. Devolve o handle (> 0) ou -2 se não foi possível criá-la (is_last_error diz porquê)
of_is_open ( ) → booleanVerdadeiro assim que a página oculta existe
of_hash (string as_algorithm, string as_text) → stringO hash de um texto, em hexadecimal; HASH_* para o algoritmo, lido sem maiúsculas, hífenes nem espaços (SHA-256 vale HASH_SHA256). NATIVO para todos os algoritmos: nenhuma página é aberta para um hash. Vazio em caso de falha
of_sha256 (string as_text) → stringof_hash com HASH_SHA256 — o que mais se usa
of_hmac (string as_algorithm, string as_key, string as_text { , string as_key_format }) → stringO HMAC de um texto sob uma chave secreta, em hexadecimal — o que uma API pede para autenticar uma chamada. A chave é um TEXTO por omissão; KEYFORMAT_HEX ou KEYFORMAT_BASE64 dão os seus BYTES: um segredo entregue codificado, ou o resultado de um HMAC anterior (AWS SigV4 encadeia quatro). Uma chave vazia é recusada, e também uma chave PEM: nunca é um segredo HMAC
of_hmac_verify (string as_algorithm, string as_key, string as_text, string as_expected { , string as_key_format }) → booleanVerdadeiro se as_expected é o HMAC de as_text sob as_key, comparado em TEMPO CONSTANTE — a verificação da assinatura de um webhook (Stripe, GitHub, Shopify) sem a denunciar, nunca if of_hmac(…) = cabeçalho. as_expected em hexadecimal (maiúsculas indiferentes) ou em base64; retire antes um prefixo como sha256=. Falso caso contrário
of_hash_file (string as_algorithm, string as_path) → stringO hash de um ficheiro do posto, de qualquer tipo e tamanho (lido por pedaços), sem o carregar no PowerBuilder. Um caminho RELATIVO procura-se na pasta atual, depois na pasta em que a aplicação arrancou, depois ao lado do EXE — a regra de todos os ficheiros deste componente. Vazio em caso de falha; is_last_error distingue o algoritmo desconhecido do ficheiro ilegível
of_base64_encode (string as_text) → stringUm texto em base64 (os seus bytes UTF-8): o que um navegador ou o Python escreveria. Vazio em caso de falha
of_base64_decode (string as_base64) → stringO TEXTO de volta (UTF-8). Um valor que não é base64, ou cujos bytes não são um texto (um PDF, uma imagem), devolve uma cadeia vazia e is_last_error — nunca ruído. Os bytes escrevem-se num ficheiro com of_base64_decode_to_file
of_base64_encode_file (string as_path) → stringOs bytes de um ficheiro do posto em base64 — um anexo, uma imagem para uma chamada JSON — lidos pela DLL, nunca carregados no PowerBuilder. Até 64 MB numa aplicação de 32 bits (o IDE do PowerBuilder é uma), 512 MB em 64 bits. Vazio em caso de falha; is_last_error diz qual: ficheiro grande demais para este processo, ilegível ou limite de demo
of_base64_decode_to_file (string as_base64, string as_path) → longEscreve os bytes de um valor base64 num ficheiro — o anexo com que uma API respondeu. Um valor VAZIO escreve um ficheiro vazio; um caminho RELATIVO é escrito na pasta em que a aplicação arrancou (a pasta atual ao carregar a biblioteca, a mesma no IDE e compilada), onde of_hash_file o reencontra. Devolve 0 uma vez escrito, -5 num caminho vazio ou que depende da pasta atual de uma unidade (\x, C:x), ou um valor que não é base64, -4 se o ficheiro não puder ser escrito ou, em demo, acima de 2048 bytes
of_encrypt (string as_password, string as_text) → stringCifra um texto com uma palavra-passe: uma única cadeia base64 a guardar, que leva as suas rondas PBKDF2 (ver Os formatos). Uma palavra-passe VAZIA é recusada — qualquer um releria o valor. Vazio em caso de falha
of_decrypt (string as_password, string as_cipher) → stringO texto de volta, com a mesma palavra-passe. Palavra-passe errada ou valor alterado: cadeia vazia e is_last_error, nunca ruído. Um valor cujo conteúdo não é um texto (um ficheiro cifrado por of_encrypt_file) devolve vazio também: of_decrypt_file
of_uuid ( ) → stringUm UUID aleatório (versão 4)
of_random_hex (integer ai_bytes) → stringai_bytes bytes aleatórios (1 a 4096) em hexadecimal: um sal, um token
of_generate_keypair (integer ai_bits, ref string as_public_pem, ref string as_private_pem) → longUm par RSA em PEM (KEY_2048, KEY_3072, KEY_4096). Devolve 0 quando ambas as chaves estão preenchidas, -5 em qualquer outro tamanho (ambas ficam vazias), -4 em caso de falha
of_sign (string as_private_pem, string as_text) → stringA assinatura de um texto pela chave privada, em base64. A chave diz a sua família: RSA (RSASSA-PKCS1-v1_5, SHA-256), ECDSA P-256 (SHA-256, assinatura em DER — o que o openssl dgst e o Java SHA256withECDSA verificam) ou Ed25519 (64 bytes). Vazio em caso de falha
of_verify (string as_public_pem, string as_text, string as_signature) → booleanVerdadeiro se a assinatura foi feita exatamente sobre este texto pela chave privada correspondente; falso — não um erro — se o texto mudou. Uma assinatura ECDSA lê-se em DER (openssl, Java) ou em P1363 (`rs`, 64 bytes: WebCrypto, .NET)
of_generate_keypair (string as_kind, ref string as_public_pem, ref string as_private_pem) → longUm par por TIPO: KEY_RSA_2048/3072/4096, KEY_EC_P256 (assinaturas curtas) ou KEY_ED25519. Devolve 0, -5 num tipo que não é nenhum destes (nunca um par RSA em vez dele), -4 em caso de falha
of_key_kind (string as_pem) → stringA família de uma chave PEM: rsa, ec-p256 ou ed25519; só se lê o PRIMEIRO bloco do texto. Uma chave PKCS#1 (BEGIN RSA PRIVATE KEY), um certificado ou uma chave cifrada é NOMEADA em is_last_error, com o comando openssl que a converte
of_base64url_encode (string as_text) → stringbase64 seguro para URL (JWT, query string)
of_base64url_decode (string as_base64url) → stringO texto de volta; vazio se não for base64url, ou se os seus bytes não forem um texto
of_hex_encode (string as_text) → stringOs bytes UTF-8 de um texto em hexadecimal
of_hex_decode (string as_hex) → stringO texto de volta; vazio se não for hexadecimal, ou se os seus bytes não forem um texto
of_random_password (integer ai_length, string as_charset) → stringUma palavra-passe aleatória (4 a 256 caracteres) num conjunto CHARSET_* (vazio = CHARSET_ALL), sem viés. Vazia num comprimento errado ou num conjunto desconhecido
of_equals_constant_time (string as_a, string as_b) → booleanIgualdade em tempo constante: comparar um token ou um código sem o trair
of_password_hash (string as_password) → stringO que se GUARDA para uma palavra-passe: PBKDF2 com sal (il_password_hash_rounds, 600.000 rondas), rondas e sal no valor. Nunca o mesmo resultado duas vezes. Uma palavra-passe vazia faz hash (verificar uma palavra-passe vazia é legítimo). O formato é PRÓPRIO do PBToolboxAI (ver Os formatos)
of_password_verify (string as_password, string as_stored) → booleanVerdadeiro se a palavra-passe é a do valor guardado, comparada em tempo constante. Um valor cujas rondas saiam de 1 000 a 10 000 000 é recusado: um valor hostil não faz a verificação correr minutos
of_totp_secret ( ) → stringUm segredo novo para os códigos de dois fatores (RFC 6238), em base32
of_totp_code (string as_secret) → stringO código de seis dígitos do momento, o que o autenticador mostra
of_totp_verify (string as_secret, string as_code { , ref long al_step }) → booleanVerdadeiro se o código é o do instante, do passo anterior ou do seguinte. Com al_step, o PASSO de 30 s para o qual o código foi aceite (0 caso contrário): um código não deve servir duas vezes (RFC 6238) — guarde o último passo aceite para o utilizador e recuse um passo que não seja maior. Um segredo que contenha algo além de A-Z, 2-7, espaços ou = é recusado (um 0 digitado por um O)
of_totp_uri (string as_secret, string as_account, string as_issuer) → stringO URI otpauth:// a colocar num código QR para inscrever o utilizador
of_generate_key ( ) → stringUma chave AES-256 nova, 32 bytes em hexadecimal
of_encrypt_with_key (string as_key, string as_text) → stringAES-256-GCM com uma chave EXPLÍCITA (hex ou base64): base64 de nonce + cifrado + tag, o que o openssl ou o Python decifram
of_decrypt_with_key (string as_key, string as_cipher) → stringO texto de volta com a mesma chave; vazio e is_last_error caso contrário
of_rsa_encrypt (string as_public_pem, string as_text) → stringUm segredo curto cifrado para uma chave PÚBLICA: só a privada o lê. A configuração EXATA, para o outro lado: RSA-OAEP, SHA-256, MGF1 com SHA-256, sem label — o Java (OAEPWithSHA-256AndMGF1Padding) e o openssl pkeyutl usam MGF1-SHA-1 por omissão, ver Os formatos. 190 bytes no máximo com uma chave de 2048 bits; além disso, is_last_error di-lo — cifre os dados com of_encrypt_with_key e só a sua chave assim. Uma chave PRIVADA no lugar da pública é nomeada
of_rsa_decrypt (string as_private_pem, string as_cipher) → stringO segredo de volta, com a chave 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 } }) → stringUm JWT assinado: JWT_HS256 (segredo partilhado), JWT_RS256/JWT_ES256/JWT_EDDSA (chave privada); o algoritmo é OBRIGATÓRIO. iat acrescentado, exp se a duração for > 0 — um exp nos claims E uma duração é recusado — e um exp/iat/nbf fornecido tem de ser um número. O cabeçalho nomeia o algoritmo como registado (EdDSA). as_key_format: o segredo HS256 em KEYFORMAT_TEXT, KEYFORMAT_HEX ou KEYFORMAT_BASE64; uma chave PEM nunca é um segredo HS256. as_header_json: membros ACRESCENTADOS ao cabeçalho, por exemplo {"kid":"2026-09"} — a chave que uma API escolhe (Apple, qualquer fornecedor que roda as suas chaves); alg e typ continuam os da biblioteca. Vazio em caso de falha
of_jwt_verify (string as_algorithm, string as_key, string as_token { , string as_key_format }) → stringOs claims (JSON) de um token cuja assinatura, algoritmo e datas estão certos. O algoritmo é OBRIGATÓRIO e comparado EXATAMENTE com o do cabeçalho: "" é recusado, e uma chave PEM nunca é um segredo HS256 — caso contrário uma chave PÚBLICA servia de segredo e um token forjado passava. Um exp ou nbf que não seja um número, e um cabeçalho crit, são recusados. A audiência (aud) e o emissor (iss) ficam por VERIFICAR por si nos claims devolvidos. as_key_format como para of_jwt_sign. Vazio e is_last_error caso contrário
of_jwt_claims (string as_token) → stringOs claims SEM verificação, para ler quem o token nomeia antes de escolher a chave
of_jwt_header (string as_token) → stringO cabeçalho (JSON) de um token SEM verificação: ler o seu kid para escolher a chave que o verificará. Nunca confiar só nele
of_protect (string as_text { , boolean ab_machine { , string as_entropy } }) → stringUm segredo protegido pelo Windows para este utilizador (ou esta máquina): DPAPI, nativo. O que se guarda no INI para uma palavra-passe de servidor. NÃO o protege de outro programa lançado pelo mesmo utilizador (nem, com ab_machine, de outra conta do posto): as_entropy, um segredo da sua aplicação, reduz esse círculo — a mesma entropia é exigida para o reler
of_unprotect (string as_protected { , string as_entropy }) → stringO texto de volta, na mesma conta e com a MESMA entropia se houver uma; vazio e is_last_error noutro lado
of_crc32 (string as_text) → stringO CRC32 de um texto, 8 dígitos hexadecimais — um controlo de integridade, não um hash
of_crc32_file (string as_path) → stringO CRC32 de um ficheiro, lido pela DLL até ao fim: uma leitura que falha a meio é um erro, nunca o CRC do início
of_encrypt_file (string as_password, string as_source, string as_target) → longUm ficheiro cifrado por palavra-passe, em nativo e por pedaços (o mesmo contentor que of_encrypt), até 64 GB — o limite de um contentor AES-GCM. Caminhos relativos: a origem procura-se como para of_hash_file, o destino escreve-se na pasta em que a aplicação arrancou. Devolve 0, -5 argumento vazio ou il_pbkdf2_rounds fora dos limites, -2 origem ilegível, -4 destino não gravável ou, em demo, origem acima de 2048 bytes, -7 origem acima de 64 GB
of_decrypt_file (string as_password, string as_source, string as_target) → longO ficheiro de volta. Devolve 0, -3 palavra-passe errada ou ficheiro alterado (nada é escrito: o texto é verificado num ficheiro temporário oculto que o Windows apaga mesmo que a aplicação morra), -5/-2/-4 como of_encrypt_file
of_close ( )Liberta a página oculta; feito por si ao destruir o objeto
of_reset ( )Regresso aos valores por defeito

Exemplos #

Verificar que um ficheiro não mudou #

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

Um anexo em base64 para uma 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 um segredo num 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)

Assinar uma encomenda, verificá-la noutro lado #

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

Um token para uma API, e a sua verificação #

// 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 a assinatura de um 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

Uma palavra-passe de servidor guardada pelo Windows, e um utilizador com dois fatores #

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

Boas práticas #

← Referência dos componentes · Índice do guia