Does external SHA‑256 hashing combined with NSSecureCoding guarantee complete and tamper‑free restoration of NSKeyedArchiver backups?
0 reputation · 07 Jun 2023, 12:17 UTC
0 reputation · 07 Jun 2023, 12:17 UTC
The goal is to confirm that a backup restored from NSKeyedArchiver is both free of tampering and semantically complete when the archiver is configured with NSSecureCoding and an external SHA‑256 hash is used to verify the archived bytes. The constraints are that NSKeyedUnarchiver’s version‑tolerant decoding will silently set missing properties to nil or default values when a newer class adds fields, and that NSSecureCoding only guards against unauthorized class substitution, not against alterations to the encoded data that keep the same class list.
Because the external hash detects any change to the NSData but cannot reveal whether the decoded object lacks expected properties, it is unclear whether the combined approach guarantees detection of all relevant failure modes. This raises the question of whether additional checks—such as version‑specific property validation or a custom checksum of the decoded object—are necessary to ensure both integrity and completeness.
Does the external SHA‑256 hash, when paired with NSSecureCoding, reliably detect both data corruption and silent property loss due to version mismatches? If not, what supplementary verification steps should be employed to confirm that restored objects contain all expected properties?
32275 reputation · 07 Jun 2023, 13:14 UTC
No. The combination of an external SHA-256 hash and NSSecureCoding does not guarantee a complete restoration. While this pairing ensures integrity (the data has not changed) and type safety (no unauthorized classes were injected), it cannot detect semantic incompleteness caused by version mismatches where NSKeyedUnarchiver silently ignores missing properties.
To understand why this approach fails to guarantee completeness, it is necessary to distinguish between the three layers of verification being used:
NSData blob is identical to the one originally hashed. It detects bit-rot or malicious tampering of the file, but it has no visibility into the object graph inside the blob.NSKeyedUnarchiver is designed to be flexible. If a backup was created with Version 1 of a class, but is restored by Version 2 (which expects an additional property), the unarchiver will simply return nil or a default value for the missing key. Because the data blob itself is valid and the class is authorized, neither the hash nor NSSecureCoding will trigger an error.To ensure that a restored object is both tamper-free and semantically complete, implement the following supplementary checks:
version integer as a top-level key in your archive. Compare this version against the current app version to determine if a migration is required.unarchivedObject(ofClass:from:) returns, call a validation method on the resulting object. This method should check that all mandatory properties are non-nil and within expected ranges.To verify that NSSecureCoding is active, attempt to decode a blob containing a class not included in your ofClass: whitelist; the method should throw an exception or return nil rather than instantiating the object.
Diagnostic Detail Needed: Are you restoring backups across different app versions, or only within the same version? If the latter, the version-tolerance issue is mitigated, but the validation steps remain best practice.
Use comments to ask for clarification. Post a solution as an answer.
32,275 reputation · 07 Jun 2023, 16:03 UTC
One subtlety worth adding to the existing answer: if the threat model includes deliberate tampering rather than just bit-rot, a plain SHA-256 hash is an integrity check, not authentication. An attacker who can modify the archive can usually also recompute and replace the stored hash. If the backup travels or sits somewhere writable, use an HMAC (or store the hash in a location the attacker cannot touch) so the checksum itself cannot be forged.
Second, a practical trap when verifying completeness: do not try to validate the decoded object by re-archiving it and comparing hashes. NSKeyedArchiver output is not guaranteed to be byte-stable across runs or OS versions (dictionary ordering and internal references can differ), so a re-encoded archive will almost never match the original hash even for a perfect restore. The hash check must happen on the exact original NSData before decoding; completeness must be checked separately after decoding.
For the completeness side, the cheapest robust pattern is to encode a schemaVersion alongside the payload and, after unarchiving, validate the required keys for that version explicitly—failing the restore rather than accepting silent nils. That turns version-tolerant decoding from a silent failure into an explicit, testable one.