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 visual | n_pbt_crypto |
| Serve para | Verificar que um ficheiro não mudou, guardar um segredo num INI, assinar uma encomenda, autenticar uma chamada a uma API |
| Princípio | Uma página oculta faz o cálculo; cada método é uma chamada PowerScript normal que devolve o seu valor |
| Dependência | O 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 #
- Hashes e HMAC: hexadecimal em minúsculas, o texto lido em UTF-8 — Python, Java e
openssl dgstdão o mesmo valor. of_encrypt: uma única cadeia base64 que leva, por esta ordem,PBK2(4 bytes), o número de rondas PBKDF2 (4 bytes, big-endian), o sal (16), o nonce (12), o cifrado AES-256-GCM e depois o seu tag (16 bytes, no fim). A chave é derivada da palavra-passe (UTF-8) por PBKDF2-HMAC-SHA-256 com ESSAS rondas —il_pbkdf2_roundsno momento da cifra. Bastaof_decryptcom a mesma palavra-passe, em qualquer posto;of_encrypt_fileescreve exatamente o mesmo contentor, em bytes.of_password_hash:pbkdf2-sha256$rondas$sal$hash, sal e hash em base64 padrão. Este formato é PRÓPRIO do PBToolboxAI: nem o passlib ($pbkdf2-sha256$…) nem o Django (pbkdf2_sha256$…) o leem tal como está.- Chaves e assinaturas: PEM, pública em SPKI (
BEGIN PUBLIC KEY), privada em PKCS#8 (BEGIN PRIVATE KEY); assinaturas em base64, segundo a família da chave — RSA: RSASSA-PKCS1-v1_5 com SHA-256; ECDSA P-256: SHA-256, em DER (openssl dgst -sha256 -verify, JavaSHA256withECDSA); Ed25519: 64 bytes.of_verifylê uma assinatura ECDSA em DER ou em P1363 (r | s); o JWT ES256 continua em P1363, como exige a RFC 7518. - RSA-OAEP (
of_rsa_encrypt): SHA-256, MGF1 com SHA-256, sem label. Em Java:OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT); com openssl:-pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 -pkeyopt rsa_mgf1_md:sha256. Uma chave PKCS#1 (BEGIN RSA PRIVATE KEY) converte-se comopenssl pkcs8 -topk8 -nocrypt. - MD5 é oferecido em NATIVO (
HASH_MD5) para as trocas antigas que o exigem — nunca para uma palavra-passe nem para uma integridade face a um adversário. - JWK (
of_jwk_to_pem,of_pem_to_jwk,of_jwt_verify_jwks): o JSON da RFC 7517 —ktyRSA, EC (P-256, P-384) ou OKP (Ed25519); um conjunto de chaves (JWKS) é{"keys":[…]}, cada chave nomeada pelo seukid. - Blobs: os métodos
of_*_blobtrabalham sobre os BYTES, em nativo — os mesmos valores que para um ficheiro que contivesse esses bytes;of_encrypt_blobescreve o contentor deof_encrypt.
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 #
| Propriedade | Tipo | Predefinição | Função |
|---|---|---|---|
il_timeout_ms | long | 20000 | Quanto pode durar uma chamada à página oculta: gerar uma chave de 4096 bits, ou derivar uma chave com milhões de rondas PBKDF2, pode levar alguns segundos. Os hashes, os ficheiros e os blobs são nativos: nenhum prazo se aplica a eles |
il_pbkdf2_rounds | long | 100000 | Rondas 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 (of_get_last_error() di-lo, -5 para um ficheiro), nunca substituir Vale também para of_encrypt_blob. Um valor leva as suas rondas: mudá-las nunca torna ilegível um valor mais antigo |
il_password_hash_rounds | long | 600000 | Rondas 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 Aumente-as, e of_password_needs_rehash diz que valores guardados renovar |
il_jwt_leeway_seconds | long | 60 | Tolerância de of_jwt_verify e of_jwt_verify_jwks sobre exp e nbf, em segundos: os relógios de duas máquinas derivam. De 0 a 86 400; qualquer outro valor faz recusar a verificação |
ipo_owner | powerobject | null | O 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 Os blobs seguem o limite dos ficheiros |
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_RSA_3072, KEY_RSA_4096, KEY_EC_P256, KEY_EC_P384, KEY_ED25519 para o 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 o esquema de uma assinatura RSA; 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étodo | Função | |
|---|---|---|
of_get_last_error ( ) → string | Porque a última chamada devolveu uma cadeia vazia, false ou um código negativo: palavra-passe errada, ficheiro ilegível, algoritmo desconhecido, limite demo Um argumento NULL conta como vazio: o motivo nunca é NULL. | |
of_open ( ) → long | Cria 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 (of_get_last_error() diz porquê) | |
of_is_open ( ) → boolean | Verdadeiro assim que a página oculta existe | |
of_hash (string as_algorithm, string as_text) → string | O 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) → string | of_hash com HASH_SHA256 — o que mais se usa | |
of_hmac (string as_algorithm, string as_key, string as_text { , string as_key_format }) → string | O 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 O base64url também é lido. | |
of_hmac_verify (string as_algorithm, string as_key, string as_text, string as_expected { , string as_key_format }) → boolean | Verdadeiro 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 as_expected também pode estar em base64url. | |
of_hash_file (string as_algorithm, string as_path) → string | O 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; of_get_last_error() distingue o algoritmo desconhecido do ficheiro ilegível | |
of_base64_encode (string as_text) → string | Um 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) → string | O 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 of_get_last_error() — nunca ruído. Os bytes escrevem-se num ficheiro com of_base64_decode_to_file Ou of_base64_decode_blob, que os devolve num blob. | |
of_base64_encode_file (string as_path) → string | Os bytes de um ficheiro do posto em base64 — um anexo, uma imagem para uma chamada JSON — lidos pela DLL, nunca carregados no PowerBuilder. Até 32 MB numa aplicação de 32 bits (o IDE PowerBuilder é uma), 512 MB em 64 bits; para lá disso, of_base64_encode_file_to_file. Vazio em caso de falha; of_get_last_error() diz qual: um ficheiro grande demais para este processo, ilegível, ou o limite de demonstração | |
of_base64_encode_file_to_file (string as_source, string as_target) → long | O base64 de um ficheiro escrito noutro ficheiro (ASCII, sem quebra de linha), pela DLL, em memória constante e seja qual for o tamanho: o que um grande envio manda. Caminhos como em of_hash_file. Devolve 0, -5 num caminho vazio ou num destino que depende da pasta atual de uma unidade (\x, C:x), -2 origem ilegível, -4 destino impossível de escrever ou, em demonstração, origem com mais de 2048 bytes | |
of_base64_decode_to_file (string as_base64, string as_path) → long | Escreve 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) → string | Cifra 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) → string | O texto de volta, com a mesma palavra-passe. Palavra-passe errada ou valor alterado: cadeia vazia e of_get_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 ou of_decrypt_blob sobre os seus bytes. | |
of_uuid ( ) → string | Um UUID aleatório (versão 4) | |
of_random_hex (integer ai_bytes) → string | ai_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) → long | Um 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 { , string as_scheme, string as_hash }) → string | A 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 as_scheme e as_hash: o que a outra parte espera — SIGN_PSS para uma chave RSA (sal do comprimento do hash, MGF1 com o mesmo hash), HASH_SHA384 ou HASH_SHA512; um texto vazio mantém os da chave; uma chave EC aceita um hash, nunca PSS; uma chave Ed25519 nem um nem outro. Uma chave PÚBLICA dada em vez dela é nomeada | |
of_verify (string as_public_pem, string as_text, string as_signature { , string as_scheme, string as_hash }) → boolean | Verdadeiro 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 (`r | s, 64 bytes: WebCrypto, .NET) Com o esquema e o hash do signatário (ver of_sign`). |
of_generate_keypair (string as_kind, ref string as_public_pem, ref string as_private_pem) → long | Um par por TIPO: KEY_RSA_2048/3072/4096, KEY_EC_P256 ou KEY_EC_P384 (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) → string | A família de uma chave PEM: rsa, ec-p256, ec-p384 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 of_get_last_error(), com o comando openssl que a converte | |
of_jwk_to_pem (string as_jwk_json) → string | Uma chave publicada como JWK (RFC 7517: kty RSA, EC P-256 / P-384 ou OKP Ed25519) em PEM, o que of_verify, of_jwt_verify e o openssl aceitam: pública em SPKI, privada em PKCS#8 quando o JWK tem d. Os seus membros de uso (alg, use, key_ops, kid) não fazem parte da chave. Vazio em caso de falha | |
of_pem_to_jwk (string as_pem) → string | Uma chave PEM como JWK (JSON): o que um conjunto de chaves publica, ou o que uma API pede para registar. Um PEM privado dá um JWK privado (com d): guarde-o para si. Vazio em caso de falha | |
of_base64url_encode (string as_text) → string | base64 seguro para URL (JWT, query string) | |
of_base64url_decode (string as_base64url) → string | O 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) → string | Os bytes UTF-8 de um texto em hexadecimal | |
of_hex_decode (string as_hex) → string | O 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) → string | Uma 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) → boolean | Igualdade em tempo constante: comparar um token ou um código sem o trair | |
of_password_hash (string as_password) → string | O 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) → boolean | Verdadeiro 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_password_needs_rehash (string as_stored) → boolean | Verdadeiro quando um valor guardado por of_password_hash tem MENOS rondas do que il_password_hash_rounds: logo após um of_password_verify bem-sucedido, quando a palavra-passe é conhecida, calcule de novo o hash e guarde o novo valor — o conselho da OWASP quando o custo sobe. Falso caso contrário; falso com of_get_last_error() quando as_stored não é um tal valor. Nenhuma página | |
of_totp_secret ( ) → string | Um segredo novo para os códigos de dois fatores (RFC 6238), em base32 | |
of_totp_code (string as_secret { , string as_hash, integer ai_digits, integer ai_period }) → string | O código de seis dígitos do momento, o que o autenticador mostra Com definições: HASH_SHA1, HASH_SHA256 ou HASH_SHA512, 6 a 8 dígitos, um período de 1 a 3 600 segundos — as que of_totp_uri escreve | |
of_totp_verify (string as_secret, string as_code { , ref long al_step }) → boolean | Verdadeiro 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) Os espaços que o utilizador escreve («864 010», como a aplicação o mostra) são ignorados. | |
of_totp_verify (string as_secret, string as_code, string as_hash, integer ai_digits, integer ai_period, ref long al_step) → boolean | A mesma verificação com as definições de of_totp_code; al_step conta períodos 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 | O URI otpauth:// a colocar num código QR para inscrever o utilizador O segredo escreve-se em base32 maiúsculo, sem espaços nem =; um segredo com outro carácter é recusado. Com as definições de of_totp_code, a aplicação mostra os códigos que ele calcula com elas | |
of_hotp_code (string as_secret, long al_counter) → string | O código de seis dígitos de um CONTADOR (HOTP, RFC 4226, SHA-1): um token físico, um código enviado por email — o servidor guarda o contador e incrementa-o a cada código aceite. al_counter: 0 ou mais. Vazio em caso de falha | |
of_generate_key ( ) → string | Uma chave AES-256 nova, 32 bytes em hexadecimal | |
of_encrypt_with_key (string as_key, string as_text { , string as_aad }) → string | AES-256-GCM com uma chave EXPLÍCITA (hex ou base64): base64 de nonce + cifrado + tag, o que o openssl ou o Python decifram as_aad: DADOS ADICIONAIS, autenticados mas não cifrados — um número de encomenda, um identificador: o valor só se decifra com os mesmos. A chave também pode estar em base64url | |
of_decrypt_with_key (string as_key, string as_cipher { , string as_aad }) → string | O texto de volta com a mesma chave; vazio e of_get_last_error() caso contrário Com os MESMOS dados adicionais quando os havia. | |
of_rsa_encrypt (string as_public_pem, string as_text { , string as_hash }) → string | Um 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, of_get_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 as_hash: o hash de OAEP e de MGF1 — HASH_SHA1 é o que usam por omissão o openssl pkeyutl, o .NET (OaepSHA1) e o Java (OAEPPadding) | |
of_rsa_decrypt (string as_private_pem, string as_cipher { , string as_hash }) → string | O segredo de volta, com a chave privada Com o hash do remetente (HASH_SHA256 por omissão). | |
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 | Um 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 Também JWT_HS384/HS512, JWT_RS384/RS512, JWT_PS256/PS384/PS512 e JWT_ES384 (chave KEY_EC_P384). Uma chave da família errada, ou a sua metade PÚBLICA, é nomeada | |
of_jwt_verify (string as_algorithm, string as_key, string as_token { , string as_key_format { , string as_expected_claims_json } }) → string | Os claims (JSON) de um token cuja assinatura, algoritmo e datas estão certos (il_jwt_leeway_seconds de tolerância, um minuto por omissão). O algoritmo é OBRIGATÓRIO e comparado EXATAMENTE com o do cabeçalho: "" é recusado, e uma chave PEM nunca é um segredo HS — senão uma chave PÚBLICA servia de segredo e um token forjado passava. Um exp ou nbf que não é um número, e um cabeçalho crit, são recusados. as_key_format como em of_jwt_sign. as_expected_claims_json: os claims que devem lá estar com esse valor — o emissor (iss), a audiência (aud, encontrada também numa lista); um claim ausente ou diferente é recusado e nomeado. Vazio e of_get_last_error() caso contrário; um claim lê-se com n_pbt_json (of_load, depois of_get_string) | |
of_jwt_verify_jwks (string as_algorithm, string as_jwks_json, string as_token { , string as_expected_claims_json }) → string | Os claims de um token verificado por um CONJUNTO DE CHAVES (JWKS, RFC 7517) — o que Azure AD, Google, Auth0 ou Keycloak publicam no seu jwks_uri (leia-o com o restclient, guarde-o algumas horas): o kid do token escolhe a chave. Um token sem kid, um kid ausente do conjunto ou presente duas vezes, uma chave de outro algoritmo ou de outro uso (use = enc): recusados, of_get_last_error() diz qual. Datas e claims esperados como em of_jwt_verify | |
of_jwt_claims (string as_token) → string | Os claims SEM verificação, para ler quem o token nomeia antes de escolher a chave | |
of_jwt_header (string as_token) → string | O 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 } }) → string | Um 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 }) → string | O texto de volta, na mesma conta e com a MESMA entropia se houver uma; vazio e of_get_last_error() noutro lado | |
of_crc32 (string as_text) → string | O CRC32 de um texto, 8 dígitos hexadecimais — um controlo de integridade, não um hash | |
of_crc32_file (string as_path) → string | O 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) → long | Um 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 Um destino que depende da pasta atual de uma unidade (\x, C:x) devolve -5, e of_get_last_error() di-lo | |
of_decrypt_file (string as_password, string as_source, string as_target) → long | O 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_base64_encode_blob (blob ab_data) → string | Os bytes de um blob em base64 — uma foto lida por SelectBlob, um PDF recebido — sem ficheiro. Nativo. Até 32 MB em 32 bits, 512 MB em 64 bits; em demonstração, 2048 bytes. Vazio em caso de falha | |
of_base64_decode_blob (string as_base64, ref blob ab_data) → long | Os BYTES de um valor base64 (ou base64url) num blob — um anexo recebido, pronto para UpdateBlob. Nativo. Devolve 0 assim que ab_data estiver preenchido, -5 quando o valor não é base64, -4 em demonstração acima de 2048 bytes; ab_data fica então vazio | |
of_hash_blob (string as_algorithm, blob ab_data) → string | O hash dos bytes de um blob, com os algoritmos de of_hash — o mesmo valor que of_hash_file sobre os mesmos bytes. Nativo. Vazio em caso de falha | |
of_hmac_blob (string as_algorithm, string as_key, blob ab_data { , string as_key_format }) → string | O HMAC dos bytes de um blob com uma chave secreta, a chave como em of_hmac: um ficheiro recebido, verificado pela assinatura que o serviço enviou com ele. Nativo. Vazio em caso de falha | |
of_crc32_blob (blob ab_data) → string | O CRC32 dos bytes de um blob, 8 dígitos hexadecimais. Nativo | |
of_encrypt_blob (string as_password, blob ab_data, ref blob ab_cipher) → long | Os bytes de um blob cifrados por palavra-passe: o contentor de of_encrypt e de of_encrypt_file, em bytes (52 a mais do que os dados) — lido em base64, decifra-se com of_decrypt, e o inverso. Nativo, il_pbkdf2_rounds. Devolve 0 assim que ab_cipher estiver preenchido, -5 palavra-passe vazia ou rondas fora dos limites, -4 em caso de falha ou, em demonstração, acima de 2048 bytes | |
of_decrypt_blob (string as_password, blob ab_cipher, ref blob ab_data) → long | Os bytes de of_encrypt_blob (ou de um valor de of_encrypt lido em bytes), com a mesma palavra-passe. Devolve 0 assim que ab_data estiver preenchido, -3 palavra-passe errada ou bytes alterados (ab_data fica vazio), -5 palavra-passe vazia ou bytes que não são um tal valor, -4 em demonstração acima de 2048 bytes | |
of_close ( ) → long | Liberta a página oculta; feito por si ao destruir o objeto Devolve 0 uma vez feito. | |
of_reset ( ) → long | Volta aos valores por omissão: il_timeout_ms, il_pbkdf2_rounds, il_password_hash_rounds, il_jwt_leeway_seconds, of_get_last_error(); ipo_owner mantém-se Devolve 0 uma vez feito. |
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 #
// 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 um segredo num 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())
Assinar uma encomenda, verificá-la noutro lado #
// 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.")
Um token para uma API, e a sua verificação #
// 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 a assinatura de um 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
Uma palavra-passe de servidor guardada pelo Windows, e um utilizador com dois fatores #
// 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 um token do Azure AD, da Google ou do 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())
Uma foto da base de dados, em 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
Boas práticas #
- Um único objeto por janela, aberto com ela (
of_open): a página oculta custa algumas centenas de milissegundos da primeira vez, nada depois. - A palavra-passe não se guarda:
of_encryptdevolve tudo o que é preciso para decifrar, exceto ela — é o contrato. - A chave privada não viaja: assine em casa, publique a chave pública.
of_verifysó precisa dessa. - Leia
of_get_last_error()quando um método devolve uma cadeia vazia: a razão está lá, e diz muitas vezes «palavra-passe errada» ou «ficheiro ilegível» — não um defeito do componente. - O DPAPI protege de OUTRA CONTA, não de outro programa: tudo o que o mesmo utilizador lança relê um
of_protect(qualquer conta do posto comab_machine). Passe uma entropia própria da aplicação para reduzir esse círculo. - Um JWT verifica-se com o SEU algoritmo, escrito no código (
n_pbt_crypto.JWT_RS256), nunca lido do token nem de um INI a que possa faltar: um algoritmo vazio é recusado, mas é esta escolha que fecha a porta à confusão HS/RS. Indique depois o emissor e a audiência esperados (as_expected_claims_json): a verificação recusa-os se faltarem.