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 #
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 é 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 com o sal (16 bytes), o nonce (12) e o cifrado AES-256-GCM; a chave é derivada da palavra-passe por PBKDF2-SHA-256, 100 000 voltas. Bastaof_decryptcom a mesma palavra-passe, em qualquer posto.- Chaves: PEM, pública em SPKI (
BEGIN PUBLIC KEY), privada em PKCS#8 (BEGIN PRIVATE KEY); assinaturas RSASSA-PKCS1-v1_5 com SHA-256, em base64 — o que oopenssllê e verifica. - Sem MD5: a Web Crypto não o oferece, e nada deveria pedi-lo mais.
Propriedades #
| Propriedade | Tipo | Predefinição | Função |
|---|---|---|---|
is_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 |
il_timeout_ms | long | 20000 | Duraçã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_rounds | long | 100000 | Rondas PBKDF2 de of_encrypt, of_password_hash e of_encrypt_file: cerca de 100 ms. Mais é mais lento para todos, atacante incluído |
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.
Métodos #
| Método | Função |
|---|---|
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 um código negativo |
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. 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 | O HMAC de um texto com uma chave secreta, em hexadecimal — o que uma API pede para autenticar uma chamada |
of_hash_file (string as_algorithm, string as_path) → string | O hash de um ficheiro do posto, de qualquer tipo e até 512 MB, sem o carregar no PowerBuilder |
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; um valor que não é base64 devolve uma cadeia vazia e is_last_error, nunca lixo |
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 — até 512 MB, lidos pela DLL, nunca carregados no PowerBuilder |
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. Devolve 0 uma vez escrito, -5 num caminho vazio ou num valor que não é base64, -4 se o ficheiro não puder ser escrito |
of_encrypt (string as_password, string as_text) → string | Cifra um texto com uma palavra-passe: uma única cadeia base64 a guardar. 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 is_last_error, nunca lixo |
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_*). Devolve 0 quando ambas as chaves estão preenchidas, -4 em caso de falha |
of_sign (string as_private_pem, string as_text) → string | A assinatura de um texto pela chave privada, em base64. Vazio em caso de falha |
of_verify (string as_public_pem, string as_text, string as_signature) → boolean | Verdadeiro se a assinatura foi feita exatamente sobre este texto pela chave privada correspondente; falso — não um erro — se o texto mudou |
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 (assinaturas curtas) ou KEY_ED25519. Devolve 0, -4 em caso de falha |
of_key_kind (string as_pem) → string` | A família de uma chave PEM: rsa, ec-p256 ou ed25519 |
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 |
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 |
of_random_password (integer ai_length, string as_charset) → string` | Uma palavra-passe aleatória (4 a 256 caracteres) num conjunto CHARSET_*, sem viés. Vazia num comprimento errado |
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_pbkdf2_rounds), rondas e sal no valor. Nunca o mesmo resultado duas vezes |
of_password_verify (string as_password, string as_stored) → boolean` | Verdadeiro se a palavra-passe é a do valor guardado, comparada em tempo constante |
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` | O código de seis dígitos do momento, o que o autenticador mostra |
of_totp_verify (string as_secret, string as_code) → boolean` | Verdadeiro se o código é o do momento, do passo anterior ou do seguinte |
of_totp_uri (string as_secret, string as_account, string as_issuer) → string` | O URI otpauth:// a colocar num código QR para inscrever o utilizador |
of_generate_key ( ) → string` | Uma chave AES-256 nova, 32 bytes em hexadecimal |
of_encrypt_with_key (string as_key, string as_text) → 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 |
of_decrypt_with_key (string as_key, string as_cipher) → string` | O 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) → string` | Um segredo curto cifrado para uma chave PÚBLICA (RSA-OAEP): só a privada o lê |
of_rsa_decrypt (string as_private_pem, string as_cipher) → string` | O 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` | Um JWT assinado: JWT_HS256 (segredo partilhado), JWT_RS256/JWT_ES256/JWT_EDDSA (chave privada); iat acrescentado, exp se a duração for > 0. Vazio em caso de falha |
of_jwt_verify (string as_algorithm, string as_key, string as_token) → string` | Os claims (JSON) de um token cuja assinatura, algoritmo e datas estão certos; vazio e is_last_error caso contrário |
of_jwt_claims (string as_token) → string` | Os claims SEM verificação, para ler quem o token nomeia antes de escolher a chave |
of_protect (string as_text { , boolean ab_machine }) → 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 |
of_unprotect (string as_protected) → string` | O texto de volta, na mesma conta; vazio e is_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 |
of_encrypt_file (string as_password, string as_source, string as_target) → long` | Um ficheiro cifrado por palavra-passe, em nativo (mesmo contentor que of_encrypt). Devolve 0, -5 argumento vazio, -2 origem ilegível, -4 destino não gravável |
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), -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 #
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.")
Um anexo em base64 para uma 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 um segredo num 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)
Assinar uma encomenda, verificá-la noutro lado #
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.")
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(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")
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
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 ...
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
is_last_errorquando 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.