如何检测运行中的macOS应用是否来自Mac App Store或自分发版本?
区分Mac App Store与自分发版本的可靠方法
这确实是Mac应用多渠道分发中很常见的需求,我在实际开发中踩过不少坑,给你梳理几个靠谱的检测方案,能避开单纯依赖收据的局限性:
一、最可靠的方式:检查应用签名信息
签名是Mac应用运行的核心凭证,App Store版本和自分发版本的签名有本质区别,非越狱环境下完全无法伪造:
1. 证书类型差异
- App Store版本的签名证书通用名称(CN)会包含"Mac App Store",证书颁发链最终指向"Apple Worldwide Developer Relations Certification Authority"
- 自分发(开发者ID签名)版本的证书通用名称是"Developer ID Application: 你的团队名称",颁发链指向"Developer ID Certification Authority"
2. 代码实现示例(Swift)
通过Security框架获取当前应用的签名信息,验证证书或权限字段:
import Security import StoreKit func isAppStoreDistributed() -> Bool { // 获取当前应用的签名对象 let flags = SecCSFlags(rawValue: kSecCSDynamicPath) var code: SecCode? guard SecCodeCopySelf(flags, &code) == errSecSuccess, let appCode = code else { return false } // 获取签名详情 var signingInfo: CFDictionary? guard SecCodeCopySigningInformation(appCode, SecCSFlags(rawValue: kSecCSSigningInformation), &signingInfo) == errSecSuccess, let infoDict = signingInfo as? [String: Any] else { return false } // 检查证书链中的App Store标识 if let certificates = infoDict[kSecCodeInfoCertificates as String] as? [SecCertificate] { for cert in certificates { if let certSubject = SecCertificateCopySubjectSummary(cert) as String? { if certSubject.contains("Mac App Store") { return true } } } } // 辅助验证:检查权限中的应用标识格式 if let entitlements = infoDict[kSecCodeInfoEntitlements as String] as? [String: Any], let appID = entitlements["com.apple.application-identifier"] as? String { // 替换成你的团队ID,App Store版本的appID必然以团队ID开头 let yourTeamID = "YOUR_TEAM_ID_HERE" if appID.hasPrefix("\(yourTeamID).") { // 结合收据刷新验证(可选,进一步确认) let receiptURL = Bundle.main.appStoreReceiptURL if receiptURL != nil, FileManager.default.fileExists(atPath: receiptURL!.path) { return true } else { // 异步尝试刷新收据,后续通过回调确认 let refreshReq = SKReceiptRefreshRequest(receiptProperties: nil) refreshReq.delegate = self // 需实现SKRequestDelegate处理结果 refreshReq.start() } } } return false } // 实现SKRequestDelegate处理收据刷新结果 extension YourViewController: SKRequestDelegate { func requestDidFinish(_ request: SKRequest) { if request is SKReceiptRefreshRequest { let receiptExists = FileManager.default.fileExists(atPath: Bundle.main.appStoreReceiptURL!.path) if receiptExists { // 确认是App Store版本 } } } func request(_ request: SKRequest, didFailWithError error: Error) { if request is SKReceiptRefreshRequest { // 刷新失败,大概率是自分发版本 } } }
二、收据验证(需规避“缺失即非App Store”的误区)
你提到的那个库的问题在于太绝对——收据缺失不代表不是App Store版本:用户可能从备份恢复应用、离线运行,都会导致收据暂时缺失。正确的做法是:
- 不要仅判断收据是否存在,而是尝试刷新收据:App Store版本能成功获取到苹果签名的收据,自分发版本会刷新失败
- 即使收据存在,也要验证其签名合法性(通过
SKReceiptValidator或手动验证),避免伪造收据
三、辅助判断:安装路径(仅作补充)
默认情况下App Store应用会安装在/Applications,但用户可以手动移动,所以这个只能作为辅助参考,不能作为核心判断依据。
关键总结
- 优先用签名检测:这是最可靠的,因为签名是应用运行的必要条件,且App Store签名无法伪造
- 收据验证作为补充:解决签名检测可能存在的边缘情况,同时要处理收据缺失的场景
- 绝对不要仅靠“收据是否存在”来判断,这会误判很多正常场景
内容的提问来源于stack exchange,提问作者Duck




