iT邦幫忙

2026 iThome 鐵人賽

DAY 12
0
Software Development

我是Java工程師,關於密碼學我想懂的不多系列 第 12 篇

Day11 - 對稱金鑰加密系統:串流密碼ChaCha20的Java實戰

  • 分享至 

  • xImage
  •  

前言

在現代密碼學中,ChaCha20(RFC 8439)已全面取代舊時代的 RC4,成為現代串流加密(Stream Cipher)的主流。今日我們就來透過使用Java JCA的實際範例來理解加解密的完整過程。

一、 ChaCha20 的核心資料結構

ChaCha20 本質上是一個確定的金鑰流產生器(Deterministic Keystream Generator)。它以 64 Bytes(512 bits)為一個 Block 單位,透過以下三個輸入參數生成對應的金鑰流(Keystream):

金鑰流 = ChaCha20Block(Secret Key, Nonce, Counter)

  1. Secret Key (256 bits / 32 Bytes):絕對保密的共享金鑰。
  2. Nonce / IV (96 bits / 12 Bytes):每次加密必須隨機生成且絕不重複的一次性數值。
  3. Initial Counter (32 bits / 4 Bytes):區塊計數器,預設起始值通常為 1。

在本地端算出金鑰流 K 後,加密與解密完全依賴經典的 XOR 位元運算:

  • 加密:C = P ⊕ K
  • 解密:P = C ⊕ K

二、我的IV/nonce會是你的IV/nonce 嗎?

加密的過程中會使用到IV/nonce對方解密時怎麼知道你的IV/nonce呢?答案是IV / Nonce 通常會直接「明文附在密文前面」一起傳送給對方,常見的做法是拼接在密文開頭 (Prefix)

[ 12 Bytes IV (明文) ] + [ 加密後的密文 (Ciphertext) ]

三、 Java API 加解密運作架構

簡單的五步驟就可以對明文進行加密,解密也是相同的只要只要在步驟4設定為`DECRYPT_MODE即可
https://ithelp.ithome.com.tw/upload/images/20260926/201280847M9XzZ4PMd.png

四、Java JCA ChaCha20 完整實作範本

Java 從 JDK 11 (JEP 329) 開始原生支援 ChaCha20 演算法。以下範本示範了標準的 JCA 五階段生命週期,並包含實務中 Payload 打包與拆解的完整流程。

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.ChaCha20ParameterSpec;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Base64;

public class ChaCha20Demo {

    private static final String ALGORITHM = "ChaCha20";
    private static final int KEY_SIZE_BITS = 256;
    private static final int NONCE_SIZE_BYTES = 12; // RFC 8439 標準 12-byte Nonce
    private static final int INITIAL_COUNTER = 1;    // 加密訊息預設 Counter 起始值為 1

    public static void main(String[] args) throws Exception {
        String originalText = "Hello IronMan! 這是一段 ChaCha20 串流加密測試訊息。";

        // Step 1: 產生 256-bit ChaCha20 主金鑰
        SecretKey secretKey = generateKey();

        // Step 2: 執行加密,產出封裝好的 Payload [ Nonce (12B) + Ciphertext ]
        byte[] encryptedPayload = encrypt(originalText, secretKey);
        System.out.println("加密後的 Payload (Base64): " + Base64.getEncoder().encodeToString(encryptedPayload));

        // Step 3: 執行解密
        String decryptedText = decrypt(encryptedPayload, secretKey);
        System.out.println("解密後的明文內容: " + decryptedText);
    }

    /**
     * 階段 1:產生 256-bit ChaCha20 對稱金鑰
     */
    public static SecretKey generateKey() throws Exception {
        KeyGenerator keyGen = KeyGenerator.getInstance(ALGORITHM);
        keyGen.init(KEY_SIZE_BITS);
        return keyGen.generateKey();
    }

    /**
     * 階段 2~5:加密流程
     */
    public static byte[] encrypt(String plaintext, SecretKey key) throws Exception {
        byte[] plaintextBytes = plaintext.getBytes(StandardCharsets.UTF_8);

        // 階段 3a: 生成強強度的隨機 Nonce (IV)
        byte[] nonce = new byte[NONCE_SIZE_BYTES];
        SecureRandom.getInstanceStrong().nextBytes(nonce);

        // 階段 3b: 封裝參數 Specification (Nonce + Counter)
        ChaCha20ParameterSpec paramSpec = new ChaCha20ParameterSpec(nonce, INITIAL_COUNTER);

        // 階段 2: 取得 Cipher 實例
        Cipher cipher = Cipher.getInstance(ALGORITHM);

        // 階段 4: 初始化 Cipher 為 ENCRYPT_MODE
        cipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);

        // 階段 5: 執行最後 XOR 運算,產出密文
        byte[] ciphertext = cipher.doFinal(plaintextBytes);

        // 工程打包: 將 [ Nonce (12 Bytes) ] 拼接在 [ 密文 ] 前端傳輸
        ByteBuffer payload = ByteBuffer.allocate(nonce.length + ciphertext.length);
        payload.put(nonce);
        payload.put(ciphertext);

        return payload.array();
    }

    /**
     * 解密流程
     */
    public static String decrypt(byte[] payload, SecretKey key) throws Exception {
        if (payload.length < NONCE_SIZE_BYTES) {
            throw new IllegalArgumentException("Payload 長度不足,無法提取 Nonce!");
        }

        // 1. 拆解 Payload:切出前 12 Bytes 的 Nonce
        byte[] nonce = Arrays.copyOfRange(payload, 0, NONCE_SIZE_BYTES);

        // 2. 拆解 Payload:提取剩餘的密文
        byte[] ciphertext = Arrays.copyOfRange(payload, NONCE_SIZE_BYTES, payload.length);

        // 3. 使用提取出的 Nonce 與約定好的 Initial Counter 初始化解密器
        ChaCha20ParameterSpec paramSpec = new ChaCha20ParameterSpec(nonce, INITIAL_COUNTER);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
    
        // 4. 設定模式為 DECRYPT_MODE
        cipher.init(Cipher.DECRYPT_MODE, key, paramSpec);

        // 5. 本地計算相同的金鑰流並執行反向 XOR 解密
        byte[] decryptedBytes = cipher.doFinal(ciphertext);

        return new String(decryptedBytes, StandardCharsets.UTF_8);
    }
}

五、如何產生與資料長度相同的Keystream

在 Java 呼叫 cipher.doFinal(plaintext) 時,你不需要手動指定金鑰流要多長,因為 Cipher 引擎會根據你傳入的明文長度,動態生成對應長度的金鑰流。

以 ChaCha20 為例,它的底層運作方式如下:

  1. 64-Byte 方塊單位 (Block-based Generation):
    ChaCha20 內部是以 64 Bytes 為一個單位(Block)來計算金鑰流的(內部帶有一個 Counter 計數器,每算完一個 Block,Counter 就 $+1$)。
  2. 按需裁切 (Truncation):
  • 情境 A(明文 43 Bytes):ChaCha20 運算 1 輪,產出第一個 64-Byte 的金鑰流方塊,並只截取前 43 Bytes 與明文做 XOR,剩下的 21 Bytes 直接捨棄。
  • 情境 B(明文 100 Bytes):ChaCha20 運算 2 輪(Counter 1 與 Counter 2),產出 $64 + 64 = 128$ Bytes 的金鑰流,並取用前 100 Bytes 與明文做 XOR。

六、 Counter(計數器)的作用是什麼?

ChaCha20 屬於以區塊為單位生成金鑰流(Block-based Keystream Generator)的串流密碼。它的內部算法每次會產出 64 Bytes(512 bits) 的金鑰流區塊。

counter 的核心作用是標記當前正在生成「第幾個 64-Byte 金鑰流區塊」,具體效益如下:

1. 確保同一份資料內,每個區塊的金鑰流不重複

ChaCha20 內部的數學運算狀態由 16 個 32-bit 的數字組成(包含 Constant、Key、Nonce 與 Counter)。

  • 生成第 1 個 64-byte 金鑰流時:狀態內的 counter = 1
  • 生成第 2 個 64-byte 金鑰流時:狀態內的 counter = 2
  • 生成第 3 個 64-byte 金鑰流時:狀態內的 counter = 3

因為 Counter 每 64 Bytes 就會自動 $+1$,這保證了即使 Key 與 Nonce 完全不變,第 1 個區塊與第 2 個區塊算出來的金鑰流也完全不同。

2. 支援「隨機存取(Random Access)」與平行運算

傳統的串流密碼(如 RC4)如果要解密第 1,000,000 個 Byte 的資料,必須先從第 1 個 Byte 依序計算到第 999,999 個 Byte。

但 ChaCha20 只要知道 offset,就能直接推算出對應的 counter 值。例如:
要讀取第 $1280$ 個 Byte(位於第 21 個區塊,因為 $1280 / 64 = 20$),直接把 counter 設定為 21,就能跳過前面的資料,單獨計算該區塊進行解密。

3. 初始值為什麼設定為 1?

在 RFC 7539 / RFC 8439 標準規範中,ChaCha20 加密資料時的初始 Counter 通常設為 1(0 往往被留給帶認證的 ChaCha20-Poly1305 模式用來生成 Poly1305 的 One-Time Key)。


上一篇
Day10 - 對稱金鑰加密系統:串流密碼(Stream Cipher)
下一篇
Day12 - 對稱金鑰加密系統:區塊密碼(Block Cipher)
系列文
我是Java工程師,關於密碼學我想懂的不多 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言