HARDWARE SECURITY

密碼演算法 RTL 設計第 2 / 2 課

AES-128 RTL 初學者圖解

這篇把 AES-128 RTL 當成硬體施工圖來讀:先看資料怎麼進出,再看 Round 控制、FSM、S-box 與 MixColumns,讓初學者知道每個模組在整條加密路線裡負責什麼。

25 分鐘

先把 RTL 想成硬體版施工圖

這篇不是要你一開始就會寫完整 AES RTL。先把 RTL 想成硬體工程師的施工圖:資料要從哪裡進來、每一拍做什麼、哪個暫存器保存中間結果、控制器什麼時候說可以輸出,都要畫清楚。

AES-128 的演算法本身像一套工序;RTL 則是把這套工序變成真的硬體節奏。圖片會帶你看資料路徑、Round 控制、FSM、S-box 與 MixColumns 各自站在哪裡。

如果你是初學者,先不要被訊號名稱嚇到。先問:資料現在在哪裡?下一拍要去哪裡?誰負責決定流程往下走?這三個問題抓住,RTL 就開始變得可讀。

AES-128 RTL 初學者圖解

感測器準備送出 16 bytes 的資料。金鑰已經載入加密核心,但接收密文的下游正忙著。這時,核心可以先算完嗎?可以;前提是它有地方保管密文,而且不會被下一筆資料覆蓋。

設計這個模組,需要同時回答兩類問題。AES 決定密文的數值;介面則決定哪個上升緣接收資料,以及何時交出結果。少了後者,就算密文算對,也可能漏送或重複送出。

本文建立一個 AES-128 iterative core:只做加密,一次處理一個區塊,每拍完成一輪。先沿著資料走過電路,再安排時序,最後用已知答案除錯。程式片段說明接線與控制契約,並非可直接編譯的完整 AES IP。

先備知識是 XOR、組合邏輯、暫存器,以及 always_ff 的 nonblocking assignment。AES 決定每一步的數值轉換;下文的 FSM、握手與拍數則是我們選定的硬體設計,不是 AES 標準規定。

想先用漫畫建立 AES 概念,可閱讀 AES 漫畫小教室。

1. 先認識 AES-128

AES-128 的「128」指金鑰長度。AES 各種版本都處理 128-bit 區塊;改用較長的 key,不代表一次能加密更多資料。這裡先固定 AES-128,避免把不同輪數與 key expansion 混入第一版。

AES-128 每次處理:

  • 一組 128-bit 明文(Plaintext)
  • 一把 128-bit 金鑰(Cipher Key)
  • 產生一組 128-bit 密文(Ciphertext)
  • 先做 initial AddRoundKey,再執行 10 個 rounds
128-bit 明文與金鑰送入 AES 加密核心
教學圖 1 · 點圖可開啟原尺寸

AES-128 不是把 128 bits 當成一個大數字計算,而是先切成 16 個 bytes。

128 bits = 16 bytes

Plaintext = 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF
              |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
             B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 B10...........B15

先看全貌:一筆資料如何走過 AES IP?

先把自己放在系統設計者的位置:處理器或感測器會送來資料,你的 AES IP 是其中一個負責加密的硬體模組。系統希望交給它一筆明文,稍後取回正確密文。它不需要知道 S-box 裡面如何實現,卻必須知道何時能交資料、何時能取結果。

我們先把核心分成三個區域。這張圖只表達分工,不是逐條訊號的接線圖;此時先不背模組名稱。

簡明 AES IP 總覽:資料處理區接收明文並產生密文,金鑰區提供每輪 key,控制區啟動、計數並通知完成
導讀圖 A · 先看三個區域的工作,再看圖 2 的內部接線。

資料處理區(Datapath)負責「算什麼」。 它把明文存住,依照 AES 的步驟修改這 16 bytes,並保存每一輪的中間結果。你後面看到的 SubBytes、ShiftRows、MixColumns 和 AddRoundKey,都屬於這條資料處理路徑。

金鑰區負責「這一輪用哪把 key」。 外面只交給核心一把原始金鑰,但 AES 每一輪使用的是由它推導出的 round key。這些 round keys 不是重新隨機產生的密碼,也不用請上游每拍傳一把;它們由核心的 key expansion 電路依規則算出。

控制區(Controller)負責「何時做」。 它記住核心正在等資料、算第幾輪,還是等下游取結果。它不替資料做 S-box 或 XOR,而是決定暫存器何時更新、目前選哪條資料路徑,以及何時宣告輸出有效。

跟著一筆明文走一次

假設 key 已載入。上游提出一筆明文,核心也表示能接收;到了雙方約定的時脈邊緣,交付才成立。核心把這 16 bytes 存進自己的暫存器,上游此後可以改變輸入。如果沒有先存住,後面的運算就可能讀到上游下一筆資料。

核心先完成 initial AddRoundKey,再進行十輪處理。每一輪,資料區讀取目前的中間值,金鑰區提供這輪所需的 key,控制區確保結果在正確時間寫回。中間值還不是可以交出去的密文,即使它已經看起來沒有規律,也不能提前把 out_valid 拉高。

最後一輪完成後,核心宣告結果有效。如果下游正在忙,密文就繼續留在暫存器裡;等到下游確實接收,這筆工作才結束。因此「計算完成」與「交易完成」可能發生在不同時間。第 14 節的 valid/ready 就是把這個交付約定變成訊號。

十輪演算法,需要十套電路嗎?

不一定。想像同一張工作桌:第一輪處理完,把結果放回桌上,再用同一套工具做第二輪。本文選擇的 iterative architecture(反覆使用同一套硬體) 就是這個意思:保留一套 round 電路,讓資料多次通過它。

另一種做法是展開多套 round 電路;若再適當切割 pipeline,可以讓不同區塊同時在不同階段運作。代價是更多硬體、暫存器與控制設計。第一次實作先用一套電路,較容易觀察「這筆資料現在在哪裡」,也方便逐輪比對。

要分清楚兩個詞:round 是演算法的一段工作,cycle 是兩個時脈邊緣之間的時間。 一輪可以分好幾拍做,也可以用較大的組合電路在一拍內完成。本文採一拍一輪,前提是該輪運算與 key expansion 能在這一拍內穩定下來。

從演算法走到硬體:誰保存資料,誰負責計算?

在軟體中,你可以寫 state = round(state, key),執行完就得到新值。硬體中,要把它拆成兩件事:暫存器保存目前的 state,組合電路根據目前輸入算出下一個 state。 兩者之間由時脈邊緣接起來。

同一組 State 暫存器把目前資料送入 round 組合電路,下一個結果在下一個時脈邊緣寫回
導讀圖 B · 圖中只有一組 State 暫存器;回授線讓同一套電路反覆使用。

時脈邊緣過後,暫存器輸出目前的值,round 電路就開始對這組輸入產生反應。因為每個邏輯閘都有延遲,結果不會瞬間正確;它需要一段時間穩定。下一個時脈邊緣到來時,暫存器才把穩定後的結果存下來,作為下一輪的起點。

因此,組合電路不是等到 clk 才開始算,時脈主要決定暫存器何時取樣。RTL 裡常把前者寫成 assign、always_comb 或純組合子模組;把後者寫在 always_ff @(posedge clk)。若組合路徑太長,下一拍來了還沒算穩,就算公式完全正確,硬體仍可能無法在目標頻率工作。

回授也不等於無止境加密。控制器會決定是否寫回:正在算 round 時更新;密文等待下游時保持。它同時記住 round counter,讓第一次走 initial XOR、一般輪走完整轉換、最後一輪略過 MixColumns。第 12、13 節會把這些動作展開成精確拍數。

讀到這裡先確認:為什麼資料區之外還需要控制器? 因為同一份中間資料、同一套電路,在不同階段要做的動作不同;電路不會自行知道目前是第一輪、最後一輪,或應該停著等下游。

2. 打開三個區域:Top-Level 架構與介面

現在再看圖 2。把 State Register 與 AES Round Datapath 合起來,就是導讀圖 A 的資料區;Original Key、Working Round Key 與 Key Expansion 是金鑰區;Controller FSM 是控制區。這次才把區域打開,看到需要保存哪些值,以及控制器要管理哪些動作。

第一遍沿著 in_data → State Register → Round Datapath → State Register → out_data 看資料;第二遍看 key;第三遍才讀虛線控制。圖中的線路都存在,但不代表每一拍都會寫入每個暫存器。

AES 核心的金鑰、輸入、輸出與控制介面
教學圖 2 · 點圖可開啟原尺寸

圖 2 的 key 路徑有一個重要約定:初始 XOR 使用 K0,而且這一拍不更新 Working Round Key。 到第 r 輪時,working register 裡仍是 K[r−1];Key Expansion 組合電路產生 K[r],這把 key 直接供本輪計算,並在輪末與資料結果一起存下來。因此圖上的 Next Round Key 是「相對於暫存器舊值的下一把」,也是「本輪要用的 key」,不是再等一輪才用。第 12 節有逐拍對照表。

Top-Level IO

訊號方向寬度功能
clkInput1系統時脈
rst_nInput1Active-low synchronous reset
key_validInput1表示 key_data 有效
key_readyOutput1核心可以接收新金鑰
key_dataInput128AES-128 cipher key
in_validInput1表示 in_data 有效
in_readyOutput1核心可以接收明文
in_dataInput128128-bit plaintext
out_validOutput1表示 out_data 有效
out_readyInput1下游可以接收密文
out_dataOutput128128-bit ciphertext
busyOutput1交易未完成,包含等待下游接收

資料只有在 valid=1 且 ready=1 的 clock edge 才真正被接收。

讀 IO 表時,可以把 valid 理解成來源說「我手上這筆資料有效」,ready 理解成接收方說「這一拍我接得下」。只有一方說好還不能算交付。例如 in_valid=1 但核心忙碌、in_ready=0,上游就必須繼續拿著同一筆明文,不能當成核心已經收走。

本文全部以 clock 上升緣取樣。上游在等待期間要維持 valid 與資料,接收端不能只看到 valid 就算接收成功。金鑰與明文是兩個獨立通道,優先順序在第 13 節明確定義。

3. 完整 AES-128 加密流程

先把初始 XOR、一般輪與最後一輪分開。它們是同一次 AES-128 計算的不同階段。控制器必須知道現在處於哪個階段,才能選對資料路徑與金鑰。

初始 AddRoundKey、九個一般輪與一個最後輪
教學圖 3 · 點圖可開啟原尺寸

初學者只要先記住:

Round 0     = AddRoundKey
Round 1~9   = SubBytes + ShiftRows + MixColumns + AddRoundKey
Round 10    = SubBytes + ShiftRows + AddRoundKey

Final Round 不做 MixColumns。

4. AES State 的 4 x 4 Byte 排列

AES 把十六個 bytes 排成四個 row(橫列)與四個 column(直欄)。這份排列稱為 state,後面的 ShiftRows 與 MixColumns 都以它為基準。它不是為了畫圖方便而任意選出的表格;排列錯誤會改變運算結果。

AES 把 16 bytes 排成 4 rows x 4 columns,而且採用 column-major 排列。

128-bit input bytes:

 B0  B1  B2  B3  B4  B5  B6  B7  B8  B9 B10 B11 B12 B13 B14 B15

放入 AES State 後:

             Column 0   Column 1   Column 2   Column 3
Row 0           B0         B4         B8        B12
Row 1           B1         B5         B9        B13
Row 2           B2         B6        B10        B14
Row 3           B3         B7        B11        B15

對應 RTL bit mapping:

B0  = state[127:120]       B8  = state[63:56]
B1  = state[119:112]       B9  = state[55:48]
B2  = state[111:104]       B10 = state[47:40]
B3  = state[103:96]        B11 = state[39:32]
B4  = state[95:88]         B12 = state[31:24]
B5  = state[87:80]         B13 = state[23:16]
B6  = state[79:72]         B14 = state[15:8]
B7  = state[71:64]         B15 = state[7:0]

這個排列方式必須先固定,否則 ShiftRows 和 MixColumns 很容易接錯 byte。

用公式表示就是 s[row][col] = B[4*col + row];在本文 packed vector 中,B[i] = state[127 - 8*i -: 8]。因此 Column 0 是 state[127:96]。這是介面位元約定,不應由 CPU 的大小端習慣猜測。

5. 一個 Normal Round 的模組連接

一般輪依序做四個轉換,但「依序」不代表需要四拍。組合電路可以把前一個轉換的輸出,直接接到下一個輸入。本篇在整輪末端才保存結果,因此一輪使用一個時脈週期。

這個安排把四個轉換的延遲放在同一條路徑。若路徑太慢,可以增加暫存階段;代價是 round、key 與 FSM 的時序都要跟著調整。模組分成幾個檔案,並不能決定硬體需要幾拍。

前面先把 round 當成一個盒子,現在把它拆開。SubBytes 改變每個 byte 的值,ShiftRows 改變 byte 的位置,MixColumns 混合 column 裡的值,最後 AddRoundKey 注入這一輪的金鑰資訊。這四個動作串成一條組合路徑;「畫成四個方塊」不代表必須花四個 cycles。

四個轉換串成一般輪的組合資料路徑
教學圖 4 · 點圖可開啟原尺寸

aes_encrypt_round IO

module aes_encrypt_round (
    input  logic [127:0] state_i,
    input  logic [127:0] round_key_i,
    output logic [127:0] state_o
);

此模組是 combinational logic。輸入一個 state 和 round key,輸出完成一個 normal round 的新 state。

6. SubBytes:每個 Byte 查 S-box

十六個 byte S-box 平行替換
教學圖 5 · 點圖可開啟原尺寸

功能

  • 將 128-bit state 分成 16 個 8-bit bytes。
  • 每個 byte 獨立通過同一張 AES S-box 查表。
  • 16 個 bytes 的位置不改變。
  • 16 S-boxes 可以平行運算。

這裡的「查表」是在描述輸入與輸出的對應,不是要求硬體執行十六次軟體查詢。若有十六個組合 S-box,十六個 bytes 可以同時轉換。若你為了省面積只放一個 S-box,就要分時處理不同 bytes,還需要暫存與 byte counter,原來一拍一輪的控制方式也必須改寫。

SubBytes 提供非線性轉換:不能只用固定的 XOR 與位元搬移取代它。實作時,先不要自行簡化標準表;先讓全部 256 種輸入都得到正確輸出,再討論不同 S-box 電路的面積與延遲。

IO

module aes_sub_bytes (
    input  logic [127:0] state_i,
    output logic [127:0] state_o
);

module aes_sbox (
    input  logic [7:0] data_i,
    output logic [7:0] data_o
);

小例子

S-box(8'h53) = 8'hED

輸入 byte  53
             |
          AES S-box
             |
輸出 byte  ED

把標準明文的第一個 byte 接到 S-box

沿用第 1 節明文與第 17 節金鑰。Initial AddRoundKey 的 B0 是 00 XOR 00 = 00,B1 是 11 XOR 01 = 10。這裡 10 是十六進位的 16,不是十進位十。十六個 XOR 得到 00102030405060708090a0b0c0d0e0f0。

S-box 的高四位選標準表的 row、低四位選 column。00 查得 63,10 查得 ca,20 查得 b7,30 查得 04。所以第一個 column 經 SubBytes 成為 63 ca b7 04。查表只換值;這四個 bytes 仍在原來的位置。00 變 63 不是 XOR key 的第二次操作。

這裡把標準 S-box 當成已定義的 256 項函式;設計查表電路不必先推導其有限體反元素與 affine transformation。進階代數實作仍須逐項比對同一張表。先在互動台核對 initial XOR、Round 1 SubBytes,才進行位置重排。

7. ShiftRows:移動位置,不改變 Byte 數值

如果每一輪都只讓同一 column 的四個 bytes 互相作用,不同 columns 就缺少交流。ShiftRows 先把不同 rows 錯開,使下一步 MixColumns 能把原本來自不同 columns 的資料混在一起。先理解這個目的,再看以下排列,就不只是背「左移幾格」。

ShiftRows 前                          ShiftRows 後

 B0   B4   B8  B12                   B0   B4   B8  B12
 B1   B5   B9  B13       --->        B5   B9  B13   B1
 B2   B6  B10  B14                   B10 B14   B2   B6
 B3   B7  B11  B15                   B15  B3   B7  B11

 Row 0:左移 0 格
 Row 1:循環左移 1 格
 Row 2:循環左移 2 格
 Row 3:循環左移 3 格

RTL 接線觀念

ShiftRows 以重新接線排列 bytes
教學圖 6 · 點圖可開啟原尺寸

IO

module aes_shift_rows (
    input  logic [127:0] state_i,
    output logic [127:0] state_o
);

ShiftRows 只需要重新接線,不需要 S-box、加法器、乘法器或 register。

「循環左移」是矩陣上的描述;固定的 AES ShiftRows 在 RTL 裡通常就是把某一個輸入 byte 接到指定的輸出位置,不必用一個可變位移器,也不是每拍搬動一格。檢查時給 B0~B15 十六個不同的值,看它們各自到了哪裡,比只看最後密文更容易找到接反的線。

同一輪,重新讀出第一個 column

Round 1 SubBytes 的四列是:

row0: 63 09 cd ba;row1: ca 53 60 70;row2: b7 d0 e0 e1;row3: 04 51 e7 8c。

Row1 左移一格後開頭變 53;row2 左移兩格後開頭變 e0;row3 左移三格後開頭變 8c。Row0 不動。因此新的 column0 是 63 53 e0 8c,不是原來的 63 ca b7 04。依欄優先順序讀回十六個 bytes,得到 6353e08c0960e104cd70b751bacad0e7。這也是互動台 Round 1 ShiftRows 右側矩陣的第一欄。

8. MixColumns:每次混合一個 Column

MixColumns 將每個 column 內的 4 個 bytes 混合。四個 columns 彼此獨立,可以平行運算。

「混合」的意思是每個輸出 byte 都由這個 column 的四個輸入 bytes 共同決定。只改一個輸入 byte,便可能改變該 column 的多個輸出 bytes;配合前面的 ShiftRows,影響會在後續 rounds 擴散。這裡仍是可逆的規則,不是把資訊丟掉的平均值或雜湊。

四個 MixColumns 各自處理一欄
教學圖 7 · 點圖可開啟原尺寸

單一 Column 內部連接

輸入 Column                         輸出 Column

 a0 ---- x2 --+                    y0 = 2*a0 ^ 3*a1 ^ a2   ^ a3
 a1 ---- x3 --+--> XOR --> y0      y1 = a0   ^ 2*a1 ^ 3*a2 ^ a3
 a2 --------- +                    y2 = a0   ^ a1   ^ 2*a2 ^ 3*a3
 a3 --------- +                    y3 = 3*a0 ^ a1   ^ a2   ^ 2*a3

這裡的乘 2、乘 3是在有限體 GF(2^8) 中運算,不是一般整數乘法。

mul2(x) = xtime(x)
mul3(x) = xtime(x) XOR x

xtime 的 reduction polynomial 是 x^8 + x^4 + x^3 + x + 1。先丟掉移出的最高位;若原本 bit 7 為 1,再 XOR 8'h1b:

function automatic logic [7:0] xtime(input logic [7:0] x);
    return {x[6:0], 1'b0} ^ (8'h1b & {8{x[7]}});
endfunction

例如 xtime(8'h57)=8'hae、xtime(8'hae)=8'h47。單一 column db 13 53 45 經 MixColumns 後應為 8e 4d a1 bc,可先用這個小測試檢查 GF 運算與 byte 順序。

IO

module aes_mix_columns (
    input  logic [127:0] state_i,
    output logic [127:0] state_o
);

module aes_mix_one_column (
    input  logic [31:0] column_i,
    output logic [31:0] column_o
);

初學者記憶方式

  • ShiftRows:bytes 在不同 columns 之間移動。
  • MixColumns:把同一 column 內的 4 個 bytes 混合。
  • AES 標準沒有 MixRows 這個步驟。

從 63 53 e0 8c 算到 5f 72 64 15

GF(2⁸) 的元素可寫成一個 byte;加法是 XOR,乘法依本節 polynomial 縮減。這不是帶 carry 的整數加法。先用 xtime 算乘 2:63→c6、53→a6、e0→db、8c→03。後兩者原 bit7=1,例如 xtime(e0)=c0 XOR 1b=db。

乘 3 等於乘 2 再 XOR 原值,所以得到 a5、f5、3b、8f。代入四條 column 公式:

y0=c6 XOR f5 XOR e0 XOR 8c=5f

y1=63 XOR a6 XOR 3b XOR 8c=72

y2=63 XOR 53 XOR db XOR 8f=64

y3=a5 XOR 53 XOR e0 XOR 03=15

每個輸出都用了同一個 column 的四個輸入。其餘三欄各自算,合起來才是第 17 節完整的 MixColumns 中間值。在互動台前進一步,核對 column0=5f 72 64 15;不必靠「看起來更亂」判斷正確。

9. AddRoundKey:State 與 Round Key 做 XOR

前面三個轉換使用固定、公開的規則。AddRoundKey 把本輪金鑰加入 state,讓結果取決於這把 key。不要因為其他轉換看起來複雜,就把金鑰路徑當成次要接線。

State 與 key 都是 128 bits,每個位元各做一次 XOR。這裡沒有 carry,也不需要逐 bit 計算 128 拍。真正要確認的是:兩邊是否使用相同的 byte 排列,送來的又是否為本輪所需的 key。

State 與本輪金鑰做 128-bit XOR
教學圖 8 · 點圖可開啟原尺寸

功能

state_o[127:0] = state_i[127:0] XOR round_key_i[127:0]

Byte 例子

State byte = 8'h53 = 0101_0011
Key byte   = 8'hCA = 1100_1010
                       ---------
Result     = 8'h99 = 1001_1001

IO

module aes_add_round_key (
    input  logic [127:0] state_i,
    input  logic [127:0] round_key_i,
    output logic [127:0] state_o
);

10. Final Round 的連接

第十輪省略 MixColumns,仍要做 SubBytes、ShiftRows 與 AddRoundKey。這是 AES-128 的規定,不能因為一般輪已經寫好,就再原封不動地呼叫一次。

最後輪只做 SubBytes、ShiftRows 與 AddRoundKey
教學圖 9 · 點圖可開啟原尺寸

Final Round 與 Normal Round 的唯一主要差別:

Normal Round:有 MixColumns
Final Round :No MixColumns

硬體不一定要另放一套最後輪電路。可以共用 SubBytes、ShiftRows 與 XOR,讓控制器在第十輪選擇繞過 MixColumns。圖在說明功能順序,並未要求兩份完整硬體。

金鑰仍需前進到 K10。若只做 bypass,卻繼續使用 K9,最後密文仍會錯。驗證這一輪時,分別看「選了哪條路」與「用了哪把 key」,比只看 counter 到了 10 更可靠。

11. AES-128 Key Expansion

先把兩種儲存需求分清楚:原始 key 要留下來,working key 則會跟著運算向前走。 如果只用一個暫存器,算完後它已經變成 K10;下一筆資料若直接接著算,就會從錯誤的 key 開始。保存 K0,讓每筆新交易重新展開,是本文採用的簡單安排。

原始 128-bit key 會被展開成 11 把 128-bit round keys。

原始金鑰與十把展開輪金鑰
教學圖 10 · 點圖可開啟原尺寸

一次 Key Expansion Step

將一把 128-bit key 分成四個 32-bit words:

key_i[127:0] = { w0, w1, w2, w3 }
RotWord、SubWord、Rcon 與連續 word XOR
教學圖 11 · 點圖可開啟原尺寸

計算式:

temp = SubWord(RotWord(w3)) XOR Rcon(round)

w4 = w0 XOR temp
w5 = w1 XOR w4
w6 = w2 XOR w5
w7 = w3 XOR w6

next_round_key = {w4, w5, w6, w7}

Key Expansion IO

module aes128_key_expand_step (
    input  logic [127:0] key_i,
    input  logic [3:0]   round_i,
    output logic [127:0] key_o
);

round_i 的有效範圍是 1 到 10。

RotWord({a,b,c,d})={b,c,d,a};SubWord 對四個 bytes 各做一次 S-box。Rcon(round) 是 32-bit word,round 1 到 10 依序為 01000000, 02000000, 04000000, 08000000, 10000000, 20000000, 40000000, 80000000, 1b000000, 36000000,常數在最高 byte,不能 XOR 到最低 byte。

Round Key 0 就是原始 key,另產生 K1~K10;所以是 10 次展開、11 把 key,並非 11 個完整 rounds。本文採 on-the-fly 展開:保存一把原始 key,另用 working round-key register 隨 round 更新,每個新區塊都從 K0 重來。預先保存全部 keys 是另一種取捨,僅 key 資料就需要 11 × 128 = 1408 bits。

算出 K1,再把它接回第一輪

標準 key 的 w0=00010203、w1=04050607、w2=08090a0b、w3=0c0d0e0f。RotWord(w3)=0d0e0f0c;四個 S-box 查表得到 d7ab76fe。Round 1 的 Rcon=01000000,故 temp=d6ab76fe。

接著 w4=00010203 XOR d6ab76fe=d6aa74fd;w5=04050607 XOR d6aa74fd=d2af72fa;w6=08090a0b XOR d2af72fa=daa678f1;w7=0c0d0e0f XOR daa678f1=d6ab76fe。這四個 words 合起來是 K1,不是四把不同輪的 key。

第 8 節已算出 Round 1 MixColumns 的第一欄 5f726415;它 XOR K1 第一個 word d6aa74fd,得到 89d810e8。其餘三欄使用 K1 對應欄,才得到完整第一輪結果。Initial XOR 使用 K0;這次 XOR 使用 K1。第 12 節在 E2 保存第一輪結果與 K1,互動台則拆成可觀察的微步,不能把每一次按「下一步」當成一拍 RTL。

12. Iterative RTL Datapath

Iterative 的意思是重複使用同一套 round 電路。State 暫存器保存上一輪結果,下一輪再把它送進同一條資料路徑。這樣不必為十輪各放一套電路,但同一時間只能推進本篇正在處理的區塊。

State 與 working key 回授到同一套輪電路
教學圖 12 · 點圖可開啟原尺寸

每個 Clock 做什麼

Clock 階段State Register 更新內容使用的 Key
接收輸入plaintext無
Round 0plaintext XOR original_keyRound Key 0
Round 1Normal Round 結果Round Key 1
Round 2Normal Round 結果Round Key 2
………
Round 9Normal Round 結果Round Key 9
Round 10Final Round 結果Round Key 10
Output保持結果直到被接收無

將拍數定義到時脈邊緣

假設 key 已載入,E0 是明文握手的上升緣。下表寫的是該邊緣完成更新後的值;Round 邏輯和 key expansion 都是組合邏輯。

邊緣動作Working key邊緣後的 FSM
E0鎖住明文,working key 載入 K0K0ROUND_0
E1State XOR K0,counter 設為 1K0NORMAL_ROUND
E2~E10各完成 round 1~9;使用組合產生的 next keyK1~K9最後進入 FINAL_ROUND
E11完成 round 10;State 直接保存密文K10OUTPUT_HOLD,out_valid=1
E12 或更晚out_valid && out_ready,密文傳出保持READY
E13 或更晚最早接收下一個明文重載 K0ROUND_0

因此,輸入握手到輸出有效是 11 個週期;最早輸出握手在 E12;無 stall 的輸入間隔是 13 個週期。這個簡單版本不在輸出握手同拍接新資料。若 clock frequency 為 f_clk,無 stall 且不換 key 時,吞吐量是 128 × f_clk / 13 bit/s;頻率必須由綜合與時序分析取得。

圖中由 State Register 直接供應 out_data,不再多接一拍 output register。NORMAL_ROUND 的核心更新概念如下,next_round_key 必須由目前的 working key 與 counter 組合產生:

// Conceptual fragment inside NORMAL_ROUND; helper functions are omitted.
state_reg     <= normal_round(state_reg, next_round_key);
round_key_reg <= next_round_key;

若寫成 normal_round(state_reg, round_key_reg),右側讀到的是更新前的 key,會錯用 K[r-1]。FINAL_ROUND 同樣使用由 K9 展開的 K10,但 bypass MixColumns。

一拍一個 round 需要整條組合路徑在一個週期內穩定;key expansion 的延遲也要納入。此例的資料路徑用 16 S-boxes,獨立 key expansion 另用 4 個。若改用同步讀取記憶體或共用少量 S-box,就要重新排時序,不能沿用這張拍數表。

13. Controller FSM

FSM 可以看成核心的工作進度表。只記 round counter 還不夠,因為 counter 無法單獨表達「還沒載 key」「等明文」「密文算好但下游還沒收」這些情況。狀態負責區分階段,counter 負責在一般輪裡計數;兩者一起決定資料路徑的選擇與暫存器的更新。

AES FSM 的等待、一般輪、最後輪與輸出保持狀態
教學圖 13 · 點圖可開啟原尺寸

狀態功能

FSM State功能
NO_KEY等待載入 128-bit key
READY已有 key,等待 plaintext
ROUND_0執行 initial AddRoundKey
NORMAL_ROUND依序執行 rounds 1 到 9
FINAL_ROUND執行 round 10,不做 MixColumns
OUTPUT_HOLDout_valid=1,保持密文直到 out_ready=1

握手與重設契約

  • key_ready = rst_n && (fsm == NO_KEY || fsm == READY)。
  • in_ready = rst_n && (fsm == READY) && !key_valid。若兩個 valid 同時出現,先收 key,明文來源維持資料到下一次握手;不會在同拍誤用舊 key。
  • out_valid = rst_n && (fsm == OUTPUT_HOLD)。計算與等待輸出期間都不接新 key 或新明文。
  • busy 定義為交易尚未結束:從 ROUND_0 到 OUTPUT_HOLD 都是 1,包含下游背壓等待時間。
  • rst_n=0 的上升緣回到 NO_KEY,清除 key、working key、state 與 counter,取消未完成交易。重設期間 ready/valid 為 0,重設解除後必須重新載入 key。

同步 reset 只有遇到 clock edge 才清除暫存器;這個教學清除流程不等同於通過產品級 zeroization 或抗側通道驗證。

14. Valid/Ready 握手圖

握手要在約定的上升緣判斷,不能只看波形上兩條線曾經碰到高電位。核心提出 valid,表示結果已準備好;下游提出 ready,表示可以接收。兩者在取樣緣同時為 1,這筆密文才完成交付。

Rising edge      E0   E1 ... E10   E11   E12   E13   E14
in_valid          1    0 ...   0     0     0     0     0
in_ready          1    0 ...   0     0     0     0     0
out_valid         0    0 ...   0     0     1     1     1
out_ready         0    0 ...   0     0     0     0     1
Transfer         IN                                    OUT

這張表列的是上升緣取樣前的訊號;E11 更新完成後 out_valid 才變成 1,所以 E12 才能最早握手。此例下游到 E14 才接收,E14 更新後回到 READY,若沒有 key_valid,in_ready 恢復為 1;E15 才能接下一筆。READY 空閒時 in_ready 可以持續為 1,不必等待 in_valid 才升高。

當 out_valid=1 但 out_ready=0:

  • out_valid 必須保持為 1。
  • out_data 必須保持不變。
  • 核心停在 OUTPUT_HOLD。

回到感測器的例子,下游忙碌不需要重新加密。核心只要停止更新 state,保留已完成的密文。此時 busy 仍為 1,因為交易還沒結束。若把 busy 當成「round 電路正在算」,就可能過早接收下一筆並覆蓋結果。

15. 建議的 RTL Module Hierarchy

Controller、round 與 key expansion 的功能階層
教學圖 14 · 點圖可開啟原尺寸

在 RTL 中 aes_sub_bytes 可以由 normal round 和 final round 共用同一套 combinational logic;上圖主要用來表達功能階層。

模組階層先幫你分配責任:controller 管時機,round 管 state 的轉換,key expansion 管下一把 key。每個積木都應有可獨立比對的輸入與輸出,才容易定位第一個錯誤。

實際例化時再決定哪些邏輯共用。若照功能樹各放一份 normal 與 final round,可能增加不必要的面積。反過來,共用較少的 S-box 雖然可能省面積,卻會增加排程工作;不能只改階層而保留原拍數。

16. 建議實作順序

第一次實作先求能定位錯誤,不必一開始就追最高吞吐量。先建立 byte 排列與四個積木的測試,再逐步整合。每加入一層,都應保留前一層的測試,避免新接線掩蓋既有錯誤。

  1. 先用一張圖說明 plaintext + key 產生 ciphertext。
  2. 解釋 128 bits 如何拆成 16 bytes 和 4 x 4 state。
  3. 只介紹四個基本積木:SubBytes、ShiftRows、MixColumns、AddRoundKey。
  4. 組合出 normal round。
  5. 說明 final round 少了 MixColumns。
  6. 再加入 key expansion。
  7. 最後才解釋 register、round counter、FSM 和 valid/ready。

讀懂這七步後,動手順序可更細:先測完 256 個 S-box 輸入,再用互不相同的 bytes 檢查 ShiftRows。接著測 xtime、MixColumns、一般輪與最後輪 bypass。Key expansion 獨立通過後,才接上暫存器與控制器。

整合最後要測兩筆連續交易。第一筆只證明從 K0 開始能算出密文;第二筆才能檢查 working key 是否重新載入 K0。再加入 key 同拍競爭、背壓與 reset,確認控制契約和數值一樣正確。

17. 最小實作檢查表

  • State byte mapping 已明確定義。
  • SubBytes 有 16 個 byte substitutions。
  • ShiftRows 只做 byte permutation。
  • MixColumns 對 4 個 columns 分別運算。
  • AddRoundKey 是 128-bit XOR。
  • Key Expansion 能產生 Round Keys 1 到 10。
  • Rounds 1 到 9 有 MixColumns。
  • Round 10 No MixColumns。
  • out_valid && !out_ready 時輸出保持不變。
  • 通過 FIPS-197 標準測試向量。

標準測試向量:

Key        = 000102030405060708090A0B0C0D0E0F
Plaintext  = 00112233445566778899AABBCCDDEEFF
Ciphertext = 69C4E0D86A7B0430D8CDB78070B4C55A

密文不對時,找第一個分歧點

以下值沿用上述 Key/Plaintext,全部依第 4 節的 packed byte 順序列出。對照 NIST 2001 版 FIPS 197 附錄 C.1,可先把錯誤縮小到一個子模組:

After initial XOR : 00102030405060708090a0b0c0d0e0f0
Round 1 SubBytes  : 63cab7040953d051cd60e0e7ba70e18c
Round 1 ShiftRows : 6353e08c0960e104cd70b751bacad0e7
Round 1 MixColumns: 5f72641557f5bc92f7be3b291db9f91a
Round Key 1       : d6aa74fdd2af72fadaa678f1d6ab76fe
Round 1 result   : 89d810e8855ace682d1843d8cb128fe4
測試要抓的錯誤
256 種 S-box 輸入逐一比對標準表查表缺項、抄錯值、latch
16 個不同 byte 做 ShiftRowsrow/column 與 packed vector 接反
每一輪 state 與 K1~K10 比對key 晚一拍、Rcon 位置錯、round counter 錯
同一 key 連續加密兩個區塊第二塊誤從 K10 開始
key_valid 與 in_valid 同時為 1換 key 優先順序未落實
out_ready 拉低數拍後恢復out_valid 消失、密文被覆蓋或重複傳出
計算中與 OUTPUT_HOLD 時 reset舊交易在 reset 後冒出、未重新載 key
多組隨機 key/明文與獨立參考模型比較單一已知答案未涵蓋的資料錯誤

Testbench 只在 valid && ready 的上升緣記一筆交易。Scoreboard 比對接收/送出數量與順序,reset 時清掉取消的期待結果,並設 timeout,避免把卡死當成測試仍在跑。

這些檢查分成數值與交易兩條線。已知答案能抓到密文錯誤,卻不會自動抓到同一筆密文被送了兩次。只檢查交易數量,也可能讓錯誤密文通過。兩條線都成立,才算完成本篇的功能驗證目標。

做對演算法後,還缺什麼?

這裡是一個單區塊加密 primitive。要處理訊息,還要選擇合適的運作模式,定義 nonce/IV、金鑰管理與完整性保護;通過向量也不代表已處理功耗/電磁側通道或故障注入。先把本篇的功能與握手驗證完成,再進到系統安全設計。

18. 參考資料與閱讀範圍

本文的 E0~E13 時序、key 優先策略與 reset 行為是教學架構的設計契約,並非 NIST 規定的介面。

本篇先省略解密、訊息運作模式、AXI/DMA 與高效能 pipeline。完成這個核心後,再依系統需求加入這些功能。側通道與故障防護屬於另外的安全驗證,不能由單一測試向量推得。

MY ACADEMY · LESSON FILM

教學影片

影片依序說明本課的資料路徑。看完一段,可以回到下面的互動練習,改變輸入或故障條件。動畫呈現教學模型;它沒有替代 RTL 模擬。

下載 MP4 · 字幕 VTT

旁白使用合成聲音。互動教學與動畫均有模型邊界;請以本課的來源與驗證範圍解讀結果。

MY ACADEMY · RTL LAB

操作 AES:看每一步如何改變 state

輸入一個 128-bit 區塊與金鑰。先停在 Round 1 的 ShiftRows,對照兩側矩陣;再走到 Round 10,確認這輪沒有 MixColumns。

矩陣使用 FIPS 197 的欄優先排列。位置 [row, column] 對應輸入 byte 4×column+row。每一步顯示運算前後的值。這是演算法微步,不宣稱每一步對應一拍 RTL。

證據範圍:瀏覽器功能教學模型。獨立比對使用瀏覽器 Web Crypto。沒有執行 RTL、綜合、形式證明或側通道測試。

獨立比對使用 Web Crypto 的 CBC 第一個區塊,IV=0,所以 C0=AES_K(P0 XOR 0)=AES_K(P0)。只取輸出的前 16 bytes;額外 padding 區塊不影響第一塊。這是在比對單區塊 AES,不是在此教學台實作訊息模式。

QD

運算前

運算後

查看區塊、排程與輪金鑰

輸出握手:valid/ready

走到 OUTPUT 後,輸出保持不變。切換 ready,再按「取樣一拍」才能記錄交付。設定 ready 本身不會交付。這個握手是另外定義的教學介面。

遷移練習:如果 downstream 尚未 ready,RTL 能否清除 output_valid 或覆寫結果?先用一句話寫出保持規則:除非 reset 取消交易,valid 與資料必須保持到交握成立。SVA 語法可作後續練習。

FIPS 197 §5.1 / §5.2

學習指南

密碼演算法 RTL 設計

0 / 2

查看課程大綱 → · 進度只計入已發布課程

先備知識

  • AES Round 運算與同步邏輯

我學會了什麼

  • 把 AES 分成資料路徑與控制
  • 定義 valid/ready 行為
  • 保持位元組順序一致

本課術語

查看術語字典 →

延伸閱讀

課後小測驗

1. 什麼應控制 Round 順序?
2. 為何要先固定 byte ordering?
3. valid 表示什麼?

讀到這裡,辛苦了。

把概念帶走,比把術語背走更重要。

#AES#RTL#數位IC#硬體架構