perf(android): one key derivation per save, not two

Reported as "Save takes 6 to 8 seconds". A save decrypts the snapshot it just
pulled and re-encrypts the patched one - with the same password. That was two
full PBKDF2 runs at 600k iterations, four to six seconds each on this phone,
for two keys that protect the same file.

DecryptContainerEx now hands back the key, its salt and the iteration count,
and EncryptContainerWithKey reuses them. The IV is still drawn fresh every
time: reusing an IV with the same key breaks GCM entirely, whereas reusing the
salt merely yields the same key - which is the whole point. A salt slows an
attack on the password; it has no reason to differ between two versions of the
same file.

EncryptContainer keeps its old shape and now delegates, so there is a single
place that composes a container.

The log times both halves, so the next measurement says where the remaining
seconds are rather than inviting another guess.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-08-17 19:50:18 +01:00
parent 440bfd5536
commit f245cdcc38
2 changed files with 54 additions and 11 deletions
+6 -2
View File
@@ -558,7 +558,9 @@ begin
LClient: TSyncClient;
LGet, LPut: TSyncResult;
LPlain, LNew, LBody, LErr: string;
LAttempt: Integer;
LKey, LSalt: TBytes;
LIter, LAttempt: Integer;
LSw: TStopwatch;
LDone: Boolean;
begin
LClient := TSyncClient.Create(FUrl, FToken);
@@ -587,7 +589,9 @@ begin
end;
end;
LBody := EncryptContainer(LNew, FSyncPwd);
LSw := TStopwatch.StartNew;
LBody := EncryptContainerWithKey(LNew, LKey, LSalt, LIter);
Log(Format('save: re-encrypt in %d ms', [LSw.ElapsedMilliseconds]));
LPut := LClient.Put(LBody, LGet.ETag, 'password change from phone');
if LPut.Status = ssOK then
begin