BISS 加密是 DVB-S 内的一个功能, 可以让 DSNG 的 MPEG-2 流使用特定的方法加密, 只有拥有特定秘钥的人才能解码 TS 流.
目前某电视台使用的是参考资料内版本的 BISS 加密, 其中有三种模式: Mode 0, Mode 1, Mode E. 其中, Mode 0为无加密, 使用无加密方法编码后, 哪怕接收机上设定了其他密码, 也可以正常解码. Mode 1 是传统的加密方式, 使用 12 位 16 进制数字作为秘钥 (SW, Session Word), 当编码器设定了秘钥后, 接收机必须也设定好这个秘钥才能正常解码. Mode E 是略新的加密方式, 它把 Mode 1 中原有的 12 位 16 进制秘钥改为两部分内容, 需要在编码器中要分别输入 16 位 16 进制的加密秘钥 (ESW, Encrypted Session Word) 和 14 位 16 进制的 Injected ID (有些设备上称为 Buried ID 或 Active ID, 本文统称为 “ID”). 一组 ESW 和 ID 能计算出唯一一个 SW, 而一个 SW 能计算出若干组 ESW 和 ID, 而 ID 在一些设备上是可以自定义的, ESW 是要手动输入的.
在编码器中输入同一个 SW 生成的某一组 ESW 和 ID 后, 编码器内部会进行一个运算, 生成实际加密所需的 SW, 然后再把需要加密的码流以 BISS Mode 1 形式加密. 接收机侧也一样, 输入同一个 SW 生成的某一组 ESW 和 ID 后, 解码器内部也会进行一次运算, 生成实际解密所需的 SW, 然后再把需要解密的码流以 BISS Mode 1 形式解密. 没错, Mode E 的加密方式其实只是在两侧的终端进行的, 实际传输流中加密方式只有 Mode 1.
通常的运营模式是: 负责传输节目流的部门随机选定一个 SW, 然后向合法的发送方和接收方索要对方编码器或接收机的 ID (有的接收机可以自定义 ID, 那么让负责人随机指定即可), 然后根据 SW 和各家的 ID, 为他们生成各自的 ESW, 这样, 各家并不能知道 SW 的内容, 且拿到的是不同的 ESW. 如果 ESW 和 ID 泄露导致节目内容泄露, 那么从泄露的 ESW 和 ID 内容就能追溯到是从哪里泄露的.
但是问题在于, 在参考资料中, Mode 1 和 Mode E 是可以互相转换的, 且转换方法是公开的. 所以理论上, 拿到一组 ESW 和 ID 的发送方, 可以自行计算出 SW, 再把 SW 输入到编码器 BISS Mode 1 中, 接收方仍然能用他之前获得的同一个 SW 下的另一组 ESW 和 ID 进行解码观看.
本文就是要实现这个转换的功能, 代码如下, 由 ChatGPT 5 编写.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# ========== 手搓 DES ==========
# 初始置换表 IP
IP = [
58, 50, 42, 34, 26, 18, 10, 2,
60, 52, 44, 36, 28, 20, 12, 4,
62, 54, 46, 38, 30, 22, 14, 6,
64, 56, 48, 40, 32, 24, 16, 8,
57, 49, 41, 33, 25, 17, 9, 1,
59, 51, 43, 35, 27, 19, 11, 3,
61, 53, 45, 37, 29, 21, 13, 5,
63, 55, 47, 39, 31, 23, 15, 7
]
# 逆置换表 FP
FP = [
40, 8, 48, 16, 56, 24, 64, 32,
39, 7, 47, 15, 55, 23, 63, 31,
38, 6, 46, 14, 54, 22, 62, 30,
37, 5, 45, 13, 53, 21, 61, 29,
36, 4, 44, 12, 52, 20, 60, 28,
35, 3, 43, 11, 51, 19, 59, 27,
34, 2, 42, 10, 50, 18, 58, 26,
33, 1, 41, 9, 49, 17, 57, 25
]
# PC-1 (64 -> 56 bits)
PC1 = [
57, 49, 41, 33, 25, 17, 9,
1, 58, 50, 42, 34, 26, 18,
10, 2, 59, 51, 43, 35, 27,
19, 11, 3, 60, 52, 44, 36,
63, 55, 47, 39, 31, 23, 15,
7, 62, 54, 46, 38, 30, 22,
14, 6, 61, 53, 45, 37, 29,
21, 13, 5, 28, 20, 12, 4
]
# PC-2 (56 -> 48 bits)
PC2 = [
14, 17, 11, 24, 1, 5,
3, 28, 15, 6, 21, 10,
23, 19, 12, 4, 26, 8,
16, 7, 27, 20, 13, 2,
41, 52, 31, 37, 47, 55,
30, 40, 51, 45, 33, 48,
44, 49, 39, 56, 34, 53,
46, 42, 50, 36, 29, 32
]
# 每轮循环左移位数
SHIFTS = [1, 1, 2, 2, 2, 2, 2, 2,
1, 2, 2, 2, 2, 2, 2, 1]
# 扩展置换 (32 -> 48 bits)
E = [
32, 1, 2, 3, 4, 5,
4, 5, 6, 7, 8, 9,
8, 9, 10, 11, 12, 13,
12, 13, 14, 15, 16, 17,
16, 17, 18, 19, 20, 21,
20, 21, 22, 23, 24, 25,
24, 25, 26, 27, 28, 29,
28, 29, 30, 31, 32, 1
]
# S盒
SBOX = [
# S1
[[14,4,13,1,2,15,11,8,3,10,6,12,5,9,0,7],
[0,15,7,4,14,2,13,1,10,6,12,11,9,5,3,8],
[4,1,14,8,13,6,2,11,15,12,9,7,3,10,5,0],
[15,12,8,2,4,9,1,7,5,11,3,14,10,0,6,13]],
# S2
[[15,1,8,14,6,11,3,4,9,7,2,13,12,0,5,10],
[3,13,4,7,15,2,8,14,12,0,1,10,6,9,11,5],
[0,14,7,11,10,4,13,1,5,8,12,6,9,3,2,15],
[13,8,10,1,3,15,4,2,11,6,7,12,0,5,14,9]],
# S3
[[10,0,9,14,6,3,15,5,1,13,12,7,11,4,2,8],
[13,7,0,9,3,4,6,10,2,8,5,14,12,11,15,1],
[13,6,4,9,8,15,3,0,11,1,2,12,5,10,14,7],
[1,10,13,0,6,9,8,7,4,15,14,3,11,5,2,12]],
# S4
[[7,13,14,3,0,6,9,10,1,2,8,5,11,12,4,15],
[13,8,11,5,6,15,0,3,4,7,2,12,1,10,14,9],
[10,6,9,0,12,11,7,13,15,1,3,14,5,2,8,4],
[3,15,0,6,10,1,13,8,9,4,5,11,12,7,2,14]],
# S5
[[2,12,4,1,7,10,11,6,8,5,3,15,13,0,14,9],
[14,11,2,12,4,7,13,1,5,0,15,10,3,9,8,6],
[4,2,1,11,10,13,7,8,15,9,12,5,6,3,0,14],
[11,8,12,7,1,14,2,13,6,15,0,9,10,4,5,3]],
# S6
[[12,1,10,15,9,2,6,8,0,13,3,4,14,7,5,11],
[10,15,4,2,7,12,9,5,6,1,13,14,0,11,3,8],
[9,14,15,5,2,8,12,3,7,0,4,10,1,13,11,6],
[4,3,2,12,9,5,15,10,11,14,1,7,6,0,8,13]],
# S7
[[4,11,2,14,15,0,8,13,3,12,9,7,5,10,6,1],
[13,0,11,7,4,9,1,10,14,3,5,12,2,15,8,6],
[1,4,11,13,12,3,7,14,10,15,6,8,0,5,9,2],
[6,11,13,8,1,4,10,7,9,5,0,15,14,2,3,12]],
# S8
[[13,2,8,4,6,15,11,1,10,9,3,14,5,0,12,7],
[1,15,13,8,10,3,7,4,12,5,6,11,0,14,9,2],
[7,11,4,1,9,12,14,2,0,6,10,13,15,3,5,8],
[2,1,14,7,4,10,8,13,15,12,9,0,3,5,6,11]]
]
# P置换
P = [
16, 7, 20, 21, 29, 12, 28, 17,
1, 15, 23, 26, 5, 18, 31, 10,
2, 8, 24, 14, 32, 27, 3, 9,
19, 13, 30, 6, 22, 11, 4, 25
]
def permute(block, table, n):
res = 0
for i, pos in enumerate(table):
if block & (1 << (64-pos)):
res |= 1 << (n-1-i)
return res
def shift_left(k, n):
return ((k << n) & 0x0fffffff) | (k >> (28-n))
def generate_keys(key64):
# PC1 64->56
k56 = permute(key64, PC1, 56)
C = (k56 >> 28) & 0xfffffff
D = k56 & 0xfffffff
keys = []
for s in SHIFTS:
C = shift_left(C, s)
D = shift_left(D, s)
k = (C << 28) | D
keys.append(permute(k << 8, PC2, 48))
return keys
def feistel(r, k):
e = permute(r << 32, E, 48)
x = e ^ k
out = 0
for i in range(8):
chunk = (x >> (42-6*i)) & 0x3f
row = ((chunk & 0x20) >> 4) | (chunk & 0x01)
col = (chunk >> 1) & 0xf
out = (out << 4) | SBOX[i][row][col]
return permute(out << 32, P, 32)
def des_decrypt_block(block, key64):
keys = generate_keys(key64)
# 初始置换
b = permute(block, IP, 64)
L, R = (b >> 32) & 0xffffffff, b & 0xffffffff
for k in reversed(keys):
L, R = R, L ^ feistel(R, k)
pre = (R << 32) | L
return permute(pre, FP, 64)
# ========== BISS-E 解密辅助函数 ==========
def map_injected_id(injected_id_hex: str) -> int:
id_bits = bin(int(injected_id_hex, 16))[2:].zfill(56)
mapped = []
for i in range(0, 56, 7):
chunk = id_bits[i:i+7]
ones = chunk.count("1")
parity = "1" if ones % 2 == 0 else "0" # 奇校验
mapped.append(chunk + parity)
mapped_bits = "".join(mapped)
return int(mapped_bits, 2)
def biss_e_to_sw(esw_hex: str, injected_id_hex: str) -> str:
esw = int(esw_hex, 16)
key64 = map_injected_id(injected_id_hex)
sw_prime = des_decrypt_block(esw, key64)
# reduction: 每个字节去掉最高位和最低位
bits = ""
for i in range(8):
byte = (sw_prime >> (56 - 8*i)) & 0xff
mid = (byte & 0b01111110) >> 1
bits += bin(mid)[2:].zfill(6)
sw_int = int(bits, 2)
return f"{sw_int:012X}"
#========== 示例验证 ==========
if __name__ == "__main__":
# 示例来自 Tech3292 附录A Table A.1 Example 1
esw = "E81816B87E5CF9C4"
injected_id = "FB5F9C585DD359"
print("解出的 SW =", biss_e_to_sw(esw, injected_id))
运行结果:
1
解出的 SW = 6651DC93B1FC