Encryption Performance Feedback — iPhone 16 Pro
TL;DR
Crypto choices (AES-256-GCM + scrypt) are solid. Performance is not — scrypt derivation takes 1.3s with N=4096, which is abnormally slow for this hardware. This points to an implementation issue rather than a configuration one.
Key Findings
1. Scrypt derivation is too slow for the parameters used
N=4096 is actually a low cost factor (OWASP recommends N=32768+), yet it takes 1.3s on an A18 Pro chip. A native C implementation should handle N=16384 in ~200-400ms on this device. This strongly suggests a non-native (pure Swift/JS) scrypt implementation is being used.
2. Encryption re-derives the key every time
Each encrypt call takes ~1.3s regardless of data size (32 bytes vs 1KB — no meaningful difference). The time is entirely spent on scrypt, not on AES. The scrypt cache clearly works in isolation (0.07ms on cache hit), but the encrypt doesn’t appear to use it.
3. UX impact
A full encrypt + decrypt cycle takes 2.82s for a simple API key. Users will notice this. Saving or retrieving credentials should feel instant.
Suggestions
1. Use the cached derived key for encrypt/decrypt — the cache mechanism already exists, it just needs to be wired into the encrypt path. This alone would drop encrypt time from 1.3s to single-digit ms.
2. Switch to a native scrypt implementation (or use CommonCrypto / a C binding). This would allow raising N to a more secure value (16384–32768) while still being faster than the current 1.3s at N=4096.
3. Consider Argon2id as an alternative KDF — it’s the current OWASP recommendation over scrypt, has good native iOS support, and is easier to tune for mobile.
Test Environment
∙ Device: iPhone 16 Pro (A18 Pro)
∙ Algorithm: AES-256-GCM
∙ KDF: scrypt (N=4096, r=8, p=1)
Log in to comment and vote
Comments4
Daniel Nguyen
Feb 26
I’ve improved this in build 25. Encryption is instant now. In the perf test screen, I intentionally clear cache for each test case. In production, it already using cached derived key (unless there is a bug).
Currently I still keep n=4096. In the next version, I will migrate it to 32k+ or argon2id.
Below is the test results, ran on my iPhone 16 Pro.
=== Encryption Performance Test Results ===
Configuration:
- Algorithm: AES-256-GCM
- KDF: scrypt
- Scrypt N: 4096
- Scrypt r,p: 8,1
- Additional benchmarks: N=16384, N=32768
Results:
Device Key Retrieval: 21.1ms (Get or create device-specific key)
Scrypt Key Derivation (N=4096): 15.5ms (N=4096, r=8, p=1)
Scrypt Key Derivation (N=16384): 42.5ms (N=16384, r=8, p=1)
Scrypt Key Derivation (N=32768): 48.3ms (N=32768, r=8, p=1, maxmem=64MB)
Scrypt (First with cache): 5.9ms (First call - derives and caches)
Scrypt (Cached): 0.062ms (Using cached key - should be instant)
Encrypt Tiny (32 bytes): 12.6ms
Decrypt (Device) Tiny (32 bytes): 4.6ms
Decrypt (Pass) Tiny (32 bytes): 6.6ms
Encrypt Small (256 bytes): 10.2ms
Decrypt (Device) Small (256 bytes): 5.0ms
Decrypt (Pass) Small (256 bytes): 6.9ms
Encrypt API Key (512 bytes): 10.5ms
Decrypt (Device) API Key (512 bytes): 6.4ms
Decrypt (Pass) API Key (512 bytes): 7.6ms
Encrypt 1KB: 11.4ms
Decrypt (Device) 1KB: 7.1ms
Decrypt (Pass) 1KB: 8.8ms
Full Cycle (Encrypt + Decrypt): 15.6ms (Typical API key size)
=== Summary ===
Scrypt derivation (N=4096): 15.5ms
Scrypt derivation (N=16384): 42.5ms
Scrypt derivation (N=32768): 48.3ms
Cached derivation: 0.062ms
Full cycle: 15.6ms
Greg
Feb 26
Great work on build 25 — the improvement is massive.
My results are nearly identical to yours, which confirms the fix is consistent across devices.
Night and day difference from the previous build.
Looking forward to the Argon2id migration. Since it would require re-encrypting user data anyway, going straight from scrypt N=4096 to Argon2id makes sense — avoids doing two migrations.
One small idea: it might be useful to add an Argon2id benchmark to the perf test screen when the time comes — would make it easy for testers to compare both KDFs side by side on real devices.
Daniel Nguyen
Feb 26
Glad to hear it works now. On a second thought, I don’t think I would switch to Argon2id as using scrypt with n >= 32k is good enough. Implementation-wise it’s a lot simpler.
Daniel Nguyen
Feb 6
Yeah the current setup is not ideal yet. I’m well aware of the performance issues. I did implement caching but there seems to be a bug where it doesn’t work.
I’ll improve this soon.