iT邦幫忙

2025 iThome 鐵人賽

DAY 28
0
Security

裸機實作 SLH-DSA 簽章:FIPS 205 之演算法實作、測試、防禦與跨平台系列 第 28

Day 28 使用 NIST PQC 提供的 KAT(Known Answer Tests)驗證 SLH-DSA 的簽章實作

  • 分享至 

  • xImage
  •  

要用 NIST KAT 驗證 sign,有三個 function 要自己寫,PQCgenKAT_sign 會去 call 他們,然後產生 PQCsignKAT_64.req 和 PQCsignKAT_64.rsp 供比對。

int
crypto_sign_keypair(unsigned char *pk, unsigned char *sk);

int
crypto_sign(unsigned char *sm, unsigned long long *smlen,
            const unsigned char *m, unsigned long long mlen,
            const unsigned char *sk);

int
crypto_sign_open(unsigned char *m, unsigned long long *mlen,
                 const unsigned char *sm, unsigned long long smlen,
                 const unsigned char *pk);

SPHINCS+ 官方的 crypto_sign_seed_keypair

int crypto_sign_seed_keypair(unsigned char *pk, unsigned char *sk,
                             const unsigned char *seed)
{
    spx_ctx ctx;

    /* Initialize SK_SEED, SK_PRF and PUB_SEED from seed. */
    memcpy(sk, seed, CRYPTO_SEEDBYTES);

    memcpy(pk, sk + 2*SPX_N, SPX_N);

    memcpy(ctx.pub_seed, pk, SPX_N);
    memcpy(ctx.sk_seed, sk, SPX_N);
    ...

    memcpy(pk + SPX_N, sk + 3*SPX_N, SPX_N);
}

SPHINCS+ 的 reference code 是先產生一個 3*n bytes 的亂數,然後 memcpy 給 sk,此即為 SK.seed, SK.prf 和 PK.seed。
第 3 個 n bytes 是 PK.seed,所以再 call 一次 memcpy,就是 pk 的第一個 byte。

     第 1 個 n bytes    第 2 個 n bytes    第 3 個 n bytes    第 4 個 n bytes
sk:     SK.seed      ||     SK.prf     ||     pk.seed     ||   pk.root
pk:     pk.seed      ||     pk.root

計算完 pk.root 之後,將 pk.root memcpy 到 sk 的第 4 個 n bytes 和 pk 的第 2 個 n bytes,crypto_sign_keypair 就完成了。

關鍵在於 xmss_node,因為從這裡開始就要自己寫 code 去計算 pk.root

因為要用 PQCgenKAT_sign.c 來驗證我的 implementation,所以在產生 sk, pk 的時候也要照著上述的順序。這時候就不需要key id了,可以直接傳 sk, pk。

crypto_sign_keypair 的 source code 大概是這樣

int crypto_sign_keypair(unsigned char *pk, unsigned char *sk)
{
    uint8_t rand[3*SPX_N];
    randombytes(rand, sizeof(rand));

    memcpy(sk, rand, sizeof(rand));

    // 第 3 個 n bytes 是 pk.seed
    memcpy(pk, rand[2*SPX_N], SPX_N);
    
    // 計算 pk.root
    ADRS adrs;
    memset(adrs, 0, 32);

    int d = 7;  // SLH-DSA-SHA2-128s, d is 7
    set_layer_addr(adrs, d-1);

    unsigned int h_prime = 9;

    uint8_t pk_root[SPX_N] = {0};
    // PK.root ← xmss_node(SK.seed, 0, ℎ′, PK.seed, ADRS)
    xmss_node(pk_root, sk_seed, 0, h_prime, pk_seed, adrs);

    memcpy(pk + SPX_N, pk_root, SPX_N);
    memcpy(pk + SPX_N, pk_root, SPX_N);
}

crypto_sign 裡放自己的 implementation, 因為 unsigned char *sk 指向一個 4*n bytes 的陣列,所以把 pointer 一次加 n 個 bytes 就能陸續取得 sk_seed、sk_prf、pk_seed 和 pk_root,然後 call slh_dsa_sign 就可以了。

int crypto_sign(unsigned char *sm, unsigned long long *smlen,
                const unsigned char *m, unsigned long long mlen,
                const unsigned char *sk)
{
    // sk: SK.seed || SK.prf || pk.seed || pk.root
    unsigned char *p = sk;
    memcpy(sk_seed, p, SPX_N); p += SPX_N;
    memcpy(sk_prf, p, SPX_N); p += SPX_N;
    memcpy(pk_seed, p, SPX_N); p += SPX_N;
    memcpy(pk_root, p, SPX_N);

    psa_key_id_t sk_key_id = 1;
    psa_key_id_t sk_prf_key_id = 2;
    psa_key_id_t pk_key_id = 3;

    uint8_t sig_out[SPX_BYTES];
    uint8_t optrand[SPX_N] = {0};
    slh_dsa_sign(sig_out, sk_key_id, sk_prf_key_id, pk_key_id, m, mlen, optrand);

    smlen = SPX_BYTES;
    memcpy(sm, sig_out, SPX_BYTES);
    return 0;
}

最後一個要實作的就是 crypto_sign_open,我發現這就是要寫驗章,所以我打算跳過這一步,直接用 PQCgenKAT_sign.c 產出的 PQCsignKAT_64.rsp 去跟 Download 3rd round NIST submission package (zip) 的 PQCsignKAT_64.rsp 比對,所以讓 crypto_sign_open 直接 return 成功,不做驗章。

int crypto_sign_open(unsigned char *m, unsigned long long *mlen,
                     const unsigned char *sm, unsigned long long smlen,
                     const unsigned char *pk)
{
    (void)pk;
    if (smlen < (unsigned long long)CRYPTO_BYTES) {
        return -1;  // malformed input
    }

    unsigned long long msglen = smlen - (unsigned long long)CRYPTO_BYTES;
    memmove(m, sm + CRYPTO_BYTES, (size_t)msglen);
    *mlen = msglen;
    return 0;       // always "valid" (KAT stub)
}

PQCsignKAT_64.rsp 這個 64 不是 x86/x64 的那個 64,他是代表 private key (sk) 的大小,由於在 sha256-128s-simple 裡 sk.seed, sk.prf, pk.seed, pk.root 都是 16 bytes,他們合併就是 sk,加起來的大小就是 4*16=64。所以,api.h 有3個 define 要改

#define CRYPTO_SECRETKEYBYTES 64
#define CRYPTO_PUBLICKEYBYTES 32
#define CRYPTO_BYTES 7856

CRYPTO_BYTES 就是 SLH-DSA signature 的大小,R || FORS signature || HT signature 的 size 就是 16 + 2912 + 4928 = 7856

SPX_N + (FORS sig length) + (HT sig length) = 16 + 2912 + 4928 = 7856
FORS signature length = k * (a + 1) * n = 14 * (12 + 1) * 16 = 2912

下一步就是改 Makefile 和 ci.yml。

PQC – Known Answer Tests and Test Vectors 有提到 KAT 依賴 OpenSSL,那麼 bare-metal 就不合適,所以就直接在 x86 上執行 elf,不使用 Renode。

In addition, you will need to have OpenSSL Version 1.10f

因為在 x86 環境下 access UART 會直接 segmentation fault (core dumped),所以 Makefile 要加 -DX86

截錄部份的 Makefile

kat:
	@mkdir -p $(KAT_BUILD)
	$(HOSTCC) $(HOSTCFLAGS) $(KAT_INCS) $(KAT_SRCS) -DX86 $(SRCS) -o $(KAT_BIN) $(HOSTLDFLAGS)
	@echo "[KAT] running in $(KAT_BUILD)"
	@cd $(KAT_BUILD) && ./PQCgenKAT_sign
	@ls -l $(KAT_BUILD)/PQCsignKAT_*.req $(KAT_BUILD)/PQCsignKAT_*.rsp 2>/dev/null || true

ci.yml 加這一段

      - name: KAT
        run: |
          set -e
          make clean
          make TARGET=x86 kat

在 uart_min.c 加這個

#ifdef X86
  #define RETURN_IF_X86 return
#else
  #define RETURN_IF_X86 ((void)0)
#endif

然後在 uart_min.c 的每一個 function 加 RETURN_IF_X86; 例如

void uarte0_init(void)
{
    RETURN_IF_X86;
    ...
}

執行的時候大概會是這個結果
https://ithelp.ithome.com.tw/upload/images/20251011/20140129QFvjf1MhNq.png

因為 xmss_node 一旦執行就會 segmentation fault (core dumped),所以我暫時跳過 xmss_node,先讓流程走到 print PQCsignKAT_64.rsp

xmss_node(pk_root, sk_seed, 0, h_prime, pk_seed, adrs);

經過了二十幾天的實驗和實作,我們己經走到了倒數第二步,就是 產生 PQCsignKAT_64.rsp。這一步的重點就是跟官方 [3] 的 PQCsignKAT_64.rsp 一致,但倒數第二步還沒有成功,我希望能在明天 Day 29 把這問題解決,然後就能走到了最後一步,將軟體的 SHA256, HMAC-SHA256 換成硬體。

References

  1. FIPS 205 Stateless Hash-Based Digital Signature Standard
  2. PQC – Known Answer Tests and Test Vectors
  3. Download 3rd round NIST submission package (zip)

上一篇
Day 27 將 psa_key_id_t 整合到 SLH-DSA 簽章演算法的實作裡
下一篇
Day 29 Writing hex to nRF5340 DK Application core
系列文
裸機實作 SLH-DSA 簽章:FIPS 205 之演算法實作、測試、防禦與跨平台30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言