c607e26c4a
The Pascal loop takes 1736 ms on a desktop CPU, so several seconds on a phone - too slow for every unlock. SecretKeyFactory is native and roughly an order of magnitude faster, but PBEKeySpec takes a char[] and the provider picks the encoding, which was the reason for avoiding it. The non-ASCII vector settles it on a real device: same key means the provider uses UTF-8 and we adopt it; a different key means the Pascal implementation stays. DecryptContainer keeps using the Pascal one until that measurement says otherwise.
172 lines
5.6 KiB
ObjectPascal
172 lines
5.6 KiB
ObjectPascal
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 1bis : le PBKDF2 natif d'Android ----------------------
|
|
// Question ouverte, tranchee ici plutot que supposee : PBEKeySpec
|
|
// prend un char[] et le fournisseur choisit l'encodage. S'il donne la
|
|
// MEME cle sur ce mot de passe non-ASCII, on peut l'adopter et gagner
|
|
// un ordre de grandeur. Sinon, le Pascal reste la seule option juste.
|
|
Log('');
|
|
Log('PBKDF2 natif (Android)...');
|
|
LSw := TStopwatch.StartNew;
|
|
LKey := DeriveKeyPBKDF2Native(TESTVECTOR_PASSWORD, LSalt, 600000, 32);
|
|
LSw.Stop;
|
|
if LKey = nil then
|
|
Log(' indisponible sur cette plateforme')
|
|
else
|
|
begin
|
|
LKeyHex := LowerCase(THash.DigestAsString(LKey));
|
|
Log(' duree : ' + IntToStr(LSw.ElapsedMilliseconds) + ' ms');
|
|
if LKeyHex = TESTVECTOR_KEY_HEX then
|
|
Log(' ==> IDENTIQUE — adoptable, encodage UTF-8 confirme')
|
|
else
|
|
begin
|
|
Log(' obtenu : ' + LKeyHex);
|
|
Log(' ==> DIFFERENT — encodage du char[] incompatible, garder le Pascal');
|
|
end;
|
|
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.
|