unit mainApk; { Banc d'essai de la crypto du conteneur, avant toute interface reelle. Deux etages testes SEPAREMENT, parce qu'un echec global ne dit pas ou : 1. PBKDF2 -> compare la cle derivee a TESTVECTOR_KEY_HEX. Tourne AUSSI sur Windows : compile en Win64 pour valider cet etage en quelques secondes, sans deployer sur telephone. 2. AES-GCM -> dechiffre et compare le SHA-256 du clair. Android uniquement (javax.crypto via JNI). Le calcul tourne dans un thread : 600 000 iterations HMAC gelent l'interface sinon, et sur Android le systeme tuerait l'application pour non-reponse. } interface uses System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, System.Hash, System.Diagnostics, System.NetEncoding, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Controls.Presentation, FMX.StdCtrls, FMX.Memo, FMX.Memo.Types, FMX.ScrollBox, FMX.Layouts; type TForm8 = class(TForm) private FLog: TMemo; FBtn: TButton; procedure RunSelfTest(Sender: TObject); procedure Log(const AMsg: string); public constructor Create(AOwner: TComponent); override; end; var Form8: TForm8; implementation uses PMA.Crypto, PMA.TestVector; {$R *.fmx} constructor TForm8.Create(AOwner: TComponent); begin inherited; Caption := 'PM — crypto self-test'; FBtn := TButton.Create(Self); FBtn.Parent := Self; FBtn.Align := TAlignLayout.Top; FBtn.Margins.Rect := RectF(8, 8, 8, 8); FBtn.Height := 48; FBtn.Text := 'Lancer le test'; FBtn.OnClick := RunSelfTest; FLog := TMemo.Create(Self); FLog.Parent := Self; FLog.Align := TAlignLayout.Client; FLog.Margins.Rect := RectF(8, 0, 8, 8); FLog.ReadOnly := True; FLog.TextSettings.Font.Family := 'monospace'; end; procedure TForm8.Log(const AMsg: string); begin // Appele depuis le thread de calcul : la mise a jour d'un controle FMX doit // repasser par le thread principal. TThread.Queue(nil, procedure begin FLog.Lines.Add(AMsg); FLog.GoToTextEnd; end); end; procedure TForm8.RunSelfTest(Sender: TObject); begin FLog.Lines.Clear; FBtn.Enabled := False; TThread.CreateAnonymousThread( procedure var LSalt, LKey: TBytes; LKeyHex, LPlain, LSha: string; LSw: TStopwatch; begin try // ---- Etage 1 : PBKDF2 ------------------------------------------- // Le sel vient du conteneur lui-meme, pas d'une copie : si le // conteneur change, le test suit. LSalt := TNetEncoding.Base64.DecodeStringToBytes( 'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8='); Log('Mot de passe : ' + IntToStr(Length(TESTVECTOR_PASSWORD)) + ' caracteres, ' + IntToStr(Length(TEncoding.UTF8.GetBytes(TESTVECTOR_PASSWORD))) + ' octets UTF-8 (attendu 25)'); Log('PBKDF2 600k...'); LSw := TStopwatch.StartNew; LKey := DeriveKeyPBKDF2(TESTVECTOR_PASSWORD, LSalt, 600000, 32); LSw.Stop; LKeyHex := LowerCase(THash.DigestAsString(LKey)); Log(' duree : ' + IntToStr(LSw.ElapsedMilliseconds) + ' ms'); Log(' obtenu : ' + LKeyHex); if LKeyHex = TESTVECTOR_KEY_HEX then Log(' ==> PBKDF2 OK') else begin Log(' attendu: ' + TESTVECTOR_KEY_HEX); Log(' ==> PBKDF2 FAUX — verifier l''encodage UTF-8 du mot de passe'); Exit; // inutile de tester AES avec une mauvaise cle end; // ---- Etage 2 : AES-GCM + conteneur ------------------------------ Log(''); Log('Dechiffrement du conteneur...'); LPlain := DecryptContainer(TESTVECTOR_JSON, TESTVECTOR_PASSWORD); LSha := LowerCase(THashSHA2.GetHashString(LPlain, THashSHA2.TSHA2Version.SHA256)); Log(' longueur : ' + IntToStr(Length(TEncoding.UTF8.GetBytes(LPlain))) + ' octets (attendu ' + IntToStr(TESTVECTOR_PLAIN_LEN) + ')'); Log(' sha256 : ' + LSha); if LSha = TESTVECTOR_PLAIN_SHA then Log(' ==> AES-GCM OK — la chaine complete est bonne') else Log(' ==> SHA DIFFERENT — comparer avec testvector.md'); Log(''); Log('Debut du clair : ' + Copy(LPlain, 1, 60)); except on E: Exception do Log('ERREUR ' + E.ClassName + ' : ' + E.Message); end; TThread.Queue(nil, procedure begin FBtn.Enabled := True; end); end).Start; end; end.