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\)),操作如下定义:
其中,\(f_i\) 是第 \(i\) 轮的轮函数,\(RK_i\) 是该轮的轮密钥。经过 8 轮迭代后,最终的 64 位输出由 \(L_8\) 和 \(R_8\) 拼接而成:\(\text{OUTPUT} = L_8 || R_8\)。
关键特性:KASUMI 的设计旨在利于硬件实现。其 S 盒经过优化,可用少量组合逻辑实现,且算法结构允许某些内部操作并行执行。
3. 轮函数 \(f_i\)
轮函数 \(f_i\) 由两个核心子函数 FL 和 FO 组合而成。这两者的组合顺序根据轮次 \(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}\)。
操作流程:
- 计算 \(R' = R \oplus \text{ROL}(L \cap \text{KL}_{i,1}, 1)\)
- 将左半部分 \(L\) 与子密钥 \(\text{KL}_{i,1}\) 进行按位与(AND)操作。
- 将结果循环左移 1 位(\(\text{ROL}\))。
- 将移位结果与右半部分 \(R\) 进行按位异或(XOR)。
- 计算 \(L' = L \oplus \text{ROL}(R' \cup \text{KL}_{i,2}, 1)\)
- 将中间值 \(R'\) 与子密钥 \(\text{KL}_{i,2}\) 进行按位或(OR)操作。
- 将结果循环左移 1 位。
- 将移位结果与左半部分 \(L\) 进行按位异或。
- 输出为 \((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\)):
- \(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\)。
- \(L_j = R_{j-1}\)
- 新的左半部分 \(L_j\) 直接等于上一轮的右半部分 \(R_{j-1}\)。
输出为 \((L_3 || R_3)\)。
设计意图:通过嵌套的 Feistel 结构和 3 次调用 FI,为算法提供充分的非线性。内部 3 轮结构增强了扩散能力。
- 新的左半部分 \(L_j\) 直接等于上一轮的右半部分 \(R_{j-1}\)。
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 个步骤):
- \(L_1 = R_0\)
\(R_1 = \text{S}9[L_0] \oplus \text{ZE}(R_0)\) - \(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}\) - \(L_3 = R_2\)
\(R_3 = \text{S}9[L_2] \oplus \text{ZE}(R_2)\) - \(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 盒:S7 和 S9。这种“不平衡”的设计是 MISTY 系列算法的一个特征,旨在提供可证明的安全性,特别是抗差分分析和线性分析的能力。S 盒的具体数值经过精心设计,其逻辑方程和查找表在规范中有详细定义。
- 复杂的位操作:数据在 7 位和 9 位路径之间交叉传播,并频繁地与子密钥异或,这极大地破坏了输入与输出之间的简单关系,提供了极强的混淆。
- 4 轮结构:在 FI 内部进行的 4 次迭代,进一步增强了非线性。
5. 密钥扩展
KASUMI 接受一个 128 位的主密钥 \(K\),并通过密钥调度算法为每一轮生成所需的子密钥。
步骤:
- 将 128 位主密钥 \(K\) 划分为八个 16 位字 \(K_1, K_2, ..., K_8\)。\[K = K_1 || K_2 || ... || K_8 \]
- 生成另一个 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, ...\))。
- 为每一轮 \(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 安全架构中不直接对用户数据进行加密,而是作为更高级别算法的核心原语。
- 保密性算法 f8:f8 是一个同步流密码。它使用 KASUMI 作为核心构建一个密钥流生成器。生成的密钥流与用户数据(信令或语音)进行异或操作,从而实现加密。
- 完整性算法 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())

浙公网安备 33010602011771号