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.
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 |
|---|---|---|---|
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 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_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 |
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 |
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é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 -2 se não foi possível criá-la (is_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 | |
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 | |
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; is_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 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) → 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é 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) → 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 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 ( ) → 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 | 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 | |
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. Uma assinatura ECDSA lê-se em DER (openssl, Java) ou em P1363 (`r | s`, 64 bytes: WebCrypto, .NET) |
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, -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 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) → 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_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 { , 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) | |
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: 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) → 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 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 | |
of_jwt_verify (string as_algorithm, string as_key, string as_token { , string as_key_format }) → string | Os 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) → 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 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 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 | |
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_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 #
- 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. - 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. Depois verifiqueaudeissnos claims devolvidos.