編碼、轉義和散列
Base64 不是加密,btoa 不是 UTF-8,encodeURI 不是encodeURIComponent,SHA-256 不是密碼雜湊。以下是它們的實際作用,以及否則假設會產生的具體錯誤。
編碼不是加密,也不是壓縮
編碼改變了資料的寫入方式。加密改變了誰可以讀取它。壓縮會改變它所佔用的空間大小。這是三項不同的工作,而 base64 只完成第一項——如果您希望完成其他兩項中的任何一項,那就很糟糕了。
Base64 一次取得三個字節,並將它們重寫為從 64 符號字母表中提取的四個字元。四個字元攜帶三個位元組意味著輸出總是比輸入大 33% 左右,加上填充。它的存在是因為大量基礎設施——電子郵件標頭、HTTP 標頭、JSON 字串值、URL、XML 屬性——都是為文字和破壞或拒絕任意位元組而設計的。 Base64 是一種適配器,可讓您透過文字形狀的管道推送位元組。
任何人都可以立即逆轉,無需鑰匙,因為沒有鑰匙。如果您對密碼進行 Base64 處理,則您已以不太方便的格式發布了密碼。這很重要,因為在人眼看來,base64 輸出看起來是混亂的,而這正是人們信任它做不到的事的特性。
為什麼 btoa() 會中斷,以及它中斷的兩種不同方式
瀏覽器為您提供 btoa() 和 atob(),它們比現代文字 API 更古老。 btoa 是在「二進位字串」上定義的:其中每個代碼單元都是單一位元組的字串,從 0 到 255。文字不是那樣的。
第一次失敗是響亮的。呼叫 btoa("世界") 會得到一個 InvalidCharacterError,因為 U+4E16 不適合一個位元組。嚴重的失敗是好的——你會立即註意到它們並尋找解決方案。
第二次故障是無聲的,並且是到達生產的故障。字元 é 是 U+00E9,它確實適合一個位元組。所以 btoa("café") 愉快地返回,將 é 編碼為單字節 0xE9。但是UTF-8中的é是兩個位元組,0xC3 0xA9。您剛剛產生的 Base64 在地球上的所有其他系統中都會解碼為非您文字的內容。幾週後,您會發現資料庫中的名稱已變成替換字元。
解決方法是停止將文字視為位元組並明確轉換它。 TextEncoder 產生 UTF-8 位元組;對這些進行編碼。 TextDecoder 將位元組轉換回文本,並使用 { fatal: true } 構造它,使其拋出無效序列,而不是悄悄地替換 U+FFFD,因此不可能正確的解碼會失敗,而不是返回看似合理的廢話。這就是該工具包使用的管道,這就是為什麼表情符號、標記和從右到左的腳本完全往返的原因。
- 使用 TextEncoder 將文字轉換為位元組 — 切勿對字串進行索引。
- 將位元組編碼為 base64。
- 反轉:將 base64 解碼為字節,然後使用 fatal: true 將位元組解碼為 UTF-8。
- 如果 UTF-8 步驟失敗,則有效負載是二進位的,而不是文字。將其顯示為十六進位而不是假裝。
base64 與 base64url 以及填充問題
标准 base64 使用 + 和 / 作为最后两个符号。兩者在 URL 中都是有意義的:+ 可以被讀取為查詢字串中的編碼空格,而 / 是路徑分隔符號。因此 RFC 4648 定義了第二個字母表,base64url,它用 - 和 _ 代替。 JWT 使用它,大多數令牌格式和許多 API 也是如此。
填充是另一個變數。標準 base64 以 = 填充,因此輸出長度總是四的倍數。 base64url 通常會刪除填充,因為 = 本身就是 URL 中的一個尷尬字符,並且可以透過算術恢復長度。堅持填充的解碼器將拒絕完全有效的 JWT 片段。
實用建議:您的解碼器應該接受兩種字母並容忍缺少的填充,因為您很少控制所收到的內容。您的編碼器應該明確其發出的內容,因為接收者可能確實關心。這裡的 base64 實用程式正是這樣做的 - 它接受任何合理的內容,並讓您精確選擇它產生的內容。
encodeURI 和encodeURIComponent:一句話的差別
兩者都使用 UTF-8 進行百分比編碼。它們的差異僅在於保留哪些字符,而這種差異就是整個故事:encodeURIComponent 轉義保留的分隔符,encodeURI 則不會。
保留的分隔符號是賦予 URL 結構的字元: : / ? #[]@! $ & ' ( ) * + , ; =。 encodeURI 假設您向它提供了一個已經正確建構的 URL,並且必須保持這種方式,因此它會保留它們 - 它不會將 https:// 轉換為 https%3A%2F%2F。 encodeURIComponent 假設您交給它一個將被放入槽中的片段,因此它會轉義它們,確保該片段不會脫離其槽。
這產生的錯誤完全是機械性的。取搜尋值a&b=c。用encodeURI對其進行編碼,並將其附加為?q=a&b=c,您就默默地創建了兩個參數:q現在只是"a",並且出現了一個雜散的b=c。使用encodeURIComponent對其進行編碼,您將得到? q=a%26b%3Dc,一個參數,正確的值。同一類錯誤可以讓精心設計的值將參數注入到程式碼建構的 URL 中,這就是為什麼「使用元件形式的值」是一條安全規則,而不僅僅是正確性規則。
表單編碼是第三條規則,與第二條規則類似。 application/x-www-form-urlencoded 將空格寫入 + 而不是 %20。如果您使用普通的decodeURIComponent 解碼表單主體,則資料中的每個加號都會變成空格。每個曾經將 "C++" 損壞為「C」的搜尋框都是這個錯誤。
HTML 實體,以及為什麼使用 innerHTML 解碼它們是一個壞習慣
HTML 的轉義範圍很窄並且很好理解:& 變成 &,< 變成 <,> 變成 >,並且內部屬性值 " 和 ' 也需要轉義。五個字元。轉義更多字元——將每個重音化為實體命名——是字元編碼的時代
解碼是壞習慣的根源。每個答案中出現的一行技巧是將字串指派給分離元素的innerHTML並讀回其textContent。它有效,但這是一個糟糕的主意。您已將不受信任的輸入傳遞給 HTML 解析器,該解析器會從中建立真實的 DOM 節點。該字串中的 <img src=x onerror=...> 成為附加了實際錯誤處理程序的實際圖像元素;如果該子樹被插入到文件中,它就會運行。它還會默默地破壞您的資料:輸入中的標籤消失而不是往返,因為解析器將它們解釋為標記而不是文字。
正確解碼實體根本不需要解析器:匹配引用、在表中尋找名稱或對數字引用進行算術。那是幾十行,它不能執行任何東西,並且它忠實地往返。該工具包就是這樣做的,這就是為什麼將腳本標記貼到實體解碼器中會顯示腳本標記。
選擇哈希,以及決定它的三個問題
加密雜湊將任何輸入轉換為固定長度的摘要,因此找到具有相同摘要的兩個輸入應該是不可行的。此屬性讓摘要代表資料-在簽名、完整性檢查或內容地址中。
第一個問題:您是在防範事故還是防範對手?防止下載損壞的校驗和只需要捕獲隨機翻轉即可; CRC32 沒問題。攻擊者可以從碰撞中受益的摘要需要仍然有效的雜湊值。這種區別就是為什麼 SHA-1 不僅僅是 "old"。
SHA-1 已損壞。在 2017 中,SHAttered 工作產生了兩個不同的 PDF 檔案,具有相同的 SHA-1 摘要。在 2020 中,「SHA-1 is a Shambles」示範了選擇前綴衝突 - 更強大且更危險的變體,因為它讓攻擊者可以碰撞兩個有意義的不同文件,而不是兩個精心構造的 blob。如果系統的安全性依賴 SHA-1 抗碰撞性,那麼這種安全性就消失了。 SHA-1 保留在該工具包中,因為 git 物件 ID 和一長串遺留 API 簽章仍然使用它,並您需要能夠重現這些值。複製價值與依賴它不同。
第二個問題:輸入的是密碼嗎?如果是這樣,那麼這些都不是答案。 SHA-256 的設計目標是快速,而快速對於密碼來說恰恰是錯誤的:這意味著使用您的資料庫的攻擊者每秒可以嘗試數十億次猜測。密碼需要一個故意緩慢、記憶體困難的函數,並帶有每個使用者的鹽——Argon2id、scrypt 或 bcrypt。這不是一個細微差別;而是。使用 SHA-256 作為密碼是最常見的嚴重雜湊錯誤。
第三個問題:您需要密鑰摘要嗎?如果您要對訊息進行身份驗證而不是對其進行指紋識別,則需要 HMAC,而不是裸露的雜湊值。連結秘密並對其進行雜湊處理是對抗長度擴展攻擊的經典自身目標; HMAC 之所以存在,是因為這種結構比看起來更難實現。
對於其他所有內容 - 對檔案、內容位址、完整性屬性進行指紋識別 - SHA-256 是合理的預設值,SHA-512 在 64 位元硬體上通常更快,同時提供更廣泛的摘要。
為什麼這裡的哈希值來自瀏覽器
該工具包中的摘要由瀏覽器本身的 Web Crypto 實作 SubtleCrypto 計算,而不是由該網站提供的 JavaScript 計算。這是一個深思熟慮的選擇:瀏覽器的實作經過審核、維護,並且通常作為優化的本機程式碼運作。頁面束中手寫的 SHA-256 是更多值得信任的程式碼,但沒有任何好處。
它有一個明顯的後果。 Web Crypto 僅在安全上下文中公開,即 https:// 或 localhost。在 LAN 位址上透過純 HTTP 開啟此頁面,crypto.subtle 將是未定義的,因此雜湊實用程式會清楚地告訴您,而不是默默地失敗或替換更弱的東西。
同樣的推理驅動 UUID 生成器。 crypto.randomUUID() 也是僅安全上下文的,因此在它不可用的情況下,工具包會回退到 crypto.getRandomValues() (它仍然是相同的加密安全來源)並自行設定版本和變體位元。它永遠不會做的是回退到 Math.random()。這是一種快速的非加密 PRNG,其內部狀態可以從其短期輸出中恢復,並且標識符有一個不幸的習慣,即被提升為會話密鑰和密碼重置連結。如果不存在安全來源,則工具不會產生任何內容並說明原因。
你貼的內容會發生什麼
- 每個轉換、哈希、解碼和差異都在您的瀏覽器標籤中運行。不會在伺服器上傳、記錄或儲存任何輸入,因為頁面載入後就不再涉及伺服器。
- 雜湊值來自瀏覽器自己的 Web Crypto 實現,UUID 來自其加密安全隨機產生器。兩者都不涉及網絡調用。
- 您鍵入的任何內容都不會寫入本機儲存或 cookie。重新載入頁面會丟棄它;關閉選項卡將丟棄它。
- 站點範圍的分析僅在配置的規範生產主機上運行,並在隱私權政策中披露;本地和預覽主機拒絕它。貼上的值、令牌、URL 和文件內容不包括在 ToolAcre 自己的分析事件中。目前配置中停用廣告。
- 也就是說:JWT 或 API 金鑰是即時憑證。安全的習慣是永遠不要將其粘貼到不是您編寫的網頁中,無論其聲明多麼可信 - 包括這個。
問題
Base64 是一種隱藏資料的方法嗎?
不。它是一種沒有密鑰的可逆文字表示形式,任何人都可以在幾分之一秒內解碼。它使數據能夠在純文字頻道中生存;它並沒有使它成為秘密。任何真正敏感的內容都需要加密,而加密結果通常會進行 Base64 編碼以進行傳輸——這就是混亂的根源。
為什麼我的 Base64 比輸入長?
由於四個輸出字元攜帶三個輸入位元組,因此輸出的大小大致為 4/3 ,加上最多兩個填充字元。這是格式所固有的。如果大小很重要,請在編碼之前進行壓縮,而不要在編碼之後進行壓縮,因為 base64 輸出的壓縮效果很差。
我應該使用哪種 URL 編碼函數?
對要插入 URL 的任何單一片段使用encodeURIComponent:查詢值、路徑段、片段。只有當您擁有僅包含空格或非 ASCII 的完整、已結構化的 URL 時,才使用encodeURI。如果您正在建立查詢字串,請首選 URLSearchParams,它會為您套用正確的規則並處理空格加號差異。
為什麼我的解碼器會拋出“URI 格式錯誤”?
因為輸入中的 % 後面沒有跟兩個十六進制數字。通常,文本包含一個字面百分號——“50% off”——從未被編碼。文字百分比必須寫為 %25。這裡的 URL 實用程式會報告有問題的轉義的確切位置,而不是僅僅拒絕。
我可以使用 SHA-256 來儲存密碼嗎?
不會。 SHA-256 的設計速度很快,這意味著竊取您資料庫的攻擊者每秒可以在商用硬體上測試數十億個候選密碼。密碼需要一個緩慢、難以記憶的加鹽函數:Argon2id、scrypt 或 bcrypt。這是該領域最常見的嚴重錯誤。
如果 SHA-1 損壞了,為什麼它還在這裡?
因為您仍然需要重現已存在的 SHA-1 值:git 物件 ID、舊的 TLS 憑證指紋、舊版 API 要求簽章。能夠計算互通性的值與依賴它來保證安全性是不同的。此工具包中出現 SHA-1 的每個位置都有對應的標籤。
為什麼兩個工具對同一文本給出不同的雜湊值?
幾乎總是位元組的差異,而不是演算法的差異。通常的罪魁禍首是尾隨換行符(檔案以換行符號結尾;文字方塊可能不是)、不同的文字編碼或 CRLF 與 LF 行結尾。該工具對您鍵入的內容的 UTF-8 bytes 進行雜湊處理,並顯示位元組數,這通常會使差異變得明顯。
限制
- 命名的 HTML 實體表涵蓋了實際子集 - 標記關鍵字元、版式、貨幣、箭頭、數學、希臘語和拉丁語 - 1 - 並非所有 2,231 HTML5 命名引用。無法識別的名字會被報告並完全按照書寫內容而不是猜測。
- 實體解碼需要終止分號。 HTML5 可以容忍少量遺留引用,但正確解碼它們取決於周圍的標記上下文,這是獨立文字工具所不具備的。
- 哈希和 UUID 產生需要安全上下文(https:// 或 localhost),因為否則 Web Crypto 不會暴露。該工具會報告這一點,而不是替換較弱的實作。
- 只有 SHA-1、SHA-256、SHA-384 和 SHA-512 可用,因為這些是 SubtleCrypto 實現的。 MD5 的缺失是出於選擇,也是出於必然。
- 這裡沒有 HMAC,沒有金鑰派生,也沒有加密。這些需要金鑰管理,這不是您在網路上找到的頁面應該處理的事情。
- 一切都受到設備記憶體的限制,因為所有內容都在一個瀏覽器標籤中運行。輸入是有上限的——每個實用程式只有幾兆位元組——並且該工具拒絕超大工作而不是凍結。