Java 中用 salt 產生 SHA-512 雜湊的標準模式為:以 java.security.MessageDigest.getInstance("SHA-512") 取得演算法,將 java.security.SecureRandom 產生的隨機 salt 位元組與密碼位元組串接,再把產生的 64 位元組摘要以 128 個小寫十六進位字元或標準填補的 Base64 表示。SHA-512 定義於 NIST FIPS 180-4,以 1024 位元區塊與八個 64 位元狀態字運作,輸出 512 位元;Java 的標準提供者所回傳的摘要在位元層級上與所有其他符合標準的實作(包括瀏覽器型 SHA-512 雜湊產生器)完全一致。Salt 本身並不會改變演算法,它改變的是輸入位元組。因此,加鹽後的 SHA-512 結果只有在確切的 salt 位元組已知、且原始輸入訊息完整保留時才能重現,這也是 salt 相關比對錯誤如此常見的主要原因。僅僅將單一 salt 串接在輸入前面或後面,本身並不是安全的密碼儲存方式:原始 SHA-512 設計上就非常快速,而快速雜湊正讓離線猜測攻擊得以實行。生產環境的密碼儲存應使用記憶體密集型的金鑰衍生函式,例如 Argon2id、scrypt 或 bcrypt,並搭配每名使用者獨立的隨機 salt、可選的 pepper,以及調整過的成本參數。

java sha512 hash with salt example
Java SHA-512 雜湊(含 Salt)範例詳解

Java 如何計算 SHA-512 摘要

Java 的 MessageDigest 是一個引擎式 API:以名稱請求演算法,餵入位元組,然後讀出摘要。對 SHA-512 而言,呼叫為 MessageDigest.getInstance("SHA-512"),之後 digest(inputBytes) 不論輸入長度為何,一律回傳剛好 64 位元組。將這 64 位元組編碼為每個位元組兩個字元的小寫十六進位會得到 128 個字元的字串;編碼為標準填補的 Base64 則會得到 88 個字元。這兩種表示法描述的是同一組位元組,因此兩者之間的轉換完全無損。

同樣的輸入位元組傳給任何符合標準的 SHA-512 實作——其他 JDK、OpenSSL、Python 的 hashlib,或瀏覽器內的產生器——都必須產生完全相同的 64 位元組。若兩個輸出不同,差異必定出在輸入位元組,而非演算法本身。SunJCE 提供者中的實作執行 FIPS 180-4 的訊息排程:將訊息填補為 1024 位元區塊、把每個區塊展開成 80 字的排程,並在整段訊息中更新八個 64 位元的狀態值。這些最終狀態值的串接結果即為 512 位元的摘要。

一個可驗證的參考範例:空字串是合法的 SHA-512 輸入,會產生一個以 cf83e135 開頭的 128 字元小寫十六進位字串,此結果已由 NIST FIPS 180-4 驗證,並可用 OpenSSL 重現。只要在輸入中多加一個位元組——即使是看不見的,例如結尾換行字元或 UTF-8 位元組順序標記——128 字元的輸出就不會有任何部分保持不變。

Salt 在 Java 程式碼中的位置

標準的 Java 模式是:使用 SecureRandom(通常為 16 位元組)為每個使用者產生隨機 salt,依固定順序將其與密碼位元組串接,把合併後的位元組餵給 MessageDigest("SHA-512"),並把 salt 與摘要一起儲存,以便驗證端能重算雜湊。最精簡的版本如下:

SecureRandom rng = new SecureRandom(); byte[] salt = new byte[16]; rng.nextBytes(salt); MessageDigest md = MessageDigest.getInstance("SHA-512"); md.update(salt); byte[] hash = md.digest(password.getBytes(StandardCharsets.UTF_8));

驗證時,以儲存的 salt 重算摘要,並使用 MessageDigest.isEqual(Java 7 起可用)或專用的常數時間比較器,與儲存的雜湊進行常數時間比對。Salt 唯一的作用就是讓每個使用者的原始輸入訊息都不同,使得預先計算的查詢表——彩虹表——無法在資料庫中跨使用者重複使用。關於與此程式碼搭配的儲存格式細節,請參閱 SHA-512 密碼雜湊(含 Salt):輸出的意義。

有三項實作細節決定了兩個 SHA-512 實作是否能在位元層級上達成一致:

  • 串接順序。即使使用相同的 salt 與密碼位元組,「salt 在前、密碼在後」與「密碼在前、salt 在後」會產生不同的摘要。選定一種並始終遵循。
  • 字串到位元組的編碼方式。Java 中不帶引數的 String.getBytes() 會採用平台預設字元集,而生產伺服器上幾乎不會是 UTF-8。請務必傳入 StandardCharsets.UTF_8。
  • 正規化。任何 Unicode 正規化(NFC、NFKC)、大小寫轉換或去頭尾空白都會改變原始輸入訊息,進而改變摘要。請在雜湊前明確套用,否則就不要套用。

為何只用 Salt 的原始 SHA-512 不足以保護密碼

Salt 能阻擋彩虹表,卻無法降低單次猜測的成本。原始 SHA-512 設計上就追求極致速度:一張現代消費型 GPU 每秒可計算數十億次 SHA-512,專用 ASIC 還要快上許多。攻擊者一旦取得一份 (salt, sha512(salt || password)) 配對的資料庫,就能以該速率遍歷候選密碼,對每一筆紀錄逐一嘗試。雖然每名使用者獨立的 salt 會迫使攻擊者對每一列花費相同的運算,但每次猜測的牆鐘成本仍舊只是一次 SHA-512 呼叫的速度。

生產環境的密碼儲存需要一種記憶體密集、參數化的構造,使每次評估在時間與記憶體上都非常昂貴,並能抵抗 GPU 與 ASIC 的平行化。目前廣為接受的選擇包括:

  • Argon2id——目前的 OWASP 推薦選項,也是最強的通用方案。
  • scrypt——具悠久實務紀錄的記憶體密集型替代方案。
  • bcrypt——僅使用 CPU 與 salt 的構造,可接受但已不再是最強者。
  • PBKDF2-HMAC-SHA-512——受限於 CPU 的構造,僅在前述選項不可用時使用,且需搭配高的迭代次數。

它們皆接收 salt、密碼、一個成本參數(Argon2id 與 scrypt 還需要記憶體參數),並輸出一個把所有輸入都編碼進去的儲存值,使驗證端能重現整個計算過程。它們沒有一個是單純的 MessageDigest 呼叫。在 Java 中,Bouncy Castle(bcprov)提供可投入生產的 Argon2 與 scrypt 支援,Spring Security 提供 bcrypt 與 Argon2 支援,而 JDK 本身目前並未內建可用於生產環境的密碼 KDF。

演算法摘要位元數摘要位元組數小寫十六進位字元數
SHA-2562563264
SHA-3843844896
SHA-51251264128
SHA-512/2562563264
SHA-512/2242242856

這五者皆是 FIPS 180-4 所規範的不同演算法。SHA-512/256 與 SHA-512/224 使用不同的初始雜湊值與不同的截斷規則,與完整的 SHA-512 不同——它們並非單純從完整 SHA-512 摘要中截取前 256 或 224 位元而得。Sha512 雜湊產生器 一律回傳完整的 512 位元,因此預期結果為 128 字元的十六進位字串;任何較短的結果都代表使用了不同的演算法。

使用 Sha512 雜湊產生器驗證摘要

Sha512 雜湊產生器在餵入相同位元組時,會與符合標準的 Java MessageDigest("SHA-512") 呼叫產生相同的 64 位元組摘要,因此這是快速確認 Java 程式碼是否雜湊了你所預期位元組的便利方法。瀏覽器在本機端執行計算,因此你所選的訊息或檔案不會被上傳。

  1. 決定你需要哪種模式。文字模式會將字串轉成 UTF-8 並回報產生的位元組數;檔案模式則直接對原始位元組進行雜湊,不會解讀檔名、字元集或 MIME 類型——這對 JAR、PDF 等二進位成品很實用,但仍受限於瀏覽器每次輸入 100 MB 的上限。
  2. 在 Java 中,請確實擷取傳入 MessageDigest.digest 的位元組。若要做單元測試,請將其以十六進位印出,以便將同一段位元組序列以文字貼回。
  3. 將這些位元組的不含 salt 版本,以 UTF-8 文字形式貼到產生器中,保留大小寫、換行符號以及任何結尾空白字元。
  4. 產生並複製 128 字元的十六進位輸出;若你的參考值是 Base64,則複製填補後的 Base64。
  5. 逐字元與 Java 對相同位元組所產生的摘要進行比對。若兩者逐字元相符,代表你的 MessageDigest 串接正確。若不相符,表示差異出在演算法之前的環節。

診斷 Java 輸出與參考值不一致的情形

當 Java 的 SHA-512 結果與已發布的摘要或參考系統不符時,差異幾乎必定出在演算法之前的環節。常見的不一致來源包括:

  • 編碼錯誤。不帶引數的 String.getBytes() 會使用平台預設字元集,而多數生產伺服器上的預設字元集並非 UTF-8。請明確指定 StandardCharsets.UTF_8。
  • 結尾或開頭的空白。結尾的換行字元屬於輸入的一部分。"abc" 與 "abc\n" 會產生截然不同的摘要。
  • 被插入 BOM。某些檔案讀取器會在開頭加上 UTF-8 位元組順序標記(即 EF BB BF 這三個位元組),但不會顯示出來。該 BOM 會成為雜湊輸入的一部分。
  • 變體混淆。你的輸出是完整的 512 位元。SHA-512/256 與 SHA-512/224 使用不同的初始值與不同的截斷方式;即使輸入相同,它們也不會相符。SHA-384 是另一種獨立的演算法,輸出長度更短。
  • 比對時把 Salt 也納入比較。未含 salt 的參考摘要永遠不會與加鹽後的摘要相符。在比對前,請從輸入中移除 salt,或改用一個固定的測試 salt。

本工具的黃金測試案例——空字串、字串 abc、FIPS 多區塊訊息、標點變化、UTF-8 文字、任意二進位位元組與常見 ASCII——提供了標準的原始輸入訊息,讓你可以把 Java 輸出與之比對。預期值已與 FIPS 180-4 測試向量及 NIST CAVP 安全雜湊 計畫進行比對,並由 OpenSSL 獨立重現。

When You Actually Need HMAC, Not a Raw Digest

Sometimes "hash with salt" in Java code is shorthand for "use a keyed construction". If the threat model includes an attacker who can read the database but cannot read the application secret, the right construction is HMAC-SHA-512, not raw SHA-512. HMAC combines a secret key with the message before hashing, so the digest cannot be recomputed without the key. In Java the call is Mac.getInstance("HmacSHA512") with a SecretKeySpec wrapping the key bytes; the output is again 64 bytes and renders as 128 hex characters.

The HMAC Generator covers HMAC-SHA-256, HMAC-SHA-384, and HMAC-SHA-512 and operates on exact UTF-8 or hexadecimal key bytes, which is useful for verifying your Java HMAC output against a known fixture. Note that HMAC-SHA-512 produces a different 64-byte digest from raw SHA-512 of the same message bytes, so an HMAC output will never match a raw-SHA-512 reference even with the same input.

If the threat model instead requires proving who sent the message — origin authentication rather than integrity — use a digital signature such as RSA-PSS, ECDSA, or Ed25519. A hash is a fingerprint; a signature is a fingerprint plus cryptographic proof of authorship. For password storage specifically, return to Argon2id, scrypt, or bcrypt with a per-user salt; the Sha512 Hash Generator remains the right tool only for verifying the raw, unsalted, full 512-bit digest.