如何將加密文件儲存在 Web 伺服器上並在本地解密?
我想將文件(圖像)儲存在公共網路伺服器上,並讓使用者在知道密碼的情況下查看它們。伺服器不應該有未加密的文件,並且伺服器只能提供文件,不能執行任何伺服器大小的計算。
關於加密,我知道的一件事是我知之甚少,所以我想回顧一下以下計劃:
想法描述:
我計劃使用 AES-256、隨機 IV 加密圖像,並將它們保存為 IV 和加密輸出的串聯。有些圖像將具有相同的密碼,有些則不會。
要查看這些文件,使用者將在網頁上的輸入框中輸入他的密碼,javascript 將下載加密文件並使用他輸入的密碼來解密圖像並顯示它。
圖像的下載和解密很慢,如果密碼錯誤,我希望它會失敗,所以我想儲存一個“密碼檢查”,它將在本地執行,在 javascript 中。我的想法是在純文字“索引文件”中儲存某種正確密碼的雜湊值。(索引文件的下載速度比圖像快得多。)在下載和顯示圖像之前,javascript 將在本地檢查密碼的雜湊值是否與索引文件中列出的內容匹配。
我計劃在 .Net 中創建索引文件和加密圖像,並使用crypto-js執行所有 javascript ,因此我使用的任何算法都應該適用於這些。如果不影響安全性,我想盡量減少下載時間和 javascript 執行時間。
問題:
如何將使用者密碼擴展為有效的 AES 密鑰? 到目前為止,我計劃使用 PBKDF2,因為它在 .Net 和 crypto-js 中可用。
我應該為每張圖片單獨使用鹽嗎?
- 如果是這樣,我應該將該鹽與 IV/加密文件一起儲存在加密圖像中還是在索引文件中?這些選項有哪些優點/缺點?例如,我可以在等待圖像下載時執行 PBKDF2。
如何儲存密碼檢查? 到目前為止,我也計劃為此使用 PBKDF2,並將隨機鹽和 PBKDF2 輸出放入索引文件中。當然,每個密碼的鹽與每個圖像的鹽是不同的,因為否則每個圖像的 AES 密鑰將是索引文件中的純文字!
- 是否有任何理由認為 PBKDF2 對於密碼檢查和圖像解密來說都是一個糟糕的選擇?
任何我沒有想到的安全錯誤? 我知道下載的 javascript 不可信,但我會讓我的使用者決定他們是要冒這個風險還是使用 https 來獲取 javascript。
**編輯,決議:**我的決議是 Ilmari Karonen 的絕佳答案。根據有效密碼,我使用 PBKDF2 生成 128 位,每次使用新的隨機鹽。我將每個密碼的鹽儲存在索引文件中。每張照片,我生成一個隨機 IV 和隨機密鑰並加密。在照片索引文件中,每張照片,我儲存照片的密鑰,由每個密碼 PBKDF2 加密。我還儲存了照片密鑰的第二次加密,這次照片密鑰的第一個字節以 1 模 256 遞增。用於檢查已驗證對某些照片有效的密碼是否對每張照片有效。將 PBKDF2 位加倍以進行身份驗證比僅嘗試所有圖像密鑰解密並測試結果要慢得多。
這是加密,而不是 stackoverflow,所以我不會在此處包含程式碼,但所有加密都是使用 .Net 完成的,所有解密都是使用 javascript 完成的,它可以工作。如果有人需要,我可以免費提供。
在編寫“JavaScript”之前,您做得很好。
當然,JavaScript 作為一種語言並非根本無法用於加密(儘管作為一種高級腳本語言,在 JavaScript 中實現的任何加密原語都可能相當緩慢且難以抵禦側通道攻擊)。但是,當您編寫“JavaScript”時,我假設您的意思是由伺服器發送並由瀏覽器在客戶端執行的程式碼。
這種安全模型存在根本缺陷,原因有兩個:
- 首先,如果您不使用 HTTPS,那麼任何可以攔截客戶端和伺服器之間通信的人(例如,通過 ARP 或 DNS 中毒,或惡意 WiFi 接入點)都可以修改發送給客戶端的程式碼並插入他們自己的後門;和
- 其次,即使您確實使用 HTTPS 來保護客戶端和伺服器之間的連接,客戶端仍然會盲目地執行伺服器提供給它的任何程式碼。
如果您在編寫時的目標是通過在客戶端加密數據來防止伺服器學習或篡改數據,那麼您還沒有真正完成任何類似的事情:加密可能在客戶端完成,但是它是使用伺服器發送的程式碼完成的,沒有什麼可以阻止該程式碼儲存使用者的密鑰和密碼並將它們隱藏在發送到伺服器的數據中。
(當然,仔細檢查程式碼及其產生的網路流量可能會發現這樣的後門,但大多數使用者既不能也不願意進行這樣的檢查。僅僅一兩個使用者是不夠的它,因為其他使用者沒有理由相信他們發送的程式碼與發送給其他人的程式碼相同。)
有關為什麼這是一個糟糕的安全架構的更多資訊,請參閱Bruce Schneier 部落格中的這篇文章以及其中連結的文章。
好的,那你該怎麼辦?好吧,如果您想在瀏覽器上執行一些 JavaScript(或其他)程式碼而伺服器無法完全控制它,您可以將其設為使用者可以安裝的瀏覽器外掛。這仍然不會完全安全,因為外掛本身可能會受到損害,但至少它會稍微限制攻擊面。(當然,您可能應該確保外掛無法自行升級,因為那樣只會完全重新打開漏洞。)
您還可以嘗試發布您的程式碼的加密雜湊(例如 SHA-256;不是MD5,這已被破壞),以允許使用者驗證它沒有被篡改。當然,如果我從您的站點下載了程式碼和雜湊值,您(或介於兩者之間的攻擊者,如果您不使用 SSL)可能已經篡改了兩者,但至少,如果我足夠小心和偏執的話,原則上,我可以在其他地方(例如 Google 的記憶體)尋找雜湊的副本,以確保我擁有與其他人相同的軟體。
然而,最重要的是,很難提供一種安全的加密服務,以防止服務提供商本身的潛在危害。您可以盡力而為,並通知您的使用者剩餘的漏洞 - 或者您可以放棄,在伺服器上進行加密並僅使用 SSL 傳輸數據(並且可能暗示使用者他們可能想要加密他們的首先本地數據,以防萬一)。但請不要試圖用瀏覽器內 JavaScript 加密等不安全的半措施來欺騙您的使用者。
附言。重讀您的問題,我注意到您最後確實解決了這個問題,或者至少揮手致意。儘管如此,我的觀點仍然成立。與執行可能不受信任的加密程式碼的巨大漏洞相比,我認為其餘任何部分都沒有明顯缺陷。
無論如何,鑑於 PBKDF2(故意)很慢,尤其是在 JavaScript 中,您可能希望盡量減少合法客戶端需要執行它的次數。所以我建議這樣的事情:
- 讓 $ P $ 是使用者的密碼。當使用者輸入它時,客戶端將它散列成一個 128 位的密鑰 $ K_P = \text{PBKDF2}(P) $ (最好用一些使用者特定的值來加鹽)。
- 當每個文件被加密時,一個隨機的 128 位密鑰 $ K_F $ 生成。該文件使用 AES 以合適的模式(不是 ECB)和密鑰進行加密 $ K_F $ 並發送到伺服器。
- 客戶端加密 $ K_F $ 和 $ K_P $ 在 ECB 模式下使用 AES(沒關係,因為我們只是加密單個 128 位塊),產生 $ C_K = \text{AES}{K_P}(K_F) $ . 它還加密** $ K_F’ = K_F + 1 $ , 在哪裡 $ + $ 表示加法模 $ 2^{128} $ (甚至只是模 $ 2^{32} $ 或者 $ 2^8 $ ,其餘位保持不變),產生 $ C_K’ = \text{AES}{K_P}(K_F’) = \text{AES}{K_P}(K_F + 1) $ .
- 客戶端發送 $ C_K $ 和 $ C_K’ $ 到伺服器,伺服器將它們儲存在索引中。
- 當客戶端要解密文件時,它首先下載索引,解密 $ C_K $ 和 $ C_K’ $ 要得到 $ K_F $ 和 $ K_F’ $ ,並檢查 $ K_F’ = K_F + 1 $ ; 如果不是,它會要求使用者確認密碼是否正確。
- 如果檢查通過,客戶端現在知道 $ K_F $ ,並且可以下載和解密文件。
- 如果使用者想修改密碼,客戶端只需要重新計算 $ C_K $ 和 $ C_K’ $ 對於每個文件並將新值發送到伺服器;文件密鑰 $ K_F $ 不需要更改,因此文件本身不需要重新加密(儘管如果客戶端也提供該選項也很好,以防使用者懷疑文件密鑰可能已被洩露)。