随笔-168  评论-223  文章-30  trackbacks-0
本文解释SM4算法解密时轮密钥为什么要反序,给出S盒的复现构造、代数性质分析,以及相关sage代码(SageMath版本为10.7)。关于SM4算法的标准,具体参考文献[1]

解密验证
 依文献[1] 解密变换与加密变换使用相同的结构,仅是轮密钥顺序不同,解密时使用轮密钥序(rk31rk30,…,rk0)。先从数学上证明如下
 
 
 再看GMSSL对SM4的实现,验证解密时是否作了反序处理。下面代码从https://github.com/guanzhi/GmSSL/blob/master/src/sm4.c 处拷贝
void sm4_set_encrypt_key(SM4_KEY *key, const uint8_t user_key[16])
{
    uint32_t X0, X1, X2, X3, X4;
    int i;

    X0 = GETU32(user_key     ) ^ FK[0];
    X1 = GETU32(user_key  + 4) ^ FK[1];
    X2 = GETU32(user_key  + 8) ^ FK[2];
    X3 = GETU32(user_key + 12) ^ FK[3];

    for (i = 0; i < 32; i++) {

        X4 = X1 ^ X2 ^ X3 ^ CK[i];
        X4 = S32(X4);
        X4 = X0 ^ L32_(X4);

        key->rk[i] = X4;

        X0 = X1;
        X1 = X2;
        X2 = X3;
        X3 = X4;
    }
}

void sm4_set_decrypt_key(SM4_KEY *key, const uint8_t user_key[16])
{
    uint32_t X0, X1, X2, X3, X4;
    int i;

    X0 = GETU32(user_key     ) ^ FK[0];
    X1 = GETU32(user_key  + 4) ^ FK[1];
    X2 = GETU32(user_key  + 8) ^ FK[2];
    X3 = GETU32(user_key + 12) ^ FK[3];

    for (i = 0; i < 32; i++) {

        X4 = X1 ^ X2 ^ X3 ^ CK[i];
        X4 = S32(X4);
        X4 = X0 ^ L32_(X4);

        key->rk[31 - i] = X4;

        X0 = X1;
        X1 = X2;
        X2 = X3;
        X3 = X4;
    }
}
 可以看到对比加密,在sm4_set_decrypt_key函数内做了反序处理。进一步可以发现sm4_set_decrypt_key被ECB和CBC操作模式调用了,
 比如在https://github.com/guanzhi/GmSSL/blob/master/src/sm4_cbc.c 中的sm4_cbc_decrypt_init函数内
int sm4_cbc_decrypt_init(SM4_CBC_CTX *ctx, const uint8_t key[SM4_BLOCK_SIZE], const uint8_t iv[SM4_BLOCK_SIZE])
{
    if (!ctx || !key || !iv) {
        error_print();
        return -1;
        }
        sm4_set_decrypt_key(&ctx->sm4_key, key);
        memcpy(ctx->iv, iv, SM4_BLOCK_SIZE);
        memset(ctx->block, 0, SM4_BLOCK_SIZE);
        ctx->block_nbytes = 0;
        return 1;
}

 S盒的复现构造
   基本思路是根据文献[2]给出的如下公式及矩阵、向量参数
 
 脚本代码如下
def  sm4_sbox(byte):
    v = vector(BF, [(byte >> i) & 1 for i in range(S_M-1, -1, -1)])

    v1 = A1 * v + C1
    r_byte = sum(int(v1[i]) << i for i in range(S_M-1, -1, -1))
    elem = byte_to_poly(r_byte)

    if elem != 0:
       inv_elem = elem^-1
       inv = poly_to_byte(inv_elem)
    else:
       inv = 0

    v2 = vector(BF,[(inv >> i)  & 1 for i in range(S_M)])  
    r = A2 * v2 + C2
    return sum(int(r[i]) << (S_M-1-i) for i in range(S_M))

sm4_sbox_table = [sm4_sbox(i) for i in range(S_SIZE)]

print(f"const uint8_t sm4_sbox[{S_SIZE}] = {{")
for i in range(0, S_SIZE, 16):
    row = "".join(f"0x{s:02X}" for s in sm4_sbox_table[i:i+16])
    print("    " + row + ",")
print("};", end="\n\n")
 
 运行输出如下
 
 以上输出表格与文献[1] 给出的完全一致

S盒的代数性质
  主要是平衡性、代数次数、非线性度、Walsh谱、差分均分度、SAC、扩散准则PC(k),具体计算根据它们的定义。代码如下
  ###########################################################
def  sbox_balance(S):
    for j in range(S_N):
       cnt = sum((S[i]>>j)&1 for i in range(S_SIZE))
       print(f"Output bit {j}: {cnt} ones")

sbox_balance(sm4_sbox_table);  print()
###########################################################
def  max_nonlinearity(n):
    if n % 2 == 0:
                return 2^(n-1) - 2^(n//2 - 1)
        else:
               return 2^(n-1) - 2^((n-1)//2)

def  sbox_boolfun_property(S):
    min_nl = infinity
    for j in range(S_N):
        bf = BooleanFunction([(S[i]>>j)&1 for i in range(S_SIZE)])
        deg = bf.algebraic_degree()
        nl = bf.nonlinearity()
        min_nl = min(min_nl, nl)
        walsh_max = max(abs(w) for w in bf.walsh_hadamard_transform())
        print(f"Bit {j}: degree={deg}, nonlinearity={nl}, max|Walsh|={walsh_max}")

    print(f"the minimum nonlinearity is {min_nl}, theory max nonlinearity is {max_nonlinearity(S_N)}")

sbox_boolfun_property(sm4_sbox_table);  print("")
###########################################################
def  sbox_differential_uniformity(S):
    ddt = {}
    for dx in range(1, S_SIZE):
        for x in range(S_SIZE):
            dy = S[x] ^^ S[x^^dx]
            ddt[(dx, dy)] = ddt.get((dx, dy), 0) + 1

    delta = max(ddt.values())
    print("Differential Uniformity =", delta)

sbox_differential_uniformity(sm4_sbox_table);  print()
###########################################################
def  sbox_fixed_points(S):
    fps = []
    for x in range(S_SIZE):
        if S[x] == x:
            fps.append(hex(x))
    return fps

fps = sbox_fixed_points(sm4_sbox_table)
print(f"has {len(fps)} fixed points: {fps}", end="\n\n")
###########################################################
def  flip_bit(x, i):
    return x ^^ (1 << i)

def  sbox_sac(S):
    sac_matrix = matrix(QQ, S_M, S_N, 0)
    for x in range(S_SIZE):
        for i in range(S_M):          
            xp = flip_bit(x, i)
            dx = S[xp] ^^ S[x]      
            for j in range(S_N):     
                if (dx >> j) & 1:
                    sac_matrix[i, j] += 1

    # Normalize to probability
    sac_matrix = sac_matrix / S_SIZE
    return sac_matrix

print(sbox_sac(sm4_sbox_table));  print()

def  sbox_check_pck(S, k):
    bool_funcs = []
    bf_satisfy_pcks = []

    for i in range(S_N):
        bf = BooleanFunction([(S[x] >> i) & 1 for x in range(S_SIZE)])
        bool_funcs.append(bf)
        bf_satisfy_pcks.append(True)

    for i, f in enumerate(bool_funcs):
        w = f.walsh_hadamard_transform()

        for a in range(1,  S_SIZE):
            if bin(a).count('1') <= k:
                if w[a] != 0:
               # D = f.derivative(a)             
               # if not D.is_balanced():
                    bf_satisfy_pcks[i] = False
                    break               
        r = bf_satisfy_pcks[i]
        print(f"bf[{i}] satify PC({k}):{r}")

for k in range(1, 4):
    sbox_check_pck(sm4_sbox_table, k);  print()
###########################################################
 sbox_check_pck函数内注释部分为用布尔函数导数的方法,结果与使用Walsh谱的方法一致。当k=1时等价于SAC。运行脚本,输出如下

 
 可以看出S盒不严格满足PC(k),SAC则是接近满足。其它指标良好

 参考文献
  [1] GB/T32907—2016 信息安全技术 SM4分组密码算法
  [2] Algebraic Cryptanalysis of SMS4: Gr¨obner Basis Attack and SAT Attack Compared
posted on 2026-04-08 13:33 春秋十二月 阅读(22) 评论(0)  编辑 收藏 引用 所属分类: Cryptography