KASUMI分组密码算法的实现

KASUMI 分组密码算法

1. 概述

KASUMI 是一种 分组密码,作为 3GPP(第三代合作伙伴计划)保密性算法 f8 和完整性算法 f9 的核心组件。它基于 Feistel 结构设计,旨在为 3G 移动通信系统提供高效的加密和完整性保护服务。

  • 分组长度:64 比特
  • 密钥长度:128 比特
  • 轮数:8 轮
  • 设计基础:基于 MISTY1 算法的优化版本,旨在平衡安全性与硬件/软件实现效率。

2. 整体架构

KASUMI 采用 8 轮 Feistel 网络结构。输入的 64 位数据块 \(I\) 被分为左右两个 32 位部分:\(L_0\)(左半部分)和 \(R_0\)(右半部分)。
对于每一轮 \(i\)\(1 \le i \le 8\)),操作如下定义:

\[R_i = L_{i-1} \]

\[L_i = R_{i-1} \oplus f_i(L_{i-1}, RK_i) \]

其中,\(f_i\) 是第 \(i\) 轮的轮函数,\(RK_i\) 是该轮的轮密钥。经过 8 轮迭代后,最终的 64 位输出由 \(L_8\)\(R_8\) 拼接而成:\(\text{OUTPUT} = L_8 || R_8\)
关键特性:KASUMI 的设计旨在利于硬件实现。其 S 盒经过优化,可用少量组合逻辑实现,且算法结构允许某些内部操作并行执行。

3. 轮函数 \(f_i\)

轮函数 \(f_i\) 由两个核心子函数 FLFO 组合而成。这两者的组合顺序根据轮次 \(i\) 的奇偶性而变化,这是一种旨在增加混淆强度的设计。

  • 奇数轮(1, 3, 5, 7)

    \[f_i(I, RK_i) = \text{FO}(\text{FL}(I, \text{KL}_i), \text{KO}_i, \text{KI}_i) \]

    数据先通过 FL 函数,再通过 FO 函数。
  • 偶数轮(2, 4, 6, 8)

    \[f_i(I, RK_i) = \text{FL}(\text{FO}(I, \text{KO}_i, \text{KI}_i), \text{KL}_i) \]

    数据先通过 FO 函数,再通过 FL 函数。
    这种交替结构打破了数据的线性传播路径,增强了密码抵抗差分分析等攻击的能力。

4. 核心子函数详解

4.1 FL 函数

FL 函数是一个 32 位输入到 32 位输出的线性变换函数。其主要目的是实现数据扩散,将密钥位的影响扩散到整个数据块。
输入

  • 32 位数据输入 \(I\),被分为两个 16 位半部分:\(I = L || R\)
  • 32 位子密钥 \(\text{KL}_i\),被分为两个 16 位子密钥:\(\text{KL}_i = \text{KL}_{i,1} || \text{KL}_{i,2}\)
    操作流程
  1. 计算 \(R' = R \oplus \text{ROL}(L \cap \text{KL}_{i,1}, 1)\)
    • 将左半部分 \(L\) 与子密钥 \(\text{KL}_{i,1}\) 进行按位与(AND)操作。
    • 将结果循环左移 1 位(\(\text{ROL}\))。
    • 将移位结果与右半部分 \(R\) 进行按位异或(XOR)。
  2. 计算 \(L' = L \oplus \text{ROL}(R' \cup \text{KL}_{i,2}, 1)\)
    • 将中间值 \(R'\) 与子密钥 \(\text{KL}_{i,2}\) 进行按位或(OR)操作。
    • 将结果循环左移 1 位。
    • 将移位结果与左半部分 \(L\) 进行按位异或。
  3. 输出\((L' || R')\)
    设计意图:使用 AND、OR 和 ROL 等线性操作,在不引入高度非线性的情况下,有效地混合数据位。\(\text{KL}_{i,2}\) 参与或操作,使其无法通过简单的差分分析被忽略。

4.2 FO 函数

FO 函数是一个 32 位输入到 32 位输出的非线性变换函数。它为算法提供了主要的混淆能力。
输入

  • 32 位数据输入 \(I = L_0 || R_0\)
  • 两个 48 位子密钥集 \(\text{KO}_i\)\(\text{KI}_i\)。每个被进一步分为三个 16 位子密钥:

    \[\text{KO}_i = \text{KO}_{i,1} || \text{KO}_{i,2} || \text{KO}_{i,3} \]

    \[\text{KI}_i = \text{KI}_{i,1} || \text{KI}_{i,2} || \text{KI}_{i,3} \]

操作流程
FO 函数本身是一个 3 轮的 Feistel 结构,每一轮都调用 FI 函数。
对于每一轮 \(j\)\(1 \le j \le 3\)):

  1. \(R_j = \text{FI}(L_{j-1} \oplus \text{KO}_{i,j}, \text{KI}_{i,j}) \oplus R_{j-1}\)
    • 将左半部分 \(L_{j-1}\) 与对应的子密钥 \(\text{KO}_{i,j}\) 异或。
    • 将结果作为输入调用 FI 函数,使用对应的子密钥 \(\text{KI}_{i,j}\)
    • FI 的输出与右半部分 \(R_{j-1}\) 异或,得到新的右半部分 \(R_j\)
  2. \(L_j = R_{j-1}\)
    • 新的左半部分 \(L_j\) 直接等于上一轮的右半部分 \(R_{j-1}\)
      输出\((L_3 || R_3)\)
      设计意图:通过嵌套的 Feistel 结构和 3 次调用 FI,为算法提供充分的非线性。内部 3 轮结构增强了扩散能力。

4.3 FI 函数

FI 函数是 KASUMI 算法非线性特性的主要来源。它是一个 16 位输入到 16 位输出的函数,是整个算法最核心、最精细的组件。
输入

  • 16 位数据输入 \(I\),被分为不等长的两个半部分:9 位左半部分 \(L_0\) 和 7 位右半部分 \(R_0\)。即 \(I = L_0 || R_0\)
  • 16 位子密钥 \(\text{KI}_{i,j}\),同样被分为 7 位和 9 位两部分:

    \[\text{KI}_{i,j} = \text{KI}_{i,j,1} || \text{KI}_{i,j,2} \]

    其中 \(\text{KI}_{i,j,1}\) 为 7 位,\(\text{KI}_{i,j,2}\) 为 9 位。
    辅助函数
  • ZE(x):将 7 位值 \(x\) 扩展为 9 位,在其最高有效端添加两个 0 位。
  • TR(x):将 9 位值 \(x\) 截断为 7 位,丢弃其两个最高有效位。
    操作流程(4 个步骤):
  1. \(L_1 = R_0\)
    \(R_1 = \text{S}9[L_0] \oplus \text{ZE}(R_0)\)
  2. \(L_2 = R_1 \oplus \text{KI}_{i,j,2}\)
    \(R_2 = \text{S}7[L_1] \oplus \text{TR}(R_1) \oplus \text{KI}_{i,j,1}\)
  3. \(L_3 = R_2\)
    \(R_3 = \text{S}9[L_2] \oplus \text{ZE}(R_2)\)
  4. \(L_4 = \text{S}7[L_3] \oplus \text{TR}(R_3)\)
    \(R_4 = R_3\)
    输出\((L_4 || R_4)\),其中 \(L_4\) 为 7 位,\(R_4\) 为 9 位。
    设计意图
  • 使用两个不同大小的 S 盒S7S9。这种“不平衡”的设计是 MISTY 系列算法的一个特征,旨在提供可证明的安全性,特别是抗差分分析和线性分析的能力。S 盒的具体数值经过精心设计,其逻辑方程和查找表在规范中有详细定义。
  • 复杂的位操作:数据在 7 位和 9 位路径之间交叉传播,并频繁地与子密钥异或,这极大地破坏了输入与输出之间的简单关系,提供了极强的混淆。
  • 4 轮结构:在 FI 内部进行的 4 次迭代,进一步增强了非线性。

5. 密钥扩展

KASUMI 接受一个 128 位的主密钥 \(K\),并通过密钥调度算法为每一轮生成所需的子密钥。
步骤

  1. 将 128 位主密钥 \(K\) 划分为八个 16 位字 \(K_1, K_2, ..., K_8\)

    \[K = K_1 || K_2 || ... || K_8 \]

  2. 生成另一个 16 位字数组 \(K'\),其中每个元素 \(K'_j\) 是原字与一个固定常数 \(C_j\) 的异或值:

    \[K'_j = K_j \oplus C_j \quad (\text{对于 } 1 \le j \le 8) \]

    常数数组 \(C\) 在规范中定义(例如 \(C_1 = 0x0123, C_2 = 0x4567, ...\))。
  3. 为每一轮 \(i\)\(1 \le i \le 8\))生成子密钥 \(\text{KL}_i, \text{KO}_i, \text{KI}_i\)。生成规则遵循规范中的表格,涉及对 \(K\)\(K'\) 数组中特定字的循环左移(ROL)。例如:
    • \(\text{KL}_{i,1} = \text{ROL}(K_i, 1)\)
    • \(\text{KL}_{i,2} = K'_{(i+2) \mod 8}\)
    • \(\text{KO}_{i,1} = \text{ROL}(K_{(i+1) \mod 8}, 5)\)
    • ... 以及其他子密钥,索引使用模 8 运算。
      设计意图
  • 简单高效:密钥扩展算法非常简单,主要依赖移位和异或,易于在硬件中实现。
  • 常数混合:引入预定义的常数 \(C_j\),防止了“弱密钥”的存在,并确保不同主密钥生成的子密钥之间有足够的差异。
  • 轮密钥独立性:通过复杂的索引和移位模式,确保了不同轮的子密钥之间相互独立,难以推导。

6. 安全性与应用

6.1 安全性

KASUMI 在设计之初经过了评估,被认为对差分分析和线性分析有较强的抵抗力。其 128 位的密钥长度在当时提供了足够的安全性。
2010 年,一种名为 “相关密钥三明治攻击” 的方法在理论上被提出,可以以较低的复杂度(约 \(2^{32}\) 次操作)攻破完整 8 轮的 KASUMI。这是一种相关密钥攻击,攻击者需要能够获取或控制与目标密钥存在特定关系的大量密钥。尽管在实际的 3G 网络环境中实施此类攻击极其困难,但该发现促使了 3GPP 在后续的 4G(LTE)和 5G 标准中,用更强大的算法(如 AES、SNOW 3G、ZUC)逐步替代了 KASUMI。

6.2 应用

KASUMI 本身是一个分组密码,在 3G 安全架构中不直接对用户数据进行加密,而是作为更高级别算法的核心原语

  1. 保密性算法 f8:f8 是一个同步流密码。它使用 KASUMI 作为核心构建一个密钥流生成器。生成的密钥流与用户数据(信令或语音)进行异或操作,从而实现加密。
  2. 完整性算法 f9:f9 是一个消息认证码(MAC)算法。它使用 KASUMI 计算消息的完整性校验码(MAC-I),用于验证消息在传输过程中未被篡改,并确认其来源的真实性。

7. 总结

KASUMI 算法是一个设计精巧、针对效率优化的分组密码。其设计核心体现在:

  • 优化的 Feistel 结构:8 轮结构,交替使用 FL 和 FO 函数。
  • 分层子函数:通过 FL 提供扩散,通过 FO 和其内部的 FI 提供强混淆。
  • 精心设计的非线性核心FI 函数内部使用不同大小的 S 盒(S7, S9)和复杂的交叉数据流,是实现其安全性的关键。
  • 简单高效的密钥调度:易于在硬件中实现,并防止了弱密钥。
    尽管后续发现了在特定攻击模型下的理论弱点,但 KASUMI 成功地支撑了 3G 移动通信系统十余年的安全运行,是密码学发展史上的一个重要案例。其设计思想,特别是对可证明安全性的探索和不平衡 Feistel 结构的应用,对后续分组密码的设计产生了深远影响。

8.实现

https://gitee.com/cloud-lumiere/cryptography/blob/master/分组密码/KASUMI/kasumi.py

KASUMI.py
"""
3GPP TS 35.202
Functions:
 - key_schedule(master_key: bytes) -> list
 - FI(data: int, subkey: int) -> int
 - FO(round_input: int, round_keys: dict) -> int
 - FL(round_input: int, round_keys: dict) -> int
 - encrypt(plaintext: bytes, key: bytes) -> bytes
 - decrypt(ciphertext: bytes, key: bytes) -> bytes
"""
from typing import List, Dict, Tuple

# S7: 128 entries (0..127)
S7: List[int] = [
    54, 50, 62, 56, 22, 34, 94, 96, 38, 6, 63, 93, 2, 18, 123, 33,
    55,113,39,114,21,67,65,12,47,73,46,27,25,111,124,81,
    53,9,121,79,52,60,58,48,101,127,40,120,104,70,71,43,
    20,122,72,61,23,109,13,100,77,1,16,7,82,10,105,98,
    117,116,76,11,89,106,0,125,118,99,86,69,30,57,126,87,
    112,51,17,5,95,14,90,84,91,8,35,103,32,97,28,66,
    102,31,26,45,75,4,85,92,37,74,80,49,68,29,115,44,
    64,107,108,24,110,83,36,78,42,19,15,41,88,119,59,3,
]

# S9: 512 entries (0..511)
S9: List[int] = [
    167,239,161,379,391,334,9,338,38,226,48,358,452,385,90,397,
    183,253,147,331,415,340,51,362,306,500,262,82,216,159,356,177,
    175,241,489,37,206,17,0,333,44,254,378,58,143,220,81,400,
    95,3,315,245,54,235,218,405,472,264,172,494,371,290,399,76,
    165,197,395,121,257,480,423,212,240,28,462,176,406,507,288,223,
    501,407,249,265,89,186,221,428,164,74,440,196,458,421,350,163,
    232,158,134,354,13,250,491,142,191,69,193,425,152,227,366,135,
    344,300,276,242,437,320,113,278,11,243,87,317,36,93,496,27,
    487,446,482,41,68,156,457,131,326,403,339,20,39,115,442,124,
    475,384,508,53,112,170,479,151,126,169,73,268,279,321,168,364,
    363,292,46,499,393,327,324,24,456,267,157,460,488,426,309,229,
    439,506,208,271,349,401,434,236,16,209,359,52,56,120,199,277,
    465,416,252,287,246,6,83,305,420,345,153,502,65,61,244,282,
    173,222,418,67,386,368,261,101,476,291,195,430,49,79,166,330,
    280,383,373,128,382,408,155,495,367,388,274,107,459,417,62,454,
    132,225,203,316,234,14,301,91,503,286,424,211,347,307,140,374,
    35,103,125,427,19,214,453,146,498,314,444,230,256,329,198,285,
    50,116,78,410,10,205,510,171,231,45,139,467,29,86,505,32,
    72,26,342,150,313,490,431,238,411,325,149,473,40,119,174,355,
    185,233,389,71,448,273,372,55,110,178,322,12,469,392,369,190,
    1,109,375,137,181,88,75,308,260,484,98,272,370,275,412,111,
    336,318,4,504,492,259,304,77,337,435,21,357,303,332,483,18,
    47,85,25,497,474,289,100,269,296,478,270,106,31,104,433,84,
    414,486,394,96,99,154,511,148,413,361,409,255,162,215,302,201,
    266,351,343,144,441,365,108,298,251,34,182,509,138,210,335,133,
    311,352,328,141,396,346,123,319,450,281,429,228,443,481,92,404,
    485,422,248,297,23,213,130,466,22,217,283,70,294,360,419,127,
    312,377,7,468,194,2,117,295,463,258,224,447,247,187,80,398,
    284,353,105,390,299,471,470,184,57,200,348,63,204,188,33,451,
    97,30,310,219,94,160,129,493,64,179,263,102,189,207,114,402,
    438,477,387,122,192,42,381,5,145,118,180,449,293,323,136,380,
    43,66,60,455,341,445,202,432,8,237,15,376,436,464,59,461,
]

# Key schedule constants C1..C8
C_CONSTS: List[int] = [0x0123, 0x4567, 0x89AB, 0xCDEF, 0xFEDC, 0xBA98, 0x7654, 0x3210]

def _rol16(x: int, n: int) -> int:
    n %= 16
    return ((x << n) & 0xFFFF) | (x >> (16 - n))

def _bytes_to_int(b: bytes) -> int:
    return int.from_bytes(b, byteorder="big")

def _int_to_bytes(x: int, length: int) -> bytes:
    return int.to_bytes(x, length, byteorder="big")


def key_schedule(master_key: bytes) -> List[Dict[str, Tuple[int, ...]]]:
    """Generate subkeys for 8 rounds.

    Returns list of 8 dictionaries, each with keys: 'KL' (tuple of two 16-bit),
    'KO' (tuple of three 16-bit), 'KI' (tuple of three 16-bit).
    """
    if len(master_key) != 16:
        raise ValueError("master_key must be 16 bytes (128 bits)")

    # Split into eight 16-bit words K1..K8 (1-based in spec; we use 0-based index)
    K_words = [ (master_key[i*2] << 8) | master_key[i*2 + 1] for i in range(8) ]

    # Kj' = Kj XOR Cj
    Kp = [K_words[i] ^ C_CONSTS[i] for i in range(8)]

    rounds = []
    for i in range(8):
        # indices using 0-based; spec uses 1-based and wraps mod 8
        def idx(off: int) -> int:
            return (i + off) % 8

        KL1 = _rol16(K_words[idx(0)], 1)
        KL2 = Kp[idx(2)]  # K_{i+2}' per table

        KO1 = _rol16(K_words[idx(1)], 5)
        KO2 = _rol16(K_words[idx(5)], 8)
        KO3 = _rol16(K_words[idx(6)], 13)

        KI1 = Kp[idx(4)]
        KI2 = Kp[idx(3)]
        KI3 = Kp[idx(7)]

        rounds.append({
            'KL': (KL1 & 0xFFFF, KL2 & 0xFFFF),
            'KO': (KO1 & 0xFFFF, KO2 & 0xFFFF, KO3 & 0xFFFF),
            'KI': (KI1 & 0xFFFF, KI2 & 0xFFFF, KI3 & 0xFFFF),
        })

    return rounds

def FI(data: int, subkey: int) -> int:
    """FI: 16-bit input -> 16-bit output.

    Sizes: input = 16 bits; split into L0 (9 bits high) || R0 (7 bits low).
    subkey is 16 bits, split into KI1 (7 bits high) || KI2 (9 bits low).
    Returns 16-bit value L4 (7 bits) || R4 (9 bits) as integer.
    """
    data &= 0xFFFF
    L0 = (data >> 7) & 0x1FF  # 9 bits
    R0 = data & 0x7F          # 7 bits

    KI1 = (subkey >> 9) & 0x7F    # 7 bits
    KI2 = subkey & 0x1FF          # 9 bits

    # Step 1
    L1 = R0                       # 7 bits
    R1 = S9[L0] ^ (R0 & 0x1FF)    # 9 bits

    # Step 2
    L2 = R1 ^ KI2                 # 9 bits
    R2 = S7[L1] ^ (R1 & 0x7F) ^ KI1  # 7 bits

    # Step 3
    L3 = R2                       # 7 bits
    R3 = S9[L2] ^ (R2 & 0x1FF)    # 9 bits

    # Step 4
    L4 = S7[L3] ^ (R3 & 0x7F)     # 7 bits
    R4 = R3                       # 9 bits

    return ((L4 & 0x7F) << 9) | (R4 & 0x1FF)


def FO(round_input: int, KO: Tuple[int, int, int], KI: Tuple[int, int, int]) -> int:
    """FO: 32-bit -> 32-bit using KO (three 16-bit) and KI (three 16-bit).

    Splits into L0||R0 (16-bit each) and runs 3 rounds calling FI.
    """
    L = (round_input >> 16) & 0xFFFF
    R = round_input & 0xFFFF

    L0, R0 = L, R
    for j in range(3):
        Li = L0
        Fi_in = (Li ^ KO[j]) & 0xFFFF
        Fi_out = FI(Fi_in, KI[j])
        Rj = Fi_out ^ R0
        L0, R0 = R0, Rj

    return ((L0 & 0xFFFF) << 16) | (R0 & 0xFFFF)


def FL(round_input: int, KL: Tuple[int, int]) -> int:
    """FL: 32-bit -> 32-bit using KL (two 16-bit words KLi,1 and KLi,2).

    L and R are 16-bit halves. Uses ROL by 1 bit as specified.
    """
    L = (round_input >> 16) & 0xFFFF
    R = round_input & 0xFFFF

    KLi1, KLi2 = KL

    Rp = R ^ _rol16(L & KLi1, 1)
    Lp = L ^ _rol16(Rp | KLi2, 1)

    return ((Lp & 0xFFFF) << 16) | (Rp & 0xFFFF)

def _fi_round(i: int, x: int, round_keys: List[Dict[str, Tuple[int, ...]]]) -> int:
    # i: round index 0..7 corresponding to rounds 1..8
    rk = round_keys[i]
    # For odd rounds (1,3,5,7) fi = FO( FL( I, KLi), KOi, KIi )
    # For even rounds (2,4,6,8) fi = FL( FO( I, KOi, KIi ), KLi )
    if (i % 2) == 0:  # i=0 -> round1 (odd)
        x1 = FL(x, rk['KL'])
        x2 = FO(x1, rk['KO'], rk['KI'])
        return x2
    else:              # even rounds
        x1 = FO(x, rk['KO'], rk['KI'])
        x2 = FL(x1, rk['KL'])
        return x2


def encrypt(plaintext: bytes, key: bytes) -> bytes:
    """Encrypt a single 64-bit block.

    `plaintext` and `key` are bytes. Returns ciphertext bytes (8 bytes).
    """
    if len(plaintext) != 8:
        raise ValueError("Plaintext must be 8 bytes (64 bits)")
    if len(key) != 16:
        raise ValueError("Key must be 16 bytes (128 bits)")

    rounds = key_schedule(key)

    I = _bytes_to_int(plaintext) & 0xFFFFFFFFFFFFFFFF
    L = (I >> 32) & 0xFFFFFFFF
    R = I & 0xFFFFFFFF

    for i in range(8):
        newR = L
        fi_out = _fi_round(i, L, rounds)
        newL = R ^ fi_out
        L, R = newL & 0xFFFFFFFF, newR & 0xFFFFFFFF

    out = ((L & 0xFFFFFFFF) << 32) | (R & 0xFFFFFFFF)
    return _int_to_bytes(out, 8)


def decrypt(ciphertext: bytes, key: bytes) -> bytes:
    """Decrypt a single 64-bit block.

    Uses inverse Feistel ordering.
    """
    if len(ciphertext) != 8:
        raise ValueError("Ciphertext must be 8 bytes (64 bits)")
    if len(key) != 16:
        raise ValueError("Key must be 16 bytes (128 bits)")

    rounds = key_schedule(key)

    I = _bytes_to_int(ciphertext) & 0xFFFFFFFFFFFFFFFF
    L = (I >> 32) & 0xFFFFFFFF
    R = I & 0xFFFFFFFF

    # reverse rounds
    for i in range(7, -1, -1):
        prevL = R
        fi_out = _fi_round(i, R, rounds)
        prevR = L ^ fi_out
        L, R = prevL & 0xFFFFFFFF, prevR & 0xFFFFFFFF

    out = ((L & 0xFFFFFFFF) << 32) | (R & 0xFFFFFFFF)
    return _int_to_bytes(out, 8)

def encrypt_n_rounds(plaintext: bytes, key: bytes, rounds: int) -> bytes:
    if rounds < 1 or rounds > 8:
        raise ValueError("rounds must be between 1 and 8")
    if len(plaintext) != 8:
        raise ValueError("Plaintext must be 8 bytes (64 bits)")
    if len(key) != 16:
        raise ValueError("Key must be 16 bytes (128 bits)")
    
    round_keys = key_schedule(key)
    I = _bytes_to_int(plaintext) & 0xFFFFFFFFFFFFFFFF
    L = (I >> 32) & 0xFFFFFFFF
    R = I & 0xFFFFFFFF
    
    for i in range(rounds):
        newR = L
        fi_out = _fi_round(i, L, round_keys)
        newL = R ^ fi_out
        L, R = newL & 0xFFFFFFFF, newR & 0xFFFFFFFF
    
    out = ((L & 0xFFFFFFFF) << 32) | (R & 0xFFFFFFFF)
    return _int_to_bytes(out, 8)

if __name__ == "__main__":
    # Quick self-check using the TS 35.202 test vector
    key = bytes.fromhex('2BD6459F82C5B300952C49104881FF48')
    pt = bytes.fromhex('EA024714AD5C4D84')
    ct = encrypt(pt, key)
    print("ciphertext:", ct.hex().upper())

posted @ 2025-12-31 10:38  lumiere_cloud  阅读(26)  评论(0)    收藏  举报