feat(crypto): wire AES-GCM and PBKDF2 to Windows CNG
The viewer compiled for Win64 but died on first use: AesGcmDecrypt existed only as JNI, so the desktop build downloaded 27 KB and then refused it. Windows has the same primitive in bcrypt.dll - no DLL to ship, no OpenSSL, and BCryptDeriveKeyPBKDF2 takes bytes, so none of the char[] encoding doubt that keeps the Pascal loop around on Android. This exists so the client can be exercised on the desktop; deploying an APK to watch a list redraw is a slow way to build a UI. selftest.dpr replaces the crypto-vector button the viewer dropped: same known-answer vector, console, exit code = failures. Measured here - Pascal 1670 ms, CNG 528 ms, identical keys on the non-ASCII password. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
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';
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
Writeln;
|
||||
if GFailures = 0 then
|
||||
Writeln('Tout est bon.')
|
||||
else
|
||||
Writeln(GFailures, ' echec(s).');
|
||||
ExitCode := GFailures;
|
||||
end.
|
||||
Reference in New Issue
Block a user