The sample contains two storage flows, so the difference is visible in the disassembly:
An insecure flow writes a sensitive value to user_profile.json in the Documents directory and later reads it back, trusting it without computing or verifying any HMAC or signature.
A secure flow (storeWithIntegrity) writes the same data to user_profile_protected.json with an appended HMAC-SHA256 and verifies the HMAC when reading it back.
Because the insecure flow stores data the app trusts without any integrity check, the app cannot detect whether that data was modified on disk.
//SUMMARY:Thissamplestoressensitivedatainafileintheapp's Documents directory and//laterreadsitbackwithoutcomputingorverifyinganyintegrityvalue(HMACorsignature).//Anattackerwhomodifiesthefileonajailbrokendevicecantamperwiththedataundetected.////Forcontrast,thefilealsocontainsaPASSroutinethatstoresthesamedatawithanappended//HMAC-SHA256andverifiesitonread.Keepingbothcasesinonebinarymakesthedifference//visibleinthedisassembly:thePASSroutinereferencesCryptoKit's HMAC, the FAIL routine//referencesnointegrityAPIatall.importFoundationimportCryptoKitstructMastgTest{//PASScase:storedatawithanappendedHMAC-SHA256tagandverifyitbeforetrustingthe//dataonread.Amismatch(forexampleafterthefileistamperedwith)isdetected.Ina//realappthekeywouldbeheldintheKeychainratherthangeneratedin-process.staticfuncstoreWithIntegrity(_data:Data,atfileURL:URL,usingkey:SymmetricKey)->String{lettagLength=32//HMAC-SHA256tagsizeinbytesdo{letmac=HMAC<SHA256>.authenticationCode(for:data,using:key)try(data+Data(mac)).write(to:fileURL)letstored=tryData(contentsOf:fileURL)letpayload=stored.prefix(stored.count-tagLength)lettag=stored.suffix(tagLength)letvalid=HMAC<SHA256>.isValidAuthenticationCode(Data(tag),authenticating:Data(payload),using:key)returnvalid?"verified":"tampering detected"}catch{return"failed: \(error.localizedDescription)"}}staticfuncmastgTest(completion:@escaping(String)->Void){//FAIL:[MASTG-TEST-0387]Theappwritessensitivedatatodiskandlaterreadsitback//withoutcomputingorverifyinganHMACorsignature,soitcannotdetecttampering.letfileManager=FileManager.defaultguardletdocuments=fileManager.urls(for:.documentDirectory,in:.userDomainMask).firstelse{completion("Could not locate the Documents directory")return}letfileURL=documents.appendingPathComponent("user_profile.json")//StoresensitivedatawithoutanyintegrityprotectionletsensitiveData=#"{"username":"alice","role":"user","premium":false}"#.data(using: .utf8)!do{trysensitiveData.write(to:fileURL)}catch{completion("Failed to write file: \(error.localizedDescription)")return}//Later,theappreadsthedatabackandtrustsitwithoutverifyingitsintegrityguardletloaded=try?Data(contentsOf:fileURL),letcontents=String(data:loaded,encoding:.utf8)else{completion("Failed to read the file back")return}//PASS:forcontrast,storethesamedatawithanHMACandverifyitonread.TheFAILpath//aboveproducesnointegrityvalue;thisroutinereturnstheverificationresult.letprotectedURL=documents.appendingPathComponent("user_profile_protected.json")letintegrityResult=storeWithIntegrity(sensitiveData,at:protectedURL,using:SymmetricKey(size:.bits256))letvalue=""" Stored file : \(fileURL.path) Contents : \(contents) FAIL (no integrity check) : the data above was read back without verifying any HMAC or signature. PASS (HMAC verified) : \(integrityResult) """completion(value)}}
The output shows that the app references both storage APIs (Foundation.Data.write(to:) and Foundation.Data(contentsOf:)) and storage-integrity APIs (CryptoKit.HMAC.authenticationCode and CryptoKit.HMAC.isValidAuthenticationCode).
Cross-referencing Data.write(to:) reveals two distinct storage flows, and the disassembly of each shows how the storage and integrity calls relate:
Flow A (fcn.0x1000050f8) calls Data.write(to:) and Data(contentsOf:) with no HMAC call on that data path.
Flow B (fcn.0x1000045bc) calls HMAC.authenticationCode before Data.write(to:) and HMAC.isValidAuthenticationCode after Data(contentsOf:).
The test case fails because the app stores data it later trusts without verifying its integrity. Although the binary references HMAC APIs, those references alone do not prove that all stored data is protected: the disassembly shows that flow A writes user_profile.json and reads it back with no HMAC computation or verification on that path, so the app cannot detect if that file is tampered with.
For contrast, flow B (storeWithIntegrity) is a passing path: it computes an HMAC over the data before writing it and verifies the HMAC after reading it back, so tampering with user_profile_protected.json would be detected. The presence of this protected flow is exactly why the references found in step 2 require manual validation: only by inspecting how each storage path uses the integrity APIs can you tell which stored data is actually protected.