現(xiàn):純 Swift 手寫 Scrypt + AES-256-GCM + Gzip)
Tinycast 解密 Raycast .rayconfig 的實(shí)現(xiàn)純 Swift 手寫 Scrypt AES-256-GCM Gzip【免費(fèi)下載鏈接】tinycastTinycast — a tiny, fully native macOS launcher, hotkeys, and clipboard history.項(xiàng)目地址: https://gitcode.com/GitHub_Trending/ti/tinycast如果你正想把 Raycast 的快捷鍵、收藏和剪貼板歷史遷移到Tinycast那么它的「Import from Raycast」功能值得深入了解Tinycast 用約 400 行純 Swift 代碼零第三方依賴地讀懂了 Raycast 加密備份文件.rayconfig。核心思路是把 Raycast 的 RAYCFG3 加密容器逐層拆開——先手寫Scrypt口令派生出密鑰再用系統(tǒng)的AES-256-GCM打開加密體最后用手寫Gzip解壓出明文 JSON。RAYCFG3 容器格式.rayconfig 長(zhǎng)什么樣Raycast v2.x 的導(dǎo)出文件并不是純 JSON而是一個(gè)分層的加密容器。整個(gè)線格式在 docs/features/raycast-import.md 里只有三行描述file RAYCFG3\n ‖ UInt32LE(header.count) ‖ gzip(header JSON) ‖ ciphertext ‖ tag(16) body AES-256-GCM(gzip(payload JSON)) key scrypt(passphrase, salt, N16384, r8, p1, dkLen32)把它拆開看一個(gè) .rayconfig 文件由五段組成區(qū)段內(nèi)容說明魔數(shù)RAYCFG3\n8 字節(jié)只憑這 8 個(gè)字節(jié)就能識(shí)別文件無需口令頭部長(zhǎng)度UInt32 小端聲明后面 gzip 頭部的字節(jié)數(shù)頭部gzip 壓縮的 JSON含schemaVersion、hex 編碼的iv和salt各 16 字節(jié)密文AES-256-GCM 密文正文是 gzip 壓縮后的全部設(shè)置 JSON認(rèn)證標(biāo)簽16 字節(jié)GCM 的校驗(yàn)和錯(cuò)一個(gè)字節(jié)就解密失敗一個(gè)容易忽略的細(xì)節(jié)Raycast即使用戶沒設(shè)密碼也會(huì)加密——它自動(dòng)生成一個(gè)口令并存在登錄鑰匙串里。Tinycast 從不讀鑰匙串口令由用戶在導(dǎo)入界面手動(dòng)輸入。Scrypt 密鑰派生純 Swift 手寫不引一個(gè)第三方庫(kù).rayconfig的解密密鑰來自 scryptRFC 7914。Tinycast 在 Scrypt.swift 里完整實(shí)現(xiàn)了它只依賴系統(tǒng)自帶的 CryptoKit。結(jié)構(gòu)是教科書式的三段式前處理PBKDF2-HMAC-SHA256 一輪把口令擴(kuò)展成內(nèi)存塊Scrypt.swiftROMix 內(nèi)存硬化循環(huán) N16384 次每次調(diào)用 BlockMixBlockMix 內(nèi)部對(duì) 128 個(gè) 64 字節(jié)塊交替做 Salsa20/8 輪函數(shù)Scrypt.swift后處理再做一輪 PBKDF2取出 32 字節(jié)密鑰其中工作量最大的 salsa20_8 是 16 輪「加、異或、循環(huán)移位」的字節(jié)碼級(jí)別手寫循環(huán)。參數(shù)n16384, r8, p1意味著一次派生要分配約 8 MiB 內(nèi)存做混合——這也正是 scrypt 抵御暴力破解的原理代價(jià)是派生要花幾百毫秒到幾秒。Tinycast 用了一個(gè)很聰明的排序所有廉價(jià)校驗(yàn)都放在 scrypt 之前只有格式完全合法的文件才會(huì)付出這筆時(shí)間成本。AES-256-GCM 解密五步拆彈流程真正的解密入口是 RaycastDecoder.swift整個(gè)流程可以概括為五步識(shí)別isExport只檢查文件頭 8 字節(jié)的RAYCFG3\n魔數(shù)RaycastDecoder.swift所以在用戶還沒輸入口令時(shí)Backup 面板就能先判斷「這確實(shí)是個(gè) Raycast 導(dǎo)出」解幀讀小端頭部長(zhǎng)度上限 1 MB算出密文區(qū)起點(diǎn)和認(rèn)證標(biāo)簽位置解頭部手寫 gunzip 解出頭部 JSON取出 hex 編碼的 iv 與 salt并確認(rèn)schemaVersion 3RaycastDecoder.swift派生 解密scrypt 出 32 字節(jié)密鑰用 CryptoKit 的AES.GCM.open打開密文RaycastDecoder.swift解正文再次 gunzip 得到明文 JSON上限 512 MB第 4 步有個(gè)產(chǎn)品細(xì)節(jié)GCM 解密一旦拋錯(cuò)一律歸類為「口令錯(cuò)誤」incorrectPassphrase——因?yàn)楦袷絾栴}早已在前幾步被攔下走到這一步還失敗十有八九是口令不對(duì)。手寫 Gzip為什么不用現(xiàn)成庫(kù)你可能會(huì)問解壓 gzip 何必自己寫因?yàn)樘O果的Compression框架只支持裸 DEFLATE 流gzip 的 10 字節(jié)文件頭、FEXTRA/FNAME 等可選字段、以及結(jié)尾的 CRC32 校驗(yàn)和都沒有人處理。Zlib.swift 用約 150 行補(bǔ)齊了這部分Zlib.swift順帶實(shí)現(xiàn)了 zlib 格式和 CRC32/Adler32 校驗(yàn)。這里有一個(gè)安全設(shè)計(jì)值得新手學(xué)習(xí)輸出上限maxOutput不是 zip 炸彈防護(hù)而是內(nèi)存保險(xiǎn)。因?yàn)?AES-GCM 已經(jīng)認(rèn)證過正文的完整性惡意膨脹攻擊在認(rèn)證前就無從談起上限存在的唯一目的是防止超大導(dǎo)出撐爆內(nèi)存。兩個(gè)上限的取值也很講究——未認(rèn)證的頭部保持 1 MB而剪貼板歷史密集的導(dǎo)出解壓后可能超過 64 MB所以正文放寬到 512 MB。錯(cuò)誤處理與測(cè)試每一字節(jié)都有斷言用戶能看到的錯(cuò)誤只有四種定義在 RaycastImportError.swift不是 Raycast 文件、口令錯(cuò)誤、文件損壞、文件過大。對(duì)應(yīng)提示都很直白比如「Incorrect passphrase, or the file is corrupted.」而保證這些分支不出錯(cuò)的是一套相當(dāng)硬核的測(cè)試 raycast-test.swift測(cè)試文件在內(nèi)存里現(xiàn)造用同一份 Scrypt 實(shí)現(xiàn)反向加密一個(gè) JSON 構(gòu)建容器避免把真實(shí) .rayconfig 提交進(jìn)倉(cāng)庫(kù)256 次截?cái)鄴呙璋讶萜鲝牡?0 字節(jié)到密文起點(diǎn)逐字節(jié)截?cái)鄶嘌悦恳坏抖紥伋稣_的錯(cuò)誤類型raycast-test.swift切片索引測(cè)試驗(yàn)證非零起點(diǎn)的 Data 切片也能正確解密——因?yàn)榻獯a器內(nèi)部所有偏移都以startIndex為基準(zhǔn)上限邊界測(cè)試現(xiàn)場(chǎng)壓縮 70 MB 數(shù)據(jù)驗(yàn)證 64 MB 默認(rèn)上限攔截、512 MB 上限放行scrypt 派生慢是測(cè)試策略的關(guān)鍵約束整個(gè)測(cè)試只派生一次密鑰并共享給所有用例凡是能在校驗(yàn)階段提前失敗的場(chǎng)景都保證不花 scrypt 的錢測(cè)試以-O編譯后派生從 47 秒降到 1 秒。解密之后數(shù)據(jù)如何落到 Tinycast解出明文 JSON 只是起點(diǎn)。RaycastImportReader.swift 負(fù)責(zé)把 Raycast 的字段映射成 Tinycast 的領(lǐng)域類型幾個(gè)有意思的轉(zhuǎn)換快捷鍵Raycast 的修飾鍵名Meta/Ctrl/Alt/Shift轉(zhuǎn)成 Carbon 鍵碼一律導(dǎo)入為組合鍵——Raycast 沒有雙擊綁定應(yīng)用命令應(yīng)用路徑藏在命令 id 的::::之后通過Bundle解析成 bundle ID快捷鍵、收藏、別名三套映射共用同一解析剪貼板只認(rèn)text/plain和文件仍存在的image/*記錄圖片文件已丟失的條目會(huì)計(jì)數(shù)上報(bào)而不是靜默丟棄按類別選擇導(dǎo)入RaycastImportOptions是一個(gè) OptionSetRaycastImport.swift用戶勾選快捷鍵、收藏、剪貼板歷史、Emoji 膚色等任意子集selecting(_:)只保留勾選的類別小結(jié)這個(gè)實(shí)現(xiàn)好在哪里回看這套 Tinycast 導(dǎo)入 Raycast 備份的實(shí)現(xiàn)它示范了一個(gè)干凈的三層結(jié)構(gòu)RaycastDecoder 只管拆容器純邏輯可獨(dú)立編譯測(cè)試→RaycastImportReader 負(fù)責(zé)字段映射依賴 AppKit留在 Service 層→RaycastImport 只是純數(shù)據(jù)。手寫 Scrypt 手寫 Gzip 換來的是整個(gè)項(xiàng)目「零第三方依賴」承諾的延續(xù)——解密一個(gè)競(jìng)品格式的加密文件只用了系統(tǒng)框架和約 400 行 Swift。延伸閱讀功能文檔docs/features/raycast-import.md解密器RaycastDecoder.swift Scrypt.swift壓縮層Zlib.swift獨(dú)立測(cè)試raycast-test.swift【免費(fèi)下載鏈接】tinycastTinycast — a tiny, fully native macOS launcher, hotkeys, and clipboard history.項(xiàng)目地址: https://gitcode.com/GitHub_Trending/ti/tinycast創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考