program selftest; { Verification de la chaine crypto contre le vecteur connu, en console. Remplace le bouton "Test crypto" qu'avait le banc d'essai : l'interface est devenue celle d'un vrai client, et un aller-retour d'APK pour verifier une constante est absurde. Ici la reponse arrive en quelques secondes, sur le poste, avec un code de sortie exploitable. Ce qu'il isole, dans cet ordre - une etape fausse rend inutile de lire les suivantes : 1. PBKDF2 en Pascal -> la cle attendue ? 2. PBKDF2 natif (CNG) -> la MEME cle ? (l'encodage du mot de passe) 3. AES-GCM -> le clair attendu ? Compilation : dcc32 -B android-client\selftest.dpr } {$APPTYPE CONSOLE} uses System.SysUtils, System.Hash, System.NetEncoding, System.Diagnostics, PMA.Crypto in 'PMA.Crypto.pas', PMA.TestVector in 'PMA.TestVector.pas', PMA.Totp in 'PMA.Totp.pas'; var GFailures: Integer = 0; procedure Check(const AName, AGot, AWant: string); begin if AGot = AWant then Writeln(' OK ', AName) else begin Writeln(' ECHEC ', AName); Writeln(' obtenu : ', AGot); Writeln(' attendu: ', AWant); Inc(GFailures); end; end; // URI plutot que base32 nu : elle porte les 8 chiffres de la RFC, et fait // passer l'analyseur d'otpauth:// dans le meme mouvement. const RFC6238_URI = 'otpauth://totp/rfc?secret=' + 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&digits=8'; procedure CheckTotp(AUnix: Int64; const AWant: string); var LLeft, LPeriod: Integer; begin Check('T=' + AUnix.ToString, TotpAt(RFC6238_URI, AUnix, LLeft, LPeriod), AWant); end; var LSalt, LKey: TBytes; LPlain: string; LSw: TStopwatch; begin try // Le sel vient du conteneur lui-meme : si le vecteur est regenere, le // test suit sans qu'on ait a recopier quoi que ce soit. LSalt := TNetEncoding.Base64.DecodeStringToBytes( 'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8='); Writeln('Mot de passe : ', Length(TEncoding.UTF8.GetBytes(TESTVECTOR_PASSWORD)), ' octets UTF-8 (attendu 25)'); Writeln('PBKDF2 Pascal 600k...'); LSw := TStopwatch.StartNew; LKey := DeriveKeyPBKDF2(TESTVECTOR_PASSWORD, LSalt, 600000, 32); LSw.Stop; Writeln(' ', LSw.ElapsedMilliseconds, ' ms'); Check('cle derivee', LowerCase(THash.DigestAsString(LKey)), TESTVECTOR_KEY_HEX); Writeln('PBKDF2 natif...'); LSw := TStopwatch.StartNew; LKey := DeriveKeyPBKDF2Native(TESTVECTOR_PASSWORD, LSalt, 600000, 32); LSw.Stop; if LKey = nil then begin Writeln(' indisponible : ', LastNativeError); Inc(GFailures); end else begin Writeln(' ', LSw.ElapsedMilliseconds, ' ms'); // Le point sensible : le mot de passe est non-ASCII, donc une cle // identique prouve que le fournisseur encode bien en UTF-8. Check('cle native identique', LowerCase(THash.DigestAsString(LKey)), TESTVECTOR_KEY_HEX); end; Writeln('Conteneur AES-GCM...'); LPlain := DecryptContainer(TESTVECTOR_JSON, TESTVECTOR_PASSWORD); Check('longueur du clair', Length(TEncoding.UTF8.GetBytes(LPlain)).ToString, TESTVECTOR_PLAIN_LEN.ToString); Check('empreinte du clair', LowerCase(THashSHA2.GetHashString(LPlain, THashSHA2.TSHA2Version.SHA256)), TESTVECTOR_PLAIN_SHA); // Un mot de passe faux DOIT echouer : sans cette verification, un // dechiffrement qui rendrait n'importe quoi passerait pour un succes. try DecryptContainer(TESTVECTOR_JSON, 'mauvais mot de passe'); Writeln(' ECHEC un mot de passe faux a ete accepte'); Inc(GFailures); except on E: EPMACrypto do Writeln(' OK mot de passe faux rejete'); end; except on E: Exception do begin Writeln('ERREUR ', E.ClassName, ' : ', E.Message); Inc(GFailures); end; end; // ---- TOTP, vecteurs de la RFC 6238 -------------------------------------- // Secret "12345678901234567890" en base32, 8 chiffres, aux instants publies // par la RFC. C'est le seul moyen de verifier a la fois la base32, le HMAC // et la troncature - un code plausible peut etre faux. Writeln('TOTP (RFC 6238)...'); try CheckTotp(59, '94287082'); CheckTotp(1111111109, '07081804'); CheckTotp(1111111111, '14050471'); CheckTotp(1234567890, '89005924'); CheckTotp(2000000000, '69279037'); CheckTotp(20000000000, '65353130'); except on E: Exception do begin Writeln(' ERREUR ', E.ClassName, ' : ', E.Message); Inc(GFailures); end; end; Writeln; if GFailures = 0 then Writeln('Tout est bon.') else Writeln(GFailures, ' echec(s).'); ExitCode := GFailures; end.