[{"content":"cuckoo过滤器 cuckoo过滤器简介 cuckoo简介\n在中国有个成语叫“鸠占鹊巢”,讲述的是一种鸟类的奇特行为。这种鸟就是布谷鸟,也叫杜鹃(古时称鸤鸠)。布谷鸟有个特别的习性,它们不会自己筑巢,而是将自己的蛋偷偷放在其他鸟类的巢里,由这些宿主鸟来孵化。等到布谷鸟的小鸟孵出来后,它们会把巢里的其他蛋全部扔出,让宿主鸟只喂养自己,这样它们可以更快地长大。\n布谷鸟过滤器\n布谷鸟过滤器(cuckoo filter)是一种高效的集合成员检测工具,主要用于判断一个元素是否存在于某个集合中。它的工作原理类似于布隆过滤器(bloom filter),但在查询效率、空间利用率上更具优势,并且还能支持元素的删除。\n布谷鸟过滤器是一种节省内存的概率性数据结构。它的查询结果有两种:一种是“元素一定不存在”,另一种是“有较大可能存在”。需要注意的是,当查询结果显示元素可能存在时,实际上可能因为哈希碰撞而导致误判。(下文详细简介)\n名字的由来\n布谷鸟过滤器之所以得名,是因为它的插入过程有点像布谷鸟的行为。如果在插入元素时发生哈希碰撞,布谷鸟过滤器会把原有的元素“踢出”位置,这种行为就像布谷鸟雏鸟把寄主鸟蛋推开一样。因此,这种过滤器被形象地称为布谷鸟过滤器。\ncuckoo过滤器有什么用 布谷鸟过滤器(cuckoo filter)在实际应用中有很多有效的场景。以下是几个典型的例子:\n数据库查询优化:布谷鸟过滤器能够显著降低数据库的负载。在执行数据库查询之前,可以通过布谷鸟过滤器先进行检测,以确定数据是否存在于数据库中。如果过滤器表明数据不存在,就可以避免不必要的数据库访问,从而提高查询效率。\n解决缓存穿透问题:在redis中,布谷鸟过滤器可以有效地应对缓存穿透问题,防止恶意攻击。通过将布谷鸟过滤器作为缓存层的一部分,可以过滤掉那些不在缓存中的请求,减少对后端数据库的直接压力。\n内存效率优化:在redis处理大规模数据集合时,传统的数据结构如哈希表会消耗大量内存。布谷鸟过滤器可以用更少的内存实现类似的功能,从而提升内存使用效率。\nredisbloom模块:redis的redisbloom模块实现了布谷鸟过滤器,作为其数据结构之一,用于高效管理大规模数据集合。通过布谷鸟过滤器,redisbloom能够提供快速的查询和更新操作,提升系统的性能和响应速度。\ncuckoo过滤器的原理 布谷鸟过滤器的作用是查询一个数据是否在一个集合之中,显然首先需要将集合中元素的指纹\u0026quot;存储\u0026ldquo;到布谷鸟过滤器之中,然后用户通过查询布谷鸟过滤器去判断该数据是否在集合之中,当集合变小时,我们可以删除布谷鸟过滤器中的相应指纹。综上可以知道,布谷鸟过滤器需要包含三个算法\u0026quot;insert\u0026rdquo;、\u0026ldquo;lookup\u0026rdquo;、\u0026ldquo;delete\u0026rdquo;,下面分别介绍这几个算法。\n预备知识 指纹:\n首先介绍密码学中的哈希函数。为了防止与下面介绍的哈希表中的哈希函数重复,我们称密码学中的哈希函数为\u0026quot;散列函数\u0026quot;。散列函数是一种数学函数,它能够将任意长度的输入转换为固定长度的字符串。散列函数具有单向性,即从散列值反推原始输入是不可行的,但可以通过简单计算得到输入的散列值。\n利用散列函数,我们可以生成文件的“指纹”。例如:对于一个文件 x,首先计算其散列值 hash = h(x),其中 h(·) 表示散列函数。然后,从 hash 中提取若干位(例如前8位),作为文件 x 的指纹。为了便于描述,我们定义一个“指纹函数” fingerprint(x),它的作用是计算文件 x 的指纹,即 fp = fingerprint(x) 是 x 的指纹。\n哈希函数:\n哈希函数是将文件映射到哈希表中相应存储位置的函数。哈希表是一种高效的数据结构,其查询操作的时间复杂度为o(1)。这种高效性得益于哈希函数的作用。常见的哈希函数包括“平方取中法”、“除留取余法”和“随机数法”等。\n在布谷鸟过滤器中,需要计算一个数据的两个索引,故需要使用两个哈希函数 $h_1(x)$ 和 $h_2(x)$。首先,我们定义一个好的哈希函数 $h(\\cdot)$。基于这个哈希函数,我们可以定义这两个哈希函数为:$h_1(x) = h(x)$ 和 $h_2(x) = h_1(x) \\oplus h(\\text{fingerprint}(x))$。其中,$h(\\cdot)$ 是我们所定义的哈希函数,而 $\\text{fingerprint}(x)$ 是数据 $x$ 的指纹。\n可以看出,两个哈希函数 $h_1(x)$ 和 $h_2(x)$ 是对称的,他们两个具有相同的地位,若已知其中任意一个,比如已知 $h_2(x)$ ,可以计算另一个 $h_1(x) = h_2(x) \\oplus h(\\text{fingerprint}(x))$。这两个哈希函数的巧妙设计在插入时有大用。\n哈希表\n首先,我们来简单介绍布谷鸟过滤器(cuckoo filter)使用的数据结构。布谷鸟过滤器的核心数据结构是哈希表,这个哈希表由多个哈希桶组成。每个哈希桶对应一个唯一的索引,并且每个哈希桶内包含 $2^n$ 个槽位。初始化时,所有哈希桶的所有槽位的值都设为 0,表示这些槽位当前未存储任何数据。下图展示了一个示例,其中 $n=2$ 并且哈希表由 16 个哈希桶组成:\n如图所示,该哈希表共有 16 个索引(哈希桶),每个索引对应 4 个槽位(因此被称为四路哈希桶)。当前每个槽位的值都为 0,表示这个哈希表尚未存储任何信息。\n当我们需要将数据存储到布谷鸟过滤器中时,并不会直接存储整个数据,而是将数据的指纹存储到布谷鸟过滤器中,以节省存储空间。具体的存储和操作方法将在下文中详细介绍。\ninsert 算法 布谷鸟过滤器的索引计算需要用到哈希函数 $h(\\cdot)$ 和指纹函数 fingerprint(·)。假设我们想要将数据 x 添加到布谷鸟过滤器中,我们按照以下步骤执行添加操作:\n计算指纹和索引: 首先我们计算 x 的指纹 fp = fingerprint(x),然后分别使用两个哈希函数计算数据在哈希表中的两个索引 $h_1 = h_1(x)$ 和 $h_2 = h_2(x)$ . 检查空闲槽位:检查哈希表中这两个索引 $h_1$ 和 $h_2$ 对应的哈希桶是否有空闲槽位。如果找到空闲槽位,则将指纹 $fp$ 存储到这个槽位中。 处理满槽位:如果两个哈希桶中的所有槽位都已满,则需要进行挤兑操作。随机选择一个满槽位,将其中的元素(假设为 magpie,实际是一个指纹)踢出,并将指纹 $fp$ 存储到该槽位上。假设该槽位的索引为 $h_{old}$,计算 magpie 的新索引 $h_{new}=h_{old}\\oplus h(magpie)$,如果 $h_{new}$处有空闲的槽位,则将 magpie 存储到该槽位上。如果 $h_{new}$ 处也没有空闲槽位,则重复上述挤兑过程,直到找到合适的位置。 循环挤兑和扩容:在挤兑过程中,可能会出现循环挤兑的情况,即被踢出的元素计算新的索引后又需要继续挤兑其他元素。为了避免无限循环,我们设定一个阈值,如果循环挤兑的次数超过该阈值,就会触发扩容操作。扩容涉及到增大哈希表的大小,并重新计算数据在新哈希表中的索引位置,虽然这一过程较为耗时,但可以有效解决循环挤兑问题。 以下是插入数据到布谷鸟过滤器的过程示例:\n对上述插入过程的简单描述如下:\n首先,使用哈希函数计算数据 $x$ 的两个位置索引 $2 = h_1(x)$ 和 $6 = h_2(x)$。然后随机选择一个位置存放数据 $x$(在这里选择了索引 6)。 由于两个索引位置都已经满了,因此我们将随机选择一个位置存放数据指纹 $x$,并将该位置原有的指纹(在这里是指纹 a)踢出。 被踢出的指纹 a 需要重新计算它的另一个位置索引,计算方式为: $h_{new}(x_a)= h_1(x_a)\\oplus h(a)$ ,其中 $h_1(x_a)$ 是当前位置的索引,$x_a$ 是指纹 a 对应的数据,也就是哈希表中存储的元素。因此,被提出的 a 可以计算自己的另一个索引 $4 = h_1(x_a)\\oplus h(fp_{x_a}) = 6\\oplus h(a)$。需要说明的是,这个过程并不会用到源数据 $x_a$ 。 元素 a 继续挤兑另一个元素(在这里是索引 4 处的元素 c),并计算出 c 的另一个位置索引为: $1 = 4\\oplus h(c)$,这个位置是空的,因此c存储到这个位置。 在上述过程中,使用一路哈希桶时,在未发生哈希碰撞的情况下,哈希桶的利用率为 50%。通过增加每个索引位置对应的哈希桶中的槽位数量可以提高空间利用率。理论上,当使用哈希桶大小为 2、4 和 8 时,空间利用率分别会提高到 84%、95% 和 98%。下图展示了二路哈希桶的添加元素过程,但不再对具体细节进行描述。\n均衡分配\n在上述的插入过程中,可以注意到,计算第二个索引位置时,指纹在与桶索引进行异或计算之前会先进行哈希处理。这种处理方式有助于在哈希表中实现均衡分配。如果第二个索引使用了 $i \\oplus h(\\text{fingerprint})$ 的计算方式,并且指纹的大小相对于哈希表的总大小(即哈希桶的数量)较小,那么被踢出的元素最终将会落在邻近的桶中。\n举个例子,如果使用的是 8 位指纹,那么被踢出的元素将被放置在距离原桶 $i$ 最多 256(即 $2^8$)个桶的位置,因为异或计算会影响桶索引的低 8 位,而高位则保持不变。这种哈希指纹的方法确保了元素能够重新分布到哈希表的不同桶中,从而实现了均衡分配,减少了哈希碰撞,并提高了哈希表的利用率。\nlookup 算法 lookup 算法用于查询一个数据是否存在于布谷鸟过滤器中。查询过程如下:\n计算待查询数据 $x$ 的指纹 $fp = \\text{fingerprint}(x)$。 根据指纹计算两个哈希索引 $h_1 = h_1(x)$ 和 $h_2 = h_2(x)$。 检查哈希表中这两个索引对应的桶是否包含该指纹 $fp$。 如果在这两个桶中都没有找到指纹 $fp$,则可以确定数据 $x$ 不在过滤器中。若在其中一个桶中找到了指纹 $fp$,则有很大可能数据 $x$ 存在于过滤器中,但不能完全确定,因为可能存在哈希碰撞。\ndelete 算法 delete 算法用于从布谷鸟过滤器中删除一个元素。操作步骤如下:\n计算待删除元素 $x$ 的指纹 $fp = \\text{fingerprint}(x)$。 根据指纹计算两个哈希索引 $h_1 = h_1(x)$ 和 $h_2 = h_2(x)$。 在哈希表中查找这两个索引对应的桶,找到并删除其中的指纹 $fp$。 一旦成功删除指纹 $fp$,则相应的元素 $x$ 被从过滤器中移除。\n删除的局限\n布谷鸟过滤器的删除并不完美,删除操作在相同哈希值仅被插入一次时是完美的,如果元素没有插入就进行删除,可能会出现误删除 (删除了相同哈希值的其他元素), 如果元素插入了多次,但是每次删除操作只删除一个值,那么就需要知道元素插入了多少次才能彻底删除,或者循环删除直到失败为止。\ncuckoofilter与bloomfilter 优点 支持删除操作:布谷鸟过滤器支持删除元素,而布隆过滤器不支持。 高负载因子的查询效率更高:在高负载因子场景下,布谷鸟过滤器的查询效率通常优于布隆过滤器。 较低的存储空间开销:在存储数据量较大且期望误判率较低(低于 3%)的场景中,布谷鸟过滤器的存储空间开销通常低于布隆过滤器。 更易于实现:布谷鸟过滤器相较于布隆过滤器更容易实现。 缺点 桶大小要求:布谷鸟过滤器使用备用候选桶的方案,其中候选桶与首选桶通过位置和指纹的哈希异或计算得出。这要求桶的大小必须是 2 的幂(如 4、8、16、32 等),限制了桶大小的灵活性。 插入性能较低:布谷鸟过滤器在插入时,如果目标位置已满,需要将已有的指纹踢出到候选桶,这会导致插入性能下降。与布隆过滤器相比,布谷鸟过滤器的插入操作由于哈希碰撞的处理而更为复杂,插入性能相对较低。 处理重复元素的限制:布隆过滤器允许重复插入相同的元素,而布谷鸟过滤器在插入已存在的元素时会执行踢出操作,因此对重复元素的插入存在一定的限制。 删除操作的不完美:布谷鸟过滤器的删除操作并不完美。当元素仅插入一次时,删除操作是有效的;但如果元素未插入却尝试删除,可能会误删除相同哈希值的其他元素。如果元素插入多次,每次删除只会删除一个副本,这就需要知道元素的插入次数才能彻底删除,或者需重复删除操作直到删除失败。值得注意的是,如果只需要保证元素“绝对不存在”的语义,直接删除即可,无论是否存在重复元素。 cuckoo过滤器的代码实现 参考文献\n布谷鸟详细介绍 cuckoo filter: practically better than bloom cuckoo filter 蛮荆 csdn zevin~ github源码 c语言实现 ","date":"2024-07-30","permalink":"http://54rookie.com/posts/cuckoo%E8%BF%87%E6%BB%A4%E5%99%A8/","summary":"Cuckoo过滤器 Cuckoo过滤器简介 Cuckoo简介 在中国有个成语叫“鸠占鹊巢”,讲述的是一种鸟类的奇特行为。这种鸟就是布谷鸟,也叫杜鹃(古时称鸤鸠)。布谷","title":"cuckoo过滤器"},]
[{"content":"abe及其应用 什么是基于属性的加密 正式描述\n基于属性加密(abe)最先在 2005年 由 waters 在论文《fuzzy identity-based encryption》中提出,并在2006年的论文中用于细粒度的访问控制,被看作是最具前景的支持细粒度访问的加密原语。abe实现了一对多的加解密。不需要像基于身份加密(ibe)一样,每次解密都必须知道接收者的身份信息,在abe中它把身份标识被看做是一系列的属性。当用户拥有的属性超过加密者所描述的预设阈值时,用户是可以解密的。\n基于属性加密主要分为两大类:密文策略的属性加密(cp-abe)和密钥策略的属性加密(kp-abe)。在cp-abe中密文和加密者定义的访问策略相关联,密钥则是和属性相关联;在kp-abe中密文则是和属性相关,而密钥与访问策略相关联。我们将在下文对他们分别进行介绍。\n通俗描述\n基于属性的加密实际上实现了这样一个功能,加密者可以选择一组属性并利用这组属性对一个消息进行加密,只有接收者满足这些属性的指定数目时才能解密密文。例如:alice 选择三个属性\u0026quot;安徽大学\u0026quot;,\u0026ldquo;计算机学院\u0026rdquo;,\u0026ldquo;老师\u0026quot;去加密消息 m 得到密文 c ,那么abe可以要求至少拥有这三个属性中的两个才能解密 c 。\nabe方案 形式化定义 setup:系统初始化阶段,输入系统安全参数,产生相应的公共参数 mpk 和系统主密钥 msk;\nkeygen:密钥生成,解密用户向系统提交自己的属性,获得属性相关联的用户密钥(sk);\nenc:加密,数据拥有者对数据进行加密得到密文(ct)并发送给用户或者发送到公共云上;\ndec:解密,解密用户获得密文,用自己的密钥sk进行解密。\nabe实例 一种直观的构造 setup:\n令属性全集为 $u=\\{1,2,\u0026hellip;,n\\}$ 这里 u 中的每个元素可以映射到一个属性,例如:1 表示\u0026quot;国家\u0026rdquo;,2 表示\u0026quot;省份\u0026quot;,3表示\u0026quot;性别\u0026quot;等等。 生成 n 个随机数 $t_1 \\in_r z_p ,\u0026hellip;,t_n \\in_r z_p$ , 计算 $t_1 = g^{t_1} ,\u0026hellip;,t_n = g^{t_n}$ 生成随机数 $y\\in_r z_p$ 计算 $y=e(g,g)^y$ 则,系统私钥为 $msk=(t_1,\u0026hellip;,t_n,y)$ , 系统公钥为 $mpk=(t_1,\u0026hellip;,t_n,y)$. keygen:\n假设在这个系统中,加密方要求解密者至少需要拥有 t 个加密使用的属性才能解密密文,则他首先生成一个 t-1 阶的多项式 $q(x) = y + a_1x + \u0026hellip; + a_{t-1}x^{t-1}$ , 然后计算第 i 个属性对应的解密密钥 $d_i=g^{q(i)/t_i}$, 其中 $ i\\in u $,若某用户拥有一个属性,则将该属性对应的密钥 $d_i$ 分发给该用户。\nencryption:\n假设一个用户想要使用一组属性 $ w\u0026rsquo; = \\{ i_1,\u0026hellip;,i_n \\}_{i\\in u}$ 对消息 m 进行加密,那么她执行以下步骤:\n生成随机数 $s\\in_r z_p$ 并计算 $c\u0026rsquo;= m \\cdot y^s$ 和 $\\{ c_i = t_i^s \\}_{i\\in {w\u0026rsquo;}}$ 令最终的密文 $c=(w\u0026rsquo;,c\u0026rsquo;,\\{c_i\\}_{i\\in w\u0026rsquo;})$ 。 decryption:\n解密者在得到了使用属性集 $w\u0026rsquo;$ 加密的密文 c 之后,如果自己有不少于 t 条属性属于 $w\u0026rsquo;$ ,那么它可以选择任意包含 t 个属性的子集 s,然后计算下式解密 c 得到消息 m 。\n$$m = c\u0026rsquo; / \\prod_{i\\in s}e(d_i,c_i)^{l_i(0)}, 其中 \\ \\ l_i(x)=\\prod_{i=1}^{t} \\frac{i}{j-i}$$\n正确性证明\n上述解密的正确性很容易证明,具体过程如下: $$\\begin{align} c\u0026rsquo; / \\prod_{i\\in s}e(d_i,c_i)^{l_i(0)} \u0026amp;= m\\cdot e(g,g)^{sy}/ \\prod_{i\\in s}e(g^{q_i/t_i},g^{s\\cdot t_i})^{l_i(0)} \\\\ \u0026amp;= m\\cdot e(g,g)^{sy}/ \\prod_{i\\in s}e(g,g)^{s\\cdot q(i)\\cdot l_i(0)} \\\\ \u0026amp;= m\\cdot \\frac{e(g,g)^{s\\cdot y}}{e(g,g)^{s\\cdot \\sum_{i=1}^{t} q(i)\\cdot l_i(0)}} \\\\ \u0026amp;= m \\end{align}$$\nlarge universe construction 在上述方案中,属性全集 $u=\\{1,2,\u0026hellip;,n\\}$, u 中每个元素 i 对应于一个属性,这导致需要额外存储一个 u 中元素与属性的对应关系表 rel。除此之外,上述方案中的公共参数的大小与属性集合的大小成正比。现在我们介绍一个优化的方案,使得属性全集 $u = z_p $,并且公共参数的大小只与描述一个实体需要的属性集大小有关。比如,根据\u0026quot;黑洞无毛定理\u0026quot;,描述一个静态黑洞只需要三个物理量(质量,角动量,电荷),那么优化后的abe只需要约为 3 个公共参数。除此之外,将 u 的范围扩展到 $z_p$ 之后,可以使用哈希函数 $h:{0,1}^* -\u0026gt; z_p $ 来将任意属性,包括字符串映射到 u 中,从而避免了存储 rel 表。\nsetup:\n令属性全集为 $u = z_p$ 。生成 $y\\in_rz_p$ , 计算 $g_1=g^y,g_2\\in_r g_1$ 其中 g 是 $g_1$ 的生成元。 生成 n+1 个随机数 $t_1 \\in_r z_p ,\u0026hellip;,t_n,t_{n+1} \\in_r g_1$ , 令 $n = {1,2,\u0026hellip;,n+1}$ 定义函数 $t(x)=g_2^{x^n}\\cdot \\prod_{i=1}^{n+1}t_i^{l_i(x)}$ 则,系统私钥为 $msk = y$ , 系统公钥为 $mpk=(g_1,g_2,t_1,\u0026hellip;,t_{n+1})$. keygen:\n假设在这个系统中,要求解密者至少需要拥有 t 个加密使用的属性才能解密密文,则首先生成一个 t-1 阶的多项式 $q(x) = y + a_1x + \u0026hellip; + a_{t-1}x^{t-1}$ . 生成随机数 $r_i\\in_r z_p , i\\in z_p^* $ , 然后计算第 i 个属性对应的解密密钥 $d_i=g^{r_i}$ 和 $d_i=g_2^{q(i)}\\cdot t(i)^{r_i}$, 其中 $ i\\in u $,若某用户拥有一个属性,则将该属性对应的密钥 $d_i$ 分发给该用户。\nencryption:\n假设一个用户想要使用属性 $ w\u0026rsquo; = \\{ i_1,\u0026hellip;,i_n \\}_{i\\in u}$ 对消息 m 进行加密,那么她执行以下步骤:\n生成随机数 $s\\in_r z_p$ 并计算 $c\u0026rsquo;= m \\cdot e(g_1,g_2)^s$ , $c\u0026rsquo;\u0026rsquo;=g^s$ 和 $\\{ c_i = t(i)^s \\}_{i\\in {w\u0026rsquo;}}$ 令最终的密文 $c=(w\u0026rsquo;,c\u0026rsquo;,c\u0026rsquo;\u0026rsquo;,\\{c_i\\}_{i\\in w\u0026rsquo;})$ 。 decryption:\n解密者在得到了使用属性集 $w\u0026rsquo;$ 加密的密文 c 之后,如果自己有不少于 t 条属性属于 $w\u0026rsquo;$ ,那么它可以选择任意包含 t 个属性的子集 s,然后计算下式解密 c 得到消息 m 。\n$$m = c\u0026rsquo; \\cdot \\prod_{i\\in s}(\\frac{e(d_i,c_i)}{e(d_i,c\u0026rsquo;\u0026rsquo;)})^{l_i(0)}, 其中 \\ \\ l_i(x)=\\prod_{i=1}^{t} \\frac{i}{j-i}$$\n正确性证明\n上述解密的正确性很容易证明,具体过程如下: $$\\begin{align} c\u0026rsquo; \\cdot \\prod_{i\\in s}(\\frac{e(d_i,c_i)}{e(d_i,c\u0026rsquo;\u0026rsquo;)})^{l_i(0)} \u0026amp;= m\\cdot e(g_1,g_2)^s \\cdot \\prod_{i\\in s}(\\frac{e(g^{r_i},t(i)^s)}{e(g_2^{q_i},g^s)e(t(i)^{r_i},g^s)})^{l_i(0)} \\\\ \u0026amp;= m\\cdot e(g,g_2)^{y\\cdot s} \\cdot \\prod_{i\\in s} \\frac{1}{e(g_1,g_2)^{q(i)\\cdot s\\cdot l_i(0)}} \\\\ \u0026amp;= m\\cdot e(g,g_2)^{y\\cdot s} \\cdot \\frac{1}{e(g_1,g_2)^{s\\cdot \\sum_{i=1}^{t} q(i)\\cdot l_i(0)}} \\\\ \u0026amp;= m\\cdot e(g,g_2)^{y\\cdot s} \\cdot \\frac{1}{e(g_1,g_2)^{s\\cdot y}} \\\\ \u0026amp;= m \\end{align}$$\n基于abe的细粒度访问控制 什么是细粒度的访问控制 正式描述 细粒度访问控制基于多个多个条件、权限来决定一个用户是否拥有对资源的访问权。与之相对的是粗粒度的访问控制,它一般基于单个因素(即角色或权限)决定用户是否拥有资源的访问权。 细粒度授权等同于基于属性的访问控制(abac)或基于策略的访问控制,而粗粒度访问控制等同于基于角色的访问控制(rbac)。\n细粒度的访问控制通过多个条件或属性为用户授权,而粗粒度的访问控制通过单个条件或属性为用户授权。举个通俗的例子来说,粗粒度访问控制是基于角色的,它规定某个人可以访问哪些文件,而细粒度访问控制是基于文件的,它规定符合哪些条件的人可以访问某个文件。\n通俗描述 在rbac中,规定某个角色可以访问哪些文件,例如:董事长可以访问文件 a,b,c ;经理可以访问文件a,b ;而员工只能访问文件a。 在abac中,规定某个文件可以被那些人访问,例如:文件c 必须是 \u0026ldquo;a公司\u0026quot;中的\u0026quot;董事长\u0026quot;才能访问,而文件b 只要是\u0026quot;a公司中\u0026quot;的\u0026quot;经理\u0026quot;即可访问,文件a则只要是\u0026quot;a公司\u0026quot;的\u0026quot;员工\u0026quot;都能访问。\n使用基于rbac的访问控制方法实现密文存储时,一般使用访问控制表(acl)去管理用户的权限,它面临维护困难、不灵活或者无法应对复杂授权需求等挑战。在一个大型企业中,可能有数千个员工和数百个不同的部门或项目组,每个员工可能需要访问多个不同的文件或系统资源。使用传统的acl,管理员可能需要为每个文件或资源设置复杂的权限列表,这不仅管理起来困难,而且容易出现安全漏洞。 而使用基于abac的访问控制方法,则可以使用属性{\u0026ldquo;a公司\u0026rdquo;,\u0026ldquo;董事长\u0026rdquo;,\u0026ldquo;经理\u0026rdquo;,\u0026ldquo;员工\u0026rdquo;}对文件a,b,c分别进行加密。使得拥有相应属性的人才能解密相应的文件。并且当文件和属性变化时,可以以较低的开销对访问控制策略进行修改。\n细粒度的访问控制有什么用 细粒度的访问控制可以根据资源的特征、用户的属性定义精细灵活的访问控制策略,并可根据环境动态的调整访问策略,在复杂的控制系统中更有效。它可以:\n解决复杂的访问控制需求:\n在一个大型企业中,可能有数千个员工和数百个不同的部门或项目组,每个员工可能需要访问多个不同的文件或系统资源。使用传统的acl,管理员可能需要为每个文件或资源设置复杂的权限列表,这不仅管理起来困难,而且容易出现安全漏洞。abe可以根据员工的角色、部门或其他属性,动态地分配和调整访问权限,避免了acl所面临的管理复杂性和错误风险。\n实现跨组织的数据共享:\n在跨多个组织进行数据共享时,例如在医疗保健行业或跨国公司中,传统的acl可能无法有效地管理不同组织之间的权限控制需求。abe允许根据数据的敏感性和用户的属性(如所属组织、授权级别等)来动态地控制数据的访问权限,而无需为每个组织设置独立的acl。\n完成动态访问控制调整:\n在动态环境中,例如云计算环境中的虚拟机实例,用户需要根据需求访问不同的资源和服务。传统acl可能无法实时调整以满足这种动态性,而abe可以根据用户的临时属性或临时授权来动态地调整访问控制策略,保证了资源的安全性和灵活性。\n如何实现细粒度的访问控制 这里介绍 waters 于 2006 年在论文 kp-abe中提出的第一个基于属性加密的细粒度访问控制方案,作者在论文中详细介绍了如何定义访问策略,如何根据访问策略实现细粒度的访问控制。\n预备知识 shamir秘密共享\n为了分发秘密,秘密分发者首先随机生成 $ t - 2 $ 个小于 $ p $ 的随机数 $ a_{1}, a_{2},\u0026hellip;, a_{t - 1} $ 并构造一个 t-1 阶的多项式 $ f (x) = a_0 + a_{1} x + \\cdots + a_{t-1} x^{t-1} \\pmod p $ , 其中 $ f (0) = a_{0} = s $ 是要保护的秘密, $ p $ 是一个大素数,且 s \u0026lt; p 。秘密分发者随机选取 $ n $ 个互不相同的整数 $ x_{1}, x_{2}, \\cdot, x_{n} $ 并将 $ n $ 个整数代入多项式函数 $f(x)$得到 $ n $ 个值 $ y_1 = f (x_1)$, $ y_2 = f(x_2)$,\u0026hellip;, $y_{n} = f (x_{n}) $ , 然后将计算得到的 $ n $ 个坐标 $(x_i,y_i)$ 分别发给 $ n $ 各参与方并销毁 $ f (x) $,即第 $ i $ 个参与方获得 $ (x_{i}, y_{i}) $ 。 为了恢复秘密,收集到 t 个参与者的份额、也就是 t 个点的坐标之后,将得到的 t 个点利用拉格朗日插值法计算出拉格朗日插值多项式,也就是 $f(x)$ , 然后计算 $f(0)=s$ 得到秘密值。 访问控制树\n为了更好的描述访问策略,我们引入访问控制树这个数据结构。假设 t 是一个表示访问结构的访问控制树,那么在 t 中,每个非叶节点(用 x 表示)表示一个阈值门,由它的子节点和一个阈值描述。假设 $num_x$ 是节点 x 的子节点数量,$k_x$ 是它的阈值,则 $0 \u0026lt; k_x \u0026lt; num_x$ 。容易看出,当 $k_x = 1$ 时,节点表示 or 门,当 $k_x = num_x$ 时,节点表示 and 门,否则节点表示阈值门。\n如何判断拥有一组属性的用户是否满足访问树呢?我们给出方法如下:设 t 是根为 r 的访问控制树,用 $t_x$ 表示根为 x 的 t 的子树,因此 $t = t_r$ 。如果一组属性 γ 满足访问树 $t_x$,则记为 $t_x(γ) = 1$。我们递归地计算 $t_x(γ)$ 如下,如果 x 是一个非叶节点,计算节点 x 的所有子节点 $x\u0026rsquo;$ 的 $t_x\u0026rsquo;(γ)$。当且仅当至少 $k_x$ 个子节点返回 1 时,$t_x(γ)$ 返回1。如果 x 是叶节点,则 $t_x(γ)$ 返回 1 当且仅当 $att(x)\\in γ$。如果 $t_r(γ)=1$, 我们说这组属性 $γ$ 满足访问控制树。\n为了方便使用访问树,我们定义 parent(x) 表示树中节点 x 的父节点。当 x 是叶节点时,函数 att(x) 表示叶节点 x 对应的属性。访问树 t 对每个节点的子节点进行编号,最简单的编号方式是将其子节点从左到右依次编号为 $1,2,\u0026hellip;,num_x$ ,函数 ind(x) 返回 x 的父节点对 x 的编号。值得注意的是,子节点的编号方式可以是任意的,只要保证同一节点的子节点编号中没有重复即可。\n访问控制树的图示如下:\n具体构造 一种直观构造 这篇文章实际上是 waters 对其 2005 年提出的\u0026quot;基于属性加密(abe)\u0026ldquo;的应用,方案几乎没有大的变化。\nsetup:\n令属性全集为 $u=\\{1,2,\u0026hellip;,n\\}$ 这里 u 中的每个元素可以映射到一个属性,例如:1 表示\u0026quot;国家\u0026rdquo;,2 表示\u0026quot;省份\u0026rdquo;,3表示\u0026quot;性别\u0026quot;等等。 生成 n 个随机数 $t_1 \\in_r z_p ,\u0026hellip;,t_n \\in_r z_p$ , 计算 $t_1 = g^{t_1} ,\u0026hellip;,t_n = g^{t_n}$ 生成随机数 $y\\in_r z_p$ 计算 $y=e(g,g)^y$ 则,系统私钥为 $msk=(t_1,\u0026hellip;,t_n,y)$ , 系统公钥为 $mpk=(t_1,\u0026hellip;,t_n,y)$. encryption:\n假设一个用户想要使用属性 $ \\gamma = \\{ i_1,\u0026hellip;,i_n \\}_{i\\in u}$ 对消息 m 进行加密,那么她执行以下步骤:\n生成随机数 $s\\in_r z_p$ 并计算 $c\u0026rsquo;= m \\cdot y^s$ 和 $\\{ c_i = t_i^s \\}_{i\\in {\\gamma}}$ 令最终的密文 $c=(\\gamma,c\u0026rsquo;,\\{c_i\\}_{i\\in \\gamma})$ 。 keygen:\n该算法输出一个密钥,当且仅当 $t(γ) = 1$ 时,用户能够解密在一组属性 $γ$ 下加密的消息。算法的过程如下。首先为树 t 中的每个节点 x (包括叶子)选择一个多项式 $q_x$。从根节点 r 开始,以自顶向下的方式选择这些多项式。对于树中的每个节点x,设置随机多项式 $q_x$ 的阶数 $d_x$ 比该节点的阈值 $k_x$ 小 1,即 $d_x = k_x - 1$ 。现在,对于根节点 r,令 $q_r(0) = y$并随机选择多项式 $d_r$ 个随机数作为 $q_r(x)$的系数得到$q_r(x) = y + a_1x + \u0026hellip; + a_{d_r}x^{d_r}$。对于其他任意节点x,设 $q_x(0) = q_{parent(x)} (ind(x))$,随机选择其他 $d_x$ 个随机数,生成多项式 $q_x(x)$。(可参考上面的访问控制树看)\n一旦确定了多项式,对于每个叶节点 x,我们计算其对应属性的解密密钥: $d_i=g^{q_x(0)/t_i}$ , 其中 $i\\in att(x) $ 若某用户拥有一个属性,则将该属性对应的密钥 $d_i$ 分发给该用户。\ndecryption:\n为了清楚的介绍解密算法,我们首先定义算法 decrypnode(c,d,x) , 该算法的定义如下:\n当 x 是叶节点时, $$decrypnode(c,d,x)= \\begin{cases} e(d_x,c_i)=e(g^{q_x(0)/t_i},g^{s\\cdot t_i})=e(g,g)^{s\\cdot q_x(0)},\u0026amp;if \\ i\\in \\gamma \\\\ \\perp , \u0026amp;otherwise \\end{cases}$$\n当 x 是非叶节点时,我们设他的子节点为 z ,并且定义 $f_z = decrypnode(c,d,z)$ 。 假设 $s_x$ 是节点 x 的一个大小为 $k_x$ 的子节点集合使得 $f_z \\neq \\perp$。如果没有这样的集合,返回 $\\perp$ ,否则计算:\n有了上述定义,我们只需要在根节点调用算法得 $ decryptnode (e;d;r) = e(g,g)^{ys} = y^s$, 然后计算 $m=c\u0026rsquo;/y^s$。 正确性证明\n上述解密的过程是正确的,首先我们知道: $$\\begin{align} c\u0026rsquo; / \\prod_{i\\in s}e(d_i,c_i)^{l_i(0)} \u0026amp;= m\\cdot e(g,g)^{sy}/ \\prod_{i\\in s}e(g^{q_i/t_i},g^{s\\cdot t_i})^{l_i(0)} \\\\ \u0026amp;= m\\cdot e(g,g)^{sy}/ \\prod_{i\\in s}e(g,g)^{s\\cdot q(i)\\cdot l_i(0)} \\\\ \u0026amp;= m\\cdot \\frac{e(g,g)^{s\\cdot y}}{e(g,g)^{s\\cdot \\sum_{i=1}^{t} q(i)\\cdot l_i(0)}} \\\\ \u0026amp;= m \\end{align}$$\ndecryption中的解密过程实际上就是把上式中的 q(i) 作为其子节点的秘密值,通过嵌套秘密共享的方式依次恢复。\nlarge universe construction 在上述方案中,属性全集 $u=\\{1,2,\u0026hellip;,n\\}$, u 中每个元素 i 对应于一个属性,这导致需要额外存储一个 u 中元素与属性的对应关系表 rel。除此之外,上述方案中的公共参数的大小与属性集合的大小成正比。现在我们介绍一个优化的方案,使得属性全集 $u = z_p^* $,并且公共参数的大小只与描述一个实体需要的属性集大小有关。比如,根据\u0026quot;黑洞无毛定理\u0026quot;,描述一个静态黑洞只需要三个物理量(质量,角动量,电荷),那么优化后的abe只需要约为 3 个公共参数。除此之外,将 u 的范围扩展到 $z_p$ 之后,可以使用哈希函数 $h:{0,1}^* -\u0026gt; z_p $ 来将任意属性,包括字符串映射到 u 中,从而避免了存储 rel表。\nsetup:\n令属性全集为 $u = z_p$ 。生成 $y\\in_rz_p$ , 计算 $g_1=g^y,g_2\\in_r g_1$ 其中 g 是 $g_1$ 的生成元。 生成 n+1 个随机数 $t_1 \\in_r z_p ,\u0026hellip;,t_n,t_{n+1} \\in_r g_1$ , 令 $n = {1,2,\u0026hellip;,n+1}$ 定义函数 $t(x)=g_2^{x^n}\\cdot \\prod_{i=1}^{n+1}t_i^{l_i(x)}$ 则,系统私钥为 $msk = y$ , 系统公钥为 $mpk=(g_1,g_2,t_1,\u0026hellip;,t_{n+1})$. encryption:\n假设一个用户想要使用属性 $ w\u0026rsquo; = \\{ i_1,\u0026hellip;,i_n \\}_{i\\in u}$ 对消息 m 进行加密,那么她执行以下步骤:\n生成随机数 $s\\in_r z_p$ 并计算 $c\u0026rsquo;= m \\cdot e(g_1,g_2)^s$ , $c\u0026rsquo;\u0026rsquo;=g^s$ 和 $\\{ c_i = t(i)^s \\}_{i\\in {w\u0026rsquo;}}$ 令最终的密文 $c=(w\u0026rsquo;,c\u0026rsquo;,c\u0026rsquo;\u0026rsquo;,\\{c_i\\}_{i\\in w\u0026rsquo;})$ 。 keygen:\n该算法输出一个密钥,当且仅当 $t(γ) = 1$ 时,用户能够解密在一组属性 $γ$ 下加密的消息。算法的过程如下。首先为树 t 中的每个节点 x (包括叶子)选择一个多项式 $q_x$。从根节点 r 开始,以自顶向下的方式选择这些多项式。对于树中的每个节点x,设置随机多项式 $q_x$ 的阶数 $d_x$ 比该节点的阈值 $k_x$ 小 1,即 $d_x = k_x - 1$ 。现在,对于根节点 r,令 $q_r(0) = y$并随机选择多项式 $d_r$ 个随机数作为 $q_r(x)$的系数得到$q_r(x) = y + a_1x + \u0026hellip; + a_{d_r}x^{d_r}$。对于其他任意节点x,设 $q_x(0) = q_{parent(x)} (ind(x))$,随机选择其他 $d_x$ 个随机数,生成多项式 $q_x(x)$。\n一旦确定了多项式,对于每个叶节点 x,我们计算其对应属性的解密密钥:\n生成随机数 $r_x\\in_r z_p$ , 然后计算该节点对应的属性解密密钥 $r_x=g^{r_x}$ 和 $d_x=g_2^{q(0)}\\cdot t(i)^{r_x}$, 其中 $ i = att(x) $, 若某用户拥有一个属性,则将该属性对应的密钥 $(r_x,d_x)$ 分发给该用户。\ndecryption:\n解密和上面的同理,敲不动了,直接贴图:\n私钥委托 对于一个已有的访问控制树,可以对其进行以下类型的修改。详情参考原论文。之所以不重构一个访问控制树是因为对访问控制树进行以下修改的开销低于直接重构一颗树,并且拥有一定权限的人可以将权限收紧之后派发给其他人。\n1. 添加平凡门\n2. 修改(t,n)门到(t+1,n)门\n3. 修改(t,n)门到(t,n-1)门\n4. 修改(t,n)门到(t+1,n+1)门\n5. 密钥的重随机\n应用 审计日志\n定向广播加密\n","date":"2024-07-16","permalink":"http://54rookie.com/posts/%E5%9F%BA%E4%BA%8E%E5%B1%9E%E6%80%A7%E7%9A%84%E5%8A%A0%E5%AF%86/","summary":"ABE及其应用 什么是基于属性的加密 正式描述 基于属性加密(ABE)最先在 2005年 由 Waters 在论文《Fuzzy Identity-Based Encryption》中提出,并在2006年的论文中用于","title":"基于属性的加密"},]
[{"content":"秘密共享 什么是秘密共享 秘密共享是将秘密以适当的方式拆分,拆分后的每一个份额由不同的参与者管理,使得单个参与者无法恢复出秘密,只有若干个参与者一同协作才能恢复秘密。更重要的是,任何适量范围内的参与者丢失份额时,秘密仍可以完整恢复。秘密共享是一种将秘密分割存储的密码技术,目的是阻止秘密过于集中,以达到分散风险和容忍入侵的目的,是信息安全和数据保密中的重要手段。\n秘密共享分为两个阶段,秘密分发阶段和秘密恢复阶段。在密码分发阶段,秘密分发者将一个秘密拆分为 n 份分发给 n 个参与者,每一份被称为一个秘密份额。每个参与者不能根据自己的份额得知到秘密的信息。在秘密恢复阶段,至少 t 个用户合作才能恢复出秘密值。秘密共享的整体过程如下:\n秘密共享有什么用 秘密共享方案是存储高度敏感和高度重要信息的理想方案。比如:加密密钥、导弹发射代码、编号重要的银行账户等。这些信息的暴露可能是灾难性的;然而,同样重要的是,这些秘密不应该丢失,否则会导致严重后果。传统的加密方法不适合同时实现高机密性和高可靠性,因为在存储加密密钥时,必须考虑是\u0026quot;在一个位置保存密钥的单个副本\u0026quot;以获得最大的保密性,还是\u0026quot;在不同位置保存密钥的多个副本\u0026quot;以获得更高的可靠性。存储单个密钥副本存在密钥丢失的风险,存储多个副本增加了密钥泄露的可能、副本有更多的机会落入坏人之手。秘密共享方案解决了这个问题,并允许实现任意高级别的机密性和可靠性。\n秘密共享还允许秘密的分发者\u0026quot;总体上\u0026quot;信任一个组。传统上,将一个秘密交给一个小组保管需要分发者完全信任小组的所有成员。秘密共享方案允许分发者安全地将秘密存储在组中,即使不是所有成员都可以始终信任。只要叛徒的数量不超过重建秘密所需的临界数量,秘密就是安全的。\n秘密共享方案在云计算环境中非常重要。因此,可以通过阈值秘密共享机制将密钥分发到许多服务器上。然后在需要时重建密钥。\n秘密共享的安全性 一个安全的秘密共享方案分配份额,这样任何拥有少于 t 份份额的人都不会比拥有0份份额的人拥有更多关于秘密的信息。秘密共享方案应避免类似下述的秘密分发方法:\n例如,考虑一个秘密共享方案,其中秘密短语\u0026quot;password\u0026quot;被分成共享\u0026quot;pa\u0026quot;、\u0026ldquo;ss\u0026rdquo;、\u0026ldquo;wo\u0026quot;和\u0026quot;rd\u0026rdquo;。一个拥有0份份额的人只知道密码由8个字母组成,因此他必须从$2^{68}$ = 2080亿种可能的组合中猜测密码。然而,拥有一份的人只需要从$2^{66}$ = 3.08亿个组合中猜出6个字母,以此类推,随着更多的人串通。因此,这个系统不是一个\u0026quot;安全\u0026quot;的秘密共享方案,因为拥有少于t个秘密份额的玩家能够减少获得内部秘密的问题,而不需要首先获得所有必要的份额。\nshamir秘密共享 shamir秘密共享方案由adi shamir于1979年在论文《how to share a secret》中提出。shamir秘密共享方案主要基于拉格朗日插值公式,由于平面上的 t 个点可以唯一确定一个 t-1 阶的多项式,因此如果选择了 n \u0026gt; t 个点并分别分发给 n 个人, 那么其中的任意的 t 的人都可以使用拉格朗日插值恢复出这个多项式。 shamir正是利用这一点,将 t-1 阶多项式的常系数作为秘密值,从而实现了安全的秘密共享。\n作者简介 阿迪·沙米尔于1952年7月6日出生于以色列,著名的密码学家、数学家和发明家。他是(rsa)算法的发明者之一(rsa分别是rivest-shamir-adleman ),fiat-shamir启发式的发明者之一,差分密码分析的发明者之一,shamir秘密共享方案的提出者,为密码学和计算机领域做出许多贡献。曾获得图灵奖、沃尔夫数学奖、uap科学奖、日本奖、以色列国家奖、以色列数学学会奖等等。shamir是以色列科学院院士;国际密码学研究协会会士;美国国家科学院院士;欧洲科学院院士;法国科学院外籍院士;英国皇家学会外籍院士;美国艺术与科学院院士。(院士这么简单?)\nshamir秘密共享方案 预备知识 拉格朗日插值公式 简介:\n在数值分析中,拉格朗日插值法是以法国十八世纪数学家约瑟夫·拉格朗日命名的一种多项式插值方法。许多实际问题中都用函数来表示某种内在联系或规律,而不少函数都只能通过实验和观测来了解。如对实践中的某个物理量进行观测,在若干个不同的地方得到相应的观测值,拉格朗日插值法可以找到一个多项式,其恰好在各个观测的点取到观测到的值。这样的多项式称为拉格朗日(插值)多项式。数学上来说,拉格朗日插值法可以给出一个恰好穿过二维平面上若干个已知点的多项式函数。拉格朗日插值法最早被英国数学家爱德华·华林于1779年发现,不久后(1783年)由莱昂哈德·欧拉再次发现。1795年,拉格朗日在其著作《师范学校数学基础教程》中发表了这个插值方法,从此他的名字就和这个方法联系在一起。\n目的:\n若 $y=f(x)$ 在互不相同的 n 个点 $x_0,\u0026hellip;,x_{n-1}$ 处的函数值分别为 $y_0,\u0026hellip;,y_{n-1}$(即该函数过 这 n 个点),拉格朗日插值用于构造一个过这 n 个点的、次数不超过 n 的多项式 $y=l_n(x)$ ,使其满足: $$l_n(x_k)=y_k \\ (k=0,1,\u0026hellip;,n-1)$$ 拉格朗日证明了满足插值条件的、次数不超过n的多项式是存在而且是唯一的,并且给出了一种构造该函数的方法,我们称之为拉格朗日插值法,通过拉格朗日插值法构造出的多项式 $l_n(x)$ 也被称为拉格朗日多项式。\nlagrange插值法:\n假设在平面上有 n 个点 $(x_0,y_0),\u0026hellip;,(x_{n-1},y_{n-1})$,我们的目标是作一个函数 $f(x)$ 使得其图像经过这 n 个点。为了便于叙述构造 $f(x)$ 的过程,设集合 $d_n$={ 0,1,..,n-1} 是关于点 (x,y) 的角标的集合,$b_k$ = { $i | i\\neq k,i\\in d_n$ },且 $p_k(x)$ 定义如下:\n$$p_k(x)=\\prod_{i\\in b_k} \\frac{x-x_i}{x_k-x_i}$$\n可以看出 $p_k(x)$ 是 n-1 次多项式,且对于任意的 $m\\in b_k$ 有 $p_k(x_m)=0$ 且 $p_k(x_k)=1$ 。我们构造拉格朗日多项式 $l_n(x)$ 如下, 则 $l_n(x)$ 就是满足满足条件的最终函数 $f(x)$ 。 $$l_n(x)=\\sum_{j=0}^{n-1}y_j \\cdot p_i(x)$$\nshamir秘密共享方案 秘密分发\n秘密分发者首先随机生成 $ t - 2 $ 个小于 $ p $ 的随机数 $ a_{1}, a_{2},\u0026hellip;, a_{t - 1} $ 并构造一个 t-1 阶的多项式 $ f (x) = a_0 + a_{1} x + \u0026hellip; + a_{t-1} x^{t-1} \\pmod p $ , 其中 $ f (0) = a_{0} = s $ 是要保护的秘密, $ p $ 是一个大素数,且 s \u0026lt; p 。\n秘密分发者随机选取 $ n $ 个互不相同的整数 $ x_{1}, x_{2}, \u0026hellip; , x_{n} $ 并将 $ n $ 个整数代入多项式函数 $f(x)$得到 $ n $ 个值 $ y_1 = f (x_1)$, $ y_2 = f(x_2)$,\u0026hellip;, $y_{n} = f (x_{n}) $ , 然后将计算得到的 $ n $ 个坐标 $(x_i,y_i)$ 分别发给 $ n $ 各参与方并销毁 $ f (x) $,即第 $ i $ 个参与方获得 $ (x_{i}, y_{i}) $ 。\n秘密恢复\n收集到 t 个参与者的份额、也就是 t 个点的坐标之后,将得到的 t 个点利用拉格朗日插值法计算出拉格朗日插值多项式,也就是 $f(x)$ , 然后计算 $f(0)=s$ 得到秘密值。事实上,恢复秘密除了按照上述方式先求解出lagrange多项式 $f(x)$ 在求代入 $x = 0$ 求得 $s = f(x)$ 之外,还可以将求多项式和求 $f(0)$ 的过程合并,得到下面一个恢复秘密的公式: $$ s = \\sum_{i=0}^{n-1}( y_i \\cdot \\prod_{i=j}^{n-1} \\frac{x_i}{x_i-x_j} )$$\n一个简单例子 在秘密分发阶段\n以(3, 4)门限为例,假设秘密 $ s = 2 $,$ p = 23 $,构造 $ f (x) = 2x^{2} + 3x + 2 \\pmod {23} $ 。根据函数可知此处 $ t = 3 $,另取 $ x_{1} = 1$, $x_{2} = 2$, $x_{3} = 3$, $x_{4} = 4 $,代入函数得 $ f (1) = 7, f (2) = 16, f (3) = 6, f (4) = 0 $。将 $(1,7),(2,16),(3,6),(4,0)$ 作为份额分别分发给四个参与者。\n在秘密恢复阶段\n任意三个参与者可以恢复出秘密 s 。我们假设取得其中 3 组数据为 $ (1, 7) 、(3, 6)、(3, 0) $,使用拉格朗日插值公式进行恢复:\n$$ s = 7 \\times \\frac{(0 - 3) \\times (0 - 4)}{(1 - 3) \\times (1 - 4)} + 6 \\times \\frac{(0 - 1) \\times (0 - 4)}{(3 - 1) \\times (3 - 4)} + 0 \\times \\frac{(0 - 1) \\times (0 - 3)}{(4 - 1) \\times (4 - 3)} \\operatorname{mod}(23) $$\n经过上述计算成功恢复出秘密 $ s = 2 $\n其他秘密共享 blakley\u0026rsquo;s secret sharing 同一平面上的两条非平行线恰好相交于一点。空间中的三个不平行平面恰好相交于一点。更一般地说,任何n个非平行(n-1)维超平面在特定点相交。秘密可以被编码为交叉点的任何单个坐标。如果秘密是使用所有坐标编码的,即使它们是随机的,那么内部人员(拥有一个或多个(n-1)维超平面的人)就会获得有关秘密的信息,因为他知道秘密一定位于他的平面上。如果内部人员能够比外部人员获得更多关于秘密的知识,那么系统就不再具有信息理论安全性。如果仅使用n个坐标中的一个,那么内部人员只知道局外人(即,对于2维系统来说,秘密必须位于x轴上)。每个玩家都获得了足够的信息来定义超平面;通过计算平面的交叉点,然后获取该交叉点的指定坐标来恢复秘密。\nblakley的方案的空间效率低于shamir的方案;虽然shamir的份额仅与原始秘密一样大,但blakley的份额大t倍,其中t是玩家的阈值数量。布莱克利的计划可以通过增加对哪些飞机可用作股份的限制来收紧。由此产生的方案相当于shamir的多项系统。\n基于crt的ss 参考这个文章 秘密共享的发展 经过多年的发展,后人对秘密共享方案进行了很多扩充,这里对其进行简单的介绍,有兴趣的读者可以自行搜索相关论文进行查看。\n当有多个秘密需要共享时,需要为每个秘密都拆分出很多秘密份额,这会导致秘密份额的数量随着秘密的增多而快速增大,为了解决这个问题,有学者提出了多秘密共享方案,该方案可以一次共享多个秘密,并且每个参与者只需要存储一个秘密份额。\n虽然一个参与者无法通过自己的份额得知秘密的信息,但是他可以在恢复秘密的时候提供错误的份额,从而导致无法正确的恢复出秘密值。这在有些情况下是不可接收的,比如基于shamir秘密共享的分布式密钥生成过程中,如果参与者提供错误的份额分布式系统中的其他节点,将导致整个系统无法正确的协商出一个密钥。为了解决这个问题,有学者提出了可验证的秘密共享vss。在vss中,任何人可以验证参与者的份额是否合法。\n在传统的秘密共享中存在一个秘密分发者,这个人知道整个系统中所有参与者的份额,并且也知道秘密本身。这个人的存在一定程度上违背秘密共享的初衷。为此,有学者提出了无秘密分发者的秘密共享,解决了这个问题。\n虽然秘密共享中,敌手必须攻破一定数量的参与者之后才能得到足够的份额恢复秘密。但是密钥的长期存储难免会导致密钥的泄露,威胁秘密的安全。为此,有学者提出了主动式秘密共享的概念,可以动态的更新秘密份额,更好的保护秘密。\n拓展阅读\n秘密共享的一个重要应用是基于属性的加密(abe),基于abe提出的细粒度访问控制被认为是最有发展前景的细粒度访问控制方法。详细请参考文章《基于属性的加密》\n","date":"2024-07-15","permalink":"http://54rookie.com/posts/%E7%A7%98%E5%AF%86%E5%85%B1%E4%BA%AB/","summary":"秘密共享 什么是秘密共享 秘密共享是将秘密以适当的方式拆分,拆分后的每一个份额由不同的参与者管理,使得单个参与者无法恢复出秘密,只有若干个参与者一同协作才能恢复秘密","title":"秘密共享"},]
[{"content":"几个简单密码学算法的实现 文章给出了密码学初学时可能会遇到的算法实现,包括求最大公约数(最小公倍数lcm(a,b)=ab/gcd(a,b))、乘法逆元,快速模幂运算、miller-rabin素数判定等等。声明一下,只是实现了功能,保证了实践复杂度是我已知的最好的,但是并未做任何优化,仅供学习参考,计算速度跟专业的库相比应该有很大差距。\nimport java.math.bigdecimal; import java.math.biginteger; import java.util.hashmap; import java.util.map; import java.util.random; /** * @author miracle * @date 2022/8/29 18:01 * @description: \u0026lt;br/\u0026gt; * 我的工具类 */ public class main { /** * 将一个十进制的数字转化为指定进制,指定位数的数字符串(位数不够,前面补零) * 如: * -\t将 33 转化为长度为 8 的二进制字符串 * -\t则结果为: \u0026#34;00100001\u0026#34; * * @param number 带转换的十进制数字 * @param radix 将要转化为的进制数 * @param length 转化后字符串的长度 * @return 满足要求的字符串 */ public static string tostring(int number, int radix, int length) { stringbuilder str = new stringbuilder(); for (int i = 0; i \u0026lt; length; i++) { int bit = number % radix; str.insert(0, bit); number = number / radix; } return str.tostring(); } // 反转指定位置的字符串 public static string reversestr(string str, int start, int end) { char[] chars = str.tochararray(); while (start \u0026lt; end) { char temp = chars[start]; chars[start] = chars[end]; chars[end] = temp; start++; end--; } return new string(chars); } // 反转指定位置的对象数组 public static \u0026lt;t\u0026gt; t[] reversearray(t[] arr, int start, int end) { while (start \u0026lt; end) { t temp = arr[start]; arr[start] = arr[end]; arr[end] = temp; start++; end--; } return arr; } // 反转指定位置的字符串 public static int[] reverseint(int[] arr, int start, int end) { while (start \u0026lt; end) { int temp = arr[start]; arr[start] = arr[end]; arr[end] = temp; start++; end--; } return arr; } /** * 大整数的大整数幂对一个大整数求幂的结果(big integer power modulus) * * @param base 底数 * @param exponent 指数 * @param n 模数 * @return base的exponent次幂模n */ public static biginteger bigintegerpm(biginteger base, biginteger exponent, biginteger n) { // 保证底数大于模数 if (base.compareto(n) == -1) { base = base.mod(n); } biginteger res = solvepm(base, exponent, n); return res; } /** * 1、数论中有如下结论 * (a + b) % p = (a % p + b % p) % p * (a - b) % p = (a % p - b % p) % p * (a * b) % p = (a % p * b % p) % p * (a ^ b) % p = ((a % p) ^ b) % p * (a ^ b) % p = ((a % p) ^ b) % p * \u0026lt;p\u0026gt; * 2、分治法求解 * (1) 若 exponent 为奇数: 令 exponent = 2k+1, k 为非负整数 , 则 * a^(2k+1) = (a^k)^2 *a ; a^(2k+1) % n = ((a^k % n)^2 % n * a % n) % n = [ (a^k % n)^2 * a ] % n * (2) 若power 为偶数: 令 power = 2k, k为非负整数, 则 * a^(2k) = (a^k)^2 ; a^(2k) % n = (a^k % n)^2 % n * (3) 若 power == 0: 返回 1。 * \u0026lt;p\u0026gt; * 3、算法分析 * 时间复杂度 t(n) = o( log(n) ) (这里的n指的是 exponent) * 空间复杂度 t(n) = o( lon(n) ) (递归函数的空间开销) * * @param base 底数 * @param exponent 指数 * @param n 模数 * @return base的exponent次幂模n */ public static biginteger solvepm(biginteger base, biginteger exponent, biginteger n) { if (exponent == biginteger.zero) { return new biginteger(\u0026#34;1\u0026#34;); } if (exponent.mod(new biginteger(\u0026#34;2\u0026#34;)) == biginteger.zero) { biginteger two = new biginteger(\u0026#34;2\u0026#34;); base = solvepm(base, exponent.divide(two), n).pow(2); return base.mod(n); } else { biginteger two = new biginteger(\u0026#34;2\u0026#34;); base = solvepm(base, exponent.divide(two), n).pow(2) .multiply(base); return base.mod(n); } } /** * 欧几里得算法(辗转相除法)求两个数的最大公约数。(递归法) * 注意,第一参数a 必须大于等于第二个参数b * * @param a:较大的参数 * @param b:较小的参数 */ public static int gcdbyrecursive(int a, int b) { return b == 0 ? a : gcdbyrecursive(b, a % b); } /** * 欧几里得算法(辗转相除法)求两个数的最大公约数。(非递归法) * * @param a:参数 * @param b:参数 */ public static int gcd(int a, int b) { int r; if (a \u0026lt; b) { r = a; a = b; b = r; } while (b != 0) { r = a % b; a = b; b = r; } return a; } /** * 扩展的欧几里得算法求最大公约数 * 设 s0 = 1 ,s1 = 0 ; t0 = 0 , t1 = 1 ; * 则: * s(i+1) = s(i-1) - si * qi ; (i = 1,2,...,n) * t(i+1) = t(i-1) - ti * qi ; (i = 1,2,...,n) * 当 n\u0026gt;1 时,按照上述地推公式迭代求得 sn 和 tn * sn 和 tn 满足 a * sn + b * tn = gcd(a,b) * (这里 这里的 n 是用辗转相除法使得 a|b 是的辗转相除的次数。) * 例如 a = 10,b = 0时,辗转相除0次,n = 0; * *\ta = 20,b = 10时,辗转相除1次,n = 1; * *\ta = 15,b = 10时,辗转相除2次,n = 2; * 当 n = 0 时, s0 = 1,t0 = 0; * 当 n = 1 时, s1 = 0,t1 = 1; * 当 n \u0026gt; 1 时, sn,tn 由上述递推公式求得。 * * @param a 第一个参数 * @param b 第二个参数 * @return 返回一个含有三个元素的map,分别为 gcd(a,b)、s和t 其中s和t满足 as + bt = gcd(a,b) */ public static map\u0026lt;string, integer\u0026gt; gcdextend(int a, int b) { map\u0026lt;string, integer\u0026gt; res = new hashmap\u0026lt;\u0026gt;(); int r, r0 = a, r1 = b; // 迭代求解 s和t 使得 as + bt = gcd(a,b) 时用到的变量 int s0 = 1, t0 = 0; int s1 = 0, t1 = 1; int s = 1, t = 0; // 这么赋值是为了保证 s,t满足 as + bt = gcd(a,b) if (a \u0026lt; b) { r0 = b; r1 = a; s0 = 0; t0 = 1; s1 = 1; t1 = 0; } // 以下两种特殊情况处理 if (r1 == 0) { res.put(\u0026#34;gcd\u0026#34;, r0); res.put(\u0026#34;s\u0026#34;, 1); res.put(\u0026#34;t\u0026#34;, 0); return res; } if (r0 % r1 == 0) { res.put(\u0026#34;gcd\u0026#34;, r1); res.put(\u0026#34;s\u0026#34;, 0); res.put(\u0026#34;t\u0026#34;, 1); return res; } // 迭代求解 s和t 使得 as + bt = gcd(a,b) int qi = 0; while (r0 % r1 != 0) { qi = r0 / r1; s = s0 - s1 * qi; t = t0 - t1 * qi; s0 = s1; t0 = t1; s1 = s; t1 = t; r = r0 % r1; r0 = r1; r1 = r; } // 找到相应的 s和t 后 , 返回结果 res.put(\u0026#34;gcd\u0026#34;, a * s + b * t); res.put(\u0026#34;s\u0026#34;, s); res.put(\u0026#34;t\u0026#34;, t); return res; } /** * 针对大整数的扩展的欧几里得算法 * 时间复杂度t(n) = o(log n) * * @param a 任意长度的整数 * @param b 任意长度的整数 * @return a 和 b 的最大公约数以及 s 和 t组成的map,其中 key = \u0026#34;gcd\u0026#34; 的 value 是 gcd(a,b) */ public static map\u0026lt;string, biginteger\u0026gt; gcdextend(biginteger a, biginteger b) { map\u0026lt;string, biginteger\u0026gt; res = new hashmap\u0026lt;\u0026gt;(); biginteger r, r0 = a, r1 = b; biginteger s0 = biginteger.one; biginteger t0 = biginteger.zero; biginteger s1 = biginteger.zero; biginteger t1 = biginteger.one; biginteger s = biginteger.one; biginteger t = biginteger.zero; // 如果 a \u0026lt; b; 交换 a,b ; 保证 a \u0026gt;= b if (a.compareto(b) == -1) { r0 = b; r1 = a; s0 = biginteger.zero; t0 = biginteger.one; s1 = biginteger.one; t1 = biginteger.zero; } // 以下两种特殊情况处理 if (r1 == biginteger.zero) { res.put(\u0026#34;gcd\u0026#34;, r0); res.put(\u0026#34;s\u0026#34;, biginteger.one); res.put(\u0026#34;t\u0026#34;, biginteger.zero); return res; } if (a.mod(b) == biginteger.zero) { res.put(\u0026#34;gcd\u0026#34;, r1); res.put(\u0026#34;s\u0026#34;, biginteger.zero); res.put(\u0026#34;t\u0026#34;, biginteger.one); return res; } // 迭代求解 s和t 使得 as + bt = gcd(a,b) biginteger qi = biginteger.zero; while (r0.mod(r1) != biginteger.zero) { qi = r0.divide(r1); s = s0.subtract(s1.multiply(qi)); t = t0.subtract(t1.multiply(qi)); s0 = s1; t0 = t1; s1 = s; t1 = t; r = r0.mod(r1); r0 = r1; r1 = r; } // 找到相应的 s和t 后 , 返回结果 biginteger as = a.multiply(s); biginteger bt = b.multiply(t); res.put(\u0026#34;gcd\u0026#34;, as.add(bt)); res.put(\u0026#34;s\u0026#34;, s); res.put(\u0026#34;t\u0026#34;, t); return res; } /** * 求 a 在 模n 下的乘法逆元(扩展的欧几里得算法) * * @param a 求a的乘法逆元 * @param n 模数 * @return 存在乘法逆元时,返回 a 在 模n 下的乘法逆元。否则返回 -1 */ public static int getmultiplicativeinverse(int a, int n) { map\u0026lt;string, integer\u0026gt; map = gcdextend(a, n); if (map.get(\u0026#34;gcd\u0026#34;) == 1) { integer s = map.get(\u0026#34;s\u0026#34;); while (s \u0026lt; 0) { s = s + n; } while (s \u0026gt;= n) { s = s - n; } return s; } else { system.out.println(\u0026#34;getmultiplicativeinverse()提示您: \u0026#34; + a + \u0026#34;在模\u0026#34; + n + \u0026#34;下没有乘法逆元!\u0026#34;); return -1; } } /** * 求 a 在 模n 下的乘法逆元(扩展的欧几里得算法) * 这里 a 和 n 都是 biginteger 类型的 * * @param a 求a的乘法逆元 * @param n 模数 * @return 存在乘法逆元时,返回 a 在 模n 下的乘法逆元。否则返回 -1 */ public static biginteger getmultiplicativeinverse(biginteger a, biginteger n) { map\u0026lt;string, biginteger\u0026gt; map = gcdextend(a, n); if (map.get(\u0026#34;gcd\u0026#34;) .compareto(biginteger.one) == 0) { biginteger s = map.get(\u0026#34;s\u0026#34;); while (s.compareto(biginteger.zero) == -1) { s = s.add(n); } while (s.compareto(n) == 1) { s = s.subtract(n); } return s; } else { system.out.println(\u0026#34;getmultiplicativeinverse()提示您: \u0026#34; + a + \u0026#34;在模\u0026#34; + n + \u0026#34;下没有乘法逆元!\u0026#34;); return new biginteger(\u0026#34;-1\u0026#34;); } } /** * 返回int型数据的位数 * * @param number 数据 * @return 数据的位数 */ public static int bitofint(int number) { if (number \u0026lt; 0) { number = -number; } int[] sizetable = {9, 99, 999, 9999, 99999, 999999, 9999999, 99999999, 999999999, integer.max_value}; int i = 0; while (number \u0026gt; sizetable[i++]) ; return i; } /** * 牛顿下降法判断大整数是不是完全平方数(具体方法可参考 https://www.jianshu.com/p/91a69f585352) * * @param f7 带判断大整数 * @return 如果是完全平方数,返回平方根,否则返回0 */ public static boolean issquare(biginteger f7) { // 牛顿法求解平方根, 求解a的平方根 // x为a的平方根,x的初始值为1, 按x = (x+a/x)/2迭代, 误差为error,事实上,当error \u0026lt; 1时即可求出x平方根的整数部分 bigdecimal x = bigdecimal.one; bigdecimal a = new bigdecimal(f7.tostring()); bigdecimal eps = new bigdecimal(\u0026#34;1\u0026#34;); final bigdecimal error = new bigdecimal(\u0026#34;1e-10\u0026#34;); int scale = 100; // 进入循环 while (eps.compareto(error) \u0026gt; 0) { // eps \u0026gt; error是执行循环 x = x.add(a.divide(x, scale, bigdecimal.round_half_up)) .divide(new bigdecimal(\u0026#34;2.0\u0026#34;), scale, bigdecimal.round_half_up); eps = x.multiply(x) .subtract(a) .abs(); } biginteger sqrt = x.tobiginteger(); // 求平方根的整数部分 if (sqrt.pow(2) .compareto(f7) == 0) { return true; } else { return false; } } /** * 牛顿下降法对大整数开根号 * 由于开根号结果不一定是整数,这里的结果使用的直接抹掉小数部分,效果即为 floor(根号f7) * * @param f7 待开放的整数 * @return 对f7开放后,结果的整数部分 */ public static biginteger sqrt(biginteger f7) { // 牛顿法求解平方根, 求解a的平方根 // x为a的平方根,x的初始值为1, 按x = (x+a/x)/2迭代, 误差为error,事实上,当error \u0026lt; 1时即可求出x平方根的整数部分 bigdecimal x = bigdecimal.one; bigdecimal a = new bigdecimal(f7.tostring()); bigdecimal eps = new bigdecimal(\u0026#34;1\u0026#34;); final bigdecimal error = new bigdecimal(\u0026#34;1e-10\u0026#34;); int scale = 100; // 进入循环 while (eps.compareto(error) \u0026gt; 0) { // eps \u0026gt; error是执行循环 x = x.add(a.divide(x, scale, bigdecimal.round_half_up)) .divide(new bigdecimal(\u0026#34;2.0\u0026#34;), scale, bigdecimal.round_half_up); eps = x.multiply(x) .subtract(a) .abs(); } biginteger sqrt = x.tobiginteger(); // 求平方根的整数部分 return sqrt; } /** * 产生一个范围在 [start,end]之间的随机大整数 * * @param start 随机数的最小值 * @param end 随机数的最大值 * @return 返回一个范围在[start, end]之间的随机大整数, 如果start\u0026lt;end , 返回null */ public static biginteger random(biginteger start, biginteger end) { if (start.compareto(end) \u0026gt; 0) { return null; } if (start.compareto(end) == 0) { return end; } biginteger a; do { a = new biginteger((end.subtract(start)).bitlength(), new random()); } while (a.compareto(end.subtract(start)) \u0026gt; 0); a = a.add(start); return a; } /** * 产生一个范围在 [start,end]之间的随机数 * * @param start 随机数的最小值 * @param end 随机数的最大值 * @return 返回一个范围在[start, end]之间的随机大整数, 如果start\u0026lt;end , 返回-1 */ public static double random(double start, double end) { if (start \u0026gt; end) { return -1; } if (start == end) { return end; } double res; random random = new random(); return start + random.nextdouble() * (end - start); } /** * 产生一个范围在 [start,end]之间的随机数 * * @param start 随机数的最小值 * @param end 随机数的最大值 * @return 返回一个范围在[start, end]之间的随机整数, 如果start\u0026lt;end , 返回-1 */ public static int random(int start, int end) { if (start \u0026gt; end) { int temp = start; start = end; end = temp; } random random = new random(); return start + random.nextint(end - start + 1); } /** * miller-rabin素性判定 * 算法思路 * 设n是一个奇数,n-1 = 2^t * m,其中m是一个奇数。要测试n是否为素数, * ① 先随机选择一个介于 [2,n-2]区间内的整数a,如果a^m ≠ 1 且 * ② 对所有的满足 r ∈ [0,k-1] 的整数有,a^(2^r * m) ≠ -1 , 则有n一定是合数。否则,n有很大可能是素数。 * (目前可以在数学上证明,miller-rabin将合数判定为素数的概率小于1/4,而事实上经验告诉我们,概率比1/4小得多) * \u0026lt;p\u0026gt; * 时间复杂度:\tt(n) = o(log n) * * @param n 待判定数字 * @param k 迭代的轮数,迭代k次后,将合数判定为素数的概率为 1/4^k * @return 是素数返回true, 是合数返回false。注意返回false一定是合数,返回true,有1/4^k 的概率是合数 */ public static boolean isprime_miller_rabin(biginteger n, int k) { if (n.compareto(biginteger.one) \u0026lt;= 0) { return false; } // 特殊情况,n为2或3时,mytools.random返回为null if (n.compareto(new biginteger(\u0026#34;2\u0026#34;)) == 0 || n.compareto(new biginteger(\u0026#34;3\u0026#34;)) == 0) { return true; } // miller-rabin素性判定 biginteger two = new biginteger(\u0026#34;2\u0026#34;); biginteger m = n.subtract(biginteger.one); int t = 0; while (m.mod(two) == biginteger.zero) { m = m.divide(two); t++; } for (int i = 0; i \u0026lt; k; i++) { biginteger a = random(two, n.subtract(two)); if (bigintegerpm(a, m, n) .compareto(biginteger.one) != 0) { int r; for (r = 0; r \u0026lt;= t - 1; r++) { if (bigintegerpm(a, two.pow(r) .multiply(m), n) .subtract(n) .compareto(new biginteger(\u0026#34;-1\u0026#34;)) == 0) { break; } } if (r == t) { return false; } } } return true; } // 获得miller-rabin素数检测的检测次数 private static int gettrytime(biginteger n) { //copy自jdk源码, n的bit数越多, 需要的miller-rabin检测次数就越少 int sizeinbits = n.bitlength(); int trytime = 0; if (sizeinbits \u0026lt; 100) { trytime = 50; } else if (sizeinbits \u0026lt; 256) { trytime = 27; } else if (sizeinbits \u0026lt; 512) { trytime = 15; } else if (sizeinbits \u0026lt; 768) { trytime = 8; } else if (sizeinbits \u0026lt; 1024) { trytime = 4; } else { trytime = 2; } return trytime; } /** * 判断一个数是否是素数 * 当 num 较小时,使用查询素数表和查看num是不是小素数的倍数的方法对num进行素性判定 * 当 使用上述方法无法判断 num 的素性时,采用 miller-rabin进行k轮素性判定,其中k的值根据num的大小不同而动态求出。 * * @param num 待判断的数 * @return 素数返回true,合数返回false */ public static boolean isprime(biginteger num) { // # 创建小素数的列表,可以大幅加快速度 int[] small_primes = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997}; if (num.compareto(biginteger.valueof(long.max_value)) \u0026lt; 0) { long n = num.longvalue(); // # 如果是小素数,那么直接返回true for (int i = 0; i \u0026lt; small_primes.length; i++) { if (n == small_primes[i]) { return true; } } // #如果大数是这些小素数的倍数,那么就是合数,返回false for (int i = 0; i \u0026lt; small_primes.length; i++) { if (n % small_primes[i] == 0) { return false; } } } // #如果这样没有分辨出来,就一定是大整数,那么就调用rabin算法 return isprime_miller_rabin(num, gettrytime(num)); } public static void main(string[] args) { // system.out.println(tostring(33,33,8)); // integer[] arr = new integer[5]; // for(int i = 0; i \u0026lt; 5; i++){ // arr[i] = i; // } // integer[] res = reversearray(arr,0,arr.length - 1); // for(integer re : res){ // system.out.println(re); // } // integer a = 5; // system.out.println(a); // system.out.println(bitofint(1000)); // system.out.println(mytools.getmultiplicativeinverse(7467,11200)); // system.out.println(mytools.bigintegerpm(new biginteger(\u0026#34;5859\u0026#34;),new biginteger(\u0026#34;3\u0026#34;),new biginteger(\u0026#34;11413\u0026#34;))); // for(int i = 0; i \u0026lt; 100; i++){ // system.out.print(random(biginteger.ten,biginteger.ten.pow(2)) + \u0026#34;,\u0026#34;); // } // for(int i = 2; i \u0026lt; 300; i++){ // system.out.println(isprime_miller_rabin(biginteger.valueof(i),10)); // } system.out.println(99 % 13); random random = new random(); int bitlength = 180; biginteger randominteger = new biginteger(bitlength, random); biginteger biginteger = new biginteger(randominteger.tostring()); boolean isprime = biginteger.isprobableprime(100); while (!isprime) { biginteger = biginteger.add(biginteger.one); isprime = biginteger.isprobableprime(100); } system.out.println(\u0026#34;生成的大素数:\u0026#34; + biginteger); } }\r","date":"2024-07-14","permalink":"http://54rookie.com/posts/crypttools/","summary":"几个简单密码学算法的实现 文章给出了密码学初学时可能会遇到的算法实现,包括求最大公约数(最小公倍数lcm(a,b)=ab/gcd(a,b))、乘法逆元,快速模幂运","title":"crypttools"},]
[{"content":"elgamal 文章给出了elgamal加密算法的一个实现。未做任何优化,仅供学习参考,计算速度跟专业的库相比应该有很大差距。虽然密码学算法是安全的,但是工程实现中可能存在其他攻击,没有足够的信心,不要使用自己写的加密算法做加密。\nimport java.math.bigdecimal; import java.math.biginteger; import java.util.*; /** * @author miracle * @date 2022/9/21 19:06 * @description: \u0026lt;br/\u0026gt; */ public class main { // --------------------------------------------------- 工具函数 ------------------------------------------------------------------- /** * 大整数的大整数幂对一个大整数求幂的结果(big integer power modulus) * * @param base 底数 * @param exponent 指数 * @param n 模数 * @return base的exponent次幂模n */ public static biginteger bigintegerpm(biginteger base,biginteger exponent,biginteger n){ // 保证底数大于模数 if(base.compareto(n) == -1){ base = base.mod(n); } biginteger res = solvepm(base,exponent,n); return res; } /** * 1、数论中有如下结论 * (a + b) % p = (a % p + b % p) % p * (a - b) % p = (a % p - b % p) % p * (a * b) % p = (a % p * b % p) % p * (a ^ b) % p = ((a % p) ^ b) % p * (a ^ b) % p = ((a % p) ^ b) % p * \u0026lt;p\u0026gt; * 2、分治法求解 * (1) 若 exponent 为奇数: 令 exponent = 2k+1, k 为非负整数 , 则 * a^(2k+1) = (a^k)^2 *a ; a^(2k+1) % n = ((a^k % n)^2 % n * a % n) % n = [ (a^k % n)^2 * a ] % n * (2) 若power 为偶数: 令 power = 2k, k为非负整数, 则 * a^(2k) = (a^k)^2 ; a^(2k) % n = (a^k % n)^2 % n * (3) 若 power == 0: 返回 1。 * \u0026lt;p\u0026gt; * 3、算法分析 * 时间复杂度 t(n) = o( log(n) ) (这里的n指的是 exponent) * 空间复杂度 t(n) = o( lon(n) ) (递归函数的空间开销) * * @param base 底数 * @param exponent 指数 * @param n 模数 * @return base的exponent次幂模n */ public static biginteger solvepm(biginteger base,biginteger exponent,biginteger n){ if(exponent == biginteger.zero){ return new biginteger(\u0026#34;1\u0026#34;); } if(exponent.mod(new biginteger(\u0026#34;2\u0026#34;)) == biginteger.zero){ biginteger two = new biginteger(\u0026#34;2\u0026#34;); base = solvepm(base,exponent.divide(two),n).pow(2); return base.mod(n); } else{ biginteger two = new biginteger(\u0026#34;2\u0026#34;); base = solvepm(base,exponent.divide(two),n).pow(2) .multiply(base); return base.mod(n); } } /** * 产生一个范围在 [start,end]之间的随机大整数 * * @param start 随机数的最小值 * @param end 随机数的最大值 * @return 返回一个范围在[start,end]之间的随机大整数,如果start\u0026lt;end , 返回null */ public static biginteger random(biginteger start,biginteger end){ if(start.compareto(end) \u0026gt; 0){ return null; } if(start.compareto(end) == 0){ return end; } biginteger a; do{ a = new biginteger((end.subtract(start)).bitlength(),new random()); } while(a.compareto(end.subtract(start)) \u0026gt; 0); a = a.add(start); return a; } /** * 针对大整数的扩展的欧几里得算法 * 时间复杂度t(n) = o(log n) * * @param a 任意长度的整数 * @param b 任意长度的整数 * @return a 和 b 的最大公约数以及 s 和 t组成的map,其中 key = \u0026#34;gcd\u0026#34; 的 value 是 gcd(a,b) */ public static map\u0026lt;string,biginteger\u0026gt; gcdextend(biginteger a,biginteger b){ map\u0026lt;string,biginteger\u0026gt; res = new hashmap\u0026lt;\u0026gt;(); biginteger r, r0 = a, r1 = b; biginteger s0 = biginteger.one; biginteger t0 = biginteger.zero; biginteger s1 = biginteger.zero; biginteger t1 = biginteger.one; biginteger s = biginteger.one; biginteger t = biginteger.zero; // 如果 a \u0026lt; b; 交换 a,b ; 保证 a \u0026gt;= b if(a.compareto(b) == -1){ r0 = b; r1 = a; s0 = biginteger.zero; t0 = biginteger.one; s1 = biginteger.one; t1 = biginteger.zero; } // 以下两种特殊情况处理 if(r1 == biginteger.zero){ res.put(\u0026#34;gcd\u0026#34;,r0); res.put(\u0026#34;s\u0026#34;,biginteger.one); res.put(\u0026#34;t\u0026#34;,biginteger.zero); return res; } if(a.mod(b) == biginteger.zero){ res.put(\u0026#34;gcd\u0026#34;,r1); res.put(\u0026#34;s\u0026#34;,biginteger.zero); res.put(\u0026#34;t\u0026#34;,biginteger.one); return res; } // 迭代求解 s和t 使得 as + bt = gcd(a,b) biginteger qi = biginteger.zero; while(r0.mod(r1) != biginteger.zero){ qi = r0.divide(r1); s = s0.subtract(s1.multiply(qi)); t = t0.subtract(t1.multiply(qi)); s0 = s1; t0 = t1; s1 = s; t1 = t; r = r0.mod(r1); r0 = r1; r1 = r; } // 找到相应的 s和t 后 , 返回结果 biginteger as = a.multiply(s); biginteger bt = b.multiply(t); res.put(\u0026#34;gcd\u0026#34;,as.add(bt)); res.put(\u0026#34;s\u0026#34;,s); res.put(\u0026#34;t\u0026#34;,t); return res; } /** * 求 a 在 模n 下的乘法逆元(扩展的欧几里得算法) * 这里 a 和 n 都是 biginteger 类型的 * * @param a 求a的乘法逆元 * @param n 模数 * @return 存在乘法逆元时,返回 a 在 模n 下的乘法逆元。否则返回 -1 */ public static biginteger getmultiplicativeinverse(biginteger a,biginteger n){ map\u0026lt;string,biginteger\u0026gt; map = gcdextend(a,n); if(map.get(\u0026#34;gcd\u0026#34;) .compareto(biginteger.one) == 0){ biginteger s = map.get(\u0026#34;s\u0026#34;); while(s.compareto(biginteger.zero) == -1){ s = s.add(n); } while(s.compareto(n) == 1){ s = s.subtract(n); } return s; } else{ system.out.println(\u0026#34;getmultiplicativeinverse()提示您: \u0026#34; + a + \u0026#34;在模\u0026#34; + n + \u0026#34;下没有乘法逆元!\u0026#34;); return new biginteger(\u0026#34;-1\u0026#34;); } } /** * 返回int型数据的位数 * * @param number 数据 * @return 数据的位数 */ public static int bitofint(int number){ if(number \u0026lt; 0){ number = -number; } int[] sizetable = {9,99,999,9999,99999,999999,9999999,99999999,999999999,integer.max_value}; int i = 0; while(number \u0026gt; sizetable[i++]) ; return i; } /** * miller-rabin素性判定 * 算法思路 * 设n是一个奇数,n-1 = 2^t * m,其中m是一个奇数。要测试n是否为素数, * ① 先随机选择一个介于 [2,n-2]区间内的整数a,如果a^m ≠ 1 且 * ② 对所有的满足 r ∈ [0,k-1] 的整数有,a^(2^r * m) ≠ -1 , 则有n一定是合数。否则,n有很大可能是素数。 * (目前可以在数学上证明,miller-rabin将合数判定为素数的概率小于1/4,而事实上经验告诉我们,概率比1/4小得多) * \u0026lt;p\u0026gt; * 时间复杂度:\tt(n) = o(log n) * * @param n 待判定数字 * @param k 迭代的轮数,迭代k次后,将合数判定为素数的概率为 1/4^k * @return 是素数返回true,是合数返回false。注意返回false一定是合数,返回true,有1/4^k 的概率是合数 */ public static boolean isprime_miller_rabin(biginteger n,int k){ if(n.compareto(biginteger.one) \u0026lt;= 0){ return false; } // 特殊情况,n为2或3时,mytools.random返回为null if(n.compareto(new biginteger(\u0026#34;2\u0026#34;)) == 0 || n.compareto(new biginteger(\u0026#34;3\u0026#34;)) == 0){ return true; } // miller-rabin素性判定 biginteger two = new biginteger(\u0026#34;2\u0026#34;); biginteger m = n.subtract(biginteger.one); int t = 0; while(m.mod(two) == biginteger.zero){ m = m.divide(two); t++; } for(int i = 0; i \u0026lt; k; i++){ biginteger a = random(two,n.subtract(two)); if(bigintegerpm(a,m,n) .compareto(biginteger.one) != 0){ int r; for(r = 0; r \u0026lt;= t - 1; r++){ if(bigintegerpm(a,two.pow(r) .multiply(m),n) .subtract(n) .compareto(new biginteger(\u0026#34;-1\u0026#34;)) == 0){ break; } } if(r == t){ return false; } } } return true; } // 获得miller-rabin素数检测的检测次数 private static int gettrytime(biginteger n){ //copy自jdk源码, n的bit数越多, 需要的miller-rabin检测次数就越少 int sizeinbits = n.bitlength(); int trytime = 0; if(sizeinbits \u0026lt; 100){ trytime = 50; } else if(sizeinbits \u0026lt; 256){ trytime = 27; } else if(sizeinbits \u0026lt; 512){ trytime = 15; } else if(sizeinbits \u0026lt; 768){ trytime = 8; } else if(sizeinbits \u0026lt; 1024){ trytime = 4; } else{ trytime = 2; } return trytime; } /** * 判断一个数是否是素数 * 当 num 较小时,使用查询素数表和查看num是不是小素数的倍数的方法对num进行素性判定 * 当 使用上述方法无法判断 num 的素性时,采用 miller-rabin进行k轮素性判定,其中k的值根据num的大小不同而动态求出。 * * @param num 待判断的数 * @return 素数返回true,合数返回false */ public static boolean isprime(biginteger num){ // # 创建小素数的列表,可以大幅加快速度 int[] small_primes = {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997}; if(num.compareto(biginteger.valueof(long.max_value)) \u0026lt; 0){ long n = num.longvalue(); // # 如果是小素数,那么直接返回true for(int i = 0; i \u0026lt; small_primes.length; i++){ if(n == small_primes[i]){ return true; } } // #如果大数是这些小素数的倍数,那么就是合数,返回false for(int i = 0; i \u0026lt; small_primes.length; i++){ if(n % small_primes[i] == 0){ return false; } } } // #如果这样没有分辨出来,就一定是大整数,那么就调用rabin算法 return isprime_miller_rabin(num,gettrytime(num)); } /** * 牛顿下降法对大整数开根号 * 由于开根号结果不一定是整数,这里的结果使用的直接抹掉小数部分,效果即为 floor(根号f7) * * @param f7 待开放的整数 * @return 对f7开放后,结果的整数部分 */ public static biginteger sqrt(biginteger f7){ // 牛顿法求解平方根, 求解a的平方根 // x为a的平方根,x的初始值为1, 按x = (x+a/x)/2迭代, 误差为error,事实上,当error \u0026lt; 1时即可求出x平方根的整数部分 bigdecimal x = bigdecimal.one; bigdecimal a = new bigdecimal(f7.tostring()); bigdecimal eps = new bigdecimal(\u0026#34;1\u0026#34;); final bigdecimal error = new bigdecimal(\u0026#34;1e-10\u0026#34;); int scale = 100; // 进入循环 while(eps.compareto(error) \u0026gt; 0){ // eps \u0026gt; error是执行循环 x = x.add(a.divide(x,scale,bigdecimal.round_half_up)) .divide(new bigdecimal(\u0026#34;2.0\u0026#34;),scale,bigdecimal.round_half_up); eps = x.multiply(x) .subtract(a) .abs(); } biginteger sqrt = x.tobiginteger(); // 求平方根的整数部分 return sqrt; } // --------------------------------------------------- 加解密函数 ------------------------------------------------------------------- /** * elgamal加密 * * @param m 密文 * @param p 随机选择的大素数 * @param alpha p的一个本原根 * @param beta 公钥 * @return 返回加密后的密文 */ private static map\u0026lt;string,biginteger\u0026gt; encryptcore(biginteger m,biginteger p,biginteger alpha,biginteger beta){ hashmap\u0026lt;string,biginteger\u0026gt; res = new hashmap\u0026lt;\u0026gt;(); biginteger two = new biginteger(\u0026#34;2\u0026#34;); // 1. 产生随机数k biginteger gcd, k; while(true){ k = random(two,p.subtract(two)); gcd = gcdextend(k,p.subtract(biginteger.one)) .get(\u0026#34;gcd\u0026#34;); if(gcd.equals(biginteger.one)){ break; } } // biginteger k = new biginteger(\u0026#34;853\u0026#34;);// 书上的例子选用的随机数 biginteger r = bigintegerpm(alpha,k,p); biginteger t = (bigintegerpm(beta,k,p)).multiply(m) .mod(p); res.put(\u0026#34;r\u0026#34;,r); res.put(\u0026#34;t\u0026#34;,t); return res; } /** * elgamal解密 * * @param r 密文 * @param t 密文 * @param x 私钥 * @param p 大素数p(公钥) * @return 返回解密后的明文 */ private static biginteger decryptcore(biginteger r,biginteger t,biginteger x,biginteger p){ biginteger m; biginteger r_ = getmultiplicativeinverse(r,p); m = (t.multiply(bigintegerpm(r_,x,p))).mod(p); return m; } // --------------------------------------------------- dsa签名 ------------------------------------------------------------------- // --------------------------------------------------- 加密解密函数 ------------------------------------------------------------------- /** * 加密字符串 * 可以将字符换转化为byte数组。将byte数组的数字划为三位数,不够三位的,首位补 9(这样可以恢复) ,将byte拼接为字符串,转化为biginteger * 随机生成一个数k,且k一与n-1互素 * 计算 r = alpha^k (mod p) , t = beta^k * m * 密文为 c = (r,t) * \u0026lt;p\u0026gt; * 注意: * -\t这里的字符串长度不能太长,必须保证字符串转化为的byte数组组成的biginteger 小于 p , 否则无法解密 * * @param plaintext 明文字符串 * @param p 公钥:模数 * @param alpha 公钥:本原根 * @param beta 公钥 * @return 加密后的字符串,r和t之间使用 \u0026#34;\\n\u0026#34; 隔开,以便于解密时解析 */ private static string encrypt(string plaintext,string p,string alpha,string beta){ byte[] bytes = plaintext.getbytes(); int[] ints = new int[bytes.length]; int i; for(i = 0; i \u0026lt; bytes.length; i++){ ints[i] = bytes[i]; } stringbuilder builder = new stringbuilder(); // 目前只能小于 1024 i = 0; while(i \u0026lt; ints.length){ if(bitofint(ints[i]) == 2){ ints[i] += 900; } builder.append(ints[i++]); } // system.out.println(\u0026#34;plain = \u0026#34; + builder.tostring()); biginteger m = new biginteger(builder.tostring()); map\u0026lt;string,biginteger\u0026gt; cipher = encryptcore(m,new biginteger(p),new biginteger(alpha),new biginteger(beta)); return cipher.get(\u0026#34;r\u0026#34;) .tostring() + \u0026#34;\\n\u0026#34; + cipher.get(\u0026#34;t\u0026#34;) .tostring(); } /** * elgamal解密 * 先解析加密后的字符串,分离 r,t 然后求 p = t*r^(-x) (mod p)恢复明文的biginteger。 * 然后将解析p,即每三位作为一个十进制的数字,如果第一位为9,则将其去掉,否则这三位不做改变。 * 将上述数字转化为字符,然后拼接成字符串即可恢复明文 * * @param ciphertext 密文 * @param x 私钥 p * @param p 私钥 q * @return 解密后的明文字符串 */ private static string decrypt(string ciphertext,string x,string p){ stringbuilder res = new stringbuilder(); stringbuilder t = new stringbuilder(); stringbuilder r = new stringbuilder(); int i; for(i = 0; i \u0026lt; ciphertext.length(); i++){ if(ciphertext.charat(i) == \u0026#39;\\n\u0026#39;){ for(int j = i + 1; j \u0026lt; ciphertext.length(); j++){ t.append(ciphertext.charat(j)); } break; } r.append(ciphertext.charat(i)); } biginteger rr = new biginteger(r.tostring()); biginteger tt = new biginteger(t.tostring()); biginteger xx = new biginteger(x); biginteger pp = new biginteger(p); string plaintext = decryptcore(rr,tt,xx,pp).tostring(); for(i = 0; i \u0026lt; plaintext.length(); i = i + 3){ stringbuilder builder = new stringbuilder(); builder.append(plaintext.charat(i)); builder.append(plaintext.charat(i + 1)); builder.append(plaintext.charat(i + 2)); int integer = new integer(builder.tostring()); if(integer / 100 == 9){ integer -= 900; } char temp = (char) integer; res.append(temp); } return res.tostring(); } // --------------------------------------------------- 测试函数 ------------------------------------------------------------------- private void testelgamalcore(){ system.out.println(\u0026#34;----------------------- 测试elgamal算法的核心加密函数 ----------------------------\u0026#34;); biginteger m = new biginteger(\u0026#34;2199\u0026#34;); biginteger p = new biginteger(\u0026#34;2579\u0026#34;); biginteger alpha = new biginteger(\u0026#34;2\u0026#34;); biginteger x = new biginteger(\u0026#34;765\u0026#34;); biginteger beta = bigintegerpm(alpha,x,p); map\u0026lt;string,biginteger\u0026gt; cipher = encryptcore(m,p,alpha,beta); system.out.println(cipher); biginteger plaintext = decryptcore(cipher.get(\u0026#34;r\u0026#34;),cipher.get(\u0026#34;t\u0026#34;),x,p); system.out.println(plaintext); } private void testelgamal(){ system.out.println(\u0026#34;----------------------- 测试elgamal算法加密字符串 ----------------------------\u0026#34;); string plain = \u0026#34;i love you!\u0026#34;; map\u0026lt;string,biginteger\u0026gt; key = getkey(35,new biginteger(\u0026#34;521\u0026#34;)); biginteger p = key.get(\u0026#34;p\u0026#34;); biginteger alpha = key.get(\u0026#34;alpha\u0026#34;); biginteger x = key.get(\u0026#34;x\u0026#34;); biginteger beta = key.get(\u0026#34;beta\u0026#34;); string encrypt = encrypt(plain,p.tostring(),alpha.tostring(),beta.tostring()); system.out.println(\u0026#34;encrypt = \u0026#34; + encrypt); string decrypt = decrypt(encrypt,x.tostring(),p.tostring()); system.out.println(\u0026#34;decrypt = \u0026#34; + decrypt); } private void testgeneratekey(){ system.out.println(\u0026#34;----------------------- 测试密钥生成算法 ----------------------------\u0026#34;); biginteger x = new biginteger(\u0026#34;521\u0026#34;); map\u0026lt;string,biginteger\u0026gt; key = getkey(4,x); system.out.println(key); system.out.println(\u0026#34;----------------------- 测试指定位数的素数生成器 ----------------------------\u0026#34;); for(int i = 0; i \u0026lt; 10; i++){ system.out.print(getprime(4) + \u0026#34;,\u0026#34;); } system.out.println(); } // --------------------------------------------------- 密钥生成 ------------------------------------------------------------------- /** * 随机大素数的生成 * 生成一个随机的 bitcount位的大素数 * 注意:可使用 biginteger.probableprime() 方法生成大素数,速度比这个函数速度快10倍以上 * * @param bitcount 生成素数的在十进制下占的位数 * @return 随机的bitcount位的大素数 */ private static biginteger getprime(int bitcount){ //随机生成一个n bit的大整数 biginteger start = biginteger.ten.pow(bitcount - 1); biginteger end = biginteger.ten.pow(bitcount) .subtract(biginteger.one); biginteger init = random(start,end); //如果n为偶数, 则加一变为奇数 if(!init.testbit(0)){ init = init.setbit(0); } //基于素数定理, 平均只需要不到n次搜索, 就能找到一个素数 while(!isprime(init)){ init = init.add(biginteger.valueof(2)); } return init; } /** * 生成elgamal算法中的公钥 * 1. 步骤: * 第一步:取一个随机的素数q,并且这个素数满足p = 2q + 1也是一个素数 * 第二步:随机取一个数g(在[2,p - 2]之间),判断g^2 mod p 与 g ^q mod p是否为1;只要有一个为1,说明g不是p的原根,就重新取随机数 * \u0026lt;p\u0026gt; * 2. 参考文献: * https://blog.csdn.net/yibaobao2019/article/details/110429353 * * @param bitcount 公钥中,大素数p的十进制位数 * @param x 私钥 * @return 返回一个map{ \u0026#34;p\u0026#34;,大素数p; \u0026#34;alpha\u0026#34;,p的一个原根; \u0026#34;beta\u0026#34;,alpha^x(mod p) 其中x为私钥; \u0026#34;x\u0026#34;,私钥 } */ private static map\u0026lt;string,biginteger\u0026gt; getkey(int bitcount,biginteger x){ hashmap\u0026lt;string,biginteger\u0026gt; res = new hashmap\u0026lt;\u0026gt;(); biginteger p; biginteger two = new biginteger(\u0026#34;2\u0026#34;); while(true){ biginteger q = getprime(bitcount); p = q.multiply(two) .add(biginteger.one); if(!isprime(p)){ continue; } biginteger alpha = random(two,p.subtract(two)); if(bigintegerpm(alpha,two,p) .equals(biginteger.one) || bigintegerpm(alpha,q,p) .equals(biginteger.one)){ continue; } else{ res.put(\u0026#34;p\u0026#34;,p); res.put(\u0026#34;alpha\u0026#34;,alpha); res.put(\u0026#34;beta\u0026#34;,bigintegerpm(alpha,x,p)); res.put(\u0026#34;x\u0026#34;,x); return res; } } } // --------------------------------------------------- dsa攻击 ------------------------------------------------------------------- /** * 大步小步算法(shank算法, baby-step giant-step algorithm) * 给出elgamal算法的公钥,求解私钥 x * ① babystep 中存储的是 ( beta * alpha^r , r ) * ② giantstep 中存储的是 ( alpha^(t*s) , t ) * 当第一个左边相同时,即 beta * alpha^r = alpha^(t*s) * 将 beta = alpha^x 代入得到: alpha^(x+r) = alpha^(t*s) * 即 x + r = t * s * 所以 x = t*s - r * \u0026lt;p\u0026gt; * 时间复杂度 t(n) = o(n^0.5) (事实上求解大的整数幂复杂度为log n,因此t(n)实际大概为 o( n^0.5 * log n^0.5 ) ) * 空间复杂度 s(n) 大概为 o( n^0.5 * log n^0.5 ) * * @param alpha 公钥 * @param beta 公钥 * @param p 公钥 * @return 私钥 */ private biginteger shanks(biginteger alpha,biginteger beta,biginteger p){ hashmap\u0026lt;biginteger,biginteger\u0026gt; babystep = new hashmap\u0026lt;\u0026gt;(); hashmap\u0026lt;biginteger,biginteger\u0026gt; giantstep = new hashmap\u0026lt;\u0026gt;(); biginteger s = sqrt(p); // 计算大步小步的值 for(biginteger r = biginteger.zero; r.compareto(s.subtract(biginteger.one)) \u0026lt;= 0; r = r.add(biginteger.one)){ biginteger babykey = beta.multiply(bigintegerpm(alpha,r,p)) .mod(p); babystep.put(babykey,r); biginteger t = r.add(biginteger.one); biginteger giantkey = bigintegerpm(alpha,t.multiply(s),p); giantstep.put(giantkey,t); } // system.out.println(babystep); // system.out.println(giantstep); // 找出键相同的值 for(biginteger babykey : babystep.keyset()){ if(giantstep.containskey(babykey)){ biginteger t = giantstep.get(babykey); biginteger r = babystep.get(babykey); return t.multiply(s) .subtract(r); } } return null; } public static void main(string[] args){ main elgamal = new main(); // elgamal.testelgamalcore(); elgamal.testelgamal(); // elgamal.testgeneratekey(); // system.out.println(elgamal.shanks(new biginteger(\u0026#34;7190183321\u0026#34;),new biginteger(\u0026#34;1333381262\u0026#34;),new biginteger(\u0026#34;7788872843\u0026#34;))); } }\r","date":"2024-07-14","permalink":"http://54rookie.com/posts/elgamal/","summary":"ElGamal 文章给出了ElGamal加密算法的一个实现。未做任何优化,仅供学习参考,计算速度跟专业的库相比应该有很大差距。虽然密码学算法是安全的,但是工程实现中可能存在其","title":"elgamal"},]
[{"content":"rsa 公钥加密思想的提出 从单表替换密码到多表替换密码,再到分组密码,密码系统的安全性不断提高,但随之而来的密钥分发问题却越来越突出。20世纪70年代,美国政府每天需要分发数吨计的密钥。虽然几乎所有人认为密钥分发的问题不可能解决,但这个问题还是被奇流般地解决了。这个方法被称为公钥密码,其发展是整个密码学发展历史上最伟大的一革命,也许可以说是唯一一次革命。\n公钥密码学与以前的密码学完全不同。首先,公钥算法是基于数学函数而不是基于替换和置换,更重要的是,与只使用一个密钥的对称传统密码不同,公钥密码是非对称的它使用两个独立的密钥。我们将会看到,使用两个独立密钥在消息的保密性、密钥分配和认证领域有着重要意义。\n1976年 whitefield diffie 和 martin hellman 提出了公开密钥(public key)密码的解密新思想”。虽然他们没能给出公钥加密的具体方案,但这个思想仍具有划时代的意义。与经典密码学相对应,业界将1976年定为现代密码学元年。也因此获得美国计算机协会 acm 授予的2015年度“图灵奖”。\n公钥密码体制简介 提到公钥密码体制,首先要介绍与之对应的对称密码体制,从而更好的了解公钥密码体制。\n对称密码体制 1970年以前,密码学一直使用对称密钥,即消息发送者alice用特定的密钥加密,接收者 bob用同一个密钥解密(如密码锁)。对称密钥体制的缺点是:\n它需要在 alice和 bob之间首先在传输密文之前使用一个安全信道交换密钥实际上,这可能很难达到。例如,假定 alice 和 bob的居住地相距很远,他们决定用 emai或手机通信,在这种情况下,alice和 bob可能无法获得一个合理的安全信道。 如果 alice 需要和多人进行通信(假如 alice 是一家银行)时,需要和每个人交換不同的密钥,她必须管理所有这些密钥,为了建立密钥还需要发送成千上万的消息。 由此可见对称密钥体制已经无法满足实际应用的需求。在公钥密码体制中,任何使用者都可以取得其他使用者的加密密钥(即公开密钥),假设alice欲通过此公开密钥密码系统加密消息给 bob,她可以先取得 bob的加密密钥,通过加密函数 $e_{bob}$.将信息加密成密文传递给 bob,bob 在收到 alice 传来的密文后,便可通过自己的解密密钥(私钥),用解密函数 $d_{bob}$ 解密。 公钥密码体制 公钥密码体制是由几个部分组成的。首先,包括可能信息的集合m(可能的明文和密文),还包括密钥的集合k。对于每一个密钥k,有一个加密函数e和解密函数d.,通常,e和d,被假定从m映射到m,虽然可能出现一些变化,允许明文和密文来自不同的集合。这些部分必须满足下列要求:\n对于每一个 $m\\in m$ 和每一个 $k\\in k,e_k(d_k(m))=m$ 和 $d_k(e_k(m))=m$; 对于每一个 m 和每一个 k , $e_k(m)$ 和 $d_k(m)$ 的值很容易计算出来: 对于几乎每一个 $k\\in k$,如果某人仅仅知道函数 $e_k$,那么找到一个算法计算出 $d_k$,在计算上是不可行的; 已知 $k\\in k$,很容易找到函数 $e_k$ 和 $d_k$。 要求(1)说明加密和解密互相抵消了;要求(2)是必需的,否则有效地加密和解密是不可能的。由于(4),一个用户能够从k中选择一个秘密的随机数 k 并得到函数$e_k$和$d_k$、要求(3)使该体制成为公钥密码体制。因为很难确定和$d_k$。所以公开 $e_k$ 而不危及系统的安全性是可能的。\n公钥加密的第一个实现rsa 1977 年《科学美国人》报道了当时 mit 的 ronald rivest、adi shamir 和 leonar adleman开创的一种“数百万年才能破解”的公开密钥密码系统(public kecryptosystem)。他们三人先申请了专利,命名为 rsa 公钥密码技术,在加州成立了 rsa 数据安全公司,并于 2002年共同获得“图灵奖”。如今 rsa密码系统已经成为使最广、安全性高的公开密钥密码系统。\n预备知识\nrsa算法需要用到数论中一些基础,比如 同余,模运算,欧拉定理等等。最基础的东西这里不做介绍,读者可以自信搜索相关资料学习。\n算术基本定理 任何一个大于1的自然数 n,如果n不为质数,那么n可以唯一分解成有限个质数的乘积 $n=p_1^{a_1}\\cdot p_2^{a_2}\\cdot \u0026hellip; \\cdot p_n^{a_n}$,这里 $p_1 \u0026lt; p2 \u0026lt; \u0026hellip; \u0026lt; p_n$ 均为质数,指数 $a_i$ 是正整数。\n证明: 我们知道,任何非零自然数可根据其是否能表示成两个不同于自身的自然数的乘积分成3类:质数、合数 和 1。下面是用反证法证明上述定理:\n假设存在大于1的自然数不能写成质数的乘积,设这些数中最小的那个为n。 由于 n 大于 1 ,显然,n不能为质数,因为质数 p 可以写成质数乘积:p = p,这与假设不相符合。因此 n 只能是合数。 我们知道每个合数都可以分解成两个小于自身而大于1的自然数的积。设其中a和b都是介于1和n之间的自然数,因此,按照n的定义,a和b都可以写成质数的乘积。从而n也可以写成质数的乘积。由此产生矛盾。因此大于1的自然数必可写成质数的乘积。 euler函数 \u0026amp; euler定理 在数论中,对于正整数n,欧拉函数(n)是小于或等于m的整数中与 n 互质的所有数的个数。欧拉函数具有两个很重要的性质:\n任何素数户的欧拉函数值 $φ(p)=p-1$ 欧拉函数可乘,即 $φ(p\\cdot q)=φ(p)\\cdot φ(q)$ 欧拉定理表述为:若正整数m和n互素,即 gcd(m,n)=1,则 $m^{φ(n)}=1 \\pmod n$\n因式分解困难问题 由上述算数基本定理可知,任何一个大于一的正整数都可以分解为素数的乘积。这在数字很小的时候很容易实现,但是当整数n非常大的时候,并没有好的办法能够将它分解为几个素数乘积的形式。当n的大小达到 2^2024 时,使用现在最先进的计算机对其进行分解也需要数百年甚至更久的时间,因此分解大整数的问题也被称为大数分解困难问题,这是rsa密码安全性的保证。\nrsa的步骤 alice 选择秘密的两个互异大素数p和q,并计算 $n = p \\cdot q$ alice 选择e满足 $gcd(e,(p-1)(q-1))=1$。 alice 计算 d 满足 $ed≡1 \\pmod {(p-1)(q-1)}$ alice 将 $(n,e)$ 作为公钥,$(d,p,q)$ 作为私钥。 bob 加密 m 为 $c≡m^e \\pmod n$,发送 c 给 alice. alice 解密 $m=c^d \\pmod n$。 rsa解密算法的正确性证明 我们知道:$m ≡ c^d \\pmod n = m^{ed} \\pmod n ≡ m^{ed} \\pmod n ≡ m^{kφ(n)+1} \\pmod n$. 由 $ed ≡1 \\pmod {φ(n)}$ 可知 $ed = kφ(n) + 1$ ,故只需要证明 $m ≡ m^{kφ(n)+1} \\pmod n$\n如果 m 和 n 互素,\n由于 m \u0026lt; n,因此 $m \\pmod n = m$ , 由欧拉公式知 $m^{φ(n)} ≡ 1 \\pmod n$ ,根据同余式的性可知,$m^{kφ(n)} ≡ 1 \\pmod n$ 综上所述,$c^d ≡ m^{ed} ≡ m^{kφ(n)+1} ≡ m \\pmod n$ 如果m和n不互素,\n那么m 和 n 存在大于 1 的公因子,由于 n 只有两个因子 p 和 q,所以 m=ap 或 bq 。 不失一般性,我们设m=ap,由于 m \u0026lt; n ,因此 gcd(m,q)=1。因此,根据欧拉定理可得下面等式成立: $m^{φ(q)} ≡ 1 \\pmod q$. 由同余的性质可得:$m^{kφ(q)} ≡ 1 \\pmod q$ , 因此$(m^{kφ(q)} )^{φ(p)} ≡ 1 \\pmod q$ 也就是 $m^{kφ(n)} ≡ 1 \\pmod q$, 因此可得 $m^{kφ(n)} = sq + 1$ 两边同乘以 m 可得:$m^{kφ(n)+1} = sq \\cdot ap + m = s\\cdot a \\cdot n + m$ 综上所述: $m^{kφ(n)+1} \\pmod n ≡ m \\pmod n$ 故无论m和n是否互素,都可以得到 $m^{kφ(n)+1} \\pmod n ≡ m \\pmod n$,rsa的正确性得证。\nrsa的实现 文章给出了rsa加密算法的一个实现。未做任何优化,仅供学习参考,计算速度跟专业的库相比应该有很大差距。虽然密码学算法是安全的,但是工程实现中可能存在其他攻击,没有足够的信心,不要使用自己写的加密算法做加密。\nimport java.math.bigdecimal; import java.math.biginteger; import java.util.hashmap; import java.util.map; import java.util.random; /** * @author miracle * @date 2022/8/31 22:56 * @description: \u0026lt;br/\u0026gt; * rsa公钥加密 */ public class main { // --------------------------------------------------- 工具函数 ------------------------------------------------------------------- /** * 产生一个范围在 [start,end]之间的随机大整数 * * @param start 随机数的最小值 * @param end 随机数的最大值 * @return 返回一个范围在[start,end]之间的随机大整数,如果start\u0026lt;end , 返回null */ public static biginteger random(biginteger start,biginteger end){ if(start.compareto(end) \u0026gt; 0){ return null; } if(start.compareto(end) == 0){ return end; } biginteger a; do{ a = new biginteger((end.subtract(start)).bitlength(),new random()); } while(a.compareto(end.subtract(start)) \u0026gt; 0); a = a.add(start); return a; } /** * 返回int型数据的位数 * * @param number 数据 * @return 数据的位数 */ public static int bitofint(int number){ if(number \u0026lt; 0){ number = -number; } int[] sizetable = {9,99,999,9999,99999,999999,9999999,99999999,999999999,integer.max_value}; int i = 0; while(number \u0026gt; sizetable[i++]) ; return i; } /** * 针对大整数的扩展的欧几里得算法 * * @param a 任意长度的整数 * @param b 任意长度的整数 * @return a 和 b 的最大公约数以及 s 和 t */ public static map\u0026lt;string, biginteger\u0026gt; gcdextend(biginteger a, biginteger b) { map\u0026lt;string, biginteger\u0026gt; res = new hashmap\u0026lt;\u0026gt;(); biginteger r, r0 = a, r1 = b; biginteger s0 = biginteger.one; biginteger t0 = biginteger.zero; biginteger s1 = biginteger.zero; biginteger t1 = biginteger.one; biginteger s = biginteger.one; biginteger t = biginteger.zero; // 如果 a \u0026lt; b; 交换 a,b ; 保证 a \u0026gt;= b if (a.compareto(b) == -1) { r0 = b; r1 = a; s0 = biginteger.zero; t0 = biginteger.one; s1 = biginteger.one; t1 = biginteger.zero; } // 以下两种特殊情况处理 if (r1 == biginteger.zero) { res.put(\u0026#34;gcd\u0026#34;, r0); res.put(\u0026#34;s\u0026#34;, biginteger.one); res.put(\u0026#34;t\u0026#34;, biginteger.zero); return res; } if (a.mod(b) == biginteger.zero) { res.put(\u0026#34;gcd\u0026#34;, r1); res.put(\u0026#34;s\u0026#34;, biginteger.zero); res.put(\u0026#34;t\u0026#34;, biginteger.one); return res; } // 迭代求解 s和t 使得 as + bt = gcd(a,b) biginteger qi = biginteger.zero; while (r0.mod(r1) != biginteger.zero) { qi = r0.divide(r1); s = s0.subtract(s1.multiply(qi)); t = t0.subtract(t1.multiply(qi)); s0 = s1; t0 = t1; s1 = s; t1 = t; r = r0.mod(r1); r0 = r1; r1 = r; } // 找到相应的 s和t 后 , 返回结果 biginteger as = a.multiply(s); biginteger bt = b.multiply(t); res.put(\u0026#34;gcd\u0026#34;, as.add(bt)); res.put(\u0026#34;s\u0026#34;, s); res.put(\u0026#34;t\u0026#34;, t); return res; } public static biginteger getmultiplicativeinverse(biginteger a, biginteger n) { map\u0026lt;string, biginteger\u0026gt; map = gcdextend(a, n); if (map.get(\u0026#34;gcd\u0026#34;).compareto(biginteger.one) == 0) { biginteger s = map.get(\u0026#34;s\u0026#34;); while (s.compareto(biginteger.zero) == -1) { s = s.add(n); } while (s.compareto(n) == 1) { s = s.subtract(n); } return s; } else { system.out.println(\u0026#34;getmultiplicativeinverse()提示您: \u0026#34; + a + \u0026#34;在模\u0026#34; + n + \u0026#34;下没有乘法逆元!\u0026#34;); return new biginteger(\u0026#34;-1\u0026#34;); } } /** * 大整数的大整数幂对一个大整数求幂的结果 * * @param base 底数 * @param exponent 指数 * @param n 模数 * @return base的exponent次幂模n */ public static biginteger largeintegerpowermodulo(biginteger base, biginteger exponent, biginteger n) { // 保证底数大于模数 if (base.compareto(n) == -1) { base = base.mod(n); } biginteger res = solve(base, exponent, n); return res; } /** * 1、数论中有如下结论 * (a + b) % p = (a % p + b % p) % p * (a - b) % p = (a % p - b % p) % p * (a * b) % p = (a % p * b % p) % p * (a ^ b) % p = ((a % p) ^ b) % p * (a ^ b) % p = ((a % p) ^ b) % p * \u0026lt;p\u0026gt; * 2、分治法求解 * (1) 若 exponent 为奇数: 令 exponent = 2k+1, k 为非负整数 , 则 * a^(2k+1) = (a^k)^2 *a ; a^(2k+1) % n = ((a^k % n)^2 % n * a % n) % n = [ (a^k % n)^2 * a ] % n * (2) 若power 为偶数: 令 power = 2k, k为非负整数, 则 * a^(2k) = (a^k)^2 ; a^(2k) % n = (a^k % n)^2 % n * (3) 若 power == 0: 返回 1。 * \u0026lt;p\u0026gt; * 3、算法分析 * 时间复杂度 t(n) = o( log(n) ) * 空间复杂度 t(n) = o( lon(n) ) (递归函数的空间开销) * * @param base 底数 * @param exponent 指数 * @param n 模数 * @return base的exponent次幂模n */ public static biginteger solve(biginteger base, biginteger exponent, biginteger n) { if (exponent == biginteger.zero) { return new biginteger(\u0026#34;1\u0026#34;); } if (exponent.mod(new biginteger(\u0026#34;2\u0026#34;)) == biginteger.zero) { biginteger two = new biginteger(\u0026#34;2\u0026#34;); base = solve(base, exponent.divide(two), n).pow(2); return base.mod(n); } else { biginteger two = new biginteger(\u0026#34;2\u0026#34;); base = solve(base, exponent.divide(two), n).pow(2) .multiply(base); return base.mod(n); } } // --------------------------------------------------- 加解密函数 ------------------------------------------------------------------- /** * rsa加密函数 * * @param m 待加密的明文消息 * @param e 加密使用的公钥 * @param n 加密使用的模数 * @return 返回加密后的消息,即密文 */ private static biginteger encryptcore(biginteger m,biginteger e,biginteger n){ return largeintegerpowermodulo(m,e,n); } /** * rsa解密函数 * * @param c 密文 * @param p 私钥:选取的两个大素数之一 * @param q 私钥:选取的两个大素数之一 * @param e 公钥 * @return 将密文恢复后得到的明文 */ private static biginteger decryptcore(biginteger c,biginteger p,biginteger q,biginteger e){ biginteger m = (p.subtract(biginteger.one)).multiply(q.subtract(biginteger.one)); biginteger d = getmultiplicativeinverse(e,m); return largeintegerpowermodulo(c,d,p.multiply(q)); } // --------------------------------------------------- rsa签名 ------------------------------------------------------------------- /** * rsa签名函数 * * @param name 待签名的消息 * @param p 选取的私钥中两个大素数之一 * @param q 选取的私钥中两个大素数之一 * @param d 私钥,是e在模n = pq 下的逆元 * @return 返回一个长度为2数组,第一个元素是待签名的消息,第二个元素是签名后的消息 */ private static biginteger[] sig(biginteger name,biginteger p,biginteger q,biginteger d){ biginteger[] res = new biginteger[2]; res[0] = name; biginteger n = p.multiply(q); res[1] = largeintegerpowermodulo(name,d,n); return res; } /** * rsa签名验证函数 * * @param sig 经rsa签名算法加密后的消息 * @param n 公开的模数n * @param e 公钥的指数e * @return 如果签名验证成功,返回true,否则返回false */ private static boolean ver(biginteger[] sig,biginteger n,biginteger e){ biginteger ver = largeintegerpowermodulo(sig[1],e,n); return ver.compareto(sig[0]) == 0; } // --------------------------------------------------- 加密解密函数 ------------------------------------------------------------------- /** * 加密字符串(这里假设只发送英文和标点,此时转化为byte数组,最大为122) * 可以将字符换转化为byte数组。将byte数组的数字划为三位数,不够三位的,首位补 9(这样可以恢复) ,将byte拼接为字符串,转化为biginteger * 并将其作为明文的底数,以公钥的指数e和模数n对其加密,将得到的biginteger转化为字符串作为密文。 * 注意: * -\t这里的字符串长度不能太长,否则将导致 m \u0026gt; n,导致错误,可将字符串转化的byte数组分成一个个长度较小的子数组分别加密,这里不在实现 * * @param plaintext 明文字符串 * @param n 公钥:模数 * @param e 公钥:指数 * @return 加密后的字符串 */ private static string encrypt(string plaintext,string n,string e){ byte[] bytes = plaintext.getbytes(); int[] ints = new int[bytes.length]; int i = 0; for(i = 0; i \u0026lt; bytes.length; i++){ ints[i] = bytes[i]; } stringbuilder builder = new stringbuilder(); // 目前只能小于 1024 biginteger c = biginteger.zero; i = 0; while(i \u0026lt; ints.length){ if(bitofint(ints[i]) == 2){ ints[i] += 900; } builder.append(ints[i++]); } // system.out.println(\u0026#34;plain = \u0026#34; + builder.tostring()); biginteger m = new biginteger(builder.tostring()); c = encryptcore(m,new biginteger(e),new biginteger(n)); return c.tostring(); } /** * rsa解密 * * @param ciphertext 密文 * @param p 私钥 p * @param q 私钥 q * @param e 指数 e * @return 解密后的明文字符串 */ private static string decrypt(string ciphertext,string p,string q,string e){ stringbuilder res = new stringbuilder(); biginteger cc = new biginteger(ciphertext); biginteger pp = new biginteger(p); biginteger qq = new biginteger(q); biginteger ee = new biginteger(e); string plaintext = decryptcore(cc,pp,qq,ee).tostring(); // system.out.println(\u0026#34;plain = \u0026#34; + plaintext); for(int i = 0; i \u0026lt; plaintext.length(); i = i + 3){ stringbuilder builder = new stringbuilder(); builder.append(plaintext.charat(i)); builder.append(plaintext.charat(i + 1)); builder.append(plaintext.charat(i + 2)); int integer = new integer(builder.tostring()); if(integer / 100 == 9){ integer -= 900; } char temp = (char) integer; res.append(temp); } return res.tostring(); } // --------------------------------------------------- 测试函数 ------------------------------------------------------------------- // 测试rsa的加密核心函数 private void testrsacore(){ system.out.println(\u0026#34;---------------------- 测试rsa核心加密函数 ----------------------------\u0026#34;); biginteger p = new biginteger(\u0026#34;885320963\u0026#34;); biginteger q = new biginteger(\u0026#34;238855417\u0026#34;); biginteger m = new biginteger(\u0026#34;30120\u0026#34;); biginteger e = new biginteger(\u0026#34;9007\u0026#34;); system.out.println(\u0026#34;待加密明文为:\u0026#34; + m); biginteger ciphertext = encryptcore(m,e,p.multiply(q)); system.out.println(\u0026#34;rsa对m加密得: \u0026#34; + ciphertext); biginteger plaintext = decryptcore(ciphertext,p,q,e); system.out.println(\u0026#34;rsa对c解密得: \u0026#34; + plaintext); } // 测试rsa签名算法 private void testrassignature(){ system.out.println(\u0026#34;---------------------- 测试rsa签名算法 ----------------------------\u0026#34;); system.out.println(\u0026#34;对19用私钥(p,q,d)=(7,17,77)进行签名\u0026#34;); biginteger[] sig = sig(new biginteger(\u0026#34;19\u0026#34;),new biginteger(\u0026#34;7\u0026#34;),new biginteger(\u0026#34;17\u0026#34;),new biginteger(\u0026#34;77\u0026#34;)); system.out.println(\u0026#34;签名后的结果为:(\u0026#34; + sig[0] + \u0026#34;,\u0026#34; + sig[1] + \u0026#34;)\u0026#34;); system.out.println(\u0026#34;对签名后的结果用公钥(n,e)=(119,5)进行验证\u0026#34;); boolean ver = ver(sig,new biginteger(\u0026#34;119\u0026#34;),new biginteger(\u0026#34;5\u0026#34;)); system.out.println(\u0026#34;ver(66) == 19 的结果为 \u0026#34; + ver); } // 测试rsa加密字符串 private void testrsa(){ system.out.println(\u0026#34;---------------------- 测试rsa算法加密字符串 ----------------------------\u0026#34;); biginteger e = new biginteger(\u0026#34;9007\u0026#34;); biginteger pp = new biginteger(\u0026#34;12026655772210679470465581609002525329245773732132014742758935511187863487919026457076252932048619706498126046597130520643092209728783224795661331197604583\u0026#34;); biginteger qq = new biginteger(\u0026#34;8002511426596424351829267099531651390448054153452321185350746845306277585856673898048740413439442356860630765545600353049345324913056448174487017235828857\u0026#34;); string cipher = encrypt(\u0026#34;i love cryptography!\u0026#34;,pp.multiply(qq) .tostring(),e.tostring()); system.out.println(\u0026#34;cipher = \u0026#34; + cipher); string plain = decrypt(cipher,pp.tostring(),qq.tostring(),e.tostring()); system.out.println(\u0026#34;plain = \u0026#34; + plain); } // --------------------------------------------------- 素性判定(rsa攻击) ------------------------------------------------------------------- /** * 试除法素性判定 * 时间复杂度 t(n) = o(n^0.5) * * @param n 待判定整数 * @return 是素数返回true,否则返回false */ private static boolean isprime(int n){ for(int i = 2; i \u0026lt;= math.sqrt(n); i++){ if(n % i == 0){ return false; } } return true; } /** * 费马素性判定 * 时间复杂度:\tt(n) = o(log n) * * @param n 待判定数字 * @param k 迭代的轮数,迭代k次后,将合数判定为素数的概率为 1/2^k * @return 是素数返回true,是合数返回false。注意返回false一定是合数,返回true,有1/2^k 的概率是合数 */ private static boolean isprime_fermat(biginteger n,int k){ // 特殊情况,n为2或3时,mytools.random返回为null if(n.compareto(new biginteger(\u0026#34;2\u0026#34;)) == 0 || n.compareto(new biginteger(\u0026#34;3\u0026#34;)) == 0){ return true; } // 费马素性判定法 biginteger two = new biginteger(\u0026#34;2\u0026#34;); for(int i = 0; i \u0026lt; k; i++){ biginteger a = random(two,n.subtract(two)); biginteger temp = largeintegerpowermodulo(a,n.subtract(biginteger.one),n); if(temp.compareto(biginteger.one) != 0){ return false; } } return true; } /** * miller-rabin素性判定 * 算法思路 * 设n是一个奇数,n-1 = 2^t * m,其中m是一个奇数。要测试n是否为素数, * ① 先随机选择一个介于 [2,n-2]区间内的整数a,如果a^m ≠ 1 且 * ② 对所有的满足 r ∈ [0,k-1] 的整数有,a^(2^r * m) ≠ -1 , 则有n一定是合数。否则,n有很大可能是素数。 * (目前可以在数学上证明,miller-rabin将合数判定为素数的概率小于1/4,而事实上经验告诉我们,概率比1/4小得多) * * 时间复杂度:\tt(n) = o(log n) * * @param n 待判定数字 * @param k 迭代的轮数,迭代k次后,将合数判定为素数的概率为 1/4^k * @return 是素数返回true,是合数返回false。注意返回false一定是合数,返回true,有1/4^k 的概率是合数 */ private static boolean isprime_miller_rabin(biginteger n,int k){ // 特殊情况,n为2或3时,mytools.random返回为null if(n.compareto(new biginteger(\u0026#34;2\u0026#34;)) == 0 || n.compareto(new biginteger(\u0026#34;3\u0026#34;)) == 0){ return true; } // miller-rabin素性判定 biginteger two = new biginteger(\u0026#34;2\u0026#34;); biginteger m = n.subtract(biginteger.one); int t = 0; while(m.mod(two) == biginteger.zero){ m = m.divide(two); t++; } for(int i = 0; i \u0026lt; k; i++){ biginteger a = random(two,n.subtract(two)); if(largeintegerpowermodulo(a,m,n).compareto(biginteger.one) != 0){ int r; for(r = 0; r \u0026lt;= t-1; r++){ if(largeintegerpowermodulo(a,two.pow(r).multiply(m),n).subtract(n).compareto(new biginteger(\u0026#34;-1\u0026#34;)) == 0){ break; } } if(r == t){ return false; } } } return true; } // --------------------------------------------------- 因式分解(rsa攻击) ------------------------------------------------------------------- /** * 牛顿下降法判断大整数是不是完全平方数 * * @param f7 待判断大整数 * @return 如果是完全平方数,返回平方根,否则返回0 */ public static biginteger getsquare(biginteger f7){ // 牛顿法求解平方根, 求解a的平方根 // x为a的平方根,x的初始值为1, 按x = (x+a/x)/2迭代, 误差为error,事实上,当error \u0026lt; 1时即可求出x平方根的整数部分 bigdecimal x = bigdecimal.one; bigdecimal a = new bigdecimal(f7.tostring()); bigdecimal eps = new bigdecimal(\u0026#34;1\u0026#34;); final bigdecimal error = new bigdecimal(\u0026#34;1e-10\u0026#34;); int scale = 100; // 进入循环 while(eps.compareto(error) \u0026gt; 0){ // eps \u0026gt; error是执行循环 x = x.add(a.divide(x,scale,bigdecimal.round_half_up)) .divide(new bigdecimal(\u0026#34;2.0\u0026#34;),scale,bigdecimal.round_half_up); eps = x.multiply(x) .subtract(a) .abs(); } biginteger sqrt = x.tobiginteger(); // 求平方根的整数部分 if(sqrt.pow(2) .compareto(f7) == 0){ return sqrt; } else{ return biginteger.zero; } } /** * 费马因式分解算法(将两个素数的乘积分解为这两个素数) * 时间复杂度:t(n) = o(|p-q|) * * @param n 待分解的整数 * @return 如果能分解,返回分解后的结果存储的map,如果无法分解,返回null */ private map\u0026lt;string,biginteger\u0026gt; fermat(biginteger n){ hashmap\u0026lt;string,biginteger\u0026gt; res = new hashmap\u0026lt;\u0026gt;(); biginteger i = biginteger.one; biginteger two = new biginteger(\u0026#34;2\u0026#34;); while(i.compareto(n.divide(two)) \u0026lt; 0){ biginteger temp = n.add(i.pow(2)); biginteger sqrt = getsquare(temp); if(sqrt != biginteger.zero){ res.put(\u0026#34;p\u0026#34;,sqrt.subtract(i)); res.put(\u0026#34;q\u0026#34;,sqrt.add(i)); return res; } i = i.add(biginteger.one); } return null; } /** * pollard p-1因式分解法 * 对于rsa加密中的 n,要将n分解为 n = x*y (这里x,y为两个素数,不使用 n =p*q是为了方便叙述) * \u0026lt;p\u0026gt; * 前置知识 * 1、费马小定理: * -\t对于任意素数p和整数 a ∈ zp,都有 a^p ≡ a(mod p) ( 也可写成 a^(p-1) ≡ 1(mod p) ) * 2、b-smooth数 * -\t如果一个整数的所有素因子的最大值为b,我们称这个整数为b-smooth数 * -\t例:30 = 2 * 3 * 5 , 因此 30 是一个 5-smooth数。 * -\t例:12 = 2 * 2 * 3 , 因此 12 是一个 3-smooth数。 * - * -\t定理 1: * -\t如果 p-1 是 b-smooth数,并且 p-1的素因子各不相同,则 (p-1) | b! * -\t证明: * -\t不妨设 p-1 = p1 * p2 * ... * pk , 其中 p1 \u0026lt; p2 \u0026lt; ... \u0026lt; pk * -\t则 b = pk , b! = 1 * 2 ... * pk , 显然 p1,p2,...,pk 都是 b!的因子 * -\t所以 p-1 | b! * -\t定理 2: * -\t对于任意的素数p,有 p | ( a^t - 1 ) , 其中 t是 p-1的非零倍数 , 即 t = k*(p-1) , k 为非零整数 * -\t证明: * -\t由费马小定理知:对于任意素数p和整数 a ∈ zp,都有 a^(p-1) ≡ 1(mod p) * -\t由同余的性质可知: a^(k*(p-1))≡ 1(mod p) , * -\t即 a^(k*(p-1)) - 1 ≡ 0 (mod p) * -\t即 p | a^(k*(p-1)) - 1 * -\t即 p | a^t - 1 * - * - * 3、pollard p-1 算法的思路 * -\t① 由定理 2可知,只需要找到合适的t即相当于素数p 是 a^t-1 的一个因子。即 p | a^t-1 ,如果p恰好又是n的一个因子,即 p | n 。则 p是n的一个非平凡因子, * -\t即 p = x 或 p = y(因为n只有x,y两个非平凡因子)。不失一般性,设p = x。 * -\t那么如何求出p呢? * -\t② 注意到 p | a^t-1 且 p | n ,说明 p是 a^t-1和 n 的公约数,而y同时又是 a^t - 1的因子的概率非常小,除非 y-1 也仅仅含有小的素数因子。 * -\t因此有很大概率 gcd(a^t-1,n) = p 是n的一个非平凡因子。 * -\t③ 还有一种可能是 gcd(a^t-1,n) = n ,这时 a^t ≡ 1(mod n),可用指数分解法分解。 * -\t那么t应该如何选取呢? * -\t④ 由定理 1 可知:对于某个合适的b,b!是p-1的倍数,即可选择 t = b! ,由于不知道 b是多少,可以采用循环尝试不同的b。 * -\t小技巧: * -\ta^(b!)的计算可以使用如下巧妙的方法: * -\t令 b1 = a (mod n) , bj = b(j-1)^j(mod n) ; 则 bb = a^(b!)(mod n) * \u0026lt;p\u0026gt; * 4、算法的过程 * -\t①选取一个大于1的整数a,为了简单不妨选择 a = 2 * -\t②选取b = 2,求 d = gcd( a^(b!)-1,n),如果 1 \u0026lt; d \u0026lt; n ,则d是其中一个因子,执行完毕,返回d * -\t③如果 第②步的 d = 1,则将 b++后重复第②步。 * -\t(由于当n = x*y中,x-1和y-1都含有大素数因子时,算法效率很低,因此可设置一个b的最大值maxb,当 b \u0026gt; maxb时,结束循环,返回-1,) * -\t④如果 第②步的 d = n,则使用指数分解法分解n(代码实现中,默认没有这种情况) * 5、时间复杂度和空间复杂度: * - t(n) = o(maxb * log n) , s(n) = o(1) * 6、参考文献 * -\thttps://blog.soreatu.com/posts/intended-solution-to-crypto-problems-in-nctf-2019/#childrsa213pt-38solvers * * @param n 待分解的合数 * @param maxb 最大循环的轮数,当达到该循环轮数后,如果还未分解成功,直接结束。显然,该值越大对于分解n越有帮助 * @return 返回分解后的结果 n = p*q ,p和q被封装在一个map中,key 分别为 \u0026#34;p\u0026#34; 和 \u0026#34;q\u0026#34; */ private map\u0026lt;string,biginteger\u0026gt; pollard_p_1(biginteger n,biginteger maxb){ hashmap\u0026lt;string,biginteger\u0026gt; map = new hashmap\u0026lt;\u0026gt;(); biginteger b = new biginteger(\u0026#34;2\u0026#34;); biginteger j = biginteger.one; biginteger b = new biginteger(\u0026#34;2\u0026#34;);//这里相当于给a赋值为2 while(b.compareto(maxb) \u0026lt; 0){ b = largeintegerpowermodulo(b,j,n); biginteger d = gcdextend(b.subtract(biginteger.one),n) .get(\u0026#34;gcd\u0026#34;); if(d.compareto(biginteger.one) \u0026gt; 0 \u0026amp;\u0026amp; d.compareto(n) \u0026lt; 0){ map.put(\u0026#34;p\u0026#34;,d); map.put(\u0026#34;q\u0026#34;,n.divide(d)); return map; } else if(d.compareto(biginteger.one) == 0){ b = b.add(biginteger.one); j = j.add(biginteger.one); } else{ // 使用指数分解法,这里返回空map,表示分解失败 map.put(\u0026#34;p\u0026#34;,biginteger.zero); map.put(\u0026#34;q\u0026#34;,biginteger.zero); return map; } } return map; } public static void main(string[] args){ main rsa = new main(); rsa.testrsacore(); rsa.testrsa(); rsa.testrassignature(); // biginteger p = new biginteger(\u0026#34;10337\u0026#34;); // biginteger q = new biginteger(\u0026#34;100493\u0026#34;); // map\u0026lt;string,biginteger\u0026gt; fermat = rsa.fermat(p.multiply(q)); // system.out.println(fermat); // biginteger pp = new biginteger(\u0026#34;21\u0026#34;); // biginteger qq = new biginteger(\u0026#34;11\u0026#34;); // map\u0026lt;string,biginteger\u0026gt; pollard_p_1 = rsa.pollard_p_1(p.multiply(q),new biginteger(\u0026#34;100\u0026#34;)); // system.out.println(pollard_p_1); // for(int i = 2; i \u0026lt; 30; i++){ // system.out.print(i + \u0026#34; : \u0026#34; + isprime_fermat(new biginteger(\u0026#34;\u0026#34; + i),10) + \u0026#34;,\u0026#34;); // system.out.println(isprime(i)); // } // system.out.println(\u0026#34;------------------------------------------------\u0026#34;); // for(int i = 2000; i \u0026lt; 2030; i++){ // system.out.print(i + \u0026#34; : \u0026#34; + isprime_miller_rabin(new biginteger(\u0026#34;\u0026#34; + i),10) + \u0026#34;,\u0026#34;); // system.out.println(isprime(i)); // // } } }\r","date":"2024-07-14","permalink":"http://54rookie.com/posts/rsa/","summary":"RSA 公钥加密思想的提出 从单表替换密码到多表替换密码,再到分组密码,密码系统的安全性不断提高,但随之而来的密钥分发问题却越来越突出。20世纪70年代,美国政府每天需","title":"rsa"},]
[{"content":"aes加密 des加密的过程 具体的原理可以参考:\n可以参考csdn上的这篇文章 本代码参考《现代密码学概论》(仲红、潘恒、熊书明、王良民著) des实现 文章给出了aes加密算法的一个实现。未做任何优化,仅供学习参考,计算速度跟专业的库相比应该有很大差距。虽然密码学算法是安全的,但是工程实现中可能存在其他攻击,没有足够的信心,不要使用自己写的加密算法做加密。\nimport java.math.biginteger; import java.util.random; /** * @author miracle * @date 2022/11/14 22:12 * @description: \u0026lt;br/\u0026gt; */ public class main { /** * 将一个十进制的数字转化为指定进制,指定位数的数字符串(位数不够,前面补零) * 如: * -\t将 33 转化为长度为 8 的二进制字符串 * -\t则结果为: \u0026#34;00100001\u0026#34; * * @param number 带转换的十进制数字 * @param radix 将要转化为的进制数 * @param length 转化后字符串的长度 * @return 满足要求的字符串 */ public static string tostring(int number, int radix, int length) { stringbuilder str = new stringbuilder(); for (int i = 0; i \u0026lt; length; i++) { int bit = number % radix; str.insert(0, bit); number = number / radix; } return str.tostring(); } // --------------------------------------------------- 常量定义 ------------------------------------------------------------------- // s盒 private static int[][] sbox = new int[][]{ {0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76},// {0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0},// {0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15},// {0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75},// {0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84},// {0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf},// {0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8},// {0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2},// {0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73},// {0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb},// {0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79},// {0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08},// {0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a},// {0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e},// {0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf},// {0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16} }; // 列混合左乘的矩阵 private static int[][] mixcol = { {2, 3, 1, 1},// {1, 2, 3, 1},// {1, 1, 2, 3},// {3, 1, 1, 2} }; // 密钥扩展算法中的轮常量 private static int[][] rc = new int[][]{ {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36},// {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},// {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},// {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} }; private static int[][] invmixcol = { {0x0e, 0x0b, 0x0d, 0x09},// {0x09, 0x0e, 0x0b, 0x0d},// {0x0d, 0x09, 0x0e, 0x0b},// {0x0b, 0x0d, 0x09, 0x0e} }; // 逆 s 盒 private static int[][] invsbox = new int[][]{ {0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb},// {0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb},// {0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e},// {0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25},// {0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92},// {0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84},// {0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06},// {0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b},// {0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73},// {0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e},// {0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b},// {0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4},// {0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f},// {0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef},// {0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61},// {0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d} }; // --------------------------------------------------- 工具函数 ------------------------------------------------------------------- // 工具函数,将int类型的二维数组以十六进制显示,用来查看数据的正确性 private static void showbyhex(int[][] a) { int i = 0; system.out.println(\u0026#34;[\u0026#34;); for (int[] rows : a) { i = 0; system.out.print(\u0026#34;[\u0026#34;); for (int row : rows) { if (i \u0026lt; 4) { string tohexstring = integer.tohexstring(row); if (tohexstring.length() == 1) { tohexstring = \u0026#34;0\u0026#34; + tohexstring; } system.out.print(tohexstring + \u0026#34;,\u0026#34;); i++; } else { i = 0; string tohexstring = integer.tohexstring(row); if (tohexstring.length() == 1) { tohexstring = \u0026#34;0\u0026#34; + tohexstring; } system.out.print(tohexstring + \u0026#34;,\u0026#34;); i++; } } system.out.println(\u0026#34;]\u0026#34;); } system.out.println(\u0026#34;]\u0026#34;); } // --------------------------------------------------- 加密算法 ------------------------------------------------------------------- // ------------------------------ 格式化工具 ---------------------------------- /** * 将给定二进制明文转换为4*4的矩阵,矩阵中的元素按照列优先的顺序排列 * * @param plaintext 长度位128bit的明文数据,或者密钥数据 * @return 将128bit的数据平均分成16份,按照列优先的顺序存储到一个4*4的矩阵中返回。 */ private static int[][] getmatrix(string plaintext) { int[][] res = new int[4][4]; for (int i = 0; i \u0026lt; plaintext.length(); ) { stringbuilder bytes = new stringbuilder(); for (int j = 0; j \u0026lt; 8; j++) { bytes.append(plaintext.charat(i++)); } int row = ((i - 1) / 8) % 4; int col = ((i - 1) / 32) % 4; res[row][col] = integer.parseint(bytes.tostring(), 2); } return res; } /** * 将明文转化的矩阵还原为明文,是getmatrix()函数的逆过程 * * @param matrix 明文转化的矩阵 * @return 还原的矩阵 */ private static string matrixtostring(int[][] matrix) { stringbuilder builder = new stringbuilder(); for (int i = 0; i \u0026lt; 4; i++) { for (int j = 0; j \u0026lt; 4; j++) { builder.append(tostring(matrix[j][i], 2, 8)); } } return builder.tostring(); } // ------------------------------ 主要加密过程 ---------------------------------- /** * 初始变换 * 将明文对应的矩阵和密钥转化的矩阵取异或(混淆) * * @return 返回异或的结果 */ private static int[][] initialround(string plaintext, string key) { int[][] res = new int[4][4]; // 将明文和密钥化为矩阵 int[][] p = getmatrix(plaintext); int[][] k = getmatrix(key); // 将明文矩阵和密钥矩阵按列异或 res = xor(p, k); return res; } // 按列异或 private static int[][] xor(int[][] a, int[][] b) { int[][] res = new int[4][4]; for (int i = 0; i \u0026lt; 4; i++) { for (int j = 0; j \u0026lt; 4; j++) { res[j][i] = a[j][i] ^ b[j][i]; } } return res; } /** * 前九轮加密 * 加密算法的核心,每次需要四个步骤 * 1. 字节代换 * 2. 行移位 * 3. 列混合 * 4. 轮密钥加 * * @param initialround 明文经过初始变换后的到的密文 * @param subkeys 一个密钥和十个子密钥 * @return 明文九轮变换后的密文 */ private static int[][] firstninerounds(int[][] initialround, int[][][] subkeys) { // for (int i = 1; i \u0026lt;= 9; i++) { int[][] subbytes = subbytes(initialround); int[][] shiftrows = shiftrows(subbytes); int[][] mixcolumns = mixcolumns(shiftrows); initialround = addroundkey(mixcolumns, subkeys[i]); } return initialround; } /** * 最后一轮加密 * 与前九轮加密相比,少了列混合这一步 * * @param initialround 明文经过初始变换后的到的密文 * @param subkeys 一个密钥和十个子密钥 * @return 第十轮加密后的密文,即最终密文 */ private static int[][] lastround(int[][] initialround, int[][][] subkeys) { int[][] subbytes = subbytes(initialround); int[][] shiftrows = shiftrows(subbytes); initialround = addroundkey(shiftrows, subkeys[10]); return initialround; } // ------------------------------ 十轮加密的细节 ---------------------------------- /** * 字节代换 * 由于明文矩阵中每个元素由2个十六进制的数字表示,以高位上的十六进制数字位行号,低位上的十六进制数字位列号,查询s盒 * 将s盒中对应位置的元素作为 字节代换后的元素。将整个矩阵都进行该代换即可得到字节代换后的密文矩阵 * * @param initialround 待加密矩阵 * @return 待加密矩阵经过字节代换后得到的矩阵 */ private static int[][] subbytes(int[][] initialround) { int[][] res = new int[4][4]; for (int i = 0; i \u0026lt; 4; i++) { for (int j = 0; j \u0026lt; 4; j++) { // 获取 行列索引 int row = initialround[j][i] \u0026gt;\u0026gt;\u0026gt; 4 \u0026amp; 0b1111; int col = initialround[j][i] \u0026amp; 0b1111; res[j][i] = sbox[row][col]; } } return res; } /** * 行移位 * 对于字节代换后的矩阵,将其第i行的元素循环左移i位(i = 0,1,2,3) * * @return 移位后的矩阵 */ private static int[][] shiftrows(int[][] subbytes) { for (int i = 1; i \u0026lt; 4; i++) { subbytes[i] = leftmove(subbytes[i], i); } return subbytes; } // 反转指定位置的字符串 private static int[] reversestr(int[] arr, int start, int end) { while (start \u0026lt; end) { int temp = arr[start]; arr[start] = arr[end]; arr[end] = temp; start++; end--; } return arr; } // 将字符串向左循环移动 n 位 private static int[] leftmove(int[] arr, int n) { arr = reversestr(arr, 0, n - 1); arr = reversestr(arr, n, arr.length - 1); return reversestr(arr, 0, arr.length - 1); } /** * 列混合 * 将行移位后的矩阵左乘一个给定的矩阵 mixcol 这里的乘法是定义在 优先于gf(2^8)上的乘法 * * @param shiftrows 行移位后的矩阵 * @return 列混合后的矩阵 */ private static int[][] mixcolumns(int[][] shiftrows) { int[][] res = new int[4][4]; int i, j, k; for (i = 0; i \u0026lt; 4; i++) { for (j = 0; j \u0026lt; 4; j++) { for (k = 0; k \u0026lt; 4; k++) { res[i][j] ^= multiple(mixcol[i][k], shiftrows[k][j]); } } } return res; } // 有限域 gf(2^8) 上的乘法 private static int multiple(int a, int b) { int[] multiplier = new int[8]; // 获取乘数 multiplier[0] = a; int prior = a; for (int i = 1; i \u0026lt; 8; i++) { multiplier[i] = prior \u0026amp; 0b1111111; multiplier[i] \u0026lt;\u0026lt;= 1; if ((prior \u0026gt;\u0026gt;\u0026gt; 7 \u0026amp; 1) == 1) { multiplier[i] ^= 0b00011011; } prior = multiplier[i]; } int res = 0; for (int i = 0; i \u0026lt; 8; i++) { if ((b \u0026amp; 1) == 1) { res = res ^ multiplier[i]; } b \u0026gt;\u0026gt;\u0026gt;= 1; } return res; } /** * 轮密钥加 * 将列混合后的矩阵与对应的子密钥按列进行异或 * * @param mixcolumns 列混合后的矩阵 * @param subkey 本轮加密对应的子密钥 * @return 轮密钥加后的矩阵 */ private static int[][] addroundkey(int[][] mixcolumns, int[][] subkey) { int[][] res = new int[4][4]; for (int i = 0; i \u0026lt; 4; i++) { for (int j = 0; j \u0026lt; 4; j++) { res[i][j] = mixcolumns[i][j] ^ subkey[i][j]; } } return res; } // --------------------------------------------------- 密钥扩展算法 --------------------------------------------------------------------- /** * 密钥扩展算法 * 通过给定的16个字节密钥,生成一个176字节的轮密钥,里面包括原密钥和其生成的十个子密钥(每个子密钥都是16个字节) * * @param key 密钥 * @return 子密钥集合 */ private static int[][][] getsubkeys(int[][] key) { int[][][] subkeys = new int[11][4][4]; // 分别获取十个子密钥 int[][] w = new int[4][44]; for (int i = 0; i \u0026lt; 4; i++) { for (int j = 0; j \u0026lt; 4; j++) { w[i][j] = key[i][j]; } } int round = 0; for (int i = 4; i \u0026lt; 44; i++) { if (i % 4 != 0) { for (int j = 0; j \u0026lt; 4; j++) { w[j][i] = w[j][i - 4] ^ w[j][i - 1]; } } else { int[] wi = new int[]{w[0][i - 1], w[1][i - 1], w[2][i - 1], w[3][i - 1]}; int[] t = t(wi, round++); for (int j = 0; j \u0026lt; 4; j++) { w[j][i] = w[j][i - 4] ^ t[j]; } } } // 密钥何其生成的十个子密钥 for (int k = 0; k \u0026lt; 11; k++) { for (int i = 0; i \u0026lt; 4; i++) { for (int j = k * 4; j \u0026lt; k * 4 + 4; j++) { subkeys[k][i][j % 4] = w[i][j]; } } } return subkeys; } /** * t函数 * 当i是四的倍数时,用来处理w[i-1],以增加密码的安全性 * * @param wi 待处理的列向量,该向量的i是四的倍数 * @param round aes一共要进行十轮的加密,round记录现在是第几轮加密(round从1开始) * @return 处理后的列向量 */ private static int[] t(int[] wi, int round) { int[] wordcycle = wordcycle(wi); int[] sboxreplace = sboxreplace(wordcycle); return roundconstantxor(sboxreplace, round); } // t函数 ----- 字循环 private static int[] wordcycle(int[] wi) { return leftmove(wi, 1); } // t函数 ----- sbox代换 private static int[] sboxreplace(int[] wordcycle) { for (int i = 0; i \u0026lt; 4; i++) { // 获取 行列索引 int row = wordcycle[i] \u0026gt;\u0026gt;\u0026gt; 4 \u0026amp; 0b1111; int col = wordcycle[i] \u0026amp; 0b1111; wordcycle[i] = sbox[row][col]; } return wordcycle; } // t函数 ----- 轮常量异或 private static int[] roundconstantxor(int[] sboxreplace, int round) { for (int i = 0; i \u0026lt; 4; i++) { sboxreplace[i] ^= rc[i][round]; } return sboxreplace; } // --------------------------------------------------- 解密算法 ------------------------------------------------------------------- private static int[][] invinitialround(string ciphertext, int[][] key) { int[][] res; // 将明文和密钥化为矩阵 int[][] c = getmatrix(ciphertext); // 将明文矩阵和密钥矩阵按列异或 res = xor(c, key); return res; } private static int[][] invfirstninerounds(int[][] initialround, int[][][] subkeys) { // for (int i = 1; i \u0026lt;= 9; i++) { int[][] invshiftrows = invshiftrows(initialround); int[][] invsubbytes = invsubbytes(invshiftrows); int[][] invaddroundkey = invaddroundkey(invsubbytes, subkeys[10 - i]); initialround = invmixcolumns(invaddroundkey); } return initialround; } private static int[][] invshiftrows(int[][] initialround) { for (int i = 1; i \u0026lt; 4; i++) { initialround[i] = leftmove(initialround[i], 4 - i); } return initialround; } private static int[][] invsubbytes(int[][] invshiftrows) { int[][] res = new int[4][4]; for (int i = 0; i \u0026lt; 4; i++) { for (int j = 0; j \u0026lt; 4; j++) { // 获取 行列索引 int row = invshiftrows[j][i] \u0026gt;\u0026gt;\u0026gt; 4 \u0026amp; 0b1111; int col = invshiftrows[j][i] \u0026amp; 0b1111; res[j][i] = invsbox[row][col]; } } return res; } private static int[][] invaddroundkey(int[][] mixcolumns, int[][] subkey) { int[][] res = new int[4][4]; for (int i = 0; i \u0026lt; 4; i++) { for (int j = 0; j \u0026lt; 4; j++) { res[i][j] = mixcolumns[i][j] ^ subkey[i][j]; } } return res; } private static int[][] invmixcolumns(int[][] invaddroundkey) { int[][] res = new int[4][4]; int i, j, k; for (i = 0; i \u0026lt; 4; i++) { for (j = 0; j \u0026lt; 4; j++) { for (k = 0; k \u0026lt; 4; k++) { res[i][j] ^= multiple(invmixcol[i][k], invaddroundkey[k][j]); } } } return res; } private static int[][] invlastround(int[][] initialround, int[][][] subkeys) { int[][] invshiftrows = invshiftrows(initialround); int[][] invsubbytes = invsubbytes(invshiftrows); initialround = invaddroundkey(invsubbytes, subkeys[0]); return initialround; } // --------------------------------------------------- 128bit加密解密 ------------------------------------------------------------------- /** * 加密核心算法(一次加密128bit数据) * * @param plaintext 明文转化的二进制组成的字符串。 * @param key 密钥转化的以二进制表示的字符串 * @return 128bit明文加密得到的密文 */ private static string encryptcore(string plaintext, string key) { // 1. 初始变换 int[][] initialround = initialround(plaintext, key); // 2. 获取子密钥 int[][][] subkeys = getsubkeys(getmatrix(key)); // 3. 前九轮变换 int[][] firstninerounds = firstninerounds(initialround, subkeys); // 4. 第十轮变换 int[][] lastround = lastround(firstninerounds, subkeys); // 5. 将密文矩阵转为加密后的二进制字符串返回 return matrixtostring(lastround); } /** * 解密核心算法(一次解密128bit) * * @param ciphertext 密文 * @return 加密后的明文 */ private static string decryptcore(string ciphertext, string key) { // 1. 获取密钥 int[][][] subkeys = getsubkeys(getmatrix(key)); // 2. 轮密钥加 int[][] invinitialround = invinitialround(ciphertext, subkeys[10]); // 3. 逆九轮循环 int[][] invfirstninerounds = invfirstninerounds(invinitialround, subkeys); // 4. 逆最后一轮 int[][] invlastround = invlastround(invfirstninerounds, subkeys); // 5. 将密文转化为明文字符串 return matrixtostring(invlastround); } // --------------------------------------------------- 任意字符串加密解密 ------------------------------------------------------------------- /** * 加密任意长度的字符串 * * @param plaintext 明文,可以是任意字符串 * @param key 密钥,128bit的二进制组成的字符串 * @return plaintext经过密钥key加密后的密文 */ private static string encrypt(string plaintext, string key) { byte[] bytes = plaintext.getbytes(); int remainder = bytes.length % 16; stringbuilder res = new stringbuilder(); if (remainder != 0) { // 先将前面被十六整除的字节加密 for (int i = 0; i \u0026lt; bytes.length - remainder; ) { stringbuilder p = new stringbuilder(); for (int j = 0; j \u0026lt; 16; j++) { p.append(tostring(bytes[i++], 2, 8)); } string c = encryptcore(p.tostring(), key); res.append(c); } // 剩下的字节不够16个,用 \u0026#34;$\u0026#34; 即 36 填充到 16 个,并加密 stringbuilder temp = new stringbuilder(); for (int i = bytes.length - remainder; i \u0026lt; bytes.length; i++) { temp.append(tostring(bytes[i], 2, 8)); } for (int i = 0; i \u0026lt; 16 - remainder; i++) { temp.append(tostring(36, 2, 8)); } string c = encryptcore(temp.tostring(), key); res.append(c); } else { for (int i = 0; i \u0026lt; bytes.length; ) { stringbuilder p = new stringbuilder(); for (int j = 0; j \u0026lt; 16; j++) { p.append(tostring(bytes[i++], 2, 8)); } string c = encryptcore(p.tostring(), key); res.append(c); } } return res.tostring(); } /** * 解密密文,将其恢复为明文 * * @param ciphertext 密文 * @param key 密钥 * @return 明文 */ private static string decrypt(string ciphertext, string key) { stringbuilder res = new stringbuilder(); for (int i = 0; i \u0026lt; ciphertext.length(); ) { stringbuilder c = new stringbuilder(); for (int j = 0; j \u0026lt; 128; j++) { c.append(ciphertext.charat(i++)); } string p = decryptcore(c.tostring(), key); string string = binarytostring(p); res.append(string); } return res.tostring(); } /** * 佛南工具函数,将二进制数字转化为字符串 * * @param binary 用字符串转化的二进制 * @return 二进制转换为的字符串 */ private static string binarytostring(string binary) { int i = 0; int len = binary.length(); byte[] bytes = new byte[16]; int idx = 0; while (i \u0026lt; len) { stringbuilder builder = new stringbuilder(); boolean ispositve = true; ispositve = binary.charat(i++) == 1 ? false : true; for (int j = 0; j \u0026lt; 7; j++) { builder.append(binary.charat(i++)); } if (ispositve) { bytes[idx++] = byte.parsebyte(builder.tostring(), 2); } else { bytes[idx++] = byte.parsebyte(\u0026#34;-\u0026#34; + builder.tostring(), 2); } } return new string(bytes); } // --------------------------------------------------- 测试函数 ------------------------------------------------------------------- private static void testfunctions() { // int[][] matrix = getmatrix(\u0026#34;00000001\u0026#34; + \u0026#34;00100011\u0026#34; + \u0026#34;01010001\u0026#34; + \u0026#34;01100111\u0026#34; + \u0026#34;10001001\u0026#34; + \u0026#34;10101011\u0026#34; + \u0026#34;11001101\u0026#34; + \u0026#34;11101111\u0026#34; + \u0026#34;00000001\u0026#34; + \u0026#34;00100011\u0026#34; + \u0026#34;01000101\u0026#34; + \u0026#34;01100111\u0026#34; + \u0026#34;10001001\u0026#34; + \u0026#34;10101011\u0026#34; + \u0026#34;11001101\u0026#34; + \u0026#34;11101111\u0026#34;); // showbyhex(matrix); // string string = matrixtostring(matrix); // system.out.println(string); // int[][] key = getmatrix(\u0026#34;00000011\u0026#34; + \u0026#34;00100111\u0026#34; + \u0026#34;01010001\u0026#34; + \u0026#34;01100111\u0026#34; + \u0026#34;10001001\u0026#34; + \u0026#34;10101011\u0026#34; + \u0026#34;11001101\u0026#34; + \u0026#34;11101111\u0026#34; + \u0026#34;00000001\u0026#34; + \u0026#34;00100011\u0026#34; + \u0026#34;01000101\u0026#34; + \u0026#34;01100111\u0026#34; + \u0026#34;10001001\u0026#34; + \u0026#34;10101011\u0026#34; + \u0026#34;11001101\u0026#34; + \u0026#34;11101111\u0026#34;); // showbyhex(key); // int[][] xor = xor(matrix,key); // showbyhex(xor); // system.out.println(); // showbyhex(sbox); // int[][] subbytes = subbytes(matrix); // showbyhex(subbytes); // int[][] shiftrows = shiftrows(subbytes); // showbyhex(shiftrows); // int[][] invshiftrows = invshiftrows(subbytes); // showbyhex(invshiftrows); // int[][] addroundkey = addroundkey(matrix,subbytes); // showbyhex(matrix); // showbyhex(subbytes); // showbyhex(addroundkey); // // system.out.println(multiple(0b01101011,0b10001101)); // // system.out.println(mytools.tostring(184,2,8)); // int[][] col = new int[][]{ // {0xd4,0xe0,0xb8,0x1e},{0xbf,0xb4,0x41,0x27},{0x5d,0x52,0x11,0x98},{0x30,0xae,0xf1,0xe5} // }; // int[][] mixcolumns = mixcolumns(col); // showbyhex(mixcolumns); // int[] wordcycle = wordcycle(new int[]{1,2,3,4}); // for(int i : wordcycle){ // system.out.print(i + \u0026#34;,\u0026#34;); // } // system.out.println(); // int[] sboxreplace = sboxreplace(wordcycle); // for(int i : sboxreplace){ // system.out.print(integer.tobinarystring(i) + \u0026#34;,\u0026#34;); // } // system.out.println(); // int[] roundconstantxor = roundconstantxor(sboxreplace,0); // for(int i : roundconstantxor){ // system.out.print(integer.tobinarystring(i) + \u0026#34;,\u0026#34;); // } // int[][] key = new int[][]{ // {0x2b,0x28,0xab,0x09},// // {0x7e,0xae,0xf7,0xcf},// // {0x15,0xd2,0x15,0x4f},// // {0x16,0xa6,0x88,0x3c} // }; // int[][][] subkeys = getsubkeys(key); // for(int i = 0; i \u0026lt; subkeys.length; i++){ // showbyhex(subkeys[i]); // } // int[] parr = new int[]{0x32,0x43,0xf6,0xa8,0x88,0x5a,0x30,0x8d,0x31,0x31,0x98,0xa2,0xe0,0x37,0x07,0x34}; // stringbuilder p = new stringbuilder(); // for(int item : parr){ // p.append(mytools.tostring(item,2,8)); // } // int[] carr = new int[]{0x2b,0x7e,0x15,0x16,0x28,0xae,0xd2,0xa6,0xab,0xf7,0x15,0x88,0x09,0xcf,0x4f,0x3c}; // stringbuilder c = new stringbuilder(); // for(int item : carr){ // c.append(mytools.tostring(item,2,8)); // } // string ciphertext = encryptcore(p.tostring(),c.tostring()); // system.out.println(ciphertext); // showbyhex(getmatrix(ciphertext)); // string plaintext = decryptcore(ciphertext,c.tostring()); // system.out.println(plaintext); // showbyhex(getmatrix(plaintext)); // system.out.println(\u0026#34;---------------------------------------------------------\u0026#34;); } public static void main(string[] args) { // testfunctions(); string plain = \u0026#34;0123456789 aes i love you!\u0026#34;; string key = \u0026#34;10101110110110111010111011011011101011101101101110101110110110111010111011011011101011101101101110101110110110111010111011011011\u0026#34;; string cipher = encrypt(plain, key); system.out.println(\u0026#34;密文为:\\n\u0026#34; + cipher); plain = decrypt(cipher, key); system.out.println(\u0026#34;明文为:\\n\u0026#34; + plain); system.out.println(\u0026#34;注意,解密过程中,最后一部分不足16byte,末尾补$\u0026#34;); } }\r","date":"2024-07-14","permalink":"http://54rookie.com/posts/aes/","summary":"AES加密 DES加密的过程 具体的原理可以参考: 可以参考CSDN上的这篇文章 本代码参考《现代密码学概论》(仲红、潘恒、熊书明、王良民著) DES实现 文章给出了AES","title":"aes"},]
[{"content":"des加密 des加密的过程 具体的原理可以参考:\n可以参考csdn上的这篇文章 本代码参考《现代密码学概论》(仲红、潘恒、熊书明、王良民著) des实现 文章给出了des加密算法的一个实现。未做任何优化,仅供学习参考,计算速度跟专业的库相比应该有很大差距。虽然密码学算法是安全的,但是工程实现中可能存在其他攻击,没有足够的信心,不要使用自己写的加密算法做加密。\n/** * @author miracle * @date 2022/8/18 10:47 * @description: \u0026lt;br/\u0026gt; * des加密算法 * 可使用下面网站上的例子测试:\thttps://blog.csdn.net/m0_51088812/article/details/124407368 * -\t明文:string plaintext = \u0026#34;0000000100100011010001010110011110001001101010111100110111101111\u0026#34;; * -\t密钥:string key = \u0026#34;0001001100110100010101110111100110011011101111001101111111110001\u0026#34;; */ public class main { // ---------------------------------------------------- 准备工作 ------------------------------------------------------------------ //记录所有的子秘钥 string[] k = new string[16]; //记录左移次数 ,该数组长度位16 int[] leftmove = new int[]{ 1,1,2,2,2,2,2,2,1,2,2,2,2,2,2,1 }; // 8个 s盒 (8*16*4) int[][][] sbox = { //s1 { {14,4,13,1,2,15,11,8,3,10,6,12,5,9,0,7},// {0,15,7,4,14,2,13,1,10,6,12,11,9,5,3,8},// {4,1,14,8,13,6,2,11,15,12,9,7,3,10,5,0},// {15,12,8,2,4,9,1,7,5,11,3,14,10,0,6,13} }, //s2 { {15,1,8,14,6,11,3,4,9,7,2,13,12,0,5,10},// {3,13,4,7,15,2,8,14,12,0,1,10,6,9,11,5},// {0,14,7,11,10,4,13,1,5,8,12,6,9,3,2,15},// {13,8,10,1,3,15,4,2,11,6,7,12,0,5,14,9} }, //s3 { {10,0,9,14,6,3,15,5,1,13,12,7,11,4,2,8},// {13,7,0,9,3,4,6,10,2,8,5,14,12,11,15,1},// {13,6,4,9,8,15,3,0,11,1,2,12,5,10,14,7},// {1,10,13,0,6,9,8,7,4,15,14,3,11,5,2,12} }, //s4 { {7,13,14,3,0,6,9,10,1,2,8,5,11,12,4,15},// {13,8,11,5,6,15,0,3,4,7,2,12,1,10,14,9},// {10,6,9,0,12,11,7,13,15,1,3,14,5,2,8,4},// {3,15,0,6,10,1,13,8,9,4,5,11,12,7,2,14} }, //s5 { {2,12,4,1,7,10,11,6,8,5,3,15,13,0,14,9},// {14,11,2,12,4,7,13,1,5,0,15,10,3,9,8,6},// {4,2,1,11,10,13,7,8,15,9,12,5,6,3,0,14},// {11,8,12,7,1,14,2,13,6,15,0,9,10,4,5,3} }, //s6 { {12,1,10,15,9,2,6,8,0,13,3,4,14,7,5,11},// {10,15,4,2,7,12,9,5,6,1,13,14,0,11,3,8},// {9,14,15,5,2,8,12,3,7,0,4,10,1,13,11,6},// {4,3,2,12,9,5,15,10,11,14,1,7,6,0,8,13} }, //s7 { {4,11,2,14,15,0,8,13,3,12,9,7,5,10,6,1},// {13,0,11,7,4,9,1,10,14,3,5,12,2,15,8,6},// {1,4,11,13,12,3,7,14,10,15,6,8,0,5,9,2},// {6,11,13,8,1,4,10,7,9,5,0,15,14,2,3,12} }, //s8 { {13,2,8,4,6,15,11,1,10,9,3,14,5,0,12,7},// {1,15,13,8,10,3,7,4,12,5,6,11,0,14,9,2},// {7,11,4,1,9,12,14,2,0,6,10,13,15,3,5,8},// {2,1,14,7,4,10,8,13,15,12,9,0,3,5,6,11} } }; //p盒置换 int[][] p = new int[][]{ {16,7,20,21,29,12,28,17},// {1,15,23,26,5,18,31,10},// {2,8,24,14,32,27,3,9},// {19,13,30,6,22,11,4,25} }; //e扩展 int[][] extend = new int[][]{ {32,1,2,3,4,5},// {4,5,6,7,8,9},// {8,9,10,11,12,13},// {12,13,14,15,16,17},// {16,17,18,19,20,21},// {20,21,22,23,24,25},// {24,25,26,27,28,29},// {28,29,30,31,32,1} }; //秘钥置换 int[][] keyswap = new int[][]{ {14,17,11,24,1,5},// {3,28,15,6,21,10},// {23,19,12,4,26,8},// {16,7,27,20,13,2},// {41,52,31,37,47,55},// {30,40,51,45,33,48},// {44,49,39,56,34,53},// {46,42,50,36,29,32} }; //密文初始置换 int[][] ip = new int[][]{ {58,50,42,34,26,18,10,2,},// {60,52,44,36,28,20,12,4,},// {62,54,46,38,30,22,14,6,},// {64,56,48,40,32,24,16,8,},// {57,49,41,33,25,17,9,1,},// {59,51,43,35,27,19,11,3,},// {61,53,45,37,29,21,13,5,},// {63,55,47,39,31,23,15,7} }; //密文逆置换 int[][] ip_1 = new int[][]{ {40,8,48,16,56,24,64,32},// {39,7,47,15,55,23,63,31},// {38,6,46,14,54,22,62,30},// {37,5,45,13,53,21,61,29},// {36,4,44,12,52,20,60,28},// {35,3,43,11,51,19,59,27},// {34,2,42,10,50,18,58,26},// {33,1,41,9,49,17,57,25} }; //64-\u0026gt;56选择 int[][] pc_1 = new int[][]{ {57,49,41,33,25,17,9},// {1,58,50,42,34,26,18},// {10,2,59,51,43,35,27},// {19,11,3,60,52,44,36},// {63,55,47,39,31,23,15},// {7,62,54,46,38,30,22},// {14,6,61,53,45,37,29},// {21,13,5,28,20,12,4} }; //56-\u0026gt;48选择 int[][] pc_2 = new int[][]{ {14,17,11,24,1,5},// {3,28,15,6,21,10},// {23,19,12,4,26,8},// {16,7,27,20,13,2},// {41,52,31,37,47,55},// {30,40,51,45,33,48},// {44,49,39,56,34,53},// {46,42,50,36,29,32} }; // ---------------------------------------------------- 核心函数 ------------------------------------------------------------------ /** * 初始置换,即ip置换 * \u0026lt;p\u0026gt; * 这里给除一个测试字符串,用来测试ip置换是否正确: * -\tip置换前:plaintext = \u0026#34;0110001101011100001110111011010100010111100000010011100000100110\u0026#34; * -\tip置换后:plaintext = \u0026#34;0000001101011110100110100011110100101000110011010100011010010101\u0026#34; * * @param plaintext 明文 * @return 明文经过ip置换后得到的结果 */ private string ip(string plaintext){ return transfer(plaintext,ip); } // 初始逆置换,和初始置换类似 private string ip_1(string p){ return transfer(p,ip_1); } // ----------------------------------- 密钥生成算法 ------------------------------------- // 很多变换是类似的,用该函数表示这一类变换 private string transfer(string str,int[][] matrix){ stringbuilder builder = new stringbuilder(str); int idx = 0; for(int[] rows : matrix){ for(int row : rows){ builder.setcharat(idx++,str.charat(row - 1)); } } return builder.tostring(); } /** * 对密钥进行 pc_1置换,去除64bit数据中的8bit校验位,得到 56bit的密钥 * \u0026lt;p\u0026gt; * 这里给除一个测试字符串,用来测试 pc_1 置换是否正确: * -\tpc_1置换前:key = \u0026#34;0001001100110100010101110111100110011011101111001101111111110001\u0026#34; * -\tpc_1置换后:key = \u0026#34;11110000110011001010101011110101010101100110011110001111\u0026#34; * * @param key 含校验位的密钥 * @return 不含校验位的密钥 */ private string pc_1(string key){ return transfer(key,pc_1).substring(0,56); } /** * 对 pc_1的到的结果进行 pc_2置换,得到 48bit的子密钥 * \u0026lt;p\u0026gt; * 这里给除一个测试字符串,用来测试 置换是否正确: * -\t输入一: c = \u0026#34;1111000011001100101010101111\u0026#34; * -\t输入二: d = \u0026#34;0101010101100110011110001111\u0026#34; * -\t返回的: k = \u0026#34;\u0026#34; * * @param c pc_1置换结果的左半部分(即左边28bit) * @param d pc_1置换结果的右半部分(即右边28bit) * @return 返回一个子密钥 */ private string pc_2(string c,string d){ string key = c + d; return transfer(key,pc_2).substring(0,48); } private string[] getsubkeys(string key){ // pc_1去掉校验位 key = pc_1(key); // string c = key.substring(0,28); string d = key.substring(28,56); //记录所有的子秘钥 string[] k = new string[16]; // 依次获取子密钥 for(int i = 0; i \u0026lt; leftmove.length; i++){ // 循环左移 int move_len = leftmove[i]; c = leftmove(c,move_len); d = leftmove(d,move_len); // pc_2产生子密钥 k[i] = pc_2(c,d); } return k; } // 反转指定位置的字符串 public string reversestr(string str,int start,int end){ char[] chars = str.tochararray(); while(start \u0026lt; end){ char temp = chars[start]; chars[start] = chars[end]; chars[end] = temp; start++; end--; } return new string(chars); } // 将字符串向左循环移动 n 位 public string leftmove(string str,int n){ str = reversestr(str,0,n - 1); str = reversestr(str,n,str.length() - 1); return reversestr(str,0,str.length() - 1); } // ----------------------------------- f(ri,k(i+1))函数 ------------------------------------- /** * f函数 * * @param ri 输入的32bit数据 * @param subkey 用来异或的子密钥 * @return 经f桉树处理后的数据 */ private string f(string ri,string subkey){ // 1. e扩展 string extend = extend(ri); // 2. 与子密钥异或 string xor = xor(extend,subkey); // 3. s盒压缩 string sbox = sbox(xor); // 4. p置换后返回 return permute(sbox); } private string xor(string extend,string subkey){ stringbuilder builder = new stringbuilder(); for(int i = 0; i \u0026lt; extend.length(); i++){ if(extend.charat(i) == subkey.charat(i)){ builder.append(0); } else{ builder.append(1); } } return builder.tostring(); } /** * e 扩展,将 32bit的ri扩展为48bit * \u0026lt;p\u0026gt; * 这里给除一个测试字符串,用来测试 pc_1 置换是否正确: * -\t扩展前:ri = \u0026#34;11010001001101000010001100111011\u0026#34; * -\t扩展后:ri = \u0026#34;111010100010100110101000000100000110100111110111\u0026#34; * * @param ri feistel体制中,f函数的一个输入 * @return 返回e扩展后的 ri */ private string extend(string ri){ stringbuilder builder = new stringbuilder(ri).append(new char[16]); ri = builder.tostring(); return transfer(ri,extend); } /** * s盒压缩 * 将输入的48bit的ri分成8组,每组含有6个字符。 * 1. 对于一个组号为i的分组中的六个字符,取首字符和尾字符组成一个二进制的数字,将其转化为十进制作为s盒的行号row * 2. 剩下的中间4个字符组成的二进制数转为十进制作为s盒的列号col * 3. 则 将 sbox[i][row][col] 转化为长度为4的二进制(不够四位,高位补零),这样就把6个字符压缩为4个字符 * 4. 重复1,2,3将八组全部转换后,即可将48bit的数据压缩为32bit * * @param ri e扩展后的数据与对应的子密钥异或得到的结果 * @return 压缩后的数据 */ private string sbox(string ri){ stringbuilder res = new stringbuilder(); string[] group = new string[8]; for(int i = 0; i \u0026lt; 8; i++){ group[i] = ri.substring(i * 6,i * 6 + 6); } int i = 0; for(string str : group){ string rowstr = new string(new char[]{str.charat(0),str.charat(5)}); int row = integer.parseint(rowstr,2); string colstr = str.substring(1,5); int col = integer.parseint(colstr,2); int num = sbox[i++][row][col]; res.append(tofixedlengthbinary(num,4)); } return res.tostring(); } /** * 将一个给定的整数num转换为长度为 size 的二进制,如果转化后长度不够,在高位补零 * * @param num 待转化的十进制数 * @param size 转化后的二进制数长度 * @return num转化而来的定长二进制数 */ private string tofixedlengthbinary(int num,int size){ stringbuilder builder = new stringbuilder(); for(int i = size - 1; i \u0026gt;= 0; i--){ builder.append(num \u0026gt;\u0026gt;\u0026gt; i \u0026amp; 1); } return builder.tostring(); } /** * p置换 * 对s盒处理后的数据进行p置换得到f函数的输出 * * @param rk s盒变换后得到的字符串 * @return p置换后得到的字符串r(i + 1) */ private string permute(string rk){ return transfer(rk,p); } // ----------------------------------- feistel结构 ------------------------------------- /** * 单轮 feistel * * @param ip 经过初始置换(ip)得到的密文 * @return 单论feistel置换后的密文 */ private string feistel(string ip,string subkey){ // 将 ip 分为左右两部分,每部分长度32bit string l = ip.substring(0,32); string r = ip.substring(32,64); // 获取一轮feistel后的结果 return r + xor(l,f(r,subkey)); } // ----------------------------------- 加密解密函数 ------------------------------------- /** * des加密函数 * * @param plaintext 明文 * @param key 密钥 * @return 密文 */ private string encryptcore(string plaintext,string key){ // 获取16个子密钥 string[] subkeys = getsubkeys(key); // 1. 初始ip置换 string ip = ip(plaintext); // 2. 16轮 feistel 迭代 for(int i = 0; i \u0026lt; 16; i++){ ip = feistel(ip,subkeys[i]); } // 3. 32bit互换 ip = ip.substring(32,64) + ip.substring(0,32); // 3. ip逆置换得到密文并返回 return ip_1(ip); } private string decryptcore(string cipher,string key){ // 获取16个子密钥 string[] subkeys = getsubkeys(key); // 1. 初始ip置换 string ip = ip(cipher); // 2. 16轮 feistel 迭代 for(int i = 15; i \u0026gt;= 0; i--){ ip = feistel(ip,subkeys[i]); } // 3. 32bit互换 ip = ip.substring(32,64) + ip.substring(0,32); // 3. ip逆置换得到密文并返回 return ip_1(ip); } // ----------------------------------- 加密解密数据 ------------------------------------- /** * 加密字符串 * 可以优化,子密钥不用每次都求,这里不再优化 * * @param plaintext * @param key * @return */ private string encrypt(string plaintext,string key){ byte[] bytes = plaintext.getbytes(); int remainder = bytes.length % 8; stringbuilder res = new stringbuilder(); if(remainder != 0){ // 先将前面被八整除的字节加密 for(int i = 0; i \u0026lt; bytes.length - remainder; ){ stringbuilder p = new stringbuilder(); for(int j = 0; j \u0026lt; 8; j++){ p.append(tofixedlengthbinary(bytes[i++],8)); } string c = encryptcore(p.tostring(),key); res.append(c); } // 剩下的字节不够8个,用 \u0026#34;$\u0026#34; 即 36 填充到 8 个,并加密 stringbuilder temp = new stringbuilder(); for(int i = bytes.length - remainder; i \u0026lt; bytes.length; i++){ temp.append(tofixedlengthbinary(bytes[i],8)); } for(int i = 0; i \u0026lt; 8 - remainder; i++){ temp.append(tofixedlengthbinary(36,8)); } string c = encryptcore(temp.tostring(),key); res.append(c); } else{ for(int i = 0; i \u0026lt; bytes.length; ){ stringbuilder p = new stringbuilder(); for(int j = 0; j \u0026lt; 8; j++){ p.append(tofixedlengthbinary(bytes[i++],8)); } string c = encryptcore(p.tostring(),key); res.append(c); } } return res.tostring(); } private string decrypt(string cipher,string key){ stringbuilder res = new stringbuilder(); for(int i = 0; i \u0026lt; cipher.length();){ stringbuilder c = new stringbuilder(); for(int j = 0; j \u0026lt; 64; j++){ c.append(cipher.charat(i++)); } string p = decryptcore(c.tostring(),key); string string = binarytostring(p); res.append(string); } return res.tostring(); } /** * 将字符串表示的64bit带符号的二进制转化为byte * @param binary * @return */ private string binarytostring(string binary){ int i = 0; int len = binary.length(); byte[] bytes = new byte[8]; int idx = 0; while(i \u0026lt; len){ stringbuilder builder = new stringbuilder(); boolean ispositve = true; ispositve = binary.charat(i++) == 1 ? false : true; for(int j = 0; j \u0026lt; 7; j++){ builder.append(binary.charat(i++)); } if(ispositve){ bytes[idx++] = byte.parsebyte(builder.tostring(),2); }else { bytes[idx++] = byte.parsebyte(\u0026#34;-\u0026#34; + builder.tostring(),2); } } return new string(bytes); } // ----------------------------------- 主函数 ------------------------------------- public static void main(string[] args){ main des = new main(); // 加密核心函数的测试 string plaintext = \u0026#34;0000000100100011010001010110011110001001101010111100110111101111\u0026#34;; string key = \u0026#34;0001001100110100010101110111100110011011101111001101111111110001\u0026#34;; string cipher = des.encryptcore(plaintext,key); system.out.println(\u0026#34;加密后的密文: \u0026#34; + cipher); string decrypt = des.decryptcore(cipher,key); system.out.println(\u0026#34;解密后的原文: \u0026#34; + decrypt); // 加密字符串测试 plaintext = \u0026#34;0123456789 des i love you!\u0026#34;; string c = des.encrypt(plaintext,key); system.out.println(\u0026#34;将字符串加密得: \u0026#34; + c); string p = des.decrypt(c,key); system.out.println(\u0026#34;将字符串解密得: \u0026#34; + p); system.out.println(\u0026#34;注意,解密过程中,最后一部分不足16byte,末尾补$\u0026#34;); } }\r","date":"2024-07-14","permalink":"http://54rookie.com/posts/des/","summary":"DES加密 DES加密的过程 具体的原理可以参考: 可以参考CSDN上的这篇文章 本代码参考《现代密码学概论》(仲红、潘恒、熊书明、王良民著) DES实现 文章给出了DES","title":"des"},]
[{"content":"git 常用知识 一、git简介 git是一个快速、可扩展的分布式版本控制系统,它具有极为丰富的命令集,对内部系统提供了高级操作和完全访问。git像是把发生变化的文件作快照后,记录在一个微型的文件系统中。每次提交更新时,它会纵览一遍所有文件的指纹信息并对文件作一快照,然后保存一个指向这次快照的索引。为提高性能,若文件没有变化,git不会再次保存,而只对上次保存的快照作一链接。git的工作方式就像下图所示:\n对于任何一个文件,在git内都只有三种状态: - 已修改(modified): 已修改表示修改了某个文件,但还没有提交保存, - 已暂存(staged): 已暂存表示把已修改的文件放在暂存区中;add - 己提交(committed): 已提交表示该文件已经被安全地保存在本地数据库中了。commit\n由此我们看到gt管理项目时,文件流转的三个工作区域:的工作区,暂存区,以及本地库。\n工作区:我们在本地创建的文件都存储在工作区之中,可以在这里进行文件的各种操作。我们在工作区进行的各种修改并不会被记录到 git 的版本库中。如果想要其被记录,首先需要将文件添加到暂存区中。\n暂存区:由于一个项目可能会涉及到很多文件的修改,如果每次修改一个文件就作为一个版本的话,不符合实际的版控制需求。因此,我们开发项目的时候,首先将修改的文件依次加入到暂存区之中,最后统一提交到本地库中,作为该项目的一个版本进行管理。\n本地库:版本库,用户将一个项目完成之后,就将其提交到本地库。git 就会将其作为一个版本维护起来。提交到本地库的项目就有了一个版本号,相当于有了一个备份。以后再对文件进行修改得到一个新的版本,这个老版本也不会消失。\n分支功能: 在版本控制过程中,同时推进多个任务,对于每个任务,我们可以创建一个单独的分支。使用分支意味着程序员可以把自己的工作从开发主线上分离开来。程序员开发自己分支的时候,不会影响主线分支的运行,分支开发完成之后,再将分支合并到主分支即可完成版本升级。对于初学者而言,分支可以简单理解为副本,一个分支就是一个单独的副本。(分支底层其实也是指针的引用)\n对于上图来说,假如客户在玩一款游戏,这个游戏运行在master分支服务器上。此时,程序员为了进行游戏的更新,需要进一步进行游戏开发,测试等工作。此时显然不能在master分支上直接开发,否则将会影响用户当前的体验。为此,开发人员客可以创建一个新的分支branch1,并在branch1上进行游戏开发。同理,测试人员同样可以创建一个新的分支dev来进行测试。当开发人员和测试人员确定新开发的游戏没有问题了之后,可以将自己的分支合并到master分支上,从而完成游戏的版本迭代。\n二、git的历史。 三、最常用的 git 命令 git 的配置: 更改设置。可以是版本库的设置,也可以是系统的或全局的\n# 显示当前的git配置 $ git config --list # 输出、设置基本的全局变量,格式如下: # git config --global user.email [邮箱] # git config --global user.name [用户名] $ git config --global user.email \u0026#34;[email protected]\u0026#34; $ git config --global user.name \u0026#34;my name\u0026#34; # 定义当前用户所有提交使用的作者邮箱。 $ git config --global alias.\u0026lt;alias-name \u0026lt;git-command\u0026gt; # 为git命令创建一个快捷方式(别名)。 $ git config --system core.editor \u0026lt;editor\u0026gt;\r最简单的版本控制 创建git仓库 想要使用 git 进行版本控制,首先必须告诉 git 、你想要对那个项目(文件夹)进行版本控制。实际就是告诉 git 你将哪个文件夹作为 git 仓库。 在 git 中,使用 git init 来初始化一个版本库,这个版本库的配置、存储等信息会被保存到 .git 这个文件夹中。ps: .git 文件夹默认情况下是隐藏的,想要可视化的查看,需要显示隐藏文件。\n# 初始化当前项目(文件夹)为 git版本库 $ git init # 新建一个目录,并将其初始化为git代码库 $ git init [project-name] # 下载一个项目和它的整个代码历史,这个命令就是将一个版本库拷贝到另一个目录中, # 同时也将分支都拷贝到新的版本库中。这样就可以在新的版本库中提交到远程分支 $ git clone [url]\r查看本地库状态 用来查看 git 仓库目前的状态。包括当前在哪个分支,有哪些未跟踪的文件,哪些文件被修改了等。\n# 显示分支,未跟踪文件,更改和其他不同 $ git status # 查看其他的git status的用法 $ git help status\r添加文件暂存区 添加文件到暂存区中。执行commit命令时,是将暂存区的文件提交到版本库。因此,需要先将修改的文件存储到暂存区。\n# 添加一个文件 $ git add test.js # 添加多个文件到暂存区 $ git add [file1] [file2] ... # 添加一个子目录中的文件 $ git add /path/to/file/test.js # 支持正则表达式 $ git add ./*.js # 添加指定目录到暂存区,包括子目录 $ git add [dir] # 添加当前目录的所有文件到暂存区 $ git add .\r从暂存区删除文件: rm 和上面的 add 命令相反,从工作空间中去掉某个文件\n# 移除 helloworld.js $ git rm helloworld.js # 删除工作区文件,并且将这次删除放入暂存区 $ git rm [file1] [file2] ... # 移除子目录中的文件 $ git rm /pather/to/the/file/helloworld.js\r提交文件到本地库 commit 将当前索引的更改保存为一个新的提交,这个提交包括用户做出的更改与信息\n# 提交暂存区到仓库区附带提交信息 $ git commit -m [message] # 提交暂存区的指定文件到仓库区 $ git commit [file1] [file2] ... -m [message] # 提交工作区自上次commit之后的变化,直接到仓库区 $ git commit -a # 提交时显示所有diff信息 $ git commit -v\r显示版本库的提交日志 # 显示所有提交 $ git log # 显示某几条提交信息 $ git log -n 10 # 显示提交的简略信息 $ git reflog\r版本穿梭 # 重置当前分支的head为指定commit,同时重置暂存区和工作区,与指定commit一致 $ git reset --hard [commit] # 重置暂存区的指定文件,与上一次commit保持一致,但工作区不变 $ git reset [file] # 重置暂存区与工作区,与上一次commit保持一致 $ git reset --hard # 重置当前分支的指针为指定commit,同时重置暂存区,但工作区不变 $ git reset [commit]\r模块化版本控制(分支) # 参看分支 $ git branch -v # 创建分支 $ git branch 分支名 # 切换分支 $ git checkout 分支名 # 将指定分支合并到当前分支上 $ git merge 分支名 # 删除本地分支 $ git branch -d localbranchname # 删除远程分支 $ git push [远程库别名,默认是origin] --delete remotebranchname\r四、团队协作 团队内协作 想要使用git进行团队协作,首先需要一个代码托管中心【远程库,如github】。团队中的成员a将自己的本地库首先**推送(push)到远程库。团队内的成员b就可以从远程库将成员a存储在远程库的代码克隆(clone)下来,随后对克隆的代码进行编辑。当成员b将代码修改好了以后,可以将修改后的代码推送(pull)**到远程库中。最后,成员a从远程库将成员b修改之后的代码拉取回来。这样就完成了最简单的团队合作。\n要想继进行团队合作,首先需要创建一个远程库。这个步骤可以在 github 首页通过可视化的界面进行操作。具体的操作过程可以参考:\n远程同步设置 远程同步的远端分支\n# 显示所有远程仓库 $ git remote -v # 显示某个远程仓库的信息 $ git remote show [remote] # 增加一个新的远程仓库,并命名 $ git remote add [shortname] [url] # 添加远程仓库地址 $ git remote add origin git@ github:xxx/xxx.git # 设置远程仓库地址(用于修改远程仓库地址) $ git remote set-url origin git@ github.com:xxx/xxx.git # 删除远程仓库 $ git remote rm \u0026lt;repository\u0026gt;\r往远程库推送代码 # 上传本地指定分支到远程仓库 # 把本地的分支更新到远端origin的master分支上 # git push \u0026lt;远端 \u0026lt;分支\u0026gt; # git push 相当于 git push origin master $ git push [remote] [branch] # 强行推送当前分支到远程仓库,即使有冲突 $ git push [remote] --force # 推送所有分支到远程仓库 $ git push [remote] --all\r从远程库拉取代码 # 从远端origin的master分支更新版本库 # git pull \u0026lt;远端 \u0026lt;分支\u0026gt; $ git pull origin master # 抓取远程仓库所有分支更新并合并到本地,不要快进合并 $ git pull --no-ff # 当两个分支是两个不同的版本,具有不同的提交历史时,直接拉取会出现: # fatal: refusing to merge unrelated histories. 此时使用下面的方法解决 $ git pull origin master --allow-unrelated-histories\r去远程库克隆代码 # 从指定远程库地址克隆项目 $ git clone \u0026lt;repository url\u0026gt; # 当远程库有多个分支时候,选择指定的分支进行克隆 $ git clone -b \u0026lt;branch name\u0026gt;\u0026lt;repository url\u0026gt;\r跨团队协作 在进行跨团队合作时,过程会更加复杂一点。 由于不同的团队之间是互相不信任的,因此团队b不能直接从团队a的远程库克隆代码。此时,b先把a的代码叉(fork)到自己的远程之中,随后,团队b的人就可以从自己的远程库将代码克隆下来并进行修改了。当b中的成员将代码修改完成之后,他可以将修改好的代码推送到自己的远程库之中。随后,团队b向团队a发送一个拉取请求(pull request),团队a的成员对修改后的代码进行审核,如果觉得代码没有问题,就可以将这些代码**合并(merge)到自己的远程库中,从而使得团队a的成员可以从远程库拉取(pull)**团队b修改后的代码。这样就完成了跨团队之间的协作。\n五、ssh免密登录 彩蛋: 一台电脑双github账户配置: 有时候可能需要在一个电脑上搞两个github账号,一个用来正常的和别人合作项目,另一个用来自己写一些代码,希望能做到两个本地账户和远程账号都完全隔离,没有联系,也不会被混淆使用。该效果的实现方式参考下面的文章:\n参考文章:https://zhuanlan.zhihu.com/p/107341502 六、git常用命令大全 总结一下 git 中常用的命令及其功能。\ngit init # 初始化本地git仓库(创建新仓库) git config --global user.name \u0026#34;xxx\u0026#34; # 配置用户名 git config --global user.email \u0026#34;[email protected]\u0026#34; # 配置邮件 git config --global color.ui true # git status等命令自动着色 git config --global color.status auto git config --global color.diff auto git config --global color.branch auto git config --global color.interactive auto git config --global --unset http.proxy # remove proxy configuration on git git clone git+ssh://[email protected]/vt.git # clone远程仓库 git status # 查看当前版本状态(是否修改) git add xyz # 添加xyz文件至index git add . # 增加当前子目录下所有更改过的文件至index git commit -m \u0026#39;xxx\u0026#39; # 提交 git commit --amend -m \u0026#39;xxx\u0026#39; # 合并上一次提交(用于反复修改) git commit -am \u0026#39;xxx\u0026#39; # 将add和commit合为一步 git rm xxx # 删除index中的文件 git rm -r * # 递归删除 git log # 显示提交日志 git log -1 # 显示1行日志 -n为n行 git log -5 git log --stat # 显示提交日志及相关变动文件 git log -p -m git show dfb02e6e4f2f7b573337763e5c0013802e392818 # 显示某个提交的详细内容 git show dfb02 # 可只用commitid的前几位 git show head # 显示head提交日志 git show head^ # 显示head的父(上一个版本)的提交日志 ^^为上两个版本 ^5为上5个版本 git tag # 显示已存在的tag git tag -a v2.0 -m \u0026#39;xxx\u0026#39; # 增加v2.0的tag git show v2.0 # 显示v2.0的日志及详细内容 git log v2.0 # 显示v2.0的日志 git diff # 显示所有未添加至index的变更 git diff --cached # 显示所有已添加index但还未commit的变更 git diff head^ # 比较与上一个版本的差异 git diff head -- ./lib # 比较与head版本lib目录的差异 git diff origin/master..master # 比较远程分支master上有本地分支master上没有的 git diff origin/master..master --stat # 只显示差异的文件,不显示具体内容 git remote add origin git+ssh://[email protected]/vt.git # 增加远程定义(用于push/pull/fetch) git branch # 显示本地分支 git branch --contains 50089 # 显示包含提交50089的分支 git branch -a # 显示所有分支 git branch -r # 显示所有原创分支 git branch --merged # 显示所有已合并到当前分支的分支 git branch --no-merged # 显示所有未合并到当前分支的分支 git branch -m master master_copy # 本地分支改名 git checkout -b master_copy # 从当前分支创建新分支master_copy并检出 git checkout -b master master_copy # 上面的完整版 git checkout features/performance # 检出已存在的features/performance分支 git checkout --track hotfixes/bjvep933 # 检出远程分支hotfixes/bjvep933并创建本地跟踪分支 git checkout v2.0 # 检出版本v2.0 git checkout -b devel origin/develop # 从远程分支develop创建新本地分支devel并检出 git checkout -- readme # 检出head版本的readme文件(可用于修改错误回退) git merge origin/master # 合并远程master分支至当前分支 git cherry-pick ff44785404a8e # 合并提交ff44785404a8e的修改 git push origin master # 将当前分支push到远程master分支 git push origin :hotfixes/bjvep933 # 删除远程仓库的hotfixes/bjvep933分支 git push --tags # 把所有tag推送到远程仓库 git fetch # 获取所有远程分支(不更新本地分支,另需merge) git fetch --prune # 获取所有原创分支并清除服务器上已删掉的分支 git pull origin master # 获取远程分支master并merge到当前分支 git mv readme readme2 # 重命名文件readme为readme2 git reset --hard head # 将当前版本重置为head(通常用于merge失败回退) git rebase git branch -d hotfixes/bjvep933 # 删除分支hotfixes/bjvep933(本分支修改已合并到其他分支) git branch -d hotfixes/bjvep933 # 强制删除分支hotfixes/bjvep933 git ls-files # 列出git index包含的文件 git show-branch # 图示当前分支历史 git show-branch --all # 图示所有分支历史 git whatchanged # 显示提交历史对应的文件修改 git revert dfb02e6e4f2f7b573337763e5c0013802e392818 # 撤销提交dfb02e6e4f2f7b573337763e5c0013802e392818 git ls-tree head # 内部命令:显示某个git对象 git rev-parse v2.0 # 内部命令:显示某个ref对于的sha1 hash git reflog # 显示所有提交,包括孤立节点 git show head@{5} git show master@{yesterday} # 显示master分支昨天的状态 git log --pretty=format:\u0026#39;%h %s\u0026#39; --graph # 图示提交日志 git show head~3 git show -s --pretty=raw 2be7fcb476 git stash # 暂存当前修改,将所有至为head状态 git stash list # 查看所有暂存 git stash show -p stash@{0} # 参考第一次暂存 git stash apply stash@{0} # 应用第一次暂存 git grep \u0026#34;delete from\u0026#34; # 文件中搜索文本“delete from” git grep -e \u0026#39;#define\u0026#39; --and -e sort_dirent git gc git fsck\rgit 的配置: 更改设置。可以是版本库的设置,也可以是系统的或全局的\n# 显示当前的git配置 $ git config --list # 编辑git配置文件 $ git config -e [--global] # 输出、设置基本的全局变量,格式如下: # git config --global user.email [邮箱] # git config --global user.name [用户名] $ git config --global user.email \u0026#34;[email protected]\u0026#34; $ git config --global user.name \u0026#34;my name\u0026#34; # 定义当前用户所有提交使用的作者邮箱。 $ git config --global alias.\u0026lt;alias-name \u0026lt;git-command\u0026gt; # 为git命令创建一个快捷方式(别名)。 $ git config --system core.editor \u0026lt;editor\u0026gt;\r七、本地进行版本控制 1.1 创建git仓库 想要使用 git 进行版本控制,首先必须告诉 git 、你想要对那个项目(文件夹)进行版本控制。实际就是告诉 git 你将哪个文件夹作为 git 仓库。 在 git 中,使用 git init 来初始化一个版本库,这个版本库的配置、存储等信息会被保存到 .git 这个文件夹中。ps: .git 文件夹默认情况下是隐藏的,想要可视化的查看,需要显示隐藏文件。\n# 初始化当前项目(文件夹)为 git版本库 $ git init # 新建一个目录,并将其初始化为git代码库 $ git init [project-name] # 在指定目录创建一个空的 git 仓库。运行这个命令会创建一个名为 directory, # 并且只包含 .git 子目录的空目录。 $ git init --bare \u0026lt;directory\u0026gt; # 下载一个项目和它的整个代码历史,这个命令就是将一个版本库拷贝到另一个目录中, # 同时也将分支都拷贝到新的版本库中。这样就可以在新的版本库中提交到远程分支 $ git clone [url]\r1.2 查看本地库状态 用来查看 git 仓库目前的状态。包括当前在哪个分支,有哪些未跟踪的文件,哪些文件被修改了等。\n# 显示分支,未跟踪文件,更改和其他不同 $ git status # 查看其他的git status的用法 $ git help status\r1.3 添加文件暂存区 添加文件到暂存区中。执行commit命令时,是将暂存区的文件提交到版本库。因此,需要先将修改的文件存储到暂存区。\n# 添加一个文件 $ git add test.js # 添加一个子目录中的文件 $ git add /path/to/file/test.js # 支持正则表达式 $ git add ./*.js # 添加指定文件到暂存区 $ git add [file1] [file2] ... # 添加指定目录到暂存区,包括子目录 $ git add [dir] # 添加当前目录的所有文件到暂存区 $ git add . # 添加每个变化前,都会要求确认 # 对于同一个文件的多处变化,可以实现分次提交 $ git add -p\r1.4 从暂存区删除文件: rm 和上面的 add 命令相反,从工作空间中去掉某个文件\n# 移除 helloworld.js $ git rm helloworld.js # 移除子目录中的文件 $ git rm /pather/to/the/file/helloworld.js # 删除工作区文件,并且将这次删除放入暂存区 $ git rm [file1] [file2] ... # 停止追踪指定文件,但该文件会保留在工作区 $ git rm --cached [file]\r1.5 提交文件到本地库 commit 将当前索引的更改保存为一个新的提交,这个提交包括用户做出的更改与信息\n# 提交暂存区到仓库区附带提交信息 $ git commit -m [message] # 提交暂存区的指定文件到仓库区 $ git commit [file1] [file2] ... -m [message] # 提交工作区自上次commit之后的变化,直接到仓库区 $ git commit -a # 提交时显示所有diff信息 $ git commit -v # 使用一次新的commit,替代上一次提交 # 如果代码没有任何新变化,则用来改写上一次commit的提交信息 $ git commit --amend -m [message] # 重做上一次commit,并包括指定文件的新变化 $ git commit --amend [file1] [file2] ...\r1.6 显示版本库的提交日志 # 显示所有提交 $ git log # 显示某几条提交信息 $ git log -n 10 # 仅显示合并提交 $ git log --merges # 查看该文件每次提交记录 $ git log \u0026lt;file\u0026gt; # 查看每次详细修改内容的diff $ git log -p \u0026lt;file\u0026gt; # 查看最近两次详细修改内容的diff $ git log -p -2 #查看提交统计信息 $ git log --stat # ----------------------------------------------------------------------------- # 搜索提交历史,根据关键词 $ git log -s [keyword] # 显示某个commit之后的所有变动,每个commit占据一行 $ git log [tag] head --pretty=format:%s # 显示某个commit之后的所有变动,其\u0026#34;提交说明\u0026#34;必须符合搜索条件 $ git log [tag] head --grep feature # 显示某个文件的版本历史,包括文件改名 $ git log --follow [file] $ git whatchanged [file] # 显示指定文件相关的每一次diff $ git log -p [file] # 显示过去5次提交 $ git log -5 --pretty --oneline # 显示所有提交过的用户,按提交次数排序 $ git shortlog -sn # 显示指定文件是什么人在什么时间修改过 $ git blame [file] # 显示暂存区和工作区的差异 $ git diff # 显示暂存区和上一个commit的差异 $ git diff --cached [file] # 显示工作区与当前分支最新commit之间的差异 $ git diff head # 显示两次提交之间的差异 $ git diff [first-branch]...[second-branch] # 显示今天你写了多少行代码 $ git diff --shortstat \u0026#34;@{0 day ago}\u0026#34; # 比较暂存区和版本库差异 $ git diff --staged # 比较暂存区和版本库差异 $ git diff --cached # 仅仅比较统计信息 $ git diff --stat # 显示某次提交的元数据和内容变化 $ git show [commit] # 显示某次提交发生变化的文件 $ git show --name-only [commit] # 显示某次提交时,某个文件的内容 $ git show [commit]:[filename] # 显示当前分支的最近几次提交 $ git reflog # 查看远程分支 $ git br -r # 创建新的分支 $ git br \u0026lt;new_branch\u0026gt; # 查看各个分支最后提交信息 $ git br -v # 查看已经被合并到当前分支的分支 $ git br --merged # 查看尚未被合并到当前分支的分支 $ git br --no-merged\r本地模块化的版本控制 2.1 分支 管理分支,可以通过下列命令对分支进行增删改查切换等\n# 列出所有本地分支 $ git branch # 创建一个新的分支 $ git branch [branch-name] # 切换到指定分支,并更新工作区 $ git checkout [branch-name] # 切换到上一个分支 $ git checkout - # 删除某个分支 $ git br -d \u0026lt;branch\u0026gt; # 强制删除某个分支 (未被合并的分支被删除的时候需要强制) $ git br -d \u0026lt;branch\u0026gt; # ---------------------------------------------------------------------- # 查看所有的分支和远程分支 $ git branch -a # 重命名分支 # git branch -m \u0026lt;旧名称 \u0026lt;新名称\u0026gt; $ git branch -m [branch-name] [new-branch-name] # 编辑分支的介绍 $ git branch [branch-name] --edit-description # 列出所有远程分支 $ git branch -r # 新建一个分支,但依然停留在当前分支 $ git branch [branch-name] # 新建一个分支,并切换到该分支 $ git checkout -b [branch] # 新建一个分支,指向指定commit $ git branch [branch] [commit] # 新建一个分支,与指定的远程分支建立追踪关系 $ git branch --track [branch] [remote-branch] # 建立追踪关系,在现有分支与指定的远程分支之间 $ git branch --set-upstream [branch] [remote-branch] # 合并指定分支到当前分支 $ git merge [branch] # 选择一个commit,合并进当前分支 $ git cherry-pick [commit] # 删除分支 $ git branch -d [branch-name] # 删除远程分支 $ git push origin --delete [branch-name] $ git branch -dr [remote/branch] # 切换到某个分支 $ git co \u0026lt;branch\u0026gt; # 创建新的分支,并且切换过去 $ git co -b \u0026lt;new_branch\u0026gt; # 基于branch创建新的new_branch $ git co -b \u0026lt;new_branch \u0026lt;branch\u0026gt; # 把某次历史提交记录checkout出来,但无分支信息,切换到其他分支会自动删除 $ git co $id # 把某次历史提交记录checkout出来,创建成一个分支 $ git co $id -b \u0026lt;new_branch\u0026gt;\r2.2 切换分支 检出:将当前工作空间更新到索引所标识的或者某一特定的工作空间。实际上就是切换分支\n# 检出一个版本库,默认将更新到master分支 $ git checkout # 检出到一个特定的分支 $ git checkout branchname # 新建一个分支,并且切换过去,相当于\u0026#34;git branch \u0026lt;名字\u0026gt;; git checkout \u0026lt;名字\u0026gt;\u0026#34; $ git checkout -b newbranch\r2.3 合并分支 merge: 合并就是将外部的提交合并到自己的分支中\n# 将其他分支合并到当前分支 $ git merge branchname # 在合并时创建一个新的合并后的提交 # 不要 fast-foward 合并,这样可以生成 merge 提交 $ git merge --no-ff branchname\r2.4 显示当前工作空间和提交的不同 # 显示工作目录和索引的不同 $ git diff # 显示索引和最近一次提交的不同 $ git diff --cached # 显示工作目录和最近一次提交的不同 $ git diff head\r远程同步:远程同步的远端分支 # 下载远程仓库的所有变动 $ git fetch [remote] # 显示所有远程仓库 $ git remote -v # 显示某个远程仓库的信息 $ git remote show [remote] # 增加一个新的远程仓库,并命名 $ git remote add [shortname] [url] # 查看远程服务器地址和仓库名称 $ git remote -v # 添加远程仓库地址 $ git remote add origin git@ github:xxx/xxx.git # 设置远程仓库地址(用于修改远程仓库地址) $ git remote set-url origin git@ github.com:xxx/xxx.git # 删除远程仓库 $ git remote rm \u0026lt;repository\u0026gt; # 上传本地指定分支到远程仓库 # 把本地的分支更新到远端origin的master分支上 # git push \u0026lt;远端 \u0026lt;分支\u0026gt; # git push 相当于 git push origin master $ git push [remote] [branch] # 强行推送当前分支到远程仓库,即使有冲突 $ git push [remote] --force # 推送所有分支到远程仓库 $ git push [remote] --all\r2.4 撤销相关的操作 # 恢复暂存区的指定文件到工作区 $ git checkout [file] # 恢复某个commit的指定文件到暂存区和工作区 $ git checkout [commit] [file] # 恢复暂存区的所有文件到工作区 $ git checkout . # 重置暂存区的指定文件,与上一次commit保持一致,但工作区不变 $ git reset [file] # 重置暂存区与工作区,与上一次commit保持一致 $ git reset --hard # 重置当前分支的指针为指定commit,同时重置暂存区,但工作区不变 $ git reset [commit] # 重置当前分支的head为指定commit,同时重置暂存区和工作区,与指定commit一致 $ git reset --hard [commit] # 重置当前head为指定commit,但保持暂存区和工作区不变 $ git reset --keep [commit] # 新建一个commit,用来撤销指定commit # 后者的所有变化都将被前者抵消,并且应用到当前分支 $ git revert [commit] # 恢复最后一次提交的状态 $ git revert head # 暂时将未提交的变化移除,稍后再移入 $ git stash $ git stash pop # 列所有stash $ git stash list # 恢复暂存的内容 $ git stash apply # 删除暂存区 $ git stash drop\rgrep:可以在版本库中快速查找 可选配置: # 感谢travis jeffery提供的以下用法: # 在搜索结果中显示行号 $ git config --global grep.linenumber true # 是搜索结果可读性更好 $ git config --global alias.g \u0026#34;grep --break --heading --line-number\u0026#34; # 在所有的java中查找variablename $ git grep \u0026#39;variablename\u0026#39; -- \u0026#39;*.java\u0026#39; # 搜索包含 \u0026#34;arraylistname\u0026#34; 和, \u0026#34;add\u0026#34; 或 \u0026#34;remove\u0026#34; 的所有行 $ git grep -e \u0026#39;arraylistname\u0026#39; --and \\( -e add -e remove \\)\rmv:重命名或移动一个文件 # 重命名 $ git mv test.js test2.js # 移动 $ git mv test.js ./new/path/test.js # 改名文件,并且将这个改名放入暂存区 $ git mv [file-original] [file-renamed] # 强制重命名或移动 # 这个文件已经存在,将要覆盖掉 $ git mv -f myfile existingfile\rtag: # 列出所有tag $ git tag # 新建一个tag在当前commit $ git tag [tag] # 新建一个tag在指定commit $ git tag [tag] [commit] # 删除本地tag $ git tag -d [tag] # 删除远程tag $ git push origin :refs/tags/[tagname] # 查看tag信息 $ git show [tag] # 提交指定tag $ git push [remote] [tag] # 提交所有tag $ git push [remote] --tags # 新建一个分支,指向某个tag $ git checkout -b [branch] [tag]\rpull:从远端版本库合并到当前分支 # 从远端origin的master分支更新版本库 # git pull \u0026lt;远端 \u0026lt;分支\u0026gt; $ git pull origin master # 抓取远程仓库所有分支更新并合并到本地,不要快进合并 $ git pull --no-ff\rci: $ git ci \u0026lt;file\u0026gt; $ git ci . # 将git add, git rm和git ci等操作都合并在一起做 $ git ci -am \u0026#34;some comments\u0026#34; # 修改最后一次提交记录 $ git ci --amend\rrebase(谨慎使用):将一个分支上所有的提交历史都应用到另一个分支上不要在一个已经公开的远端分支上使用 rebase. # 将experimentbranch应用到master上面 # git rebase \u0026lt;basebranch \u0026lt;topicbranch\u0026gt; $ git rebase master experimentbranch reset (谨慎使用) 将当前的头指针复位到一个特定的状态。这样可以使你撤销 merge、pull、commits、add 等 这是个很强大的命令,但是在使用时一定要清楚其所产生的后果 # 使 staging 区域恢复到上次提交时的状态,不改变现在的工作目录 $ git reset # 使 staging 区域恢复到上次提交时的状态,覆盖现在的工作目录 $ git reset --hard # 将当前分支恢复到某次提交,不改变现在的工作目录 # 在工作目录中所有的改变仍然存在 $ git reset dha78as # 将当前分支恢复到某次提交,覆盖现在的工作目录 # 并且删除所有未提交的改变和指定提交之后的所有提交 $ git reset --hard dha78as\r其他: # 生成一个可供发布的压缩包 $ git archive # 打补丁 $ git apply ../sync.patch # 测试补丁能否成功 $ git apply --check ../sync.patch # 查看git的版本 $ git --version\rhttps://zhuanlan.zhihu.com/p/40461007\n","date":"2024-07-13","permalink":"http://54rookie.com/posts/git/","summary":"Git 常用知识 一、Git简介 Git是一个快速、可扩展的分布式版本控制系统,它具有极为丰富的命令集,对内部系统提供了高级操作和完全访问。Git像是把发生变化的文件作快","title":"git"},]
[{"content":"代理重加密 代理重加密 什么是代理重加密(pre) 代理重加密(proxy re-encryption)通过代理服务器将一个用户用自己公钥加密的密文转换为另一个用户可以用自己私钥解密的密文,且不泄露用户的私钥和明文信息,从而实现信息的共享。\n代理重加密常用于云存储中,因为在云存储中,基于用户数据隐私性考虑,用户存放在云端的数据都是密文形式存在的。而云环境中存在着大量数据共享的场景。由于数据拥有者对云服务提供商并不完全信任,不能将解密密文的密钥发送给云端,由云端来解密并分享出去。数据拥有者自己下载密文解密后,再用数据接收方的公钥加密并分享,无疑给数据拥有者带来很大的麻烦,同时也失去了云端数据共享的意义。代理重加密可以在不泄漏数据拥有者解密密钥的情况下,实现云端密文数据共享。\n具体过程 直观的理解:\n假设 alice 想要将消息 m 存储到云服务器上,并且 bob 想要查看消息 m 。由于 alice 不想让云服务器知道消息 m ,因此他可以使用代理重加密按照下面的过程安全的与 bob 分享消息 m 。\nalice: 将明文 m 用自己的公钥$pk_a$加密,得到$c_{pk_a}=enc_{pk_a} (m)$,其中的 m 就是 alice 想要给 bob 的明文内容。 alice:将$c_{pk_a}$发给半诚实代理商,并为其生成重加密密钥$rk_{a→b}$; proxy:用 alice 生成的重加密密钥 $rk_{a→b}$ 将密文 $c_{pk_a}$ 转化为 bob 的私钥能够解密的密文 $c_{pk_b}$,其中proxy只是提供计算转化服务,无法获取明文; proxy:将生成好的$c_{pk_b}$发给bob; bob:解密获得 alice 想要共享的明文 m; 该过程主要解放了a,a只需生成代理密钥,具体文件的传输,文件的转化,文件的存放都是半诚实代理商完成的。\n形式化定义\n代理重加密可由以下五个算法构成:\nkeygen:根据一个系统安全参数生成用户的密钥对。\nencrypt:用户是用自己的公钥加密消息 m 得到相应的密文 c,并将密文 c 上传给代理服务器。\nregenkey:用户使用数据接收者的公钥生成重加密密钥rk,并发送给代理服务器。\nreencrypt:代理服务器使用重加密密钥 rk 对密文c进行重加密,得到重加密密文c\u0026rsquo; ,然后将c\u0026rsquo;发送给数据接收者。\ndecrypt:数据接收者使用自己的私钥解密 c\u0026rsquo; 得到明文 m 。\n下面为图解:\n具体实例 基于elgamal的代理重加密 假设 alice 想要将消息 m 存储到云服务器上,并且 bob 想要查看消息 m 。由于 alice 不想让云服务器知道消息 m ,因此他可以使用代理重加密按照下面的过程安全的与 bob 分享消息 m 。\nkeygen:\nalice 输入elgamal加密的公共参数 pp ,然后生成用户 alice 和 bob 的公私钥对分别为 $(sk_a,pk_a)$ 和 $(sk_b,pk_b)$。其中 $pk_a=g^{sk_a}$ , $pk_b=g^{sk_b}$。\nencrypt:\n假设 alice 想要将消息 m 存储到代理服务器上。alice 使用自己的公钥使用 elgamal加密算法加密消息 m 得到相应的密文 $c=(c_1,c_2)=(g^r,m\\cdot pk_a^r)$,其中 $r\\in_r z_p^* $是一个随机数。alice 将密文 c 上传给代理服务器。\nregenkey:\nalice 使用数据接收者 bob 的公钥生成重加密密钥 $rk= sk_a / h(pk_b^{sk_a})$,并将 rk 发送给代理服务器。\nreencrypt:\n代理服务器使用重加密密钥 rk 对密文c进行重加密,得到重加密密文$c\u0026rsquo;=(c_1\u0026rsquo;,c_2\u0026rsquo;)=(c_1^{rk},c_2)$,然后将c\u0026rsquo;发送给数据接收者。\ndecrypt:\nbob 使用自己的私钥 $sk_b$ 解密 c\u0026rsquo; 得到明文 $m=c_2\u0026rsquo; / c_1\u0026rsquo;^{h(pk_a^{sk_b})}$。\n正确性验证很容易,这里不再赘述。\n代理重加密的优化 由于非对称公私钥加密的效率低下,对于较大文件的数据共享是不合适,所以需要对pre流程进行优化。我们可以使用对称加密来保护数据,使用pre来保护对称加密的密钥。详细流程如下所示。\n解释如下:\nalice生成 $sk_1, pk_1$,bob 生成 $sk_2, pk_2$ alice生成辅助信息 $aux$ ,并结合自己的公钥 $pk_1$ 计算出对称密钥 k;用 k 加密大文件 m 得到密文 c ;然后将 $(c,aux)$ 发送给代理proxy 当 bob 想要使用消息 m 时,他首先向alice发送自己的公钥 $pk_2$ alice根据bob的公钥 $pk_2$ 和自己的私钥 $sk_1$ 生成重加密密钥 delkey 并将 delkey 发送给 proxy proxy 用delkey重加密密文c。得到newc,并将newc发送给bob bob用自己的私钥 $sk_2$ 和 $aux$ 计算对称密钥 k,并用 k 解密 newc 得到 m . 基于对称加密的代理重加密 setup:\n假设 g 是椭圆曲线群 g 的生成器,p 是一个大素数,也是 g 的阶。$h_i$,i=2, 3, 4是哈希函数。m 是 alice 想要共享的消息或明文。server 是第三方服务器。\nkeygen:\n对于任意一个用户,生成随机数 $sk \\in_r z_p$作为私钥,计算公钥 $pk=g^{sk}$。我们假设alice和bob的密钥对分别是$(sk_a,pk_a=g^{sk_a})$ 和 $(sk_b,pk_b=g^{sk_b})$\nencrypt:\n假设 alice 想要将消息 m 存储到代理服务器上。alice 。执行下面的操作:\n生成两个随机数 $e,v\\in_r z_p$ 并计算 $e=g^e,v=g^v$ 计算 $s=v+r\\cdot h_2(e||v)$ 使用aes的密钥生成算法 $\\mathcal{g}$ 计算 $k=\\mathcal{g}((pk_a)^{e+v})$ 使用aes加密算法 $\\mathcal{e}$ 计算消息 m 的密文为 $c=\\mathcal{e}_{aes}(m,k)$ 令 $aux=(e,v,s)$ , alice 将 $(aux,c)$发送给代理服务器。 regenkey:\nalice 使用数据接收者 bob 的公钥生成重加密密钥:\n生成随机数 $x_a \\in_r z_p$ 计算 $x_a=g^{x_a}$ 计算 $d=h_3(x_a||pk_b||pk_b^{x_a})$ $rk= sk_a \\cdot d^{-1}$,并将 rk 发送给代理服务器。 reencrypt:\n代理服务器使用重加密密钥 rk 对密文c进行重加密如下:\n判断 $g^s=v\\cdot e^{h(e||v)}$ 是否成立 , 如果成立则计算 $e\u0026rsquo;=e^{rk},v\u0026rsquo;=v^{rk}$ , 令 $aux\u0026rsquo;=(e\u0026rsquo;,v\u0026rsquo;,s)$ 代理服务器将 $(aux\u0026rsquo;,c)$ 发送给数据接收者 bob。 decrypt:\nbob 使用 $aux\u0026rsquo;$ 和自己的私钥 $sk_b$ 解密密文:\n计算 $d=h_3(x_a||pk_b||s^{sk_b})$ 计算 $k=\\mathcal{g}((e\u0026rsquo;\\cdot v\u0026rsquo;)^d)$ 解密密文得到明文 $m=\\mathcal{d}_{aes}(c,k)$ 大佬github上的实现:\njava:pre java-sdk go:gorecrypt ","date":"2024-07-12","permalink":"http://54rookie.com/posts/%E4%BB%A3%E7%90%86%E9%87%8D%E5%8A%A0%E5%AF%86/","summary":"代理重加密 代理重加密 什么是代理重加密(PRE) 代理重加密(Proxy re-encryption)通过代理服务器将一个用户用自己公钥加密的密文转换为另一个用户可以","title":"代理重加密"},]
[{"content":"盲签名 盲签名(blind signature) 盲签名的简介 盲签名是由chaum, david提出的一种数字签名方式,其中消息的内容在签名之前对签名者是不可见的(盲化)。经过盲签名得到的签名值可以使用原始的非盲消息使用常规数字签名验证的方式进行公开验证。盲签名可以有效的保护隐私,其中签名者和消息作者不同,在电子投票系统和数字现金系统中会被使用。\n盲签名常常被类比成下面的场景:alice想让bob在自己的文件上签名,但是不希望bob看到文件内容,于是alice在文件上方叠放了一张复写纸,然后将文件和复写纸放入信封密封起来交给bob。bob再拿到信封后验证了alice的身份后,直接在密封好的信封上签字,这样虽然bob是对密封后的信封签字,但是alice拿到签名后的信封后,拆开信封就可以拿到经过bob签字的文件。\n盲签名(blind signature)性质 签名者对其所签署的消息是不可见的,即签名者不知道他所签署消息的具体内容。 签名消息不可追踪,即当签名消息被公布后,签名者无法知道这是他哪次的签署的。 盲签名的一般过程 接收者首先将待签数据进行盲变换,把变换后的盲数据发给签名者。 经签名者签名后再发给接收者。 接收者对签名再作去盲变换,得出的便是签名者对原数据的盲签名。 这样便满足了条件①。要满足条件②,必须使签名者事后看到盲签名时不能与盲数据联系起来,这通常是依靠某种协议来实现的。 图示如下:\n基于rsa的盲签名实现 schnorr盲签名算法 https://github.com/alexiachen/alexiachen.github.io/issues/166 bls盲签名方案 ","date":"2024-07-12","permalink":"http://54rookie.com/posts/%E7%9B%B2%E7%AD%BE%E5%90%8D/","summary":"盲签名 盲签名(Blind Signature) 盲签名的简介 盲签名是由Chaum, David提出的一种数字签名方式,其中消息的内容在签名之前对签名者是不可见的(盲化)。经过盲签名得到","title":"盲签名"},]
[{"content":"环签名 环签名 环签名概述 环签名(ring signature)是一种数字签名方案,最初由rivest等人提出,环签名可以看成一种简化的群签名, 环签名中只有环成员没有管理者, 不需要环成员间的合作。【在群签名中需要事先生成一个群,随后用户才能加入该群,从而代表该群进行签名】\n环签名的定义 假定有 n 个用户,每一个用户 拥有一个公钥和与之对应的私钥。环签名是一个能够实现签名者无条件匿名的签名方案,它主要由下述算法组成:\ngen:概率多项式时间(ppt)算法,输入安全参数 k 后输出公私钥对。这里假定gen为每个用户产生一个公私钥对,并且不同用户的公私钥可能来自不同的公钥体制,如rsa或者dl。 sign:一个ppt算法,在输入消息 m 和 n 个环成员的公钥 l={y1 , ⋯ , yn}以及其中一个成员的私钥 xs 后,对消息 m 产生一个签名 r,其中 r 中的某个参数根据一定的规则呈环状。 verify:一个确定性算法,在输入(m,r)后,若 r 为m 的环签名则算法输出“true”,否则算法输出“false”。 环签名因为其签名隐含的某个参数按照一定的规则组成环状而得名。而在之后提出的许多方案中不要求签名的构成结构成环形,只要签名的形成满足自发性、匿名性和群特性,也称之为环签名。\n环签名的性质: 正确性:如果按照正确的签名步骤对消息进行签名,并且在传播过程中签名没被篡改,那么环签名满足签名验证等式。\n无条件匿名性:攻击者即使非法获取了所有可能签名者的私钥,他能确定出真正的签名者的概率不超过1/n这里n为所有可能签名者的个数。\n不可伪造性:外部攻击在不知道任何成员私钥的情况下,即使能从一个产生环签名的随机预言者那里得到任何消息m的签名,他成功伪造一个合法签名的概率也是可以忽略的。\n环签名与群签名的比较:\n相同点:\n都实现了匿名性。 在群签名中,签名者代表这个群进行签名,任何人都可以使用群公钥对这个签名进行验证。在验证过程中,验证者只知道签名者来自这个群,但是不知道签名者是群中的哪一个成员。 在环签名中,签名者代表这个环进行签名,任何人都可以对这个环签名进行验证,在验证过程中,验证者只知道签名者来自这个环,但是不知道签名者具体是环中的哪一个成员。 不同点:\n匿名性不同: 群签名实现了有条件的隐私保护,群管理员可以从群签名中找出签名者的真实身份。 环签名是无条件的匿名性。没有任何人可以去除环签名的匿名性。 创建签名的难度方法不同: 用户想要代表某个群对某个消息进行签名,必须由群管理员完成群的【创建】,然后用户【加入】该群后才能进行。 环签名中,用户可以将自己的身份添加到他选择的任何其他用户集合中,并在这个集合上生成一个环签名,该签名只显示出签名者属于该集合,而不暴露其他信息。特别是,未签名的成员甚至可能完全不知道他们参与了这样的签名。 ○ 签名长度不同: ■ 群签名的长度很多都是固定的,与群成员的个数无关 ■ 环签名中,大多数的环签名方案中,签名的长度随环成员个数的增加而增加。 环签名方案 基于rsa的环签名方案【历史上第一个环签名方案】 假设环签名中有n个人,则一个真实用户的签名泄露自己身份的概率是$1 / n$,为了简单起见,下面令n = 5 来举例说明rsa环签名方案。假设有5个用户,分别为1,2,3,4,5,且用户3要对消息m进行签名,则:\n密钥生成gen\n为五个用户分别生成rsa公私钥对【($e_i,n_i$),($d_i,n_i$)】。( i = 1,2,3,4,5 )\n签名sign\n签名需要用到一个对称加密算法,这里使用aes算法。环签名的计算过程如下:\n为了简化上述过程,不妨设有一个操作$r_i(c_i)$定义为:$r_i(c_i)$=$c_{i+1}$,其中$x_i$为随机数。则rsa环签名计算过程如下:\n$y_i=rsa_enc_{pk_i}(x_i)=x_i^{e_i}\\ mod \\ n_i$ $z_{i+1}=c_i \\oplus y_i $ $c_{i+1}=aes_enc_{key}(z_{i+1})$ 为了展示计算过程,上述操作表示为:$r_i(c_i)=c_{i+1}$ - - - - - 【$x_i,y_i,z_{i+1},c_{i+1}$】 通过上述符号假设,上述的计算过程可以简化为:签名者【用户3】选择随机数$c_4$,然后依次计算: $$r_4(c_4)=c_5 \u0026mdash;-【x_4, y_4, z_{5}, c_{5}】$$ $$r_5(c_5)=c_1 \u0026mdash;-【x_5, y_5, z_{1}, c_{1}】$$ $$r_1(c_1)=c_2 \u0026mdash;-【x_1, y_1, z_{2}, c_{2}】$$ $$r_2(c_2)=c_3 \u0026mdash;-【x_2, y_2, z_{3}, c_{3}】$$ $$r_3(c_3)\u0026rsquo;=c_4\u0026rsquo; \u0026mdash;-【x_3\u0026rsquo;, y_3\u0026rsquo;, z_{4}\u0026rsquo;, c_{4}\u0026rsquo;】$$\n分析\n上面计算的$r_3(c_3)\u0026rsquo;$=【$x_3\u0026rsquo;, y_3\u0026rsquo;, z_{4}\u0026rsquo;, c_{4}\u0026rsquo;$】中,$c_4\u0026rsquo;$满足$c_4\u0026rsquo;=c_4$的概率可以忽略不计,因为上述等式成立的概率仅为为$1/2^{256}$(因为aes加密的输出是256比特)\n为了使其相等,令$c_4‘=c_4$,反向计算出$z_4$, 然后异或计算出$y_3$,随后【用户3】可以使用自己的私钥($d_3, n_3$)解密$y_3$得到$x_3$;\n将$r_3(c_3)\u0026rsquo;=c_4\u0026rsquo;$\u0026mdash;-【$x_3\u0026rsquo;, y_3\u0026rsquo;, z_{4}\u0026rsquo;, c_{4}\u0026rsquo;$】替换为$r_3(c_3)=c_4$\u0026mdash;-【$x_3, y_3, z_{4}, c_{4}$】; 这样即可得到环签名$σ={(e_1, n_1), (e_2, n_2), (e_3, n_3), (e_4, n_4), (e_5, n_5), c_1, x_1, x_2, x_3, x_4, x_5}$。\n上述签名者【用户3】通过将正向运算替换为反向运算,得到了一个$x_3$,从而得到了环签名 σ 。 环签名的计算过程正好形成一个环,如下图:\nverify:\n验证者计算: $$r_1(c_1)=c_2 \u0026mdash;-【x_1, y_1, z_{2}, c_{2}】$$ $$r_2(c_2)=c_3 \u0026mdash;-【x_2, y_2, z_{3}, c_{3}】$$ $$r_3(c_3)=c_4 \u0026mdash;-【x_3, y_3, z_{4}, c_{4}】$$ $$r_4(c_4)=c_5 \u0026mdash;-【x_4, y_4, z_{5}, c_{5}】$$ $$r_5(c_5)=c_1\u0026rsquo; \u0026mdash;-【x_5, y_5, z_{1}, c_{1}】$$ 检查 $c_1\u0026rsquo;=c_1$是否成立,如果成立,输出true ,否则输出 false 这里的解密的实质就是等式$r_5(r_4(r_3(r_2(r_1(c_1)))))=c_1$成立。\n参考文献:\nhttps://zhuanlan.zhihu.com/p/450180396 https://zhuanlan.zhihu.com/p/510078836 基础密码学系列课程3: rsa、环签名、同态加密-p1_哔哩哔哩_bilibili 基于rabin的环签名 以前在语雀记载过,迁移过来太麻烦了,直接贴图片:\n基于pederson承诺的环签名 以前在语雀记载过,迁移过来太麻烦了,直接贴图片:\n门罗币中使用的环签名 上述环签名存储在的问题\n在上述基于离散对数的环签名方案中,由于每个环成员都可以使用自己的私钥伪造一个签名,因此在一些实际应用中仍存在一些问题。为了能解决这个问题,门罗币签名引入了密钥镜像的概念,从而避免的环成员对签名的伪造。\n","date":"2024-07-12","permalink":"http://54rookie.com/posts/%E7%8E%AF%E7%AD%BE%E5%90%8D/","summary":"环签名 环签名 环签名概述 环签名(ring signature)是一种数字签名方案,最初由Rivest等人提出,环签名可以看成一种简化的群签名, 环签名中只有环成员没有","title":"环签名"},]
[{"content":"群签名 群签名 概念 群签名是这样一个数字签名:在一个群签名的方案中,一个群体中的任意一个成员可以代表这个群体对消息进行签名,并且其他人可以使用群公钥对这个签名进行公开验证。在验证时,验证者只能验证签名是来自这个群组的,但是无法知道到底是群中的哪个成员对消息进行了签名。\n在群签名中,存在一个群管理员,该管理员可以通过对签名进行操作得到签名者的真实身份。\n形式化定义 一个群签名通常包含下面几个步骤: 创建:一个用以产生群公钥和私钥的概率多项式时间算法。\n加入:一个用户和群管理员之间的使用户成为群管理员的交互式协议。执行该协议可以产生群员的私钥和成员证书,并使群管理员得到群成员的私有密钥。\n签名:一个概率算法,当输入一个消息和一个群成员的私钥后,输出对消息的签名。\n验证:一个概率算法,当输入一个群成员的签名和群公钥后,可以判断签名是否属于这个群。\n打开:一个在给定一个签名及群私钥的条件下确认签名人的合法身份的算法。 群签名的性质 早期的群签名存在公钥大小随群成员数量线性增长的情况,随着群签名的发展,现在的群签名方案一般具有以下特点:\n公钥大小和签名大小固定,与群成员的数量无关; 实现了对群成员的动态管理,允许用户再需要的时候动态的加入群组,或者允许群管理员动态的对群成员进行撤销。 一些群签名方案实现了 opener 和 manager 分离,使得 manager 管理群成员的加入和离开,opener负责揭露恶意群成员的真实身份,解除其匿名性,实现有条件的隐私保护。 在群签名中,常见的实现群组成员撤销的机制有以下几种:\n① 组管理员更新组公钥,并将其发送给为被撤销的用户\n② 使用累加器:它可以高效的队组成员进行撤销和加入\n③ 签名者在签署消息,或根据组的变化更新自己的密钥时,需要提供合格的成员资格证明。常见的方法是把非法用户的 id 放入黑名单,使得在更新密钥的时候,黑名单内的用户证书将面临除零异常,从而导致其无法更新证书。\n④ 验证者本地撤销:gm发现恶意用户的签名之后,将签名者的身份发给签名的验证者。这种撤销方式只有验证者拥有撤销列表。\n基于rsa的群签名 系统初始化 群管理员选择rsa的公钥(n,e)\n群管理员选择生成元为 g 的循环群 g,g 的阶为 n\n选择一个 $a\\in z_n^* $,这里 a 对于 n 的两个素数因子 p ,q 都有较大的乘法阶\n选择密钥长度的上界 λ ,和一个常数 ε ,其中 ε 1 该群的公共参数为 params = {n,e,g,g,a,λ,ε}\n成员加入 假设alice想要加入这个群组,她和群管理员进行以下交互:\nalice 执行:\n选择一个私钥 x 并计算 $y = a^x (mod\\ n)$ 和 $z = g^y$(z作为成员密钥)\n然后alice发送 y,z 和自己对 z 的签名给群管理员gm,并使用知识签名证明自己知道满足$y=a^x(mod\\ n)$的 x 。 gm 执行:\ngm验证y和z的合法性,验证知识签名从而确定alice是知道 x 的。\ngm保留(y,z)用于日后打开群签名。\ngm生成alice的群成员证书$v = (y + 1) ^ {1/e}\\ (mod\\ n)$, 并将 v 发送给alice。 alice 执行:\nalice可以通过计算验证 v 的正确性。如果验证通过,则alice 存储这个证书,用于后续生成群签名。 成员生成群签名 随机选择 $r∈z_n^*$并计算$\\widetilde{g}=g^r$ 计算$\\widetilde{z}=\\widetilde{g}^{\\ y}(mod \\ n)$ 计算$v_1$ = skloglog[ $x:\\widetilde{z}=\\widetilde{g}^{α^x}$ ]\n计算$v_2$ = skrootlog[ $v:\\widetilde{z}\\widetilde{g}=\\widetilde{g}^{v^e}$ ]\n群签名的结果为$sig={\\widetilde{g}, \\widetilde{z}, v_1, v_2}$\n群签名的验证 验证 $v_1$ 即可证明签名者知道私钥 x, 同时验证者可以据此得知 $\\widetilde{z}$ 的结果形如 $\\widetilde{g}^{α^x}$ 。因此,我们可以得知 $\\widetilde{z}\\widetilde{g}$ 的结果是性心如 $\\widetilde{g}^{\\ a^x+1}$ 的形式。 用知识证明验证$v_2$时可知:$\\widetilde{z}\\widetilde{g}$ 的形式是 $\\widetilde{g}^{\\ a^x+1}$ 验证 $v_2$ 如果成功,证明 alice 知道 $a^x+1$ 的 e 次根,即 $(a^x+1)^{1/e}=v$ 这意味着 alice 知道秘钥x和她的成员密钥的成员证书 v。\n综上:只要验证了上述群签名就能验证签名 sig 确实来自这个群组的某个成员。(上述验证中验证了签名者群成员证书v的合法性)\n打开群签名 当遇到特殊情况时,需要知道签名者的真实身份,这是群管理员可以做到这一点:由于$\\widetilde{z}=\\widetilde{g}^{\\ y}(mod \\ n)$,且gm存储了每个群成员的(y,z)。所以,gm遍历所有的 y ,如果满足上述等式,就能找到签名者的真实身份。\n参考文献:\n97年发表的论文《 efficient group signature schemes for large groups 》 csdn:https://blog.csdn.net/zhang_hui_cs/article/details/8965338 群签名开山作: 这是第一篇介绍群签名的论文中提出的方案【一共提出了四个方案,这是其中的一个方案】\n预备知识 零知识证明:\n也就是 p 向 v 证明自己知道一个秘密值$s_i$,,满足:$s_i=m^{s_i}\\ (mod\\ p) 且 g^{s_i}∈{k_j\\ |\\ j∈γ }$ 在证明过程中,$p,g,h,s,γ$是公开的。 其中,p是一个大素数,$g,h∈z_p^*$ 。s是群签名,$γ$ 表示群成员的索引集合。\n具体签名过程 假设 p 是一个大素数,$g,h\\in z_p^* $, 群公钥为 $gpk={k_1,k_2,\u0026hellip;,k_n}$ ,假设用户 i 想对消息 m 进行签名:\n说明:\n如果证明者p不知道$s_i$那么他不能计算出 $t_3+s_i$;因为b=0时,验证者可以通过验证保证$y=m^{t_3}$的成立,而$s=m^{s_i}$是公开的,故$ys=m^{t_3+s_i}$,故p必须知道$s_i$并给出$t_3+s_i$才能完成b=1时的验证 第二个等式中,由于$k_i=g^{s_i}$是公开的,由于等式化简过程中,将$g^{s_i}$化简为了$(g/m)^{s_i}*m^{s_i}$,这必须保证$k_i=g^{s_i}$中的$s_i$与$s=m^{s_i}$中使用的$s_{s_i}$相等. 这就保证了群签名s中的秘密值确实来自某个群成员公钥对应的私钥. 置换$τ$是用来隐藏群成员的真实身份的. 由于验证的时候使用的是置换后的群成员索引,因此证明者不能知道群成员在公钥集合中的真实索引. acjt群签名方案 acjt算法在2000年提出\n预备知识 知识签名:\n知识签名,=:非交互式的零知识证明。 elgamal加密: 方案详情 参数设置\n创建:\n群管理员执行:【qr(n)是一个阶为$p\u0026rsquo;q\u0026rsquo;$的循环群】 加入:\n用户执行:【 区间 ]a,b[ 表示出去a,b以外的值 】 注意:\n由于p,q是保密的,只有群管理员知道,因此敌手不能自己伪造$a_i$,因为敌手求不出$e_i$在(p-1)(q-1)下的逆元$1/e_i$. 关于$u,v,w$的证明之中,前两个证明保证了$x_i$的计算过程是正确的. 第三个等式则证明了$c_1$的计算过程是正确的. 因为:令$u=α_i\\tilde{x_i}+β_i \\ mod\\ 2^{λ_2}\\ ;则 c_2/a^{2^{λ_1}}=g^u$;上述等式如果成立,显然$x_i$是以正确的方法计算得到的. 因为: 如果$c_1$是正确计算的,即 $c_1=g^{\\tilde{x_i}}h^{\\tilde{r}}$ ; $c_1^{α_i}g^{β_i}=g^{α_i\\tilde{x_i}+β_i}h^{\\tilde{r}α_i}$ 由于$x_i=2^{λ_1}+u$;而$α_i\\tilde{x_i}+β_i=v*2^{λ_2}+u$(其中v是一个整数) 所以 $ c_1^{α_i} g^{β_i} = g^{α_i \\tilde{x_i} + β_i} h^{\\tilde{r} α_i } = g^{ v\\cdot 2^{λ_2} + u}\\cdot h^{\\tilde{r}α_i} = g^u\\cdot (g^{2^{λ_2}})^vh^{\\tilde{r}α_i}$ 令 $w={\\tilde{r}α_i}$则有:$c_1^{α_i}g^{β_i}=g^u(g^{2^{λ_2}})^vh^w$ 如果上述$c_1$不是正确计算的,那么就无法证明上述的等式成立. 综上所述,如果上述给出的知识签名都是正确的,那么$x_i$的计算就是按照协议进行的。 我的疑问: 这里join\u0026rsquo;阶段给出的群成员证书是由随机数构造而成的,如果用户自己选择一堆随机数按照上述格式伪造一个证书,岂不是可以伪造组成员进行签名. 签名: 群成员执行:\n注意:\n这里$(t_1,t_2)$相当于对$a_i$做elgamal加密,打开就是进行elgamal解密。 这里的$(d_1,d_2,d_3,d_4)$和对应的$(s_1,s_2,s_3,s_4)$相当于知识签名。 验签:\n打开:\n群管理员执行:\nelgamal算法解密得到身份信息。用知识签名证明自己确实是群管理员gm,即我知道群私钥x,满足$x=log_gy=log_{t_2}(t_1/a_i\\ mod\\ n)$【防止其它人假装自己是gm,因为没有群私钥的人无法打开签名,获取签名者的身份】\n参考文献\n原论文:\na practical and provably securecoalition-resistant group signature scheme bbs群签名算法 预备知识 线性加密(le):\n决策线性问题(dlp)产生了线性加密(le)方案,它是elgamal加密的自然扩展。与elgamal加密不同,线性加密即使在存在ddh决定算法的组中也是安全的。在此方案中,用户的公钥是一个三组生成器$u, v, h∈g_1$;她的私钥是指数$x, y∈z_p$,使得$u^x = v^y = h$。要对消息$m∈g_1$进行加密: 随机选择$a, b∈zp$, 并计算输出三元组 $(u^a, v^b, m·h^{a+b})$。 为从加密$(t_1, t_2, t_3)$中恢复消息m,用户计算$m=t_3/(t_1^x·t_2^y)$。 通过对elgamal安全性证明的自然扩展,假设decision-la成立,le在语义上是安全的,不会受到选择明文攻击. 方案详情: 方案思路总览:\ngm首先生成群签名中需要用到的参数,其中包括一个秘密参数$\\gamma$,这个参数用来为群成员颁发证书。 当用户想要加入一个群组的时候,gm用$\\gamma$为其生成一个群成员证书$(a_i,x_i)$ 用户进行签名的时候,证明自己知道群成员证书【】,并用gm的公钥使用用le加密将自己的身份加密【gm可用私钥解密】 正确性证明 验签过程需要验证下面五个等式\n分别对其正确性进行证明如下:\n关于方案的说明:\n$π_i$表示的是一个非交互式的零知识证明,他的具体说明。原论文中有,见参考文献 这里bbs签名实际上就是一个零知识证明,证明签名者拥有$(a,x)$满足$a^{γ+x}=g_1$ 也即是证明签名者拥有$(a,x)$满足$e(a,wg_1^x)=e(g_1,g_2)$ 在不知道$\\gamma$的情况下,伪造$(a,x)$满足$a=g_1^{1/{r+x}}$在计算上是不可行的。故,验证了上述零知识证明就相当验证了签名者拥有群成员证书。 这里的${r_1,r_2,r_3,r_4,r_5,}$以及${s_α,s_β,s_x,s_{δ_1},s_{δ_2}}$的计算过程实际上就是将构造的sigma协议经过fait-shamir启发式得到的非交互式零知识证明。 这里${r_1,r_2}$证明了签名者知道${t_1,t_2}$中的$\\alpha,\\beta$; 而${r_4,r_5}$保证了${\\delta_1,\\delta_2}$是按照正确的方式计算出来的; 它们与$r_3$配合证明了存在$(a,x)$满足$a^{r+x}=g_1$ 参考文献 bbs签名原论文:\n《short group signatures》 bbs签名的go语言实现:\nhttps://blog.csdn.net/gassuih/article/details/117417328 cl签名方案 三个版本 作者在论文中循序渐进的提出cl签名方案,前两个方案是为第三个方案做铺垫的。这里第一个版本和第二个版本是第三个版本的特例\n第一个版本的cl签名:【记作cl01】 参数生成:\n通过安全参数生成公共参数:$params={q,g_1,g_2,e,g_1,g_2,e}$ 其中$g_1=\u0026lt;g_1\u0026gt;,g_2=\u0026lt;g_2\u0026gt;$是阶为 q 的,循环群, $e:g_1×g_1→g_2$双线性映射. 密钥生成:\n选择$x∈_rz_q^* \\ ;\\ y∈_rz_q^* $计算$x=g_1^x\\ ;\\ y=g_1^y$ ; 则私钥为$(x,y)$公钥为$(x,y)$ 签名:\n对于消息m , 签名者执行以下步骤对消息进行签名. 选择$a∈_rg$ ; 计算$\\sigma=(a,a^y,a^{x+mxy})$ 验签:\n给定消息 m 的签名 σ 和签名者的公钥,对签名进行验证,如果签名正确,返回yes,否则返回no 检验: $\\begin{align} e(a,y)\\overset{?}{=}e(g_1,b) \\ \\ \\ \\ ;\\ \\ \\ e(x,a)e(x,b)^m\\overset{?}{=}e(g_1,c) \\end{align}$ 都成立,返回yes 说明:\n事实上,签名者不需要知道消息m也能对消息m进行签名. 例如: v给签名者s一个$m=g^m$; 则签名者s可以通过下面的式子计算签名: $\\sigma=(a,a^y,a^xm^{rxy})$ 容易看出,上述方式计算出的签名和正常计算得到的签名是完全一样的,区别在于用上述方法生成签名不需要知道消息m. 类似于盲签名. 在构造群签名时,为了加入一个组,一个新成员将选择一个秘密m,例如向gm提供$g^m$,证明对m的了解,并获得成员证书(a, b, c)。如果用户使用相同的秘密m成为多个组的成员,不同组的组管理员可以发现他们正在与同一个用户交谈。当两个组管理器使用相同的生成元g时,该用户在两个群中的证书完全相同,这很容易看出. 但是,即使一个gm使用生成元g,而另一个gm使用生成元h,这也是正确的: 因为在双线性配对的组中,决策的diffie-hellman问题很容易: - 对于用户给出的$g^m$和$h^m$两个组管理员通过比较$e(g^m,h) = e(g,h)^m = e(g,h^m)$可以得出用户为同一个人. 第二个签名:【记作cl02】 说明:\n事实上,签名者不需要知道消息m也能对消息m进行签名. 例如: v给签名者s一个$m=g^mz^r$; 则签名者s可以通过下面的式子计算签名: $\\sigma=(a,a=a^z,b=a^y,b=a^y,c=a^xm^{αxy})$ 容易看出,上述方式计算出的签名和正常计算得到的签名是完全一样的,区别在于用上述方法生成签名不需要知道消息m. 类似于盲签名. 在这种签名中,敌手在不知道消息m的情况下,可以直接向喻言机请求m的签名,从而伪造签名.,尽管他不知道如何用g和z去表示m. 为了防止这种预测,可以使用一个零知识证明向签名方证明自己知道如何用g和z去表示m也就是知道$m=g^mz^r$中的m和r . 第三个签名:【记作cl03】 说明: 上面两个签名都是这一个签名的特殊形式, 该签名需要说明的地方与上一个签名类似,这里不再赘述. 唯一的区别是,这里给出的零知识证明不同.\n注意:\ncl组签名方案【记作cl04】 方案思路总览:\ngm首先生成签名需要用到的各种参数; 成员在加入组时从组管理器获得关于成员公钥【p】的证书【a,b,c】 成员想要代表组签名时,她①用gm的公钥【$pk_r$】加密她的成员资格公钥【p】,然后②签名者证明她拥有加密的成员资格公钥上的证书【a,b,c】,并且③用户知道它对应的私钥【k】。 join阶段: 用户选择一个私钥 k ,计算成员公钥 p 并将其向=发给 gm 进行注册,以申请加入群组。gm用自己的私钥对秘密值 p 进行签名得到$(a,b,c)$发给用户,用户将该签名作为群成员证书。 签名阶段: 隐藏的$(c_1,c_2,c_3,c_4)$【①用gm的公钥【$pk_r$】加密她的成员资格公钥【p】】 使用elgamal加密的方式对群成员的身份进行加密得到$(c_1,c_2,c_3)$, 使用$c_4$生成$(c_1,c_2,c_3)$的承诺,保证$(c_1,c_2,c_3)$的正确性。 盲化的群成员证书$(a\u0026rsquo;,b\u0026rsquo;,c\u0026rsquo;)$【②签名者证明她拥有加密的成员资格公钥上的证书【a,b,c】】 用户使用随机数对自己的证书$(a,b,c)$进行盲化得到$(a\u0026rsquo;,b\u0026rsquo;,c\u0026rsquo;)$,防止暴露自己的证书 这里$c\u0026rsquo;$的产生方式中多了一个随机数 r , 零知识证明$σ$中,证明了以下事情: 其中$c_3=y_1^ug^k\\ \\ \\ (g=e(p,g_1))$的证明,证明了【③用户知道它对应的私钥【k】】 elgamal加密中使用的随机数是用户自己选的。 方案a【cl01】的验签需判断两个双线性映射,零知识证明中包含了一个双线性映射的判断。 方案详情:\n注意:\n这里$\\mathcal{p}$是组成员的身份,而$(c_1,c_3)$实际上是用elgamal加密算法对$\\mathcal{p}c_2^{x_2}$进行了加密,如下:$(c_1,c_3)=(g_2^u,\\mathcal{p}y_1^u) =(g_2^u,\\mathcal{p}(g_2^{x_1}h^{x_2})^u) =(g_2^u,\\mathcal{p}(g_2^{x_1})^u(h^u)^{x_2})) =(g_2^u,\\mathcal{p}c_2^{x_2}(g_2^{x_1})^u)$ 由于gm知道群私钥$(x_1,x_2)$,因此可以解密得到群成员的身份$\\mathcal{p}$ 参考文献 cl原论文:\n《signature schemes and anonymous credentials from bilinear maps》 参考地址:\n基于ps签名的群签名方案 可撤销的动态群签名 该群签名方案根据ps签名和elgamal签名组合设计而成,具有签名短,计算开销小,可撤销群成员等优点。但是群成员的撤销时静态的撤销。 静态撤销是指,每次撤销之后需要重新生成群内的公共信息 动态撤销是指,撤销群成员不需要重新生成群内的公共信息 ps签名类似于cl签名中提出的第一个签名。 预备知识:\n签名方案如下:\n说明:\nissue阶段中,gm将用户的私钥 usk 作为ps签名中的消息 m 进行签名,得到$(a,b)=(\\sigma_1,\\sigma_2)$作为改用户加入群时gm为其颁发的群成员证书。【gm为用户的 usk 颁发了证书】 sign 阶段中,作者提出的方案主要证明了两件事 ① 证明【自己拥有群成员证书】、具体过程中对自己的群成员证书进行了随机化,以达到匿名的效果。 $t_0,t_1$使用随机数对证书$(a,b)$进行了随机化,保证证书不会暴露。 $t_2$保证了用户确实知道群证书中的私钥,从而证明了自己确实是群成员。 ② 证明【elgaml加密的公钥upk 所对应的私钥usk】正是【gm颁发过证书中的私钥usk】。 $r_0,r_1,r_2$作为零知识证明,证明了elgamal加密的公钥确实是群成员证书中的私钥对应的公钥,并且证明了作者知道elgamal加密中使用的随机数。 verify阶段中,只需要验证上面的零知识证明即可。 具体方案:\n群成员的撤销操作:\n可批量验证的群签名 预备知识,\nps签名。【见 二、1.1节 】 具体方案:\n说明:\n实际上,打开的过程就是elgamal的解密过程。 首先根据elgamal加密算法解密得到$\\hat{f}$, 然后计算计算出$\\tau=e(g,\\hat{f})$, 根据 τ 即可在注册表中查找到签名信息$reg_i$ 打开完成后,opener生成一个打开的证明。任何人都能根据这个证明验证打开的合法性。 优缺点:\n优点: 本文提出的方案中,签名和验证签名花费的时间很少, 本签名方案的签名大小很小, 本签名方案实现了cca匿名性,并且支持批量验证 缺点 但是加入群组花费的时间很多。 打开签名的时间随群成员数量的增涨呈线性增长。 总结: 因此适用于群组成员流动性不大的场景之中。在这种场景可以最大化的利用本签名方案的优势。 思考:\n如果将elgamal加密放在签名中产生,这样打开签名的时间复杂度可以降低到$o(1)$ 用数字签名对 τ 进行签名的意义不大,不如直接将 f 存储在$reg_i$之中。 【但是这样就不能实现judge功能了】 基于ecdsa的群签名方案、 参考文献:\nhttps://www.hanspub.org/journal/paperinformation.aspx?paperid=43012\u0026amp;btwaf=22311340 ","date":"2024-07-12","permalink":"http://54rookie.com/posts/%E7%BE%A4%E7%AD%BE%E5%90%8D/","summary":"群签名 群签名 概念 群签名是这样一个数字签名:在一个群签名的方案中,一个群体中的任意一个成员可以代表这个群体对消息进行签名,并且其他人可以使用群公钥对这个签名进行公","title":"群签名"},]
[{"content":"数据库原理 数据库原理 ******************************************** 基本概念 ********************************************** 一、数据库系统基本概念 1、信息:信息是客观存在的,是对“现实世界存在的事物”的“运动状态”和“特征”的描述。 2、数据:数据是用来记录信息的可识别的符号(描述信息的符号记录),是信息的具体表现形式之一。 3、数据和信息的联系:数据是信息的符号表示或载体。信息是数据的内涵,是对数据语义的解释。 4、数据库:database,简称db。 --指长期存储在计算机中的、有组织的、可共享的数据集合。 特征: * 数据按照一定的数据模型组织、描述和存储 (结构化)。 * 可以被各种用户共享 (可共享)。 * 数据冗余度小 (冗余度小)。 * 数据易扩展 (易扩展)。 * 数据独立性较高 (独立性高)。 数据库模式 --数据库中全体数据的逻辑结构和特征的描述,分为外模式、逻辑模式、内模式。 5、数据库管理系统:database management system,简称dbms --是位于 用户 和 操作系统 之间的一层 数据管理软件。 作用: * 能够科学的组织和存储数据 * 能够高效的获取和维护数据 主要功能: * 数据定义:提供数据定义语言ddl,可用于定义数据库中的数据对象。 * 数据操纵:提供数据操纵语言dml,用于实现对数据对象的基本操作(增删改查) * 数据库的建立与维护: 提供实用的程序,完成 数据库数据 的\u0026#34;批量装入\u0026#34;、\u0026#34;数据库转储\u0026#34;、\u0026#34;介质故障修复\u0026#34;、数据库 的 \u0026#34;重组织\u0026#34; 和 \u0026#34;性能监控\u0026#34;等。 * 数据库的运行管理: 保证数据的\u0026#34;安全性\u0026#34;、\u0026#34;完整性\u0026#34;、多用户对数据库的\u0026#34;并发使用\u0026#34;、发生故障后的\u0026#34;系统恢复\u0026#34;。 6、数据库系统:database system,简称dbs。 --指在计算机系统中引入数据库后的系统构成。 包括: * 数据库 * 数据库管理系统 * 应用系统(及其开发工具) * 相关人员(用户、数据库管理员、应用系统开发人员等) 二、数据管理技术的产生与发展 1、人工管理阶段 时期: 20世纪40年代中---50年代中 特点: * 数据的管理者:应用程序,数据 不保存 * 数据面向的对象:某一应用程序 * 数据的共享程度:不共享、冗余度极大 * 数据的独立性:不独立,完全依赖于程序 * 数据的结构化:无结构 * 数据控制能力:应用程序自己控制 2、文件系统阶段 时期: 20世纪50年代末--60年代中 特点: * 数据的管理者:文件系统,数据可长期保存 * 数据面向的对象:某一应用程序 * 数据的共享程度:共享性差、冗余度大 * 数据的结构化:记录内有结构, 整体无结构 * 数据的独立性:独立性差,数据的逻辑结构改变必须修改应用程序 * 数据控制能力:应用程序自己控制 3、数据库系统阶段 时期: 20世纪60年代末至今 特点: * 数据的 管理者: :dbms * 数据 面向的对象 :现实世界 * 数据的 共享程度 :共享性高 - 降低数据的冗余度,节省存储空间 - 避免数据间的不一致性 - 使系统易于扩充 * 数据的 独立性 :高度的物理独立性和一定的逻辑独立性 * 数据的 结构化 :整体结构化 * 数据的结构用数据模型描述,无需程序定义和解释、数据可以更改长度 * 数据的最小存取单位:数据项 ̶* 数据 控制能力 :由dbms控制 4 、数据库系统的特点(使用数据库系统的好处) * 整体数据结构化: 在数据库系统中,数据记录的结构和数据记录之间是具有联系的,且这些联系由数据库管理系统 进行维护,从而减轻了程序员的工作量,提高了工作效率。 * 数据的共享性高、冗余度低且易扩充: 多个用户、多个应用可以同时存取数据库中的数据,用户可以用各种方式通过接口使用数据库中的 数据。同时,数据库通过实现数据共享大大减少了数据冗余,还能够避免数据之间的不相容性和不 一致性。(数据的不一致性:指同一数据不同副本的值不一样) * 数据独立性高: 数据独立性包括数据的 物理独立性 和 逻辑独立性,即用户的应用程序与数据库中数据的物理存 储 和数据的 逻辑结构 均相互独立。 * 数据由数据库管理系统 统一管理和控制: 利用数据库可对数据进行集中控制和管理,并通过数据模型表示各种数据的组织及数据间的联系 同时数据库管理系统提供了以下几个方面的数据控制功能,以解决数据共享带来的安全隐患。 - 数据的安全性保护: 保护数据以防止不合法使用造成的数据泄密和破坏; - 数据的完整性检查: 保证数据的正确性、有效性和相容性(数据中的相容性是指表示同一事实的两个数据应相同, 或者满足某一约束关系的一组数据不应发生互斥); - 并发控制: 使在同一周期内,允许对数据实现多路存取,又能防止用户之间的不正常交互作用(例如, 当多个用户的并发进程同时存取、修改数据库时,可能会发生相互干扰而得到错误的结果或 使得数据库的完整性遭到破坏); - 数据库恢复: 数据库管理系统能及时发现故障,并将数据库从错误状态恢复到某一已知的正确状态(亦称为 完整状态或一致状态)。 * 数据库系统和文件系统 - 操作系统中负责管理和存储文件信息的软件机构称为文件管理系统,简称文件系统 * 联系和区别: 其联系在于: (1)都是对数据进行 组织和管理 的技术。 (2)均由数据管理软件管理数据,程序与数据之间用存取方法进行转换。 (3)数据库系统是在文件系统的基础上发展而来的。 其区别在于: (1)文件系统用文件将数据长期保存在外存上,数据库系统用数据库统一存储数据。(存储位置) (2)文件系统中的 程序和数据有一定的联系,数据库系统中的 程序和数据分离。(数据独立性) (3)文件系统用操作系统中的存取方法对数据进行管理,数据库系统用dbms统一管理和控制数据。(数据管理) (4)文件系统实现以文件为单位的数据共享,数据库系统实现以记录和字段为单位的数据共享。(共享程度) * 并不是说数据库系统一定优于文件系统 - 适合于文件系统不适合数据库系统: 数据的备份、软件或应用程序使用过程中的临时数据存储一般使用文件比较合适。早期的功能比较 简单,比较固定的应用系统也适用文件系统。 - 适合于数据库系统不适合文件系统: 几乎所有企业或部门的信息系统都以数据库系统为基础,都是用数据库。如一个工厂的管理系统。 三、数据模型 --简单来说,数据模型是对现实世界中 数据特征的抽象。 它描述了 数据、数据之间的联系、数据上的操作和数据的完整性约束。 在数据库中用 数据模型 这个工具来抽象、表示和处理现实世界中的数据和信息。 1、数据模型应满足三方面要求: * 能比较真实地模拟现实世界 * 容易为人所理解 * 便于在计算机上实现 2、数据模型的分类: * 概念模型 --从现实世界的客观对象中抽象出的某种 信息结构,它不依赖于计算机系统,也不是某种dbms支持 的数据模型,仅仅是概念级的模型,称为概念模型。(从用户的观点出发的。 eg.e-r图) * 解释: 概念模型用于信息世界的建模,是 现实世界 到 信息世界 的第一层抽象。为了把现实世界中的 具体事物抽象、组织为某一数据库管理系统支持的 数据模型 ,人们常常首先将现实世界抽象 为信息世界,然后将信息世界转换为机器世界。. 也就是说,首先把现实世界中的客观对象抽 象为某一种信息结构,这种信息结构并不依赖于具体的计算机系统,不是某一个数据库管理系 统(dbms)支持的数据模型,而是概念级的模型,称为概念模型。. * 概念模型中的一些术语: - 实体: 客观存在并可相互区别的事物。 - 实体型: 用实体名及其 属性名集合 来抽象和刻画 同类实体,称为实体型。 (具有相同属性的实体必然具有共同的特征和性质。如 student(name,age,sex) ) - 实体集: 同一类型实体的集合叫做实体集 - 属性: 实体所具有的某一特性叫做这个实体的属性。 - 码: 唯一标识实体的属性叫做码。 - 联系: * 联系(relationship)表示一个或多个实体之间的 关联关系 * 联系集(relationship set)是指同一类联系构成 的集合 * 将联系、联系集等统称为联系。 * 联系的元数: -- 一个联系涉及的实体集的个数叫做联系的元数或度数(degree) * 分类: • 一元联系(递归联系):同一个实体集内部实体之间的联系 • 二元联系: 两个不同实体集实体之间的联系 • 多元联系: 多个不同实体集实体之间的联系 * 二元联系的类型:(设有两个实体集e1、e2) * 一对一的联系: (1:1) 对于e1中的一个实体来说,e2中最多存在一个实体与其对应 对于e2中的一个实体来说,e1中最多存在一个实体与其对应 * 一对多的联系: (1:n) e1中的一个实体可以与e2中的多个实体相对应,e2中的一个 实体最多与一个e1中的实体相对应。 * 多对多的联系: (m:n) e1中的一个实体可以与e2中的多个实体相对应,e2中的一个 实体与e1中的多个实体相对应。 * 逻辑模型 --描述了数据库系统中 所有实体、实体的属性以及实体间的联系。反映了数据之间的逻辑结构。 (按照计算机系统的观点进行数据建模,用于dbms的实现) 反映的是系统分析设计人员对数据存储的观点,是对概念数据模型进一步的分解和细化。 逻辑模型不关心数据在物理存储上的存储结构,只关心数据之间的逻辑关系、 * 关系模型: 见307行的《关系数据库》章节 * 半结构化的数据模型 ✓ 层次模型:类似树的结构 ✓ 网状模型:类似图的结构 ✓ xml、json、. . . * 物理模型 --描述数据在计算机硬件上的存储结构。(从计算机硬件角度出发) 注意: 这些数据模型的根本区别在于 数据结构 不同 3、数据模型的三要素: * 数据结构 是所研究的对象类型的集合,用于描述数据库的 组成对象以及对象之间的联系。 (描述 数据模型中有哪些对象,这些对象之间有什么联系) (关系模型只有一种数据结构--关系) * 数据操作 是指对 数据库中各种对象(型)的实例(值)允许进行的操作的集合,包括 操作 及有关的操作规则, 用于描述数据的 动态特征。 (定义 我们能对数据模型中各对象的实例进行哪些操作) * 数据的约束条件 是一组完整性规则的集合。 完整性规则是 给定的数据模型中 数据及其联系 所具有的制约和依存规则,用以限定符合数据模型的 数据库状态以及状态的变化,以保证数据的正确、有效、相容。 (数据模型中的 数据及联系应该满足哪些约束条件) 四、数据库系统的体系结构 1、三级模式两级映像 三级模式: * 外模式:也称子模式或用户模式。 --是 数据库用户使用的局部数据的 逻辑结构和特征 的描述。 特点: * 逻辑模式和外模式的关系:一对多 * 外模式和应用之间的关系:一对多 外模式的作用: * 保证每个用户只能访问和看到所对应外模式中的数据。 * 是保证数据独立性的一个有力措施。 * 逻辑模式:也称模式。 --是 数据库中全体数据的 逻辑结构和特征 的描述,是综合了所有用户需求的公共数据视图。 特点: * 与数据的物理存储细节和硬件环境无关。 * 与具体的应用程序、开发工具、高级程序设计语言无关。 逻辑模式定义的内容: * 数据的逻辑结构(数据项名、类型、取值范围等) * 数据之间的联系 * 数据有关的安全性、完整性要求 * 内模式:也称存储模式。 --是数据 物理结构和存储方式 的描述、是数据在数据库内部的表现形式。 包括: * 记录的存储方式 * 索引的组织方式 * 数据是否压缩存储 * 数据是否加密 * 数据数据存储记录结构的规定 注意: * 一个数据库只有一个内模式 两级映像: * 外模式/逻辑模式映像: --定义外模式与逻辑模式之间的对应关系。 * 每一个外模式都对应一个外模式/模式映象 * 映象定义通常包含在各自外模式的描述中 * 逻辑模式/内模式映像: -- 逻辑模式/内模式映象定义了数据 全局逻辑结构与存储结构之间的对应关系。 * 数据库中模式/内模式映象是 唯一 的 * 该映象定义通常包含在逻辑模式描述中 五、数据独立性: 1、逻辑独立性:(也叫数据于程序的逻辑独立性) --指用户的应用程序与数据库的逻辑结构是相互独立的。数据的逻辑结构改变了,应用程序也可以不变。 说明: 外模式/模式映象保证了数据的 逻辑独立性 * 当模式改变时,数据库管理员修改有关的外模式/模式映象,使外模式保持不变 * 应用程序是依据数据的外模式编写的,从而应用程序不必修改,保证了数据与程 序的逻辑独立性 。 2、物理独立性:(也叫数据于程序的物理独立性) --指用户的应用程序与存储在磁盘上的数据库中数据是相互独立的。当数据的物理存储改变了,应用程序不用改变。 说明: 模式/内模式映象的用途是保证数据的物理独立性 * 当数据库的存储结构改变了 ( 例如选用了另一种存储结构), 数据库管理员修改 模式/内模式映象使模式保持不变。 * 应用程序不受物理存储方式的影响, 保证了数据与程序的物理独立性。 ******************************************* 关系数据库 ********************************************** ---关系模型 一、关系的数据结构 * 关系:(详解见: https://www.cnblogs.com/labixiaohei/p/12105453.html) --现实世界中的实体以及实体之间的联系可用关系表示。(对应于实体) * 关系模型中 数据的逻辑结构:二维表 --从用户来看,关系模型中数据的逻辑结构就是一张二维表。 * 关系模型的基础:集合代数 --术语介绍: * 域: --具有相同数据类型的值的集合。 eg. 整数、实数、{\u0026#39;男\u0026#39;,\u0026#39;女\u0026#39;}等等。 * 笛卡尔积: --给定一组域d1,d2,,… dn – 这些域可以完全不同 – 也可以部分或全部相同 则d1 ,d2 ,… ,dn 的笛卡尔积为: d1 × d2 × … × dn = {(d1 , d2 , … , dn )|di ∈ di ,i=1, 2, … , n} – 所有域的所有取值的一个组合 – 结果中不能有重复的元组(即行) * 关系: --d1 × d2 × … ×dn 的子集叫作在域d1 , d2 , … , dn 上的关系,表示为: r(d1 ,d2 ,… ,dn) r : 关系名 n : 关系的 目 或 度( (degree) ) 当n=1时 时 , 称该关系为 单元关系(unary relation) 当n=2时 时 , 称该关系为 二元关系(binary relation) 关系的表示: * 关系是一个二维表,表的每一行对应一个元组,每一列对应一个域。 * 关系中不同列可以对应相同的域,为了加以区分,对每列起一个名字,称为属性 关系的分类: * 基本关系:实际存在的表,是实际存储数据的逻辑表示。 * 查询表:查询结果对应的表,并不存储在磁盘上。 * 视图:由基本表或其他视图导出的表,是虚表。 关系的性质: * 行的顺序可交换,列的顺序也可交换 * 每一列中的分量是同一类型的数据,来自同一个域 * 不同的列可以取自同一个域,但他们的属性名必须不同 * 属性必须取原子值,即一个属性是一列,不能有一个大属性下面有两个小属性 * 码: --有一个或多个属性组成。 ✓ 候选码 (candidate key): 在关系中能唯一标识元组的属性或属性集 * 注意: - 一个关系可以有多个候选码 - 候选码可以是多个属性的组合 ✓ 主属性 (prime attribute): 候选码的各个属性 * 注意: - 候选码的每个属性都是主属性 ✓ 主码 (primary key): 用户选择的、作元组标识的候选码 * 注意: - 一个关系必须有一个主码 - 一个关系最多有一个主码 - 主码不能取空值 ✓ 全码 (all- - key): 关系的所有属性是这个关系的候选码 ✓ 外码 (foreign key):如果一个关系r 中的一个属性f 对应着另一关系s的 的 主码k 那么f 在关系r 中称为 外码。其中s称为被参照表,r称为参照表。 * 注意: – 关系r 和s 可以是同一个关系 – 被参照关系s 的主码k 和参照关系的外码f 必须定义在同一个( 或一组 ) 域上 – 外码并不一定要与相应的主码同名 – 当外码与相应的主码属于不同关系时 , 往往取相同的名字 ,以便于识别 * 关系模式: -- 关系模式是对关系的描述,关系模式是型、关系是具体的值。(对应于实体型) eg. 用关系表示学生这个实体,则: 关系模式:学生( 学号, 姓名 , 年龄, 性别, 籍贯) 关系:具体的每个学生的信息 * 表示方法: 关系模式通常可以记为 r (u) 或 r (a 1 ,a 2 , ,… ,a n ) - r : 关系名 - a1,a2 ,… ,an : 属性名 * 关系和关系模式的区别: * 关系模式是对关系的描述。是静态的,稳定的 * 关系是关系模式在某一时刻的状态或内容。是动态的,随时间不断变化的。 * 关系数据库: --在一个给定的应用领域中,用来表示 所有实体及实体之间联系的 关系的集合 构成一个关系数据库。 例: : 教学管理数据库中有四个关系 : 教师关系t ,课程关系c ,学生关系s ,选课关系sc * 关系数据库模式 是关系数据库的型,是对关系数据库的描述 – 例: : 教学管理数据库 中有四个 关系模式 : t(tid,tname,title) c(cid,cname,tid) s(sid,sname,age,sex) sc(sid,cid,score) * 关系数据库的型: 关系数据库的型也称为关系数据库的模式,是对关系数据库的描述。 (包括若干域的定义以及在这些域上定义的若干关系模式) * 关系数据库的值: 关系数据库的值是这些关系模式在某一时刻的取值,通常称为关系数据库。 二、关系操作: 1、常用的关系操作: 查询、插入、删除、修改 2、关系操作的特点: 集合操作方式,即操作的对象和结果都是集合。 3、关系数据语言的种类: * 关系代数语言(早期的,抽象的) * 关系演算语言(早期的,抽象的) * sql语言:具有两者双重特点的语言,是现在最流行的语言之一。 4、关系数据语言的特点: * 是一种高度非过程化的语言 * 能够嵌入到高级语言中使用 三、完整性约束: 1、完整性规则: --关系的完整性规则是指对关系的某种约束条件,包括三类: * 实体完整性: - 主码唯一 - 主属性不能取空值 * 参照完整性: - 外码要么取空值,要么取被参照表中某个元组的主码值 * 自定义完整性: - 指用户定义的完整性 是针对某一具体关系数据库的约束条件,反映了某一具体应用所涉及的数据 必须满足的语义要求。(如: 规定 studen.sex 只能从 {\u0026#34;男\u0026#34;,\u0026#34;女\u0026#34;} 中取值 ) - 关系模型 应提供定义和检验这类完整性的机制 , 以便用统一的系统的方法处理它们 , 而不要 由应用程序承担这一功能。 2、完整性约束的作用 保证数据库中数据的正确性,防止不符合规范的数据进入数据库。 eg. sc以sno为外键,当违反参照完整性,如插入sno不存在的学生信息,这将导致数据库中的数据无意义。 四、关系代数的定义: --是一种抽象的数据查询语言,用对关系的运算来表达查询。 * 运算对象和运算结果:关系 * 运算符:四类 1、传统的集合运算符 * 并 \u0026#34;∪\u0026#34; :r ∪ s ,结果由r或s中的所有元组组成 * 差 \u0026#34;-\u0026#34; :r- s , 结果 由属于r 而不属于s 的所有元组组成 * 交 \u0026#34;∩\u0026#34; :r ∩ s , 结果由即属于r又属于s的元组构成 注意: – r 和 s的属性个数必须相同 – 对应属性必须取自同一个域 2、专门 的关系运算符 * 投影 π :从r中选择出若干属性列组成新的关系 π l (r) * l 为r 中的属性列表 * 结果为只包含r中某些列的新的关系 * 结果要去掉重复元组 * 选择 σ :在关系r 中选择满足给定条件的各个元组 σ c (r) * c :选择条件,是一个逻辑表达式 * 结果为只包含r中 某些元组的新的关系 * 笛卡尔积 x : 假设: r 关系:n 个属性,k1 个元组 s 关系:m 个属性,k2 个元组 则 r × s 结果: * 将r 中的每个元组t1 和s 中的每个元组t2 配对连接 * 列数:n\u0026#43;m * 前n 列是关系r 的一个元组t1 * 后m 列是关系s 的一个元组t2 * 行数: :k1 ×k2 * 当r 和s 中有重名属性a 时,则采用r.a 和s.a * 连接 ⋈ : * 连接也称为θ 连接 r ⋈ s aθb * a 和 b :分别为r 和s 上度数相等(即属性的个数相等)且可比的属性组 * θ :比较运算符 – 连接运算从 从r 和s的 的 笛卡尔积r ×s 中选取(r 关系)在a 属性组上的 值与(s 关系)在b 属性组上值满足比较条件的元组 * 等值连接: * θ 为 = 的连接称为等值连接 * 相当于从关系r与s的笛卡尔积中选取a 、b 属性值相等的那些元组 * 自然连接: --自然连接是在公共属性(组)上进行的 等值连接 注意: – 两个关系中必须具有公共属性(组) – 在结果中把重复的属性列去掉 * 自然连接和等值连接的异同点 - 相同点:都是属性值是否相等进行连接。 - 不同点:自然连接在相同属性值上进行相等比较并投影删除相同的属性; 等值连接并不要求在相同的属性上进行相等比较,也不删除相同的属性。 * 除 ÷ : --设关系r除以关系s的结果为关系t,那么: - t中的属性等于r-s的属性 - t中元组和s中元组的组合在r中 * 注意: - s中的属性如果比r中的多,结果为空集 (也有定义说 s的属性必须是r的真子集才能除) - t × s ∈ r (即 t和s的笛卡尔积中的元组都是r中的元组 ) * 重命名 ρ : ρ s(a1,a2 ... an)(r) --将关系r重命名为s,并将r中的属性名重命名为a1,a2 ... an 注意: * s可以和r相同 * a1,a2 ... an可以和原属性相同 3、比较符 运算符 \u0026#34;\u0026lt;\u0026#34; \u0026#34;\u0026lt;=\u0026#34; \u0026#34;\u0026gt;\u0026#34; \u0026#34;\u0026gt;=\u0026#34; \u0026#34;=\u0026#34; \u0026#34;\u0026lt;\u0026#34; \u0026#34;\u0026gt;\u0026#34; 4、逻辑运算符 \u0026#34;﹁\u0026#34; \u0026#34;∧\u0026#34; \u0026#34;∨\u0026#34; 5、关系运算符的优先级:(从上到下,优先级依次递减) * 括号 * 单目运算符优先级最高--- select, project * 笛卡尔积和连接运算符 * 交 * 并和差 ******************************************** sql、ddl、dcl、dml *************************************** 一、sql语言: --即,结构化查询语言(structured query language) * sql语言的特点: ⒈ 综合统一 ̶- sql集 数据定义语言(ddl),数据操纵语言(dml),数据库控制语言(dcl)语言的功能于一体,语言风格统一, 能独立完成数据库周期中的全部活动。 2. 高度非过程化 - 用户只需提出“做什么”,而不必指明“怎么做” - 存取路径的选择以及sql 语句的操作过程由系统自动完成。 3. 面向集合的操作方式 - 操作对象、查找结果可以是元组的集合。 - 一次插入、删除、更新操作的对象可以是元组的集合 4. 同一种语法结构提供两种使用方式 - 自含式语言 - 嵌入式语言 5. 语言简捷,易学易用 - 3 大类,11 个命令词 类 别 动 词 数据定义ddl create drop alter 数据操纵dml select insert update delete 数据控制dcl grant revoke 二、数据定义语言 ddl 1、sql的数据定义语言: 操作对象 操 作 方 式 - 创 建 删 除 修 改 模式 create schema rdrop schema 表 create table drop table alter table 视图 create view drop view 索引 create index drop index alter index ** ** ** 说明: 以下的语句中, \u0026lt;\u0026gt;表示内容必须写 []表示内容可选择一部分写,或不写 * ddl: * 预备知识: * 常用的数据类型 * 第一大类: 整数 数据 – bigint: 以8 个字节来存储正负数, 范围:-2^63 到 到 2^63 -1 – int: 以4 个字节来存储正负数,范围:-2^31 到 到 2^31 -1 – smallint: 以2 个字节来存储正负数.范围:-2^15 到 到 2^15 -1 – tinyint: 是最小的整数类型, 存储正整数,仅用1 字节, 范围:0 至2^8 -1 – bit: 值只能是0 或1 ,当输入0 以外的其他值时, 系统 均认为是1 * 第二大类: 精确数值 数据 – decimal: 用来存储从-10^38 \u0026#43;1 到10^38 -1 的固定精度和范围的数值型数据 • 必须指定范围和 精度:decimal (p[,q]) 例 例: :decimal (10,2) – numeric: 和decimal 相同 * 第三大类: 浮点 数值数据 – float: 用8 个字节来存储数据. 最多可为53 位.范围为 为:-1.79e\u0026#43;308 至1.79e\u0026#43;308. – real: 位数为24, 用4个字节、数字范围:-3.04e\u0026#43;38 至3.04e\u0026#43;38 * 第四大类: 字符串 数据 – char: char(n) 固定的长度为 n 个字符的字符串, 不足的长度会用空格补上. – varchar: varchar(n) 可变的最长长度为n 个字符的字符串,尾部的空格会去掉. * 第五大类: 日期时间 数据 – date: 日期类型 • date \u0026#39;yyyy-mm-dd\u0026#39; • example: date \u0026#39;2004-09-30\u0026#39; – time: 时间类型 • time \u0026#39;hh:mm:ss\u0026#39; • example: time \u0026#39;15:30:02.5\u0026#39; – datetime: 日期时间类型 * 常用的完整性约束 – 主码约束: primary key – 参照完整性约束: foreign key…references… – 唯一性约束:unique – 非空值约束:not null – 取值约束:check * 模式 - 模式的定义 create schema \u0026lt;模式名\u0026gt; authorization \u0026lt;用户名\u0026gt; - 模式的删除 drop schema \u0026lt;模式名\u0026gt; cascade 或 restrict casacade:级联 表示删除模式时,把该模式种的所有对象全部删除(包括,表,视图等) restrict:限制 如果模式已经定义了下属对象(如,表,视图等),则拒绝该删除语句的执行。 * 基本表 - 创建基本表 create table \u0026lt;表名\u0026gt; ( \u0026lt;列名\u0026gt; \u0026lt;数据类型\u0026gt; \u0026lt;列级完整性约束\u0026gt; \u0026lt;列名\u0026gt; \u0026lt;数据类型\u0026gt; \u0026lt;列级完整性约束\u0026gt; ...... [表级完整性约束] ) - 修改基本表 alter table \u0026lt;表名\u0026gt; [ add [column] \u0026lt;新列名\u0026gt; \u0026lt;数据类型\u0026gt;[完整性约束] ] [ add \u0026lt;表级完整性约束\u0026gt; ] [ drop [column] \u0026lt;列名\u0026gt; [cascade 或 restrict] ] [ drop constrict\u0026lt;完整性约束名\u0026gt; [cascade 或 restrict] ] alter column \u0026lt;列名\u0026gt; \u0026lt;数据类型\u0026gt; * cascade : 级联 自动删除引用了该列的其他对象 * restrict : 限制 如果该列被其他对象引用,拒绝删除此列 * drop constrict : 删除完整性约束 - 删除基本表 drop table \u0026lt;表名\u0026gt; [ cascade 或 restrict ] - 基本表和视图 * 什么是基本表? 基本表是数据库中真实存在且存储数据的独立的表。 * 什么是视图? 视图是数据库中虚拟的表,并不真实存储数据,只存储数据的定义。 视图在概念上与基本表等同,用户可以在基本表那样使用视图,可以在视图上再定义视图。 * 两者的区别和联系是什么? 区别: 存储方式不同(基本表,真实存储数据;视图,不存储真实数据,只存储视图的定义) 存在形式不同(基本表,真实存在于数据库中;视图,并不真实存在于数据库中,是一张虚拟的表) 联系: 视图是由一个或者多个基本表导出的 * 视图的作用: 1.对机密数据提供一定的保护功能 在设计数据库时,可以对不同的用户定义不同的视图,使机密数据不出现在不应该看到这些数据 的用户视图上。 eg. student 表涉及15个院系的学生信息,可以在上面定义15个视图,每个视图只包含一个院系 的学生数据,并且只允许每个院系的主任查询和修改本院系的学生视图。 2.对重构数据库提供 一定的逻辑独立性 eg. 数据库重构中,常常将一个表垂直拆分为多个表,例如: 将student(sno,sname,ssex,sage,sdept)拆为sx(sno,sname,sage)和sy(sno,ssex,sdept) 这时,student表 是sx和sy两个表自然连接的结果。如果建立一个视图student 如下: create view student(sno,sname,ssex,sage,sdept) as select sx.sno,sx.sname,sy.ssex,sx.sage,sy.sdept from sx,sy where sx.sno = sy.sno 此时,尽管数据库的逻辑结构改变了,从student拆分为了sx和sy,但应用程序不用改变。 因为新建立的视图结构和原来的关系完全一样,用户原来的应用程序通过视图仍能查到数据。 3.简化用户的数据查询操作 eg. 某些查询需要用到多张表,这时查询语句需要进行多表连接,用户 需要思考如何连接这些表 如果先将这些表连接后定义成一个视图,则 以后只需要对这个表进行简单查询就能得到相同 的结果。 4.让用户从多种角度看待同一个数据 eg. 如,企业将 \u0026#39;学历\u0026#39;属性当作招聘的要求,数据分析部门把学历当作一个变量使用。 5.适当利用视图可以更清晰的表达查询 * 视图的更新: --视图不实际存储数据,对视图的更新最终会转化成对基本表的更新。 注意:并不是所有的视图都可以进行更新,有些视图的更新不能唯一的转化为有意义的基本表的更新。 * 行列子集视图:只从单个基本表导出的,去掉基本表中某些行和某些列,但保留了主码的视图。 - 行列子集视图是可更新的 - 有些视图在理论上是可更新的,有些则在理论上就是不可更新的。 * 视图的更新: drop view \u0026lt;视图名\u0026gt; [cascade] 视图如果没有导出其他视图,则可以直接删除,否则必须加上cascade、否则无法删除。 加上cascade后,将该视图以及该视图导出的其他视图全部删除。 * 索引 - 创建索引 create [unique] [cluster] index \u0026lt;索引名\u0026gt; on \u0026lt;表名\u0026gt;( \u0026lt;列名\u0026gt;[\u0026lt;次序\u0026gt;] \u0026lt;列名\u0026gt;[\u0026lt;次序\u0026gt;] ...... ) * unique 表示此索引的一个值只对应于一个记录。 * cluster 表示建立的索引是聚簇索引。 * \u0026lt;次序\u0026gt; 可选 asc(升序,默认) 或 desc(降序) - 修改索引 alter index \u0026lt;旧索引名\u0026gt; rename to \u0026lt;新索引名\u0026gt; - 删除索引 drop index \u0026lt;索引名\u0026gt; * dml: * 数据查询 * 数据更新 * 数据修改 * 数据删除 * dcl: * 数据授权 * 权限收回 * 空值的处理 * 视图 * sql的一些总结: * 常用的关键字: all / distinct all / any in / not in is null / not is null in / exists between and like / not like \u0026#39;%\u0026#39; , \u0026#39;_\u0026#39; 和 \u0026#39;\\\u0026#39; 的用法 where / having 相关子查询 / 不相关子查询 join / left join / right join / all join asc / desc * select语句的执行顺序: 1、from 子句组装来自不同数据源的数据; 2、where 子句基于指定的条件对记录行进行筛选; 3、group by 子句将数据划分为多个分组; 4、使用聚集函数进行计算; 5、having 子句筛选分组; 6、计算所有的表达式; 7、select 的字段; 8、使用 order by 对结果集进行排序。 * 易混淆的关键词比较: - in 和 exists 的区别: * a是员工表,b是部门表。下面为查询 所有部门的 所有员工的两种实现方式: select * from a where deptid in(select deptid from b); select * from a where deptid exists(select deptid from b); * 分析: --对于用in的查询方式。先执行子查询,即 从b表中查出所有 deptid 、再从子查询结果 中拿出一个deptid 与 主查询 a中的deptid 依次比较,将deptid=a.deptid 的记录放 入结果表中。 然后取出子查询结果表中的下一条 deptid 、重复上述步骤。 (相当于子查询是外层循环,主查询是内层循环) --对于用exists的查询方式,正好与上述过程相反(主查询是外层循环,子查询是内层循环) * mysql的优化原则是\u0026#34;小表驱动大表\u0026#34;,即应该让数据量大的表作为内层循环。因此 - 当主查询表的元组数量多于子查询表的元组数量时,用in的查询效率高 - 当主查询表的元组数量少于子查询表的元组数量时,用exists的查询效率高 - where 和 having 的区别: ---where的作用对象是基本表或视图,having的作用对象是分好的组,一般跟在group by 之后。 * having并不是一定要跟在group by 之后。 eg. select a,b,c from tablea where a \u0026gt; 100; select a,b,c from tablea having a \u0026gt; 100; 上述两个句子是等价的,都可以(因为select中已经提前筛选出了\u0026#39;a\u0026#39;属性) eg. select b,c from tablea where a \u0026gt; 100; select b,c from tablea having a \u0026gt; 100; 上述having会报错(因为select中没有筛选出了\u0026#39;a\u0026#39;属性,having不能对基本表进行操作) eg. select avg(a) as av,b,c from tablea where av \u0026gt; 100 group by d; select avg(a) as av,b,c from tablea group by d having av \u0026gt; 100; 上述where将会报错(因为where对基本表进行操作,基本表中没有 \u0026#39;av\u0026#39; 这个属性) * where - where在查询结果集返回之前,根据条件进行筛选 然后返回结果集。 - where中不能使用聚合函数 - where是一个约束声明 * having - having在查询结果集返回之后,根据条件对结果集的内容进行筛选。 - having中可以使用聚集函数 - having是一个过滤声明 - char 和 varchar 的区别: * char: 定长字符串,如果插入的字符长度小于定义的长度,用空格补齐 存取速度比varchar快得多 方便程序的存储和查找 * varchar: 变长字符串,插入的字符长度是多少,就占用多少个字符空间,但是会增加一个字节记录字符串长度 存取速度较慢 查找不方便 * char是用空间换时间、varchar用时间换空间 ****************************************** 数据库的安全性 *********************** *************** 一、数据库的安全性 --数据库的安全性指 防止由于数据库的不合法使用造成的数据泄露、更改或破坏。 * 数据库系统的安全保护措施是否有效是数据库系统主要的性能指标之一 1、数据库中 数据的共享 * 数据库系统中的数据共享不能是无条件的共享,否则将会导致一系列的安全问题。如泄露国家机密等 * 数据库中数据的共享 是在dbms严格统一的控制之下的共享,只允许有合法使用权限的用户访问允许他存取的数据。 2.数据库的不安全因素 * 非授权用户对数据库的恶意存取和破坏 * 数据库中重要或敏感的数据被泄露 * 安全环境的脆弱性 3、安全性控制的一般方法 (1) 用户身份鉴别 * 静态口令鉴别: 设置口令(密码),输入正确的口令后才能对数据库进行操作。(即,需要先登录数据库管理系统) * 动态口令鉴别: 每次登录都需要使用动态产生的新口令。(如、短信验证码登录) * 生物特征鉴别: 通过每个生物体所特有的、可测量、识别和验证的稳定的生物特征。(指纹,虹膜等) * 智能卡鉴别: 智能卡是一种不可复制的硬件,内置集成电路芯片,具有硬件加密的功能、 (2) 存取控制 * 存取控制要达到的目的: 保证每个用户只能访问到自己有权读取的数据。 * 存取控制包括: - 用户权限定义: 必须预先对每个用户定义存取权限,这些定义被编译后存储在数据字典中。被称为授权规则。 - 合法权限检查: 用户提出存取数据库的操作后,数据库管理系统查找数据字典,根据安全规则进行合法权限检查, 如果用户的操作请求超出了它的权限,系统将拒绝执行此操作。 * 用户权限的组成 数据对象:用户权限的作用对象 操作类型:用户可以对操作对象进行哪些类型的操作。 -- 定义用户权限就是 \u0026#34;定义一个用户可以在哪些数据对象上进行哪些类型的操作\u0026#34;。 * 授权粒度: --指能把一个用户的权限对象划分到多详细,它是衡量授权机制是否灵活的一个重要指标。 * 授权定义中数据对象的粒度越细,即可以定义的数据对象的范围越小,授权子系统就越灵活。 * 关系数据库中授权的数据对象粒度分为 – 数据库 – 表 – 属性列 – 行 * 自主存取控制方法: --用户可以自主的决定将数据的存储权限授予任何人,甚至决定是否 将\u0026#34;授权\u0026#34;的权限授予别人。 因此,这样的存取控制称为自主存取控制。 * 授权: 为某个用户 定义存取权限称为授权。 - 语法: grant\u0026lt;权限\u0026gt; [ \u0026lt;权限\u0026gt; ]... on \u0026lt;对象类型\u0026gt; \u0026lt;对象名\u0026gt; [ \u0026lt;对象类型\u0026gt; \u0026lt;对象名\u0026gt; ]... to \u0026lt;用户\u0026gt; [ \u0026lt;用户\u0026gt; ]... [ with grant option ] * 收回: 剥夺某用户的 存取权限称为收回。 - 语法: revoke\u0026lt;权限\u0026gt; [ \u0026lt;权限\u0026gt; ]... on \u0026lt;对象类型\u0026gt; \u0026lt;对象名\u0026gt; [ \u0026lt;对象类型\u0026gt; \u0026lt;对象名\u0026gt; ]... from \u0026lt;用户\u0026gt; [ \u0026lt;用户\u0026gt; ]... [ cascade|restrict ] * 数据库角色 --数据库角色是一组被命名的,与数据库操作相关的权限,角色是权限的集合。 语法参考数据库课本p146页内容 - 角色的创建 - 角色的授权 - 将一个角色授予其他角色或用户 - 角色权限的收回 * 强制存取控制方式 -- 将dbms管理的所有实体分为 主体和客体 两类,dbms为他们的每个实例指派一个敏感度标记。 主体的敏感度标记叫做 \u0026#34;许可证级别\u0026#34;,客体的叫做 \u0026#34;密级\u0026#34; 。 * 主体对客体的存取操作必须遵循以下规则 - 当主体的许可证级别 大于等于 客体的密级时,该主体才能读取相应的客体。 - 当主体的许可证级别 小于等于 课题的密级时,该主体才能写相应的客体。 * 强制存取控制是对数据本身进行密级标记,无论数据如何复制,标记和数据都是一个不可分割的整体, 只有符合密级标记要求的用户才能操纵数据,从而提高了更高的安全性。 (3) 视图 --视图机制把要保密的数据对无权存取这些数据的用户隐藏起来,从而自动地对数据提供一定程度的安全保护。 视图机制更主要的功能在于提供数据独立性,其安全保护功能不太精细,往往远不能达到应用系统的要求。 (4) 审计 --审计功能把用户对数据库的操作全部记录下来,放入审计日志(audit log),审计员可以利用审计日志监控数 据库中的各种行为,重现导致数据库现有状况的一系列事件,找出非法存取数据的人、时间和内容等。 * 注意:审计功能很费时间和空间,dbms往往将审计功能作为 可选择开启选项。 * audit 和 noaudit 语句 (5) 密码存储 -- 根据一定的算法,将原始数据(明文)变换为不可直接识别的格式(密文)、 这样就使得不知道解密算法的人无法获知数据内容。 * 存储加密 - 透明加密:当对加密数据进行增删改查时,dbms自动对数据进行加密解密操作,用户完全感知不到。 基于数据库内核的存储加密、解密方法性能较好,安全完备性高。 - 非透明加密: 通过多个加密函数实现。 * 传输加密 (6) 其他安全保护 * 推理控制:防止用户通过自己能够访问的数据推知更高密级的数据 * 隐蔽信道:看书上p153的例子 ******************************************** 数据库的完整性 **************************************** 数据库的完整性 定义: 数据库的完整性指的是 数据库的 正确性 和相容性 正确性:数据是符合现实世界语义,反应当前的实际情况的。 ( eg. 一个人的年龄是 -12岁,显然不正确 ) 相容性:指数据库同一对象在不同关系中的数据是否符合逻辑。 ( eg. 小明在选课表中没有选 数学课,但是它的成绩表却显示它的数学成绩为100分 显然不相容) 一、实体完整性 * 内容 - 主键的取值非空,并且取值唯一 - 主属性不能为空 * 违约处理 每当用户向数据库中插入一条数据,dbms都会对实体完整性进行检查 - 判断主码的值是否唯一,如果不唯一,则拒绝插入此记录 - 判断主属性的值是否为空,如果为空,则拒绝插入此记录 * 注意: 由于要检查实体完整性,因此每次插入数据都要对整个表的记录进行扫描,这是一个非常耗时的过程。 为了解决这个问题,dbms一般在主码上自动建立一个索引,如b\u0026#43;树索引。这样就可以索引查找本表中 是否存在待插入的主码值,大大提高了效率。 二、参照完整性 * 内容 - 外码的值要么取空值,要么取被参照表中主键的某个值。 * 违约处理: --对参照表和被参照表进行操作时,可能破快参照完整性,因此操作前需要进行检查 - 当往参照表内插入记录,或修改参照表中的外码的值时,可能破坏参照完整性、 - 当删除被参照表中的记录,或者修改被参照表中的主码值时,可能破坏参照完整性、 --当参照完整性被破坏时,dbms的处理策略 - 拒绝执行(no action): 拒绝该操作的执行,这是默认的策略。 - 级联操作(cascade): 当删除被参照表中的一个元组,或修改被参照表中的元组导致参照表的数据不一致时,删除参照表中 所有导致不一致的元组。 eg. 删除students中学号为001的学生记录,采用级联操作时,sc表中所有学号为001的学生的成绩 都会被删除。 - 设置空值法 当删除被参照表中的一个元组,或修改被参照表中的元组导致参照表的数据不一致时,将参照表中 所有导致不一致的元组的相关属性设置为空值。 * 注意: 只有删除或修改被参照表中的元组时,才会用到cascade和设置空值法,在参照表中插入元组 或修改参照表中的外码值时,如果违反参照完整性,则直接拒绝执行。 三、用户自定义的完整性 * 列值非空(not null) * 列值唯一(unique) * 检查列值是否满足某一表达式(check) * ... ... 四、完整性约束命名子句 1、给完整性约束命名 * constrict \u0026lt;完整性约束名\u0026gt; \u0026lt;完整性约束条件\u0026gt; eg. constrict name1 not null; constrict name2 check(sage\u0026gt;10); 2、删除某个完整性约束 * drop constrict \u0026lt;完整性约束名\u0026gt; eg. drop constrict name1; 断言 触发器 *、数据库的完整性和安全性 * 完整性:数据库的完整性是指,数据库中数据的正确性、一致性和相容性 * 安全性:防止用户非法使用数据库造成数据泄露,更改或破坏。 * 区别和联系 联系:都时为了保护数据库中的数据,是一个问题的两个方面、 区别:完整性是防止数据库的合法用户对数据更改时,破坏数据库的一致性; 安全性旨在防止未经授权的用户访问数据库,对数据库中的数据进行进行恶意破坏和修改。 ******************************************** 范式 ********************************************** 一、关系模式的形式化定义 1、关系模式由五部分组成 --r(u,d,dom,f) r: 关系名 u: 组成该关系的属性名集合 d: 属性集合中的属性所来自的域 dom: 属性向域的映像集合 f: 属性间数据依赖关系的集合 2、数据依赖 --\u0026#34;属性值\u0026#34;间的相互关连(主要体现于值的相等与否)的关系叫做 数据依赖 ,它是数据库模式设计的关键。 * 注意: ✓ 数据依赖通过一个关系中 属性间值的相等与否 体现数据间的相互关系 ✓ 数据依赖是现实世界 属性间相互联系 的抽象 ✓ 数据依赖是数据内在的性质 ✓ 数据依赖是语义的体现 * 数据依赖的分类: * 函数依赖(functional dependency ,简记为fd): --设 r(u)是一个属性集u上的关系模式,x和y是u的子集,若对于r(u)的任意一个可能的关系r满足: r中不可能存在两个元组在x上的属性值相等, 而在 y 上的属性值不等,则称\u0026#34;x函数确定y\u0026#34; 或 \u0026#34;y函数依赖于x\u0026#34; ,记作x→y。 x 称为这个函数依赖的 决定属性集(determinant)。 * 说明: 1. 函数依赖是指r的所有关系实例均要满足的约束条件 2. 函数依赖是 语义范畴的 概念 例如 “姓名→ 年龄”这个函数依赖只有在不允许有同名人的条件下成立 3. 数据库设计者可以对现实世界作强制的规定 例如 设计者可以强行规定不允许同名人出现,因而使函数依赖“姓名→年龄”成立 4. 若x→y ,并且y→x, 则记为 x↔y 5. 若x→y ,且y不函数依赖于x, 则记为 x→y。 * 平凡函数依赖 和 非平凡函数依赖 --在关系模式r(u) 中,对于u 的子集x 和 y * 如果x→y ,但y ∈ x ,则称x→y是 平凡的函数依赖 例 :在关系模式sc(sno, cno, grade) 中 非平凡函数依赖 :(sno, cno) → grade 平凡函数依赖 :(sno, cno) → sno 或 (sno, cno) → cno * 如果x→y ,但y ∉ x ,则称x→y是 非平凡的函数依赖 * 注意: 对于任一关系模式,平凡函数依赖都是必然成立的, 它不反映新的语义。 * 完全函数依赖 和 部分函数依赖 --在关系模式r(u)中 * 如果 x→y ,并对于x的任何一个真子集x,满足x不能函数确定y, 则称y完全函数 依赖于x,记作 x →f y。 (f在箭头上面标着) 小技巧: (即 如果x函数确定y , 那么x的任意真子集一定不函数确定y) * 如果 x→y ,但 y 不完全函数依赖于 x ,则称y 部分函数依赖于x。记作 x →p y。 例 例: 对于关系模式 sc(sno, cno, grade) 若: - grade由sno和cno共同确定 - sno不能函数确定grade ,cno不能函数确定grade 则: - (sno, cno) →f grade - (sno, cno) →p sno, (sno, cno) →p cno * 传递函数依赖 --在关系模式r(u)中,如果x →f y ,y→z 、x不函数依赖于y ,则称z传递函数依赖于x 如果y→x,即x←→y ,则z直接依赖于x。 (注:若y∈x,相当于x→z) * 例: 在关系std(sno, sdept, mname) 中, 有:sno → sdept ,sdept → mname 则:mname 传递函数依赖于sno * 码: --设k为关系模式r(u,f) 中的属性或属性组合 若k →f u ,则k 称为r 的一个 侯选码(candidate key)。 若关系模式r有多个候选码,则选定其中的一个做为主码(primary key)。 * 补充: * 候选码 能够唯一地标别关系的元组,是关系模式中一组最重要的属性 * 主码 又和 外码 一起提供了一个表示关系间联系的手段 * 多值依赖(multivalued dependency ,简记为mvd) * 连接依赖 3、范式 --关系数据库中的关系必须满足一定的要求,满足不同要求的为不同范式。 --范式是指 满足某一级别的关系模式 的集合。 * 分类 * 第一范式(1nf): --如果一个关系数据库的所有属性都是不可再分的基本数据项,则r∈1nf。 * 第一范式是对关系模式的最基本的要求,不满足第一范式的数据库模式不能称为关系数据库。 * 但是满足第一范式的关系模式并 不一定是一个好的关系模式。 * 例: 式 关系模式 slc(sno, sdept, sloc, cno, grade) 其中各属性分别为学号,所在系,宿舍楼,课程号和成绩。 规定: * 一个学生就属于一个系,每个系的学生住在同一个宿舍楼 * 一个学生学的每一门课程都有唯一的成绩。 写出所有的函数依赖 : sno → sdept ,sdept → sloc,(sno, cno) → grade (sno, cno) → sno ,(sno, cno) → sdept (sno, cno) → sloc, (sno, cno) → cno * 分析: - slc满足第一范式 - 非主属性sdept 和sloc 部分函数依赖 于码(sno, cno) 假设 sno = 95102 , sdept = is , sloc=n 的学生还未选课,因课程号是主属性,因此 该学生的信息无法 插入。(诸如此类的问题还有很多) * 结论: 仅满足第一范式的关系模式不一定是一个好的关系模式,如果非主属性部分函数依赖于码, 很可能存在插入异常、删除异常、数据冗余度大、修改复杂等问题。 * 第二范式(2nf): --如果r∈1nf,且不存在非主属性对码的部分函数依赖,则r∈2nf。 * 例:slc(sno, sdept, sloc, cno, grade) ∈ 1nf 将slc进行拆分得到下面两个关系模式,则: sc(sno,cno,grade)∈ 2nf sl(sno,sdept,sloc)∈ 2nf * 分析: 写出所有函数依赖 : sno→sdept sno→sloc sdept→sloc - sl 的码是sno - sloc 传递函数依赖于sno ,即sl 中存在 非主属性对码的传递函数依赖。 假设学校调整学生住处时,由于关于每个系的住处信息是重复存储的,修改 时必须同时更新该系所有学生的sloc。(诸如此类的问题还有很多) * 结论: 采用 投影分解法 将一个1nf 的关系分解为多个2nf 的关系,可以在一定程度上减轻原1nf 关系中存在的插入异常、删除异常、数据冗余度大、修改复杂等问题。将一个1nf 关系分解 为多个 2nf 的关系, 并不能完全消除 关系模式中的各种异常情况和数据冗余。 * 第三范式(3nf): --关系模式 r∈1nf ,且不存在非主属性对码的传递函数依赖 ,则称r∈3nf。 * 例 sl(sno, sdept, sloc) ∈ 2nf 将关系模式sl进行分解得到sd和dl,则: sd(sno,sdept)∈ 3nf dl(sdept,sloc)∈ 3nf * 分析: 采用投影分解法将一个2nf 的关系分解为多个3nf 的关系,可以在一定程度上解决原2nf 关系中存在的插入异常、删除异常、数据冗余度大、修改复杂等问题。将一个2nf 关系分 解为多个3nf的关系后,在有些情况下,不能完全消除关系模式中的各种异常情况和数据冗余。 * bc 范式(bcnf): --设关系模式r ∈ 1nf,如果对于r的每个函数依赖x→y ,若y不属于x ,则 x 必含有候选码,那么r∈bcnf。 --换句话说,在关系模式r 中,如果每一个 决定属性集 都包含候选码,则r∈bcnf。 * bcnf 的关系模式所具有的 性质: - 所有 非主属性 都完全函数依赖于每个候选码 - 所有 主属性 都完全函数依赖于每个不包含它的候选码 - 没有任何属性 完全函数依赖于 非码 * 3nf和bcnf的关系: – 如果关系模式r∈bcnf ,必定有r ∈ 3nf。 – 如果r∈3nf,且r 只有一个候选码,则r 必属于bcnf。 * 总结: 如果一个关系数据库中的所有关系模式都属于bcnf ,那么在 函数依赖 范畴内,它已实现了 模式的彻底分解,达到了最高的规范化程度,消除了插入异常和删除异常。 * 第四范式(4nf): * 第五范式(5nf): * 各个范式之间的联系: * 1nf是对关系模式的最基本要求,不满足1nf的关系模式不能被称为关系数据库 * 2nf在1nf的基础上消除了部分函数依赖,减轻了插入、删除异常等问题。 * 3nf在1nf的基础上消除了传递函数依赖,进一步减轻了各类异常。(可以证明满足3nf一定满足2nf) * bcnf消除了\u0026#34;主属性对候选键的部分依赖和传递依\u0026#34;,在函数依赖范围内实现了彻底分解 消除了产生插入异常和删除异常的根源,并将数据冗余减轻到极小的程度。 * 小技巧: 某一关系模式r 为第n 范式,可简记为r ∈nnf * 各个范式之间的关系: 1nf ⊂ 2nf ⊂ 3nf ⊂ bcnf ⊂ 4nf ⊂ 5nf 4、关系模式的规范化 * 前言: 关系数据库的规范化理论是数据库逻辑设计的工具,一个关系只要其分量都是不可分的数据项,它就是规范化的关系, 但这只是最基本的规范化。规范化程度可以有6 个不同的级别,即6个范式。 规范化程度过低的关系不一定能够很好地描述现实世界,可能会存在 插入异常、删除异常、修改复杂、数据冗余等问 题,解决方法就是对其进行规范化,转换成高级范式。 一个低一级范式的关系模式,通过模式分解可以转换为若干个 高一级范式的关系模式集合,这种过程就叫 关系模式的规范化。 * 关系模式规范化的步骤: 1nf -\u0026gt; 2nf:消除 非主属性 对码的部分函数依赖,从而达到2nf 2nf -\u0026gt; 3nf:消除 非主属性 对码的传递函数依赖,从而达到3nf 3nf -\u0026gt; bcnf:消除 主属性 对码的 部分函数依赖 和 传递函数依赖 从而到达 bcnf bcnf -\u0026gt; 4nf:消除 平凡且非函数依赖的 多值依赖,从而到达4nf 4nf -\u0026gt; 5nf:消除不是由 候选码 所蕴含的 连接依赖,从而到达5nf * 注意: - 不能说规范化程度越高的关系模式就越好 - 在设计数据库模式结构时,必须对现实世界的实际情况和用户应用需求作进一步分析,确定一个合适的、 能够反映现实世界的模式。 - 关系模式的规范化可以在上述任何一步终止 * 关系模式的分解: --将一个关系模式r(u,f) 分解为若干个关系模式r1(u1,f1),r2(u2,f2) ,… ,rn(un,fn) ✓ 其中u = u1 ∪ u2 ∪ … ∪ un ,且不存在ui ⊆ uj ,fi 为 f 在ui上的投影。 ✓ 上述分解意味着相应地将存储在一个二维表t 中的数据分散到若干个二维表t1 ,t2 ,… ,tn 中去 其中ti 是t 在属性集ui上的投影。(i=1,2,3...n) * 模式分解的 无损连接性(模式分解的第一个要求) * 设关系模式r(u,f) 被分解为若干个关系模式r1(u1,f1) ,r2(u2,f2) ,… , ,rn(un,fn) ✓ 其中u=u1 ∪u2 ∪… ∪un ,且不存在ui ⊆ uj ✓ fi 为f 在ui 上的投影 * 无损连接性: --若r与r1 、r2 、… 、rn自然连接的结果相等,则称关系模式r的这个分解具有无损连接性(lossless join) * 注意: * 只有具有无损连接性的分解才能够 保证不丢失信息 * 无损连接性 不一定能解决 插入异常、删除异常、修改复杂、数据冗余等问题 * 保持原关系中的函数依赖(模式分解的第二个要求) * 设关系模式r(u,f) 被分解为若干个关系模式r1(u1,f1) ,r2(u2,f2) ,… ,rn(un,fn) ✓ 其中u=u1 ∪u2 ∪… ∪un ,且不存在ui ⊆ uj ✓ fi 为f 在ui 上的投影 * 保持函数依赖的(preserve dependency): --若f 所逻辑蕴含的函数依赖一定也由分解得到的某个关系模式中的 函数依赖fi 所逻辑蕴含, 则称关系模式r的这个分解是 保持函数依赖的(preserve dependency )。 * 关系模式的一个分解与原关系模式等价的充要条件是 * 分解具有无损连接性 * 分解保持函数依赖 * 说明: * 如果一个分解具有 无损连接性 ,则它能够保证不丢失信息 * 如果一个分解保持了 函数依赖 ,则它可以减轻或解决各种异常情况 * 分解具有无损连接性和分解保持函数依赖是两个互相独立的标准 ✓ 具有无损连接性的分解不一定能够保持函数依赖 ✓ 同样,保持函数依赖的分解也不一定具有无损连接性 * 模式分解的算法 规范化理论提供了一套完整的模式分解算法,按照这套算法可以做到 – 若要求分解具有无损连接性,那么模式分解一定能够达到4nf – 若要求分解保持函数依赖,那么模式分解一定能够达到3nf ,但不一定能够达到bcnf – 若要求分解既具有无损连接性,又保持函数依赖,则模式分解一定能够达到3nf ******************************************** 数据库设计 **************************************** 一、数据库设计 1、 数据库设计(database design ,简记为 dbd): -- 构造最优的数据模型,建立数据库及其应用系统的过程。 小提示:数据库设计的优劣将直接影响应用系统的质量和运行效果。 2、数据库设计的步骤: * 数据库系统的生存期: --规划阶段: * 规划的意义: --对于数据库系统, 特别是大型数据库系统,规划阶段是十分必要的。规划的好坏将直接影响到整个系统的成功与否。 * 规划的步骤: * 系统调查 • 对应用单位作全面的调查,发现其存在的主要问题 • 画出 组织层次图 ,以了解企业的组织机构 * 可行性分析 • 从技术、经济、效益、法律等方面对建立数据库系统的可行性进行分析 • 写出 可行性分析报告 • 组织专家进行讨论其可行性 * 确定数据库系统的总目标 • 对应用单位的工作流程进行优化和制订项目开发计划 • 在得到决策部门批准后,就正式进入数据库系统的开发工作 1 需求分析阶段: --进行数据库设计必须首先 准确了解用户的需求,需求分析是整个设计过程的基础,是最困难最耗时的一步。 (分析用户需要用数据库存储哪些信息,需要对这些信息进行哪些操作,对这些信息的安全性和完整性有什么需求) * 需求分析的内容: • 计算机人员(系统分析员)和用户双方共同收集数据库所需要的信息内容和用户对处理的需求。 • 以 需求说明书 的形式确定下来,作为以后系统开发的指南和系统验证的依据。 * 需求分析的步骤: • 分析用户活动,产生 业务流程图 • 确定系统范围,产生 系统关联图 • 分析用户活动涉及的数据,产生 数据流图 • 分析系统数据,产生 数据字典 * 数据字典: - 组成: 包括五部分: 数据项、数据结构、数据流、数据存储、处理过程 数据字典里存储了 数据库中所有数据元素的定义。 - 作用: 可以作为分析阶段的工具,它给数据流程图上的每个成分做出详细说明,供人们查询使用。 2 概念设计阶段: --通过对用户需求进行综合、归纳与抽象,形成一个独立于dbms的概念模型。(概念设计是整个数据库设计的关键) (对用户需求进行综合归纳,抽象出独立于计算机系统,不依赖于dbms的信息结构,也就是概念模型) * 目标: 产生反映用户需求的 数据库概念结构,即 概念模型。它具有硬件独立和软件独立的特点。 * 概念设计的主要步骤: 1. 进行数据抽象,设计局部概念模型 • 从实际的人、物、事和概念中抽取所关心的 共同特性,忽略非本质的细节, 并把这些特性用各种概念加以 精确描述 • 概念结构是对现实世界的一种抽象 2. 将局部概念模型综合成 全局概念模型 • 综合各局部概念结构就可得到反映所有用户需求的全局概念结构 • 在综合过程中,主要处理各局部模式对各种对象定义的 冲突( 属性冲突、命名冲突、结构冲突) 3. 评审 • 消除了所有冲突后,就可把全局结构提交评审 • 评审分为用户评审与dba及应用开发人员评审两部分 * 概念设计的方法: • 概念设计中最著名的方法就是 实体联系方法(er 方法) • 概念设计的结果是得到一个与dbms无关的概念模型。 3 逻辑设计阶段: -- 将概念结构转换为dbms上支持的数据模型,并对其进行优化。 * 逻辑设计的目的: 把概念设计阶段设计好的 概念模型转换成与选用的具体机器上的dbms 所支持的数据模型相符合的 逻辑结构 (包括数据库逻辑模型和外模型)。 * 对于逻辑设计而言,应首先选择dbms ,但往往数据库设计人员没有挑选的余地,都是在指定的dbms 上进行逻辑结构的设计。 * 逻辑设计的步骤: 1、把概念模型转换成逻辑模型 2、设计外模型 3、设计应用程序与数据库的接口 4、评价模型 5、修正模型 4 物理设计阶段: --为逻辑数据模型选取一个 最适合应用环境的 物理结构的过程,称为 物理设计。 * 物理设计的步骤; * 物理结构设计: ⑴ 存储记录结构设计 ⑵ 确定数据存放位置 ⑶ 存取方法的设计 * 约束和具体的程序设计: ⑷ 完整性和安全性考虑 ⑸ 程序设计 5 数据库的实施阶段: --设计人员利用dbms提供的数据库语言及其宿主语言,根据逻辑设计和物理设计的结果建立数据库、编写 与调试应用程序、并进行试运行。 * 数据库实现的过程 • 用ddl 定义数据库结构 • 组织数据入库 • 编制与调试应用程序 • 数据库试运行 6 数据库的运行与维护: --一个完善的数据库应用系统不可能一蹴而就。因此,在数据库系统运行过程中必须对其不断地评估、调整与修改。 * 对数据库经常性的维护工作主要是由dba 完成的 – 数据库的转储和恢复 – 数据库安全性、完整性控制 – 数据库性能的监督、分析和改进 – 数据库的重组织和重构造 * 若应用变化太大,已无法通过重构数据库来满足新的需求,或重构数据库的代价太大, 则表明现有数据库应用系统的生命周期已经结束,应该重新设计新的数据库系统,开始 新数据库应用系统的生命周期 了。 二、e-r模型 (entity relationship mode) 1、e-r模型的基本元素 * 实体(entity) 是指 数据对象,指应用中可以区别的客观存在的事物。 * 实体集(entity set) 是指同一类实体构成的 集合 例: 实 体 实 体 集 一个学生 全部学生 一门课程 全部课程 一个老师 全部老师 * 属性(attribute): – 实体的某一特性称为 属性(attribute) – 在一个实体中,能够惟一标识实体的属性或属性集 称为“ 实体标识符 ” – 一个实体只有一个标识符,没有候选标识符的概念。实体标识符有时也称为 实体的主键 – 实体若干属性的一组特定值,确定了一个特定的实体 * 联系(relationship): – 联系( (relationship )表示一个或多个实体之间的 关联关系 – 联系集(relationship set )是指同一类联系构成 的集合 – 将联系、联系集等统称为联系 。 * 联系的元数: 一个联系涉及的实体集的个数叫做联系的元数或度数(degree) * 分类: • 一元联系(递归联系):同一个实体集内部实体之间的联系 • 二元联系: 两个不同实体集实体之间的联系 • 多元联系: 多个不同实体集实体之间的联系 * 二元联系的类型:(设有两个实体集e1、e2) * 一对一的联系: (1:1) 对于e1中的一个实体来说,e2中最多存在一个实体与其对应 对于e2中的一个实体来说,e1中最多存在一个实体与其对应 * 一对多的联系: (1:n) e1中的一个实体可以与e2中的多个实体相对应,e2中的一个 实体最多与一个e1中的实体相对应。 * 多对多的联系: (m:n) e1中的一个实体可以与e2中的多个实体相对应,e2中的一个 实体与e1中的多个实体相对应。 2、e-r图的画法: * 实体型:用矩形表示,矩形内写明实体名 * 属性:属性用椭圆表示,并用无向边将其与它对应的实体型连接起来 * 联系:用菱形表示,菱形内写明联系名,并用无向边分别与相关的实体型连接起来 3、采用e-r模型进行数据库的概念设计的步骤: * 首先设计 局部e-r 模型 * 步骤: – 确定局部结构范围 • 范围的划分要自然,易于管理;界面要清晰;大小要适度 – 确定实体 • 采用人们习惯划分;避免冗余;依据用户的需求 – 确定属性 • 属性应该是不可再分解的语义单位 • 实体与属性之间的关系只能是1:n • 不同实体类型的属性之间应无直接关联关系 – 确定实体间联系 • 确定联系,联系类型, 防止冗余 * 两条准则 • 属性不能具有需要描述的性质 • 属性不能与其他实体具有联系 * 然后把各局部e-r 模型综合成一个 全局er 模型: * 步骤 * 确定公共实体类型 • 根据实体类型名和键来认定公共实体类型 * 合并局部er模型 • 首先进行两两合并,先合并那些现实世界中有联系的局部结构 • 合并从公共类型开始,最后再加入独立的 局部结构 * 消除冲突 • 属性冲突: * 属性域冲突: 属性的取值类型、取值范围或取值集合不同。 eg. 有的部门把零件号定义为整数类型,有的定义为字符型 解决方法:各部门讨论协商 * 属性取值单位冲突: 不同的子系统对同一属性的取值 单位选用不同 eg. 零件重量有的部门以公斤为单位,有的以斤为单位。 解决方法:各部门讨论协商 • 命名冲突: * 同名异意: 不同意义的对象在不同的领域具有相同的名字 eg. 用 强壮 分别描述男生和女生 * 异名同意: 相同意义的对象在不同的领域具有不同的名字。 eg. 帅哥 欧巴 ..... 解决方法:各部门讨论协商 • 结构冲突: * 同一对象在不同的应用中具有不同的抽象: eg. 职工在某个局部被当作实体,在另一个局部被当作属性 解决方法:按照一定的规则,将属性变为实体 或 将实体变为属性 * 同一实体在不同的子系统中包含的属性个数和属性排列次序不完全相等 eg. 一个部门只关注员工的姓名和年龄,另一个部门只关注员工的工资 解决方法:取各个子系统中 这个实体的属性的并集。 * 实体间的联系类型在不同的 子系统中各不相同 eg. e1 和 e2 在一个关系中是一对多,在另一个关系中是多对多 解决方法:根据应用的语义对实体联系进行综合的分析。 * 最后对全局e-r 模型进行优化,得到最终的er 模型,即概念模型 * 优化原则: – 合并实体类型 – 消除冗余属性 – 消除冗余联系 3、将e-r图转换成关系模式集 * 步骤: 将实体类型转换为一个关系模式,实体的属性就是关系模式的属性,实体标识符即为关系模式的键。 * 二元联系类型的转换: * 若实体间联系是1:1 : 可以在两个实体类型转换成的两个关系模式中任意一个关系模式的属性中加入另 一个关系模式的键和联系类型的属性。 (口诀:在一方加入另一方的主键作为外键) * 若实体间联系是1:n : 则在n 端实体类型转换成的关系模式中加入1 端实体类型的键和联系类型的属性。 (口诀:在多方加入一方的主键作为外键,同时把联系的属性也加到多方去) * 若实体间联系是m:n : 则将联系类型也转换成关系模式,其属性为两端实体类型的键加上联系类型的属 性,而键为两端实体键的组合。 (口诀:将联系本身转换成一个关系模式,联系的名字就是关系模式的名字,它包含的属性由联系双方 的主键和这个联系本身的属性组成,这个关系模式的主键是两个实体标识符的组合) * 三元联系类型的转换: * 若实体间联系是1:1:1 ,可以在转换成的三个关系模式中任意一个关系模式的属性中加入另两个关系模 式的键(作为外键)和联系类型的属性。 * 若实体间联系是1:1:n ,则在n 端实体类型转换成的关系模式中加入两个1端 端实体类型的键(作为外 键)和联系类型的属性。 * 若实体间联系是1:m:n ,则将联系类型也转换成关系模式,其属性为三端实体类型的键加上联系类型的 属性,而键为m 端和n 端实体键的组合。 * 若实体间联系是m:n:p ,则将联系类型也转换成关系模式,其属性为三端实体类型的键加上联系类型的 属性,而键为三端实体键的组合。 4、采用e-r模型的逻辑设计步骤: (1) 导出初始关系模式集 根据上面e-r图的转化结果可得到初始的关系模式图。 (2) 规范化处理 --数据库的逻辑设计 结果不唯一,直接根据e-r图转换而来的关系模式很可能规范性很低(如:只满足1nf) 这时需要对其进行规范化处理。 * 逐一考察关系模式判断它们是否满足 规范要求。 * 通过模式分解关系模式化为满足更高范式的关系模式。 (3) 模式评价 (4) 模式修正 (5) 设计子模式(外模式) (6) 设计物理结构 三、unml模型(unified modeling language): * uml 用于面向对象建模,但是现在也用于数据库建模 * uml 与 e-r 模型相似,但是不提供多元联系 * uml 和 er模型的属于对比 u m l 模 型 术 语 e - r 模 型 术 语 class(类) entity set(实体集) association(关联) binary relationship(二元联系) association class(关联类) attributes on a relationship(联系的属性) subclass(子类) isa hierarchy(isa层次关系) aggregation(聚集) many-one relationship(多对一联系) composition many-one relationship with referential integrity (组成) (带参照完整性的多对一联系) 由于板书困难 详情见 https://www.bilibili.com/video/bv1ra4y1t7xy?p=32 ******************************************** 数据库恢复技术 ********************************************** 一、事务 1、概念: - 事务是用户定义的一个数据库操作序列,这些操作要么全做,要么全不做,是一个不可分割的工作单位。 - 事务是数据库的逻辑工作单位。 2、事务的acid特性: * 原子性: 事务时数据库的逻辑工作单位,事务中的诸操作要么全做,要么全不做。 * 一致性: 事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。 * 补充:一致性是指数据处于一种语义上的有意义且正确的状态。 * 举个栗子 张三给李四转账100元。事务要做的是从张三账户上减掉100元,李四账户上加上100元。一致性 的含义是其他事务要么看到张三还没有给李四转账的状态,要么张三已经成功转账给李四的状态, 而对于张三少了100元,李四还没加上100元这个中间状态是不可见的。 * 隔离性: 一个事务的执行不能被其他事务干扰。 即,一个事务的内部操作和使用的数据对其他的并行事务时隔离的,并发执行的各个事务之间不能互相干扰。 * 持续性: 也称永久性,指事务一旦提交,他对数据库中的数据的修改就是永久的。接下来的其他操作或故障不会 对其执行结果有任何影响。 * 补充: * 原子性和一致性的区别: 原子性和一致性的的侧重点不同:原子性关注状态,要么全部成功,要么全部失败,不存在部分成功的状态。 一致性关注数据的可见性,中间状态的数据对外部不可见,只有最初状态和最终状态的数据对外可见。 * 关于一个事务执行过程中,中间产生的数据变化何时写入物理数据库(磁盘)的问题 - 事务执行的过程中,即使事务还没有执行完毕,它已执行部分对数据库的修改也可能写入磁盘。 - 事务执行完毕并提交后,修改的数据可能仍在缓冲区中,并未更新到磁盘。 * 事务四大性质的总结: - 在事务处理的acid属性中,一致性是最基本的属性,其它的三个属性都为了保证一致性而存在的。 - 事务的原子性由数据库管理系统保证,为了实现原子性,需要通过日志(所有对数据的更新操作都 会被写入日志)。如果一个事务的一部分操作已经成功,但以后的操作由于 断电/系统崩溃 等 无法正常执行,则通过回溯日志,将已经执行成功的操作撤销,从而达到\u0026#34;全部操作失败\u0026#34;的目的。 - 在事务处理的acid属性中,一致性是最基本的属性,其它的三个属性都为了保证一致性而存在的。 - 但是,原子性并不能完全保证一致性。在多个事务并行进行的情况下,即使保证了每一个事务的原 子性,仍然可能导致数据不一致的结果。 * 例如,事务1需要将100元转入帐号a:先读取帐号a的值,然后在这个值上加上100。但是,在 这两个操作之间,另一个事务2修改了帐号a的值,为它增加了100元。那么最后的结果应该是a增 加了200元。但事实上,事务1最终完成后,帐号a只增加了100元,因为事务2的修改结果被事务1 覆盖掉了。 - 为了保证并发情况下的一致性,引入了隔离性,即保证每一个事务能够看到的数据总是一致的,就好 象其它并发事务并不存在一样。用术语来说,就是多个事务并发执行后的状态,和它们串行执行后 的状态是等价的。(用x锁和s锁实现隔离性) 二、故障的种类: 1、事务故障: - 事务故障指事务没有达到预期的终点。 - 发生事务故障时,数据库可能处于不正确状态。 * 恢复思路: - 事务撤销:在不影响其他事务运行的情况下,强行回滚该事务,撤销该事务对数据库进行的所有修改,使 该事务好像从来没有执行过一样。 2、系统故障: - 指某些事件造成系统停止运转,需要重新启动。如cpu故障,dbms故障,操作系统故障等。 - 系统故障影响所有正在运行的事务,但不破坏数据库。 * 恢复思路: - 撤销所有未完成事务,应且重做所有已提交的事务。 3、介质故障: - 指外存故障,如磁盘损坏,磁头碰撞,瞬时强磁场干扰等。 - 系统故障将破坏数据库,或部分破坏数据库,并影响正在存取这部分数据的所有事务。 * 恢复思路: 一般通过数据备份和日志文件配合恢复。 4、计算机病毒: - 略 三、恢复技术的实现: 1、数据转储: - 数据库管理员定期的将整个数据库复制到其他存储介质上的过程。(其他存储介质包括:磁带、磁盘等) - 这些备用数据被称为 \u0026#34;后备副本\u0026#34; 或 \u0026#34;后援副本\u0026#34; 。 * 静态转储:在系统中无运行事务的时候进行转储操作。 * 动态转储:在转储期间允许对数据库中的数据进行存取和修改。 * 海量转储:每次转储整个数据库的数据。 * 增量转储:只转储上一次转储后更新过的数据。 2、登记日志文件: * 日志文件: --日志文件是用来记录事务对数据库的更新操作 的文件。 * 日志文件需要登记的内容有: - 各个事务的开始标记 (begin transaction) - 各个事务的结束标记 (commit 或 rollback) - 各个事务所有的更新操作 * 每个日志记录的内容包括: - 事务的标识 (标明是哪个事务) - 操作类型 (事务对数据库的更新类型-删除-插入或修改) - 操作的对象 (记录操作的是数据库的哪个对象) - 更新前数据的旧值 (对插入操作来说,此项为空) - 更新后数据的新值 (对删除操作来说,此项为空) * 日志文件的作用 - 事务故障恢复和系统故障恢复必须用到日志文件 - 在动态转储方式中,必须建立日志文件,后备副本和日志文件配合起来才能有效的恢复数据库。 - 静态转储也要建立日志文件,数据库毁坏后,可装入后援副本把数据库恢复到转储结束时刻的 正确状态,然后用日志文件把已完成的事务进行重做处理,对故障发生时未完成的事务做撤销处理 这样既可将数据库恢复至故障发生时的状态。 * 登记日志文件的两条原则: - 登记的次序严格按照并发事务执行的时间顺序。 - 必须先写日志文件,后写数据库。 3、事务故障的恢复 * 事务故障的修复:(由系统自动完成,不需要用户干预) 概括: 由恢复子系统应利用日志文件撤消(undo )此事务已对数据库进行的修改 具体步骤: (1) 反向扫描文件日志( 即从最后向前扫描日志文件) ,查找该事务的更新操作 (2) 对该事务的更新操作执行逆操作。即将日志记录中“更新前的值”写入数据库 – 如果记录中是插入操作,则相当于做删除操作( 因为此时“更新前的值”为空 ) – 若记录中是删除操作,则相当于做插入操作( 因为此时“更新后的值”为空 ) – 若是修改操作,则相当于用修改前值代替修改后值 (3) 继续反向扫描日志文件,查找该事务的其他更新操作,并做同样处理 (4) 如此处理下去,直至读到此事务的开始标记,事务故障恢复就完成了 * 系统故障的修复:(由系统自动完成,不需要用户干预) 概括: (1) 撤消故障发生时未完成的事务 (2) 重做已完成的事务 具体步骤: (1) 正向扫描日志文件(即从头扫描日志文件) – 找出在故障发生前已经提交的事务, 将事务标识记入重做队列 – 同时找出故障发生时尚未完成的事务, 将事务标识记入撤消队列 (2) 对撤消队列中的各个事务进行撤消(undo) 处理 – 反向扫描日志文件,对每个undo 事务的更新操作执行逆操作,即将日志记录中“更新前的值”写入数据库 (3) 对重做队列中的各个事务进行重做(redo) 处理 – 正向扫描日志文件,对每个redo 事务重新执行登记的操作。即将日志记录中“更新后的值”写入数据库 * 介质故障的修复: 概括: (1) 重装数据库,使数据库恢复到一致性状态 (2) 重做已完成的事务 具体步骤: (1) 装入最新的后备数据库副本,使数据库恢复到最近一次转储时的一致性状态。 – 对于静态转储的数据库副本,装入后数据库即处于一致性状态 – 对于动态转储的数据库副本,还须同时装入转储时刻的日志文件副本,利用与 恢复系统故障相同的方法(即redo\u0026#43;undo ),才能将数据库恢复到一致性状态。 (2) 装入有关的日志文件副本,重做已完成的事务。 – 首先扫描日志文件,找出故障发生时已提交的事务的标识,将其记入重做队列。 – 然后正向扫描日志文件,对重做队列中的所有事务进行重做处理。即将日志记录中“更新后的值”写入数据库。 注意: 介质故障的修复需要dba介入,dba的工作如下: • 重装最近转储的数据库副本和有关的各日志文件副本 • 执行系统提供的恢复命令 (具体的恢复操作仍由dbms 完成) ******************************************** 并发控制 ********************************************** 一、并发控制 1、并发处理事务带来的问题 * 在并发操作中,dbms需要对并发操作进行正确的调度,否则将会导致数据不一致。 * 数据不一致的类型 - 丢失修改:事务t1和t2读入同一个数据并修改,t2提交的结果破坏了t1提交的结果,使得t1的修改丢失。 - 不可重复读:事务t1会对某一个数据进行多次的读入,t2则会对这个数据进行修改。如果t1两次读入该 数据的中间,t2对该数据进行了修改,将导致t1两次读入的数据不一致。 - 读\u0026#34;脏\u0026#34;数据:事务t1想读入某个数据并对其进行修改,事务t2也会读入该数据使用,若t1读入该数据并 修改后,t2读入了修改后的数据,此时由于某种原因事务t1被撤销,此时t2读入的数据与数据库中的 数据不一致。 2、封锁 * 锁的类型: - 排他锁(写锁-x锁):t对对象a加x锁,则只有t能对a进行读写操作,其他对象只能等待。 t对a加了x锁后,其他事务不能对a加任何锁,直到t释放x锁。 - 共享锁(读锁-s锁):t对对象a加s锁,则只有t能读a但不能修改a,此时其他事务也能读a。 t对a加了s锁后,其他事务只能对a加s锁,不能加x锁,直到t释放s锁。 * 封锁协议: --在运用x锁和s锁对数据对象加锁时,需要约定一些规则,叫做封锁协议。 eg. 何时申请x锁,s锁、持锁时间、何时释放等。 * 一级封锁协议: 事务t在修改数据r之前,必须先对其加x锁,直到事务结束才释放x锁 (解决了 丢失修改 的问题) * 二级封锁协议: 在一级封锁协议的基础上,t在读数据r前,要先对r加s锁,读完即可释放s锁 (解决了 读脏数据 的问题) * 三级封锁协议:在一级封锁协议的基础上,t在读数据r前,要先对r加s锁,直到事务结束才可释放s锁 (解决了 不可重复读 的问题) 3、死锁和活锁 * 活锁: --类似于操作系统的饥饿状态、指许多事务同时申请封锁某数据时,由于系统的调度策略不合理,可能导致 某个事务一直处于等待状态。 * 解决方法:采用合适的调度策略,如先来先服务等。 * 死锁: --多个事务各封锁一个数据后,互相请求求封锁对方已经封锁的数据,导致双方的封锁请求都永远得不到满足。 这种情况称为死锁。 * 死锁的预防: - 一次封锁法:事务t必须一次将其要用到的数据全部封锁,否则就不执行事务t。 - 顺序封锁法:预先对数据对象规定一个封锁次序,所有事务都要按照这个封锁次序实施封锁。 * 死锁的诊断与解除: * 诊断 - 超时法:一旦一个事务的执行时间超过预设的时间,就认为该事务发生了死锁。 - 事务等待图法:事务等待图是一个有向图,如果这个有向图出现回路,代表发生死锁。 * 解除 选择一个处理死锁代价最小的事务,将其撤销,释放此事务持有的所有锁,使其他事务 能运行下去。 4、并发调度的可串行化: * 正确调度 一个并发调度,当且仅当它时可串行化的,才称它是正确的调度。 * 可串行化调度 说多个并发事务的执行是正确的,当且仅当它的执行结果和按照某一次序串行的执行这些事务所得到 的结果相同。称这种调度是可串行化的调度。 * 冲突可串行化调度 - 冲突操作: 不同事务对同一数据的读写操作和写写操作 ri(x) 和 wj(x) 即 事务i读数据x、事务j写数据x wi(x) 和 wj(x) 即 事务i写数据x、事务j写数据x - 不可交换的操作 不同事务的冲突操作 和 同一事务的两个操作 即 ri(x) 和 wj(x) 、 wi(x) 和 wj(x) 、 ri(x) 和 wi(x) 、 wi(x) 和 wi(x) - 可交换的操作 不同事务对同一数据的读读操作 和 不同事务对不同数据的所有操作 即 ri(x) 和 rj(x)、ri(x) 和 rj(x)、ri(x) 和 rj(y)、ri(x) 和 wj(y)、wi(x) 和 wj(y) - 冲突可串行化 如果一个调度序列sc通过交换 可交换操作 改变调度顺序,得到sc\u0026#39; 。如果sc\u0026#39; 是可串行化的调度 则称sc是冲突可串行化的调度。 * 如果一个调度是冲突可串行化的,那么它一定是可串行化的调度。(反之不一定成立) * 两段锁协议: --为了实现并发调度的可串行化,目前dbms普遍采用 两段锁协议。 * 两段锁协议的内容: --把事务分为两个阶段,第一个阶段 获得封锁,也称扩展阶段。第二个阶段 释放封锁,也称收缩阶段。 * 扩展阶段:事务可以申请获得任何数据项上的任何类型的锁,但是不能释放任何类型的锁。 * 收缩阶段:事务可以释放任何任何类型的锁,但是不能申请任何锁。 小提示: 也就是说,每个事务中,申请锁的操作必须全在一个事务的前面,释放锁则在后面。 eg. slock a; slock b;xlock c; unlock b;unlock a;unlock c; 符合 | ---- 扩展阶段 ---- | | ---- 收缩阶段 ---- | eg. slock a; unlock a;xlock c; unlock c;slock b;unlock b; 不符合 * 遵循两段锁协议的调度一定是可串行化的调度。反之不一定成立。\r","date":"2024-07-11","permalink":"http://54rookie.com/posts/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%8E%9F%E7%90%86/","summary":"数据库原理 数据库原理 ******************************************** 基本概念 ********************************************** 一、数据库系统基本概念 1、信息:信息是客观存在的,是对“现实世界存在的事物”的“运动状态”和“特征”的描述。 2、数据:数据是用来","title":"数据库原理"},]
[{"content":"密码学基础 数论基础 -------------------------------- 数论的简单基础 -------------------------------- 一、整除性 1、整除的定义: 设 a,b ∈ z ,如果存在一个 q ∈ z 使得 a = qb; 则称 \u0026#34;b整除a\u0026#34; ,记作 \u0026#34;b|a\u0026#34;。 (或称 \u0026#34;b是a的因子\u0026#34;,\u0026#34;a是b的倍数\u0026#34;,\u0026#34;a被b整除\u0026#34;) 2、整除的性质: 对于任意的a,b,c ∈ z,有以下性质: (1) 基本性质: - b|0, (任意一个整数都是0的因子) - 1|a, (1是任意整数的因子) - 0|a 等价于 a=0, (0是a的因子,等价于a=0) - b|a 等价于 b|-a 等价于 -b|a, (2) 自反性: a|a (3) 传递性: b|a 且 a|c 则 b|c (4) 相乘性: b|a 则 bc|ac (5) 消去性: bc|ac 且 c≠0 则 b|a (6) 线性性: b|a 且 b|c ,则对任意的 s,t∈ z 有 b|(sa ± tc) (7) 比较性:b|a 且 a,b ∈ n ,则 b ≤ a 定理1 设 a,b ∈ z ; 当且仅当 a = ±b 时 , a|b 并且 b|a 特别的: 当且仅当 a = ±1 时 a|1 3、练习题: 证明对于任意自然数x,其各位上的数字相加之和如果能被3整除,那么该自然数也能被3整除。 设 a = a0 \u0026#43; a1*10 \u0026#43; ... \u0026#43; ak*10^k、b = a0 \u0026#43; a1 \u0026#43; ... \u0026#43; ak 则,证明上述结论即证明 \u0026#34;如果 3|b ,则 3|a\u0026#34; 将a化简如下: a = a0 \u0026#43; a1*10 \u0026#43; a2*10^2 \u0026#43; ... \u0026#43; ak*10^k = a0 \u0026#43; (a1 \u0026#43; 9*a1) \u0026#43; (a2 \u0026#43; 99*a2) \u0026#43; ... \u0026#43; (ak \u0026#43; 9...9*ak) = (a0 \u0026#43; a1 \u0026#43; a2 \u0026#43; ... \u0026#43; ak) \u0026#43; (9*a1 \u0026#43; 99*a2 \u0026#43; ... \u0026#43; 9...9*ak) = b \u0026#43; 3 * (3*a1 \u0026#43; 33*a2 \u0026#43; ... \u0026#43; 3...3*ak) 因为 3|b ,因此 存在q ∈ z 使得 b = 3*q 。 将其代入上式可得: a = 3*q \u0026#43; 3 * (3*a1 \u0026#43; 33*a2 \u0026#43; ... \u0026#43; 3...3*ak) = 3 * (q \u0026#43; 3*a1 \u0026#43; 33*a2 \u0026#43; ... \u0026#43; 3...3*ak) 所以 3|a 。 二、素数 1、素数的定义 设 n ∈ n ,且 n ≥ 2 。如果除了 1 和 n 外,没有其他正整数能整除n。称 n 为素数。 素数通常用 p 表示。除素数外的正整数称为合数。(1既不是素数也不是合数) 2、素数相关的定理: (1) 如果 n 不是素数(即n是合数),则 n = ab , 其中 1 \u0026lt; a \u0026lt; n , 1 \u0026lt; b \u0026lt; n 。 (2) 任何大于一的整数必有素因子 (3) 任何合数都有一个不超过 \u0026#34;根号n\u0026#34; 的素因子 (4) 算术基本定理: 任何非零整数 n 都可以表示成 一些素数的乘积,并且表示形式唯一。 (5) 欧几里得定理: 素数有无穷多个。 补充: (6) 设有一个\u0026#34;由素数组成的公差为d长度为k的等差数列\u0026#34;,则d能被小于k的所有素数整除 推论: 不存在无穷长的素数等差数列 (7) 存在任意长度的素数等差数列 三、模运算 1、模运算的定义: 设 a,b ∈ z 且 b \u0026gt; 0 , 如果 q,r ∈ z 并且满足 \u0026#34;a = qb \u0026#43; r , 且 0 ≤ r \u0026lt; b\u0026#34;、 则 a mod b := r ,读作: a 模 b 得 r 2、模运算得性质: (1) 如果 b|a , 那么 a mod b = 0 (2) (a \u0026#43; b) mod n = (a mod n \u0026#43; b mod n) mod n (3) (a - b) mod n = (a mod n - b mod n) mod n (4) (a * b) mod n = (a mod n * b mod n) mod n 四、最大公约数 1、最大公约数的定义 设 a,b ∈ z , 如果 d ∈ z 且 d|a , d|b , 则称 d 是 a和b 的公因子(公约数)。 如果 d \u0026gt;= 0 且 a 和 b的所有公因子都整除 d ,则称 d 是 a和b 的最大公约数, 记作 gcd(a,b) = d 。 注意: - 公因子可以是任意整数(包括0和负整数)。 - 最大公约数显然大于等于0 2、最大公约数的性质: (1) gcd(-a,-b) = gcd(-a,b) = gcd(a,-b) = gcd(a,b) (2) 设 a,b,c不全为0,且 a = b*q \u0026#43; c , 则 gcd(a,b) = gcd(b,c) 推论: a,b的公因数 等价于 gcd(a,b)的因数 (3) 设a,b为不全为0的整数,则: - 对于任意的正整数m,有gcd(am,bm) = gcd(a,b)*m - 若 m 是a,b的公因数,gcd(a/m,b/m) = (a,b) / |m| (4) 若 a,b,c是三个整数,且b和c至少有一个不为0,且gcd(a,b) = 1,则: - a*b 与 c的公因数与 b和c的公因数相同 - gcd(ab,c) = gcd(b,c) (5) 设 a,b ∈ z, d = gcd(a,b) , 则 存在 s,t∈z , 使得 as \u0026#43; bt = d . 特别的: 当 gcd(a,b) = 1 ,存在 s,t∈z , 使得 as \u0026#43; bt = 1 . (不定方程: ax \u0026#43; by = c 有整数解的充要条件是 gcd(a,b) | c ) 3、互素的定义 设 a,b ∈ z , 如果 gcd(a,b) = 1 ,称 a和b 互素。 注意: 互素的两个数公因子只有 ±1 性质: (1) 设 gcd(a,b) = d ,则存在 q1,q2 ∈ z , 使得 a = q1*d , b = q2*d 且 gcd(q1,q2) = 1 。(当gcd(q1,q2) = t \u0026gt; 1 时 gcd(a,b) = t*d \u0026gt; d , 与gcd(a,b) = d 矛盾) (2) 若p是质数,a是任意整数,则 要么 p|a , 要么 gcd(a,p) = 1 (一个整数与一个质数只存在整除与互素两种关系) 4、欧几里得算法(辗转相除法)求最大公约数: 设 a,b ∈ z ,该算法用来求 gcd(a,b)。 因为 gcd(-a,-b)=gcd(-a,b)=gcd(a,-b)=gcd(a,b)、不失一般性设 a \u0026gt;= b \u0026gt;= 0 (1) gcd(a,0) = a; (2) gcd(a,b) = gcd(b,r); 其中 r = a mod b 证明: - 因为 a|a 且 a|0 、 所以 gcd(a,0) = a。 - 因为 r = a mod b , 所以 a = q*b \u0026#43; r 对于 a 和 b 的公因子d、存在q1,q2 ∈ z, 使得 a = q1 * d , b = q2 * d 即 q1 * d = q * q2 * d \u0026#43; r 即 r = d * (q1 - q * q2) 即 d 是 r 的因子 所以 d 是 b和r 的因子 所以 a和b 的公因子一定是 b和r 的公因子 所以 gcd(a,b) = gcd(b,r) 根据(1)(2)可知,求两个较大数的最大公约数可以转换为其余两个较小数的最大公约数,即 gcd(a,b) = gcd(b,r)。令 r1 = b mod r 则 gcd(a,b) = gcd(b,r) = gcd(r,r1) 依次迭代,最终 rk 必等于0 ,根据(1),gcd(a,b) = r(k-1).这就是欧几里得算法的思路。 关于 rk 必定等于0 ,这里解释一下: 容易看出 对于任意的k、 满足 rk \u0026lt; r(k-1)、且 rk \u0026gt;= 0 , 因此不断迭代下去,必 定存在一个k使得 rk = 0。 5、扩展的欧几里得算法: - 最大公约数表示定理: 设 a,b ∈ z, d = gcd(a,b) , 则 存在 s,t∈z , 使得 as \u0026#43; bt = d . 特别的: 当 gcd(a,b) = 1 ,存在 s,t∈z , 使得 as \u0026#43; bt = 1 . 推论: 如果 d|v 则,存在 s,t∈z , 使得 as \u0026#43; bt = v . - 扩展的欧几里得算法是 计算 as \u0026#43; bt = gcd(a,b) 中的 s和t , 从而求出 gcd(a,b) 具体的求解可使用如下方法迭代求得: 首先介绍欧几里得算法,如下所示: 初始时: a = r0 , b = r1; 辗转相除: r0 = r1 * q1 \u0026#43; r2 ; 15 = 10 * 1 \u0026#43; 5 r1 = r2 * q2 \u0026#43; r3 ; 10 = 5 * 2 \u0026#43; 0 r2 = r3 * q3 \u0026#43; r4 ; ... ... r(i-1) = ri * qi \u0026#43; r(i\u0026#43;1) ; ... ... r(n-3) = r(n-2) * q(n-2) \u0026#43; r(n-1) ; r(n-2) = r(n-1) * q(n-1) \u0026#43; rn ; r(n-1) = rn * qn ; 当 rn|r(n-1) 时,求得最终结果为 rn 扩展的欧几里得算法: 设 s0 = 1 ,s1 = 0 ; t0 = 0 , t1 = 1 ; 则: s(i\u0026#43;1) = s(i-1) - si * qi ; (i = 1,2,...,n) t(i\u0026#43;1) = t(i-1) - ti * qi ; (i = 1,2,...,n) 易知上述s,t满足: a * si \u0026#43; b * ti = ri ; (i = 0,1,2,...,n) 可用数学归纳法证明如下: 归纳奠基: 当 i = 0 时,a*s0 \u0026#43; b*t0 = a = r0 成立 当 i= 1 时, a*s1 \u0026#43; b*t1 = b = r1 成立 归纳递推: 假设 a * s(i-1) \u0026#43; b * t(i-1) = ri 成立,那么: a * si \u0026#43; b * ti = a * (s(i-2)-s(i-1)*q(i-1)) \u0026#43; b * (t(i-2)-t(i-1)*q(i-1)) = a * s(i-2) \u0026#43; b * t(i-2) - (a*s(i-1)\u0026#43;b*t(i-1)) * q(i-1) = r(i-2) - r(i-1) * q(i-1) 由辗转相除的步骤可知: r(i-2) = r(i-1) * q(i-1) \u0026#43; ri ;代入上式可得 a * si \u0026#43; b * ti = ri 得出结论: a * si \u0026#43; b * ti = ri ; (i = 0,1,2,...,n) 成立 在欧几里得算法的基础上,每一步都求出相应的 si 和 ti ,最终即可求出 s,t 满足 as \u0026#43; bt = gcd(a,b) 五、最小公倍数(least common multiple) 1、最小公倍数的定义 设 a,b ∈ z , 如果 m ∈ z 且m既是a的倍数,又是b的倍数 , 则称m是a和b的公倍数。 若 ab ≠ 0 , 且m是a和b的正的公倍数中,最小的那个,称 m 是 a和b 的最小公倍数。 记作 lcm(a,b) 性质: (1) a,b的公倍数 等价于 lcm(a,b)的倍数。 (2) 如果 gcd(a,b) = 1 , 则 lcm(a,b) = ab ; (3) lcm(a,b) = ab / gcd(a,b) 2、求最小公倍数的方法: 对于 a,b 。 - 首先令 m = a;n = b; - 如果 m \u0026lt; n ,那么 令 m = m \u0026#43; a;否则,令 n = n \u0026#43; b; - 循环第二步,直到 m = n 为止,此时 m 即为最小公倍数。 六、同余 1、等价关系 设有集合s和定义在s上的二元关系r。如果r满足如下性质,则称他为等价关系: - 自反性: 对于任意 a ∈ s ,都有(a,a) ∈ r - 对称性: 对于任意 a,b ∈ s ,若(a,b) ∈ r , 则 (b,a) ∈ r - 传递性: 对于任意 a,b,c ∈ s,若(a,b) ∈ r,(b,c) ∈ r,则(a,c) ∈ r 练习: s = r ,二元关系 r = {(a,b):x² = y², x,y ∈ s} , 问 r 是不是等价关系? - 对于任意的 a ∈ s , a² = a² , 即 (a,a) ∈ r , r 满足自反性 - 对于任意的 a,b ∈ s , 若 (a,b) ∈ r, 则 a² = b² , 即 b² = a² , 所以 (b,a) ∈ r , 即r满足对称性。 - 对于任意的 a,b,c ∈ s ,若 (a,b) ∈ r,(b,c) ∈ r, 则 a² = b²,b² = c² 所以 a² = c²,所以 (a,c) ∈ r , 即 r 满足传递性。 综上所述,r是等价关系。 2、同余(congreuence) 同余定义: 设 n 为正整数, a和b分别模n, 如果得到的余数相等, 则称 a和b在模n的条件下满足 同余关系。简称同余。记作 a≡b(mod n) 。 这里 n 叫做 \u0026#34;模数\u0026#34; 同余关系的严格数学定义: 设 a,b,n ∈ z, n \u0026gt; 0, 若 n | (a-b), 就称a和b在模n下同余,记作 a≡b(mod n) 说明: 这与上述同余的定义是一样的, a和b分别模n,假设得到的余数分别为r1,r2 则 a = q1 * n \u0026#43; r1 ; b = q2 * n \u0026#43; r2 ; 由同余的定义可知,r1 = r2 所以 a-b = (q1 - q2) * n \u0026#43; (r1 - r2) = (q1 - q2) * n 即 n | (a-b) 同余的性质: \u0026#34; a≡b (mod n)\u0026#34; 等价于 \u0026#34;存在 q ∈ z , a = q*n \u0026#43; b\u0026#34; 。 同余关系是等价关系,具有 自反性: 对任意的a∈z :a ≡ a(mod n) 则 对称性: 对任意的a,b∈z : 若 a ≡ b(mod n) 则 b ≡ a(mod n) 传递性: 对任意的a,b,c∈z : 若 a ≡ b(mod n) , b ≡ c(mod n), 则 a ≡ c(mod n) 推论:若a ≡ 1(mod n), 则 a^k ≡ 1(mod n) (k = 1,2,3...) 证明: 因为 a ≡ 1(mod n) 由性质(2)的推论, a² ≡ a(mod n) 由传递性得: a² ≡ 1(mod n) 同理可证k=3,4,..... 四则运算 (a \u0026#43; b) % p = (a % p \u0026#43; b % p) % p (a - b) % p = (a % p - b % p) % p (a * b) % p = (a % p * b % p) % p (a ^ b) % p = ((a % p)^b) % p (1) 若a1 ≡ b1(mod m),a2 ≡ b2(mod m),则(a1 ± a2) ≡ (b1 ± b2)(mod m) (可加减性) 证明: 因为 a1 ≡ b1(mod m) 所以存在 q1,q2 ∈ z ,使得 a1 = q1 * m \u0026#43; r1 ; b1 = q2 * m \u0026#43; r1 同理存在 q3,q4 ∈ z ,使得 a2 = q3 * m \u0026#43; r2 ; b2 = q4 * m \u0026#43; r2 故 a1\u0026#43;a2 = (q1\u0026#43;q3)*m \u0026#43; r1 \u0026#43; r2; b1 \u0026#43; b2 = (q2\u0026#43;q4)*m \u0026#43; r1 \u0026#43; r2 故 a1\u0026#43;a2 与 b1\u0026#43;b2 的余数都是 r1\u0026#43;r2,即 (a1\u0026#43;a2) ≡ (b1 \u0026#43; b2)(mod m) (2) 若 a1 ≡ b1(mod m) ,a2 ≡ b2(mod m) ,则 (a1*a2) ≡ (b1*b2)(mod m). (可乘性) 证明: 因为 a1 ≡ b1 (mod m) 所以 存在 q1,q2 ∈ z ,使得 a1 = q1 * m \u0026#43; r1 ; b1 = q2 * m \u0026#43; r1 同理 存在 q3,q4 ∈ z ,使得 a2 = q3 * m \u0026#43; r2 ; b2 = q4 * m \u0026#43; r2 所以 a1 * a2 = q1*q3*m^2 \u0026#43; q1*r2*m \u0026#43; r1*q3*m \u0026#43; r1*r2 = ( q1 * q3 * m \u0026#43; q1 * r2 \u0026#43; r1 * q3 ) * m \u0026#43; r1 * r2 同理 b1*b2 = (q2 * q4 * m \u0026#43; q2 * r2 \u0026#43; r1 * q4) * m \u0026#43; r1 * r2 故 a1*a2 与 b1*b2 的余数都是 r1*r2,即 (a1*a2) ≡ (b1*b2)(mod m) 推论: a ≡ b(mod m) ,则 ka ≡ kb(mod m)成立。 a ≡ b(mod m) ,则 a^k ≡ b^k(mod m)成立。 a ≡ b(mod m) ,gcd(d,m) = 1 ,则 (a / d) ≡ (b / d)(mod m)。 证明: 因为 gcd(d,m) = 1 所以 d在模m下存在乘法逆元,设为d1 由同余的自反性可知 d1 ≡ d1(mod m) 又因为 a ≡ b(mod m) 所以得 a*d1 ≡ b*d1(mod m) 即 (a / d) ≡ (b / d) (mod m) (3) 若 a ≡ b(mod m) ,则 ka ≡ kb(mod km) (同乘性) 证明: 因为 a ≡ b(mod m) 所以 存在 q1,q2 ∈ z ,使得 a = q1 * m \u0026#43; r ; b = q2 * m \u0026#43; r 所以 ka = q1 * k * m \u0026#43; r * k ; kb = q2 * k * m \u0026#43; r * k 即 ka 和 kb 模 km 的余数都是 r * k , 即 ka ≡ kb(mod km) (4) 若 a ≡ b(mod m) ,d是a,b,m的公因数 , 则 a/d ≡ b/d (mod m/d) (同除性) 证明: 因为 a ≡ b(mod m) 所以 m | (a-b) 所以 m / d | (a /d - b/d) 即 a/d ≡ b/d (mod m/d) (5) 若 a ≡ b(mod m) ,若 d|m 且 d \u0026gt; 0 ,则 a ≡ b(mod d) (两个数在模m下同余,则这两个数在在模m的因子下也同余) 证明: 因为 a ≡ b(mod m) 所以 m|a-b , 即 a-b = k1 * m ; 因为 d|m ,即 m = k2 * d ; 所以 a-b = k1 * k2 * d ; 即 d | a-b 所以 a ≡ b(mod d) (6) 若 a ≡ b(mod mi)(i = 1,2,...,k),则 a ≡ b(mod lcm(m1,m2,...,mk)) (两个数在一些模下同余,则在这些模的最小公倍数下也同余) 证明: 因为 a ≡ b(mod mi) (i = 1,2,...,k) 所以 mi | (a-b) (i = 1,2,...,k) (7) 若 ac ≡ bc(mod m)且gcd(a,c) = 1,gcd(c,m) = d,则 a ≡ b (mod m/d) (解方程可以用到) 特别的,当 d = 1 时,即 c和m 互素 , 则 a ≡ b(mod m) 即,同余式两边可以同时除以一个和模数互质的数。 证明: 条件告诉我们,ac-mp = bc-mq,移项可得ac-bc = mp-mq 也就是 说(a-b)c = m(p-q)。这表明,(a-b)c里需要含有因子m,但c和m互质,因此 只有可能是a-b被m整除,也即a≡b(mod m)。(一派胡言) (8) 若 a ≡ b(mod m) ,则 gcd(a,m) = gcd(b,m) (模m同余的两个数与m的最大公约数相同) 3、乘法逆元(modular inverse) 设 a,z ∈ z , n ∈ n , 如果 az ≡ 1(mod n) , 称 z 是a在模n下的乘法逆元。 注意: - 一般乘法逆元取 [1,n)之间的数。 - a 在模n 下的乘法逆元是唯一的。 - 乘法逆元并不是一定存在的,乘法逆元存在的条件是 gcd(a,n) = 1 证明: 假设 gcd(a,n) = d ; (d \u0026gt; 1) 时,a 有乘法逆元,设为 b 则 ab ≡ 1(mod n) , 即 ab = qn \u0026#43; 1 ; 即 ab - qn = 1 ; 由于 d|a 且 d|n 所以 ab - qn 可提出一个因子 d 所以 d|1 , 而 d \u0026gt; 1 ,这是不可能的 故假设有误,d = 1 (d是最大公因数,故d \u0026gt; 0,而d \u0026lt;= 1,故d = 1) * 乘法逆元的求法: 可用扩展的欧几里得算法求乘法逆元 假设a在模n下存在乘法逆元 , 即 gcd(a,n) = 1 ; 根据最大公约数表示定理 , 存在 s,t ∈ z ,使得 as \u0026#43; nt = 1 等式两边同时模n可得 (as \u0026#43; nt) mod n = 1 mod n 即 as mod n = 1 mod n , 即 as ≡ 1(mod n) 所以s即为a在模n下的乘法逆元 4、一次同余方程 一次同余方程的一般形式为 ax ≡ b(mod n) ; 一次同余方程有解的充要条件是:gcd(a,n) | b ; 解方程可用上述介绍的同余性质。 例如: 解方程:105x - 62 ≡ 1(mod 6) 化为一般形式为: 105x ≡ 63(mod 6) gcd(105,63,6) = 3 ,根据性质(4)得: 35x ≡ 21(mod 2) 又因为 gcd(7,2) = 1 ; 根据性质(5)得: 5x ≡ 3(mod 2) 因为 gcd(5,2) = 1 ; 故 5在模2下存在乘法逆元,计算可得乘法逆元为 1 两边同乘乘法逆元得:x ≡ 3(mod 2) 即 x ≡ 1(mod 2) 所以解为 {...,-3,-1,1,3,5,7,...} 定理: 假设解方程前模数为n,最终模数为m,设 n/m = d;则 [0,n-1] 之间的解恰好有 d 个。 上述例子中 n = 6, m = 2;故 d = 3;而解中在 [0,5]之间得解为1,3,5正好 d 个。 七、剩余类(residue class) 1、等价类: - 定义: 设 ~ 是集合 s 上的等价关系, 对于 a ∈ s,定义 a 的等价类为: { x ∈ s | x ~ a } 即 s 中与 a 满足 等价关系 的元素的集合。 - 性质: 集合s上的所有等价类形成集合s的一个划分。 s中的每一个元素都存在于唯一的一个等价类中 所有等价类的并集恰好是集合s 2、剩余类: - 定义: 设 x ∈ z,定义它的剩余类为: { x ∈ z | x ≡ a (mod n) } 即 a的剩余类就是 \u0026#34;与a同余的正数集合\u0026#34;。( 易知:剩余类是等价类的一种。) - 代表元: 剩余类中的每个元素都叫做这个剩余类的代表元。 - 小提示: 模n下含有正数a的剩余类记作 [a]n,不引起歧义时简记为 [a], 一般取a ∈ [0,n] - 剩余类集合: zn = {[0],[1],...[n-1]} - 剩余类运算的定义: 在模n下: 加法: [a] \u0026#43; [b] := [(a \u0026#43; b) mod n] (其中a,b ∈ z) 乘法: [a] * [b] := [(a * b) mod n] (其中a,b ∈ z) 乘法逆元 设u = [a],v = [b] ∈ zn,若 u*v = [1],则称 u和v互为乘法逆元、 (对于剩余类[a],他有乘法逆元的充要条件是:gcd(a,n) = 1) - 含有乘法逆元的剩余类 zn* zn* = {zn中含有乘法逆元的剩余类} - zn 和 zn* 之间的关系 当n为素数时: zn* = zn \\ {[0]} ( 即 zn* 是 zn中去掉[0] ) 当n为合数时: zn* 是 zn \\ {[0]} 的真子集 八、中国剩余定理 1、中国剩余定理 设两两互素的模数n1,...,nm ∈ n,以及任意整数 a1,...,am ∈ z, n = n1*...*nm。 则:一次同余方程组: x ≡ a1 (mod n1) x ≡ a2 (mod n2) ... x ≡ am (mod nm) 必有解,且解集为: { k*m \u0026#43; ∑(ai*ti*mi);k ∈ z } 其中 m = n1*n2*...*nm; mi = m/ni; ti是mi在ni下的乘法逆元 2、设两两互素的模数 n1,n2,...,nm ∈ n, n = n1*n2*...*nm;则下面的映射是一个双射 zn → zn1×zn2×...×znm 即( a → (a mod n1,a mod n2,...,a mod nm) ) 3、欧拉函数 对于所有正整数 n ∈ n,欧拉函数 ф(n) := |zn*| 。即所有小于n且与n互素的正整数 (不包括0)的个数。且规定 ф(n) = 1 欧拉函数的性质: (1) 对于两两互素的正整数n1,n2,...nm , 设 n = n1*n2*...*nm; 则 ф(n) = ф(n1)*ф(n2)*...*ф(m) ; (使用中国剩余映射证明) (2) 对于一个素数p , ф(p^k) = p^(k-1)*ф(p) = p^(k-1)*(p-1); (使用算数基本定理和(1)易证) (3) 对于一个素数p , ф(p) = p-1 ; (由欧拉函数的定义,这是显然的) (4) 设 d1,...,dr 是整数n的所有因子,则f(n) = ф(d1) \u0026#43; ... \u0026#43; ф(dr) = n 计算 ф(n) 的方法: (1) 利用算数基本定理将n分解为素数的乘积,即 n = p1^k1 * ... * pn^kn (2) 则 ф(n) = ф(p1^k1 * p2^k2 * ... * pn^kn) (3) 利用上面三个公式轻松搞定 4、乘法阶 (multiplicative order) 设 a ∈ zn* ,使得 a^k ≡ 1(mod n) 的最小正整数k 称作a在模n下的乘法阶。 简称a的阶. ( 记作ep(a) = k ) 推论: (1) 设 a ∈ zn* 的乘法阶为k,则在模n运算中,若i不同则 a^i(mod n)(i ∈ [1,k-1]) 的值各不相同且不等于1. 证明: 假设存在 i \u0026lt; j \u0026lt; k, 使得 a^i ≡ a^j(mod n) 则 由同余的性质(2)推论可得: a^i / a^j ≡ a^j / a^j(mod n) 即 a^(i-j) ≡ 1(mod n) 所以a的乘法阶为 i-j \u0026lt; k ,这与a的乘法阶是k矛盾 所以 当i不同时, a^i(i = 1,...,k-1) 各不相同 它们的值显然不能等于1,否则和乘法阶为k矛盾。得证 (2) a^i ≡ 1 (mod n) 等价于 k | i 证明: 充分性: 因为 a^i ≡ 1 (mod n) 所以 i \u0026gt;= k 假设 k | i 不成立,则 i可以写成 i = x*k \u0026#43; y 其中 0 \u0026lt; y \u0026lt; k 则 a^i ≡ 1 (mod n) 化简为 (a^xk) * (a^y) ≡ 1 (mod n) 即 ( (a^k)^x (mod n) * a^y (mod n) ) (mod n) = 1 (mod n) 即 1 * a^y (mod n) = 1 即 a^y ≡ 1(mod n) ,此时 y\u0026lt;k 是a的阶,矛盾 因此 k | i 必要性: 因为 k | i 所以 i = x*k 因为 a^k ≡ 1(mod n) 由同余传递性的推论可知: a^i ≡ 1(mod n) (3) a^i ≡ a^j(mod n) 等价于 i ≡ j(mod k) 即 a^i(mod n) = a^(i mod k)(mod n) (当i很大时,可用此公式减小运算量) 证明: (4) 设a ∈ zn*,m ∈ z,且a在模n下的乘法阶为k,则a^m在模n下的乘法阶为k/(gcd(m,k)) 证明: 暂时不会 0.0 5、欧拉定理 设 a ∈ zn*,则 a^ф(n) ≡ 1(mod n),且 k|ф(n),k是a在模n下的阶。 (也可表述为:如果 gcd(a,n) = 1 , 则a^ф(n) ≡ 1(mod n)) 注意: 由于 a ∈ zn* ,所以 gcd(a,n) = 1,这时才有欧拉定理 欧拉定理的简单应用: a^i(mod n) = a^( i mod ф(n) )(mod n) (当i很大且不知道a的乘法阶时,可以用此公式减小运算量) 6、费马小定理 对于任意素数p和整数 a ∈ zp,都有 a^p ≡ a(mod p) (也可写成 a^(p-1) ≡ 1(mod p)) 7、幂模p与原根 离散对数: 对于一个素数p,设a,b是模p的非0整数,设 a^x ≡ b(mod p), 如果x是 满足 a^x ≡ 1(mod p)的最小正整数, 假设 0 \u0026lt;= x \u0026lt; p;则 记 x = la(b),读作 \u0026#34;与a相关的b的离散对数等于x\u0026#34;。 次整除性质: 设a是不被p整除的整数,假设 a^n ≡ 1(mod p),则 ep(a) | n . 原根的定义: 具有最高次数 ep(g) = p-1 的数g 称为模p下的原根。 若a是模p下的原根( 即ep(a) = p-1 )则 a,a^2,a^3,...,a^(p-1)余数各不相同。 原根定理: 如果整数a有原根,则a的原根数量为 ф(ф(p)) 特别的:每个素数p都有原根,且原根的个数恰好为 ф(p-1) 指标: 模素数p的原根设为g,则指标i(a)满足 g^i(a) ≡ a(mod p) ,1 \u0026lt;= a \u0026lt; p; 指标法则: - i(ab) = i(a) \u0026#43; i(b) (mod p-1) - i(a^k) = k*i(a) (mod p-1) 九、二次剩余 设 a ∈ zn* ,如果存在正整数 b ,使得 b² ≡ a(mod n) , 则称 a 为模n下的二次剩余。 并称 b 为 a 在模n下的平方根。否则称为二次非剩余。 如:1,2,4是模7下的二次剩余。3,5,6是模7下的二次非剩余。 奇素数p下的二次剩余 引言: 对于任意的奇素数p, b² ≡ (p-b)²(mod p) 总成立、 证明: 因为 (p-b)² = p² - 2*p*b \u0026#43; b² 而 p | p² 且 p | 2*p*b 所以 p | (p² - 2*p*b) 所以 p² - 2*p*b (mod p) ≡ 0(mod p) 所以 b² ≡ (p-b)²(mod p) 性质: - 设 b ∈ zp, 则 b² = 1 等价于 b = ±1 - 设 b,c ∈ zp*,则 b² = c² 等价于 b = ±c - | (zp*)² | = φ(p) / 2 (模p下二次剩余的数量) - 设p为奇素数,a,b ∈ zp*,则: 当a,b都是二次剩余时,a*b 也是二次剩余 当a,b都是二次非剩余时,a*b也是二次非剩余。 欧拉准则: 设 p 是奇素数,设 a ∈ zp* 则 a^( (p-1)/2 ) = ±1 - 当且仅当 a^( (p-1)/2 ) = \u0026#43;1 时,a是二次剩余。 - 当且仅当 a^( (p-1)/2 ) = -1 时,a是非二次剩余。 设p为奇素数,则模p^k下的二次剩余 性质: - 设 b ∈ zp^k, 则 b² = 1 等价于 b = ±1 - 设 b,c ∈ zp^k*,则 b² = c² 等价于 b = ±c - | (zp^k*)² | = φ(p^k) / 2 (模p^k下二次剩余的数量) - 设p为奇素数,a,b ∈ zp^k*,则: 当a,b都是二次剩余时,a*b 也是二次剩余 当a,b都是二次非剩余时,a*b也是二次非剩余。 判断a是否是二次剩余: 设 p 是奇素数,设 a ∈ zp* 则 a^( φ(p^k)/2 ) = ±1 - 当且仅当 a^( φ(p^k)/2 ) = \u0026#43;1 时,a是二次剩余。 - 当且仅当 a^( φ(p^k)/2 ) = -1 时,a是非二次剩余。 设n为奇数,则模n下的二次剩余: 则 |(zn*)²| = φ(n) / 2^m (其中m是n经过算数基本定理分解后,不同质因子的数量) 如: n = 45 时,模n下二次剩余的数量为 φ(45) / 2^2 = 6 这里不同的质因子分别为 3,5。因为 45 = 3²·5; 所以 m = 2 群论基础 初等群论 一、 1、命题 可谈论(但未必能确定)其真假值得语句。 二、集合 1、定义 某些恰当得群体。 如: n{s|s是正整数} z{s|s是整数} q{s|s是有理数} r{s|s是实数} c{s|s是复数} 2、集合中得概念 - 空集合: 没有任何元素得集合。 - 子集: 如果t得元素都是s得元素,成t是s得子集。 - 幂集合: 对于一个集合s,它的所有子集构成的集合叫做它的幂集合,或幂集。 - 笛卡尔积: 给定两个集合 s,t、则s和t的笛卡尔积为 s×t = {(s,t) | s∈s 且 t∈t} - 映射: 映射是指一种规则,这种规则规定了\u0026#34;集合s中的元素与集合t中元素的对应关系。 s称为该映射的逆像集(或称定义域),t叫做该映射的象集(或称值域)。映射可以用小写 字母表示。如f,g,h等。映射并不一定有公式表达,只需表达清楚映射规则即可。 - 单射: s中任意两个相异的元素被映射到t中后,得到的两个元素仍然相异。 - 满射: t中的每个元素都被某个s中的元素映射到。 - 对射: 既是单设又是漫射的映射。 - 复合映射: 给定两个映射f和g,则形如 g(f(x))的映射叫做复合映射。 - 集合的基数: 将有限元素的集合s中,元素的个数记作 |s|. 如果说集合s和集合t拥有相同的基数,意思是存在一个s到t的对射。 如果说集合s的基数不大于集合t的基数,意思是存在一个s到t的单射。 - 可数集: 如果s是有限集或者s和自然数n有相同的基数,则称s是可数集。 三、群 1、二元运算 定义: 集合s上的一个二元运算是指一个 \u0026#34;由 s×s 到 s 的映射\u0026#34; ,记作 \u0026#34;(s,▢)\u0026#34;。 (其中s×s是s与自身的笛卡尔积) 约定: 假设 s1,s2∈s, 当s1和s2做该二元运算时,可简单的写作 \u0026#34;s1 ▢ s2\u0026#34;。 这里▢可以是任意符号如 \u0026#34;*\u0026#34; , \u0026#34;\u0026#43;\u0026#34; , \u0026#34;÷\u0026#34;等等。 2、结合律 当我们说二元关系(s,▢)是结合的,意思是说:对于s中的任意三个元素a、b、c, 总有 (a ▢ b) ▢ c = a ▢ (b ▢ c) 。 3、单位元 定义: 如果s中的某个元素e在二元运算(s,▢)上满足: 任意一个s中的元素s有 s▢e = e▢s = s 称e是二元运算(s,▢)下的单位元。 注意: 二元运算下的单位元要么没有,要么只有一个。 (假设有e1,e2两个单位元,则e1▢e2 = e2▢e1 = e1 = e2,即这两个单位元一样) 4、可逆 假设二元运算(s,▢)有单位元e , 如果对于s中某个元素s,s中存在一个元素s\u0026#39; , 使得 s▢s\u0026#39; = s\u0026#39;▢s = e,则称s在(s,▢)下可逆。并且称 s\u0026#39; 是 s 在二元运 算(s,▢)下的逆元(或称反元素) 5、群 群的定义 有一个集合g和定义在g上的一种运算 ▢ , 如果 g 对 ▢ 满足以下四个条件: (1) 封闭性 ( 即▢是g上的二元运算 ) (2) 结合律 (3) 单位元 (4) 可逆性 (每一个g中的元素都有逆元) 那么称集合g是一个群。集合中的元素叫做群元。 群的性质: (1) 单位元e的逆元是其本身 (2) 逆元的逆等于群元本身 (3) (a▢b)^-1 = b^-1 ▢ a^-1 子群 定义: 对于一个群(g,·),若g的一个子集h满足下面两个条件,称h是g的子群 (1) \u0026#34;·\u0026#34; 是h上的二元运算 (2) (h,·) 是一个群 性质: - 子群的单位元等于群的单位元。 - 子群中任意元素的逆元等于群中该元素的逆元。 群中一子集生成的子群 假设(g,·)是一个群,t是g的一个子集,定义包含集合t的最小子群为: \u0026lt;t\u0026gt; := 所有g的子群的交集。则: \u0026lt;t\u0026gt; = {t1^a1·t2^a2...tn^an | t1,t2...tn都是t的元素,a1,a2...an是整数} 补充: 假设(s,·)是一个群,对于s中的一个元素s,则 s·s·s·...·s(k个s) k \u0026gt; 0 s^k := e k = 0 s^-1·...·s^-1(k个s^-1) k \u0026lt; 0 特别的: 当t只含有一个元素g时,即 t = {g},则称 \u0026lt;t\u0026gt; = {g^k | k ∈ z} 为由元素g 生成的子群。可简单记作 \u0026lt;g\u0026gt; 阿贝尔群 如果一个群 (g,▢)中,对于g中任意的两个元素a,b满足,a▢b = b▢a , 那么称该群 为交换群(或 阿贝尔群) 循环群 设a是群g的一个元素,则a的所有幂的集合 { a^n | n∈z } 构成g的一个子群,称为 由 a 生成的循环子群,记作\u0026lt;a\u0026gt; 。若群g恰好可有由它的一个元素a所生成,即存在a, 使得g = \u0026lt;a\u0026gt;,则称群 g 为循环群, a 为群 g 的生成元。 定理:每个循环群都是 abel 群。 分类: 无限循环群:\u0026lt;a\u0026gt;中的元素彼此各不相同,a的周期为无穷大。 有限循环群:\u0026lt;a\u0026gt;中出现重复的元素,即存在 s ≠ t,使得 a^s = a^t , 令 n = min|s-t|,则 a 的周期为 n 称 \u0026lt;a\u0026gt; = {a,a^2,a^3...a^(n-1)} 为 n阶循环群。 性质: 设群g中元素a的周期为n (1) e的周期为1 (2) a^-1 的周期为n (3) \u0026lt;a\u0026gt;的元素个数为n,且 \u0026lt;a\u0026gt; = {e,a,a^2,a^3...a^(n-1)} (4) a^m = e;当且仅当 n|m (5) a^s = a^t; 当且仅当 n|(s-t) 循环群的生成元 ① 若 g 是无限循环群,则 g 只有两个生成元,即 a 和 a^-1 ; ② 若 g 是 n 阶循环群,则 a^k 是 g 的生成元,当且仅当 k 与 n 互质。 因此, g 含有 φ(n) 个生成元。 循环群的子群 ① 若 g 是循环群,则 g 的子群仍是循环群; ② 若 g 是无限循环群,则 g 的子群除 {e} 以外都是无限循环群; ③ 若 g 是n阶循环群,则对于n的每个正因子d,g 恰好含有一个 n/d 阶子群。 半群 有一个集合g和定义在g上的一种运算 ▢ , 如果 g 对 ▢ 满足以下两个条件: (1) 封闭性 (2) 结合律 则称 (g,▢)是一个半群。 四、环 1、环的定义: 在集合r上定义了两种二元运算 \u0026#43; 和 · 且满足: (1) (r,\u0026#43;) 是一个阿贝尔群. (2) (r,·) 是一个半群。 (3) \u0026#43; 和 · 满足分配律。即,对于r中的任意三个元素a,b,c 有: (a\u0026#43;b)·c = a·c \u0026#43; b·c (左结合律) a·(b\u0026#43;c) = a·b \u0026#43; a·c (右结合律) 则称(r,\u0026#43;,·)是一个环。 交换环: 如果环中的二元运算 · 满足交换律,则称这个环为交换环。 含单位元的环 当环r中存在元素e,使得对环r中任意一个元素a都有e·a=a·e=a时,我们称e为环r的 单位元,并且称环r为含单位元的环。通常在不会产生混淆时,a·b简记为ab。 注意,在环中,二元运算\u0026#43;的单位元通常记为0,叫做零元 除环(或称斜环) 如果一个环中除非零元素外,其他元素在二元运算·下构成群,称该环为除环(或叫做斜环) 域: 域是一个非空集合,其上定义两种二元运算\u0026#34;\u0026#43;\u0026#34;和\u0026#34;·\u0026#34;,该集合关于二元运算\u0026#34;\u0026#43;\u0026#34;构成abel群 该集合中的非零元素关于乘法运算也构成abel群。(即\u0026#34;域是可交换的除环\u0026#34;)\r椭圆曲线 待补充。。。\n双线性映射 双线性映射的定义\n注意:现在的密码学相关论文中,习惯将$g_1$, $g_2$设置为乘法循环群。但是,基于椭圆曲线的双线性群构造中,$g_1$, $g_2$是加法群。在大约2005年以前的论文中,$g_1$,$g_2$是加法循环群。双线性映射可以通过有限域上的超椭圆曲线上的tate对或weil对来构造。\n双线性映射的分类\n根据$\\mathbb{g}_1$与$\\mathbb{g}_2$是否为同一个群,可以划分为 对称式与非对称式,具体分类见下表。\n符号 介绍 备注 $g_1×g_1→g_t$ $g_1=g_2$,被称为对称型双线性映射 type-1型配对\u0026quot;:安全性最低 $g_1×g_2→g_t$ $g_1\\neq g_2$但存在$g_1到g_2$的同态映射 type-2型配对\u0026quot;:安全性适中 $g_1×g_2→g_t$ $g_1\\neq g_2$且它们之间不存在同态关系 type-3型配对\u0026quot;:安全性较高 值得注意的是:type-3配对在带宽和计算效率方面提供了最有效的实现选择。非对称的双线性映射由于两个群$g_1, g_2$的大小可以取为不一样的,这样就会导致在两个群上计算点乘运算的计算开销不同。合理利用这一点,可以实现一些特殊的功能。【参考sm9算法】\n双线性映射的性质:\n假设加法循环群**$g_1$的生成元为$p$,则\n$e(a+b, c)=e(a, c)*e(b, c)$ $e(a, b+c)=e(a, b)*e(a, c)$ $e(xa, b)=e(a, xb)=e(a, b)^x$ $e(a+b, c+d)=e(a, c)*e(a, d)*e(b, c)*e(a, d)$ $e(a, b)^x*e(a, b)^y=e(a, b)^{x+y}$ $e(a, b)*e(c, d)=e(a+c, b+d)*e(a, d)^{-1}*e(b, c)^{-1}$ 这里给出$e(a+b, c)=e(a, c)*e(b, c)$的证明,其余定理证明同理。\n证明: 由于$a$和$b$是群$g_1$上的两个元素,故存在a, b满足 $a=ap,b=bp$;则\n$e(a+b, c)=e((a+b)p, c)=e(p, c)^{a+b}=e(p, c)^a*e(p, c)^b$. 即,\n$e(a+b, c)=e((a+b)p, c)=e(ap, c)*e(bp, c)=e(a, c)*e(b, c)$\n补充:用上述的证明方法可以证明,在非对称的双线性映射中,上述性质也是成立的。即:\n$e(a+b, c)=e(a, c)*e(b, c)$ $e(a, b+c)=e(a, b)*e(a, c)$ $e(a^x, b)=e(a, b^x)=e(a, b)^x$ $e(a+b, c+d)=e(a, c)*e(a, d)*e(b, c)*e(a, d)$ $e(a, b)^x*e(a, b)^y=e(a, b)^{x+y}$ $e(a, b)*e(c, d)=e(a+c, b+d)*e(a, d)^{-1}*e(b, c)^{-1}$ 伪随机函数(prf)与(oprf) 概念 简要介绍:\n正式定义:\n基于prf的cpa加密方案 证明备注:\n当d面对的是一个真随机函数时,a为了在游戏中获得优势,选择$m_0, m_1$之前一共进行了q(n)次oracle查询,如果他这q(n)次全部查询的是$m_0$的密文,最好的情况是:a每次询问得到的密文$\u0026lt;r, y\\oplus m\u0026gt;$中 r都不同,则他能获得$m_0$的q(n)个不同的密文。由于加密时选择的$r$一共有$2^n$种可能,也就是$m_0$至少有$2^n$个不同的密文。所以a区分$m_0$的密文成功率不超过$q(n)/2^n$, 故 上面的$ε(n)$表示当d面对的是伪随机函数的时候,a赢得游戏的优势 参考文献:\nhttps://blog.csdn.net/weixin_39578432/article/details/120405776 prf的应用 参考文献:\nhttps://www.cnblogs.com/pam-sh/p/16216240.html ","date":"2024-07-10","permalink":"http://54rookie.com/posts/%E5%AF%86%E7%A0%81%E5%AD%A6%E5%9F%BA%E7%A1%80/","summary":"密码学基础 数论基础 -------------------------------- 数论的简单基础 -------------------------------- 一、整除性 1、整除的定义: 设 a,b ∈ Z ,如果存在一个 q ∈ Z 使得 a = qb; 则称 \u0026#34;b整除a\u0026#34; ,记作 \u0026#34;b|a\u0026#3","title":"密码学基础"},]
[{"content":"常见签名算法 ecdsa签名 ecdsa简介: 椭圆曲线数字签名算法(ecdsa)是使用椭圆曲线密码(ecc)对数字签名算法(dsa)的模拟。ecdsa于1999年成为ansi标准,并于2000年成为ieee和nist标准。它在1998年既已为iso所接受,并且包含它的其他一些标准亦在iso的考虑之中。与普通的离散对数问题(discrete logarithm problem dlp)和大数分解问题(integer factorization problem ifp)不同,椭圆曲线离散对数问题(elliptic curve discrete logarithm problem ecdlp)没有亚指数时间的解决方法。因此椭圆曲线密码的单位比特强度要高于其他公钥体制。\n有趣的是,ecdsa的前身dsa是elgamal和schnorr方案的结合,而dsa签名完全是为了规避claus schnorr的专利而设计的。事实上,就在schnorr的美国专利发布两个月后,dsa的创造者美国国家标准与技术研究院(nist)也为其解决方案申请了专利。schnorr一度对此表示不满,并直接发邮件对其进行的批评。\ndsa签名方案 作为ecdsa签名的\u0026quot;前身\u0026quot;,首先对dsa签名算法做一个简单的介绍,读者可以将ecdsa签名和dsa签名算法对比学习。由于下面会详细介绍ecdsa算法,这里dsa算法的介绍不在遵循标准的签名介绍方法,二十简单对其进行介绍。\n密钥生成\n随机生成一个1024bit的大素数p,并找到p-1的一个160bit的素除数q 。找到一个q阶子群的生成元 $\\alpha$ 并选择一个随机的整数 $d\\in z_q^* $, 然后计算 $\\beta \\equiv \\alpha^d \\pmod p $ 。选择一个哈希函数 h 。 则公钥为 $k_{pub}=(p,q,\\alpha,\\beta,h)$ , 私钥为 $k_{pr}=d$.\ndsa签名\n选择一个随机的整数 $k_e \\in z_q^* $, 计算 $r\\equiv (\\alpha^{k_e} \\pmod p) \\pmod q$ 计算 $s\\equiv (h(x)+d\\cdot r)\\cdot k_e^{-1} \\pmod q$ 最终的dsa签名结果是一对 $(r,s)$ dsa验签\n计算辅助值 $w\\equiv s^{-1} \\pmod q$ 计算辅助值 $u_1 = w\\cdot h(x) \\pmod q$ 计算辅助值 $u_2 = w\\cdot r \\pmod q$ 计算 $v\\equiv (\\alpha^{u_1} \\cdot \\beta^{u_2} \\pmod p) \\pmod q$ 如果 $v=r \\pmod q$ 则认为他是一个有效的签名,否则认为他是一个无效的签名。 ecdsa签名方案: setup\n系统初始化阶段,用来生成必要的签名参数。具体过程是:选择有限域$f_p$上的一个椭圆曲线$e(a, b):y^2=x^3+ax+b \\ (mod \\ p)$且对于$e(a, b)$来说,它满足$4a^3+27b^2≠0\\ (mod \\ p)$ 选择$e(a, b)$上的一个点 $p$ 作为生成元,其中 $p$ 的阶为$n$ 。选择一个哈希函数 h , 则最终的参数为 $params = ( e(a, b), p , h )$ 。\nkeygen\n该过程用来生成用户的公私钥对,用于后续的签名(sign)和验签(verify)。选择随机数 $sk$ 作为私钥(其中k\u0026lt;n),计算公钥 $p_{pub}=skp$\nsign\n选择随机数$r$,计算$r\\cdot p=(r, y)$ .实际上 $r\\cdot p$ 是一个椭圆曲线上的点,他的横坐标和纵坐标分别为 r 和 y 计算$h=h(m)$ 计算$s = r^{-1}(h + sk\\cdot r) \\pmod n$ (这里,$k^{-1}$是k在模n下的乘法逆元) 则最终签名为$σ=(r, s)$。(注意,如果r, s中任意一个为零,重新选择随机数计算签名)\nverify\n假设收到的签名是 $(m, σ)=(m, r, s)$\n计算 $h = h(m)$ 计算 $(s^{-1}\\cdot h)p+(s^{-1}\\cdot r)p_{pub}=(x_1, y_1)$ 比较 $r_1≡x_1\\ (mod\\ p)$ 和 $r_1=r$ 是否成立,如果成立,验签成功。否则,验签失败 采用ecdsa的弊端主要有:\n1)ecdsa的验签过程中,需要进行求逆运算,计算开销较高。\n2)不支持批量认证,和多重签名,如果要验证多个签名,需要分别执行验证操作,验证开销随着签名的数量线性增加。\n参考文献\ndsa签名 ecdsa(2,2)多重签名实现 schnoor 签名: schnoor签名简介 schnorr签名由德国数学家和密码学家claus-peter schnorr于1990年在论文中提出,具有签名长度短、计算开销低、可扩展性强等优点。除此之外,schnoor签名支持密钥聚合和批量认证。schnoor签名的安全性基于离散对数困难问题,被广泛认为是一种安全可靠的签名方案。经过多年的发展,schnoor签名还衍生出了多重签名、门限签名等扩展,并在身份认证、比特币、区块链等方面获得大量应用。 schnoor签名本质上是一种零知识的技术,即prover声称知道一个密钥x的值,通过使用schnorr签名在不揭露x的情况下向verifier证明其对x的知情权。(也就是证明自己拥有私钥)\n作者简介 schnoor于1970年获得university of saarbrücken大学博士学位。schnorr对密码学的贡献主要是对schnorr groups的研究,这些研究被用于以他的名字命名的数字签名算法中。除此之外,schnorr还因其对算法信息论的贡献和创建了一种算法随机序列的定义方法而闻名,该方法可替代martin-löf随机性的概念。 schnoor于1993年获得莱布尼茨奖。2013年获得rsa数学卓越奖。\nschnoor在论文发表之前就申请了schnoor签名的专利,直到2008年,该签名的专利才到期。有趣的是,2008年正是中本聪(satoshi nakamoto)开始创造比特币的时间。在比特币的构造过程中,需要考虑的一个关键设计是使用哪种签名方案。事实上,schnoor签名与ecdsa相比更加适合于比特币系统的构建,但由于schnoor签名的专利一直被schnoor持有,而ecdsa没有专利问题,并且已经被开源加密工具openssl支持,中本聪最终选择了ecdsa。直到多多年以后,schnoor签名才被正式应用于比特币和区块链中。\n签名方案 schnoor签名分为交互式和非交互式两种,为了方便描述,我们称交互式的方案为 schnoor 协议,非交互式的为 schnoor签名。需要指出的是,在schnoor签名的原始论中,作者是基于离散对数困难问题构造的签名方案,而椭圆曲线可以用更短的签名长度达到同样的安全级别,本文的介绍使用的是椭圆曲线上的schnoor签名。\n交互式的schnorr协议 setup\n系统初始化阶段,用来生成数字签名需要用到的参数。具体过程是:选择一个p阶椭圆曲线e(a,b)以及它的生成元g,生成公共参数 pp = { e(a,b) , p , g } 并将其公布。\nkeygen\n该过程用来生成用户的公私钥对,用于后续的签名(sign)和验签(verify)。假设要给用户 $u_a$ 生成公私钥对 , 首先生成一个随机数 $a \\in_r z_p^* $ , 计算 $pk=a*g$ , 令 $u_a$ 的公私钥对为 $(sk,pk)=(a,pk)$.\nsign \u0026amp; verify\n在交互式的签名过程中,签名者和验签者需要同时参与到签名的生成和验证过程中。假设alice想要生成一个签名让bob验证,并且共公共参数 pp alice和bob都知道,alice的私钥为 a ,公钥为 pk 且 bob已经获得了 pk 。交互式的schnoor协议过程如下:\nalice 生成一个随机数 $r \\in_r z_p^* $ 并计算 $r=r\\cdot g$, 然后将 r 发送给 bob ; bob 选择一个随机数 c 作为挑战发送给 alice ; alice 计算 $z=r+c\\cdot sk$ 并将其发送给 bob ; bob 验证等式 $z\\cdot g=r+c\\cdot pk $ 是否成立,如果成立,则验证签名成功。 说明\n在协议开始之前,bob知道alice的公钥是 pk ,如果alice知道私钥 sk ,那么它可以很容易的计算出$z=r+c\\cdot sk$ ,将该等式两边同时乘g可得:$z\\cdot g=c\\cdot pk+r$。也就是说,如果bob验证了上述等式,即可验证alice确实拥有私钥sk。 (在上述协议中,验证者bob并不能得到私钥sk的信息,因此这个过程是零知识的,且是交互式的。此协议是sigma protocol的一个特例)\n上述协议的的过程如下图所示:\n该协议的一些分析\n由于椭圆曲线上的离散对数问题,知道r和g的情况下通过 $r=r\\cdot g$ 解出r是不可能的,因此 bob 无法通过 z,r,c 求出 sk ,故,上述协议是零知识性。\n但是上述协议存在一个很严重的问题:由于上述协议存在交互过程,因此只对参与交互的验证者有效,无法做到公开验证。\n如果 mallory 和 bob 如果串通随机数c,那么不参与交互的人无法判断出证明者是否真的是alice。因为 mallory 和 bob 可以按照以下过程执行协议: 假设 mallory 和 bob 进行串通,约定 bob 在协议中会回复一个特定的挑战 $c_0$。\nmallory 生成一个随机数 $r \\in_r z_p^* $ 并计算 $r=r\\cdot g - c_0 \\cdot pk$, 然后将 r 发送给 bob. 这里的 pk 表示 alice 的公钥; bob 将实现串通好的挑战 $c_0$ 发送给 mallory ; mallory 将 $z=r$ 发送给 bob ; bob 验证等式 $z\\cdot g=r+c_0 \\cdot pk $ 是否成立,如果成立,则验证签名成功。 上述过程非参与方看来和 alice 与 bob 的交互协议没有任何区别,验证也能正常通过,但是 mallory 并不知道 alice 的私钥 sk 。 我们将上述协议抽象为 prover 和 verifier 之间的协议 $\\pi$。那么上述例子表明,除了协议 $\\pi$ 中的 verifier 之外,任意人无法通过协议 $\\pi$ 的内容判断 prover 是不是alice。verifier 可以判断 prover 身份的原因是,如果 verifier 没有跟 prover 串通的情况下, prover 能生成正确的 z ,则 prover 是合法的。\n我们进一步分析标量 c 的随机性对协议的影响。通过上述协议不难发现,如果 prover 能提前猜到 verifier 选择的 c ,那么他可以伪造成任何人! 因为 prover 知道 verifier 会用 pk 和 r 相加然后再与z * g进行比较。所以 prover 可以在不知道私钥 sk 的情况下按照上述串通情况下的协议 $\\pi$ 执行,bob发现不了任何异常。\n补充一点,事实上上述协议是完备的(soundness)。如果验证者 verifier 分别提供两个不同的挑战 $c_1$ 和 $c_2$,并要求证明者 prover 给出同一承诺 r 下的两个响应 $z_1 = r + c_1\\cdot sk$ 和 $z_2 = r + c_2\\cdot sk$,那么这个验证者可计算出 sk =(z1-z2)/(c1-c2)。(在协议 $\\pi$ 中,r 称为承诺,c 称为挑战,z 称为响应。这是simga协议中的名词,了解更多请参考sigma协议)\n非交互式的schnorr签名 将交互式的schnorr协议改造为非交互式 schnoor签名非常简单。通过上述分析我们知道,保证协议中 verifier 选择的挑战 c 的随机性是保证该协议安全的关键。fiat-shamir启发式方法基于随机预言机模型,它们假设存在抗碰撞的哈希函数 h,它的结果在敌手看来是随机的,由于哈希函数的不可逆性,我们可以利用 h 改造上述交互式协议如下,得到一个非交互式的 schnoor 协议,也称 schnoor 签名。\nalice:随机选择 r,计算$r=r*g$ (椭圆曲线上的一点); alice:计算 $c = h(r, pk)$; alice:通过计算 $z=r+c\\cdot sk$, 将$(r, z)$给bob; bob: bob计算 $c=h(r,pk)$,bob验证 $z\\cdot g = c\\cdot pk+r$。 需要说明的是,在正式的schnoor签名中,存在一个被签名的消息 m ,我们需要做的是修改 c 的计算方式为 $c=h(r,pk,m)$,其余部分保持不变即可得到消息 m 的 schnoor签名 (r,z) 。\n非正式的分析:\n我们粗略的分析一下上述schnoor签名的安全性,并不是正式的安全性证明,正式的安全性证明请参考原文.\n上述 schnoor 协议之中,prover 和 verifier 之间进行串通能够骗过非协议交互方的原因是 prover 提前预知(通过串通)了一个与协议其他参数无关的挑战 c ,而在 schnoor 签名中 c 由随机预言机 h 生成,无法被 prover 预测,故 schnoor 签名是安全的。 在上述串通的 schnoor 协议中,mallory 预先串通得到 c ,通过计算 $r=r\\cdot g - c \\cdot pk$ 并令 z = r 使得 非交互式 schnoor 协议无法公开验证。 而schnoor签名(r,c,z)的计算过程中$c=h(r,pk)$。如果使用同样的方式进行伪造会发现,计算 r 需要用到 c ,计算 c 需要用到 r ;故导致敌手伪造 schnoor 签名。 sigma协议和fiat-shamir启发式:\nhttps://www.yuque.com/xiaozeng-lhkfj/guc7a3/egsn1gzebrmkfcz1#fumtk【笔记】 schnoor签名的批量认证\nschnoor签名可以进行批量认证,这是因为它的签名具有线性性质。但是批量认证并未减少计算开销,这里不再赘述。\nschnoor 签名的扩展 schnoor多重签名 实现schnoor签名的一个简单思路如下:\n密钥聚合与签名生成:\n这里我们引入一个聚合器 agg ,她负责执行一些特点的操作,agg 可以由生成多重签名的某个实体担当,也可以由其他任意实体充当。schnoor多重签名的生成过程如下:\n聚合器 agg 将签名者的公钥集合 $(pk_1,\u0026hellip;,pk_n)$ 聚合称一个聚合公钥 $p = \\sum_{i=1}^n pk_i$。 当 n 个人要生成一个多重签名时,每个用户分别选择随机数 $r_i$ 并计算 $r_i = r_ig$,随后每个人将自己的 $r_i$ 广播给其他人。 其他人收到 {$r_i$} (i ∈ [1,n]) 后计算$r=\\sum_{i=1}^nr_i$ 和 $c = h(m,p,r) ; s_i = r_i + c \\cdot sk_i $ 并广播自己的 schnoor签名 $(r_i,s_i)$。 聚合器 agg 收到 n 个人的签名 $\\sigma_1,\u0026hellip;,\\sigma_n$后,计算 $z=\\sum_{i=1}^nz_i$ , 最终的签名为 $(r,z)$ 多重签名验证:\n验证者 verifier 计算$c = h(m,p,r)$, 然后验证等式 $z\\cdot g=r+c\\cdot p$ 是否成立,如果成立,则完成了对n个用户的批量认证。\n上述多重签名的过程似乎并未减少总的计算开销,那么他有什么用呢?事实上,上述多重签名的生成与验证过程计算量是不对称的,生成签名的计算量很大,但是验证签名的计算量永远都是验证一个签名的计算量。这个性质在某些应用场景下是非常有用的,比如在区块链中,某一个交易可能需要多个人的签名才能生效,而交易发出去之后,所有人都需要验证这个交易的合法性,此时验证成本低廉很有用。\n这里提一句,schnoor签名可以通过merkle树实现(m,n)多重签名,有兴趣的读者可以自行搜索。\n存在的问题\n上述schnoor多重签名的生成过程需要签名方之间进行交互,当多重签名的用户比较多的时候,整个交互流程比较复杂。(暂时无法解决,最新的frost签名可以做到半交互式) 存在流氓密钥攻击。(可通过修改方案预防,具体来说是将签名和密钥的聚合过程改成非线性的,参考《simple schnorr multi-signatures with applications to bitcoin》或bls的方案) schnoor签名实际上是一个零知识证明,它具有soundness,也就是存在一个提取器。因此所有的签名不能使用同一个随机数 r ,否则可以提取私钥(参考零知识证明)。 参考文献:\nhttp://blockchain.whu.edu.cn/uploads/soft/201105/1_1954088811.pdf schnoor签名还可以实现(t,n)门限签名,聚合签名等,读者可自信搜索。 bls短签名 bls签名简介 bls签名,由斯坦福大学计算机系的dan boneh,ben lynn以及hovav shacham在2001年提出,其核心思想是将待签名的消息哈希到椭圆曲线上的一个点,并利用双线性映射函数e的交换性质,在不泄露私钥的情况下进行签名验证。bls签名被称为\u0026quot;bls短签名\u0026quot;,因为签名本身只是椭圆曲线上的一个点,是已知的签名长度最短的签名。\nbls签名具有多个有趣的特性,例如支持签名的聚合和批量验证,还可以构建门限签名等。这些特性使得它在区块链等领域得到广泛应用。作为第一个使用双线性映射的数字签名方案,bls签名对后续的数字签名乃至密码学的发展产生了重要影响,为后续更复杂、更广泛应用的签名方案奠定了基础。\n作者简介 dan boneh于1969年生于以色列,并于1996年从普林斯顿大学获得计算机科学博士学位、并于1997年加入斯坦福大学任教至今,在coursera上开设了大规模的开放在线课程,出版了很多密码学相关书籍、教材,以免费在线提供他的整个密码学入门课程而闻名。\nboneh与加州大学的matt franklin是基于配对密码学发展的主要贡献者之一,2016年被选为美国国家工程院院士。他在基于身份的加密、广播加密、同态加密、区块链等多个密码学领域均有重要成果发表;参与设计tcpcrypt用于传输层安全的tcp协议扩展,提出了一种针对http协议的侧信道攻击和对openssl的时序攻击,在比特币方面也有成果产出。他曾获哥德尔奖、rsa数学杰出奖、selfridge奖、斯隆奖、acm计算奖等。他担任斯坦福大学区块链研究中心的联合主任,并在2002年与三名学生创办名为voltage security的公司,随后于2015年被惠普收购,是即会理论又能实践的大佬。\nbls具体方案 map to point hash函数简介\n一般的hash(散列)函数的结果都是数值。在bls签名算法中需要用的散列函数的结果是椭圆曲线上的一个点。也就是说,散列结果对应椭圆曲线上的一个点。以 ecc 和 sha-256 为例。比特币用的椭圆曲线是secp256k1,也就是该曲线由2²⁵⁶个点组成。sha-256普通hash函数的结果也是256位。如果用hash函数的数值结果,对应于椭圆曲线上点横坐标x,有两个点y和-y都在椭圆曲线y²=x³+ax+b上。说明sha-256的结果,有50%的概率,要么找到两个点,要么找不到点。\n为了解决这个问题,需要用到直接从消息计算到一个点的哈希,给定消息 m,计算h(m),使得 h(m)∈ g,其中g是椭圆曲线点构成的群。dan boneh在文章中给出了一个具体的做法,例如采用的仍然是普通哈希,把结果(先看成一个数)当作是一个点的 x 坐标,然后带入椭圆曲线公式计算相应的 y 坐标。如果不存在,则把第一步的 x 坐标作为输入继续算哈希得到另一个 x 坐标,计算相应 y坐 标,直到求出 y 为止。根据二次剩余理论,平均算2次即可得出一个对应的 y 。这样确保首先是一个确定性的算法,可以在有限步骤内(数学期望为2次)完成计算;其次,如果采用的哈希函数本身是安全的,则整体的计算过程也是安全的。这样整个计算称为map-to-point哈希。\nbls签名方案\n假设alice的私钥为$pk$,公钥为$p = pk×g$,想对消息 m 签名,则:\n签名:计算$s = pk×h(m)$作为消息 m 的签名 验签:判断$e(p, h(m)) = e(g, s)$是否成立,成立则验签成功。 上述过程如下图所示:\n验证过程涉及e函数,计算如下的等式是否成立:$e(p, h(m)) = e(g, s)$\n证明过程如下: $e(p, h(m)) = e(pk×g, h(m)) = e(g, pk×h(m)) = e(g, s)$\n证明过程示意如下:\nbls签名的扩展 bls多重签名 多重签名定义\n在数字签名应用中,有时需要多个用户对同一个文件进行签名和认证。比如,一个公司发布的声明中涉及财务部、开发部、销售部、售后服务部等部门,需要得到这些部门签名认可,那么,就需要这些部门对这个声明文件进行签名。能够实现多个用户对同一文件进行签名的数字签名方案称作多重数字签名方案。\n在现实生活中,一份文件经常需要几个单位或部门分别签字(或盖章)才有效,多重签名技术就是在网络环境里解决这类问题的一种方法,用于同一文档必须经过多人的签名才有效的情形。多重签名通俗地讲就是指多个签名者共同参与对一份电子文档进行签名。简单地说,一个多重签名体制回答这样几个问题:哪些人参加签名,按照什么顺序签名,使用什么方法签名,怎么验证签名和安全性如何得到保证。bls签名支持多重签名方案。\nbls多重签名\n沿用上面的符号,我们假设有n个用户,它们的公私钥对分别为($p_i,pk_i$),它们欲对同一个消息 $m$ 进行签名得到一个bls多重签名。假设第 i 个用户对消息 $m$ 的 bls 签名为 $s_i$ ,我们可以使用以下方式生成这些用户对消息 $m$ 的多重签名。\n将 n 个用户的签名 $s_i$ 进行累加即可得他们对消息 m 的多重签名 $s = s_1+s_2+…+s_n$ , 其中 $s_i = pk_i×h(m)$;i={1, 2, \u0026hellip;, n}。 为了验证多重签名的合法性,首先计算 $p=\\sum_{i=1}^n p_i$ 作为验证该多重签名的聚合公钥,然后验证等式 $e(g, s) = e(p, h(m))$ 是否成立即可。\n我们可以看出,使用bls多重签名可以使得 n 个人的签名大小和一个人的签名大小完全一样,并且签名的验证过程比挨个验证 n 个签名计算量少了几乎一半。正确性显然成立,这里不再证明。\n问题与改进\n上述最直观的bls多重签名方案容易受到\u0026quot;流氓密钥攻击\u0026quot;。比如说,alice公私钥对为 $(pk_1,p_1)$ , 同时 bob 的公私钥为 $(pk_2,p_2)$ , bob可以对外声称自己的公钥为 $p\u0026rsquo;=p_2 - p_1$, 那么他可以使用自己的私钥生成bls签名 s 作为他与alice的多重签名,其他人认为 $p=p_1+p\u0026rsquo;=p_2$ 是 bob 与 alice 的聚合公钥,并使用它验证签名 s 。很显然验证可以通过,但实际上alice并未参与这个多重签名。如果一笔钱需要 alice 和 bob 共同授权才能使用,那么 bob 可以通过这种方式独自控制这笔财产。为了防止这种攻击,可以使用非线性方式构造聚合公钥。具体方式如下:\n将 n 个用户的签名 $s_i$ 进行非线性累加得 $s = a_1s_1 + … + a_ns_n$ , 其中 $s_i = pk_i×h(m)$ 且 $a_i=h(p_i,\\{p_j\\}) ; i,j=\\{1, 2, \u0026hellip;, n\\}$。我们将 $s$ 作为这 n 个用户对消息 m 的多重签名。 为了验证多重签名的合法性,验证者首先计算非线性聚合公钥 $p=\\sum_{i=1}^n a_ip_i$ 作为验证该多重签名的聚合公钥,然后验证等式 $e(g, s) = e(p, h(m))$ 是否成立即可。\n上述多重签名能够防止流氓攻击的关键是签名和聚合公钥的构造中,非线性系数 $a_i$ 与多重签名的所有参与者的公钥都有关,改变任何一个参与者的公钥都会导致所有系数的变化,这将导致攻击者无法同时控制自己的公钥和系数,从而大大增加了流氓密钥攻击的难度。\n(m,n)多重签名\n上述多重签名可以被看成是(n,n)多重签名,也就是必须要n个参与者同时签名才能生成一个有效的多重签名,这种多重签名方案的一个缺点是所有多重签名的缺点是存在单点故障问题,我们可以使用bls(m,n)多重签名来解决这个问题,为此,我们将使用到:\n正常的hash函数,输出为a number,hash(x)。\nhash to the curve,输出为curve上的point, h(x)。\n需要有“setup” phase,setup后不再需要communicate,可以用来sign any amount of transactions。\n为了方便打字,以下以2-of-3 多重签名为例进行讲解,三个不同的密钥存储在三个不同的设备中:( 可扩展至任意的 (m , n)多签 )\n1)setup phase:\n该系统要求每个签名方维护相同的序号 1 , 2 , 3 ,即依次为签名方1,签名方2,签名方3 。这个阶段的主要目的是通过(n,n)多重签名为每个参与者构建成员密钥 $mk_i$,拥有 $mk_i$ 的参与者被认为是一个有效的多重签名参与者,他将在生成阈值签名时起作用。具体的构建过程如下:\n假设 $p = a_1 × p_1 + a_2 × p_2 + a_3 × p_3$ 其中 $a_i = hash ( p_i , p_1 , p_2 , p_3 )$。 所有签名方对数字 i(其值必须在序号范围内)进行签名然后进行密钥聚合,每个签名方 i 存储与其序号一致的密钥聚合信息 $mk_i = ( a_1 ) × ( pk_1 × h ( p , i ) ) + ( a_2 ) × ( pk_2 × h ( p , i ) ) + ( a_3 ) × ( pk_3 × h ( p , i ) )$, 也即是 $mk_i = ( a_1 ⋅ pk_1 ) × h ( p , i ) + ( a_2 ⋅ pk_2 ) × h ( p , i ) + ( a_3 ⋅ pk_3 ) × h ( p , i )$。事实上,$mk_i$ 可以被理解为对 $h(p,i)$ 的多重签名,满足: $e(g,mk_i)=e(p,h(p,i))$\n2)签名阶段:\n如使用 $pk_1$ 和 $pk_3$ 签名,则 $s_1=pk_1\\times h(p,m) + mk_1,s_3=pk_3\\times h(p,m)+mk_3$ 直接累加有: $(s’,p’)=(s_1+s_3,p_1+p_3)$ 其中 p\u0026rsquo;为参与签名的签名者的聚合公钥。\n3)验签阶段:\n验签阶段,验证者0除了需要知道签名(p\u0026rsquo;,s\u0026rsquo;) 之外,还需要知道参与签名者的索引,如本例中的签名者索引为数字1和3。验证2-of-3 阈值签名,仅需验证下面的等式是否成立即可: $$e(g,s’)=e(p’,h(p,m))⋅e(p,h(p,1)+h(p,3))$$ 如果签名是合法生成的,那么上述等式恒成立。因为成员密钥 $$e(g,s’)=e(g,s_1+s_3) =e(g,pk_1×h(p,m)+mk_1+pk_3×h(p,m)+mk_3)$$ 即 $e(g,s’)=e(g,s_1+s_3)=e(p’,h(p,m))⋅e(p,h(p,1)+h(p,3))$\nbls签名的聚合 签名聚合是指\u0026quot;将多个签名聚合在一起,形成一个签名的过程\u0026quot;,由多个签名聚合成的最终签名被称为是聚合签名。通过聚合签名可以大大缩小存储多个签名所需要的空间,从而降低存储开销,这在区块链中是一个非常有用的性质。例如:在一个区块中有n个交易,每个交易都有独立的公钥$p_i$,私钥$pk_i$,签名 $s_i$以及交易内容$m_i$。如果需要知道区块中的交易的签名是否都正确,传统的方式,区块中的交易需要一个个的验证签名。然而,区块中需要存储所有交易的签名需要占用大量的内存。而使用bls算法进行签名的合并只需要存储一个33个字节的bls签名。\n这里提一句,聚合签名和多重签名的区别是,多重签名是多个人对同一个消息进行签名,并最终得到一个多重签名。而聚合签名是每个人对各自的消息进行签名,最终聚合为一个聚合签名。它们的本质区别是\u0026quot;到底是对同一个消息签名,还是对不同的消息签名\u0026quot;\n签名聚合\n沿用上面的符号,我们假设有n个用户生成bls签名,它们的公私钥对分别为($p_i,pk_i$),且对消息 $m_i$ 的签名为 $s_i$ 。我们可以使用以下方式对签名进行聚合。\n将n个用户的签名进行聚合得到聚合签名$s = s_1+s_2+…+s_n$, 其中 $s_i = pk_i×h(m_i)$;$i={1, 2, \u0026hellip;, n}$。为了验证聚合后的签名s,只需要验证下面的等式是否成立即可 $$e(g, s) = e(p_1, h(m_1))⋅e(p_2, h(m_2))⋅…⋅e(p_{n}, h(m_{n}))$$\n我们可以看出,使用bls聚合签名可以使得n个人的签名大小和一个人的签名大小完全一样,并且签名的验证过程比挨个验证n个签名计算量少了几乎一半。我们这里证明上述聚合签名的验证过程是正确的:\n$e(g, s) = e(g, s_1+s_2+…+s_{n}) = e(g, s_1)⋅e(g, s_2)⋅…⋅e(g, s_{n})$ $= e(g, pk_1×h(m_1))⋅…⋅e(g, pk_{n}×h(m_{n}))$ $= e(pk_1×g, h(m_1))⋅…⋅e(pk_{n}×g, h(m_{n}))$ $= e(p_1, h(m1))⋅e(p_2, h(m_2))⋅…⋅e(p_{n}, h(m_{n}))$\nbls阈值签名 阈值签名方案(tss)是一种从多个签名者生成单一数字签名的方法。生成的签名看起来与没有使用阈值方案生成的签名一样,但它并不是由单一私钥创建的。相反,它是由多个私钥份额创建的,这些份额是分布式的,因此没有任何单一个人完全控制私钥。在阈值签名中,假设有 n 个参与者拥有秘密份额,要生成一个消息的签名, 至少需要 t 个人参与,则我们记这种阈值签名为(t,n)阈值签名。\n秘密份额分发方法\n要想进行阈值签名,首先要为参与者分发秘密份额,这个过程有很多方法。可以使用可验证的秘密共享进行分布式密钥协商,从而得到每个人的秘密份额。也可以使用一个秘密分发者进行秘密分发的方式进行份额的分发。它们的区别是,使用分布式秘密生成份额可以在开放的环境中进行,并且没有人知道真正的秘密值,所有人都只知道自己的秘密份额,但是这种方案计算开销较大。而是用秘密分发者的方式进行份额分发,必须在安全的环境中进行,并且秘密分发者知道所有人的份额和秘密,但计算开销很小。由于分布式秘密共享需要用到多项式承诺,并且验证过程比较负责,不是本节的主要内容,因此这里使用秘密分发者在安全通道分发秘密份额。\n秘密份额分发\n秘密分发者首先随机生成 $ t - 2 $ 个小于 $ p $ 的随机数 $ a_{1}, a_{2},\u0026hellip;, a_{t - 1} $ 并构造一个 t-1 阶的多项式 $ f (x) = a_0 + a_{1} x + \u0026hellip; + a_{t-1} x^{t-1}$ , 其中 $ f (0) = a_{0} = s $ 是群组私钥,$pk = sp$ 是群组公钥。\n秘密分发者随机选取 $ n $ 个互不相同的整数 $ x_{1}, x_{2}, \\cdot, x_{n} $ 并将 $ n $ 个整数代入多项式函数 $f(x)$得到 $ n $ 个值 $ sk_1 = f (x_1)$, $ sk_2 = f(x_2)$,\u0026hellip;, $sk_{n} = f (x_{n}) $ , 然后将计算得到的 $ n $ 个坐标 $(x_i,y_i)$ 分别发给 $ n $ 各参与方并销毁 $ f (x) $,即第 $ i $ 个参与方获得 $ (x_{i}, y_{i}) $ 。为了简单起见,可以假设 $x_i = i$ ,这并不影响方案的安全性。\n阈值签名的生成\ncase1: 为了生成一个阈值签名,至少需要 t 个参与者合作,参与者 i 计算拉格朗日插值函数值 $l_i = \\prod_{j\\in[n]}^{j\\neq i} \\frac{i}{j-i}$ ,然后使用自己的秘密份额生成 消息 m 的部分签名 $s_i=sk_i\\cdot l_i\\cdot h(m)$ 。随后将自己的部分签名广播出去。\ncase2: 另外,也可以把拉格朗日插值函数值的计算放到验证者哪里执行,此时签名者 i 只需要计算消息 m 正常的bls签名 $s_i=sk_ih(m)$并广播出去。\n阈值签名的验证\ncase1-验证: 任何人在收到 t 个合法的部分签名之后可以使用群组公钥 $pk$ 验证阈值签名的合法性。验证者首先计算阈值签名 $s=s_1+\u0026hellip;+s_t$ 。 然后验证等式 $e(s,g)=e(h(m),pk)$ 是否成立,如果成立,则签名验证通过。\ncase2-验证: 任何人在收到 t 个合法的部分签名之后可以使用群组公钥 $pk$ 验证阈值签名的合法性。验证者首先计算拉格朗日插值函数值$l_i = \\prod_{j\\in[n]}^{j\\neq i} \\frac{i}{j-i}$,然后计算阈值签名 $s=l_1s_1+\u0026hellip;+l_ts_t$ 。 然后验证等式 $e(s,g)=e(h(m),pk)$ 是否成立,如果成立,则签名验证通过。\n参考文献:\necdsa schnoor bls 对比 国密sm9算法 sm9简介 sm是“商密”的拼音,是我国\u0026quot;自主知识产权\u0026quot;的算法。sm9则是其中的一种基于身份的密码系统,包括加密和签名两个算法。sm的家族里还有sm1/2/3/4等对称、非对称、摘要算法,它们虽然也是ecc的一种实现,但是存在很多创新之处。当然由于rsa提出的很早并且迅速成立公司占领市场,使得它的应用非常广泛,短时间内很难被替代。因此在应用生态方面sm系列还有待努力。sm9是一种用户体验很好的算法,应该是很好的助推工具。\nsm9签名算法 预备知识 pki与ibc简介:\n上面提到sm9是一种基于身份的加密算法(ibc),这里首先对ibc进行一个解释。为此,我们首先介绍基于公钥基础设施(pki)的密码体制。 简单来说,传统的非对称密码体制需要pki的辅助,主要是需要一个证书颁发机构ca(certification authority)给用户颁发证书并负责这些证书的管理个维护工作,用户之间通过证书完成身份认证、从而保证用户公钥等信息的合法性。基于pki的公钥系统存在一个缺点,那就是用户的公钥是一段晦涩难懂的二进制字符串,而用户使用公钥加密的时候记忆公钥称为一个非常头疼的问题,正因为如此,用户无法将一个公钥与一个用户对应起来,这就需要ca为每个公钥颁发证书,保证这些公钥确实对应于某一个合法用户,这也是需要ca对公钥颁发证书的一个重要原因。\nibc密码系统是一种解决了上述问题的非对称密码系统。在ibc中,任何一个用户标识符都可以作为用户的公钥,人们可以使用自己的邮件地址,手机号码,身份证号码等作为自己的公钥,而这些信息可以很容易的与特定的用户联系在一起,因此不需要使用证书系统。一个需要解决的问题是,有了公钥之后,如何生成对应的私钥。为了实现这个功能,ibc系统中引入了一个新的实体,密钥生成中心(kgc)。用户只需要将自己的公钥给kgc,kgc就能生成一个对应的私钥返回给用户,这样用户就可以使用公私钥对完成相应的加密和签名操作了。\n密码学工具简介\nh(a,q):表示一种哈希函数,对a进行哈希,得到一个小于 q 的哈希值。\nkgc:密钥生成中心,用户将自己的身份作为公钥传输给kgc,kgc为其生成对应的私钥。\n双线性映射: 本文使用$g_1×g_2→g_t$的双线性映射,其中$g_2$上的点的大小是$g_1$上的2倍。$g_1$和$g_2$是加法循环群,$g_t$是乘法循环群。\n详细方案 密钥生成:\n在这个阶段,用户将自己的标识符 $id$作为公钥发给kgc,kgc为其生成对应的私钥$d_{id}$。除此之外,用户还会生成整个系统的公私钥对$(ks, p_{pub_s})$,并将公钥$p_{pub_s}$公开。 具体过程如下:\nsm9签名\n在这个阶段,alice使用自己的私钥$d_{id}$和系统公钥$p_{pub_s}$对消息进行签名, 得到签名$(h, s)$ 具体过程如下: sm9验签 在这个阶段, 任何人都可以使用alice的公钥$id$对他的签名$(h, s)$进行验签. 具体过程如下: 流程图如下\nsm9加密算法 预备知识 kdf($\\cdot$):密钥导出函数,根据输入输出一个密钥 $k=(k_1, k_2)$。\nmac(k, m): 消息认证码,通过k认证消息m的完整性。\n注意:kdf在sm9中有专门的介绍,这里省略了这一部分。 sm9加解密\n正确性的证明:\n参考资料:\n国家标准全文阅读|标准检索 【区块链与密码学】第6-7讲:sm9数字签名算法 ","date":"2024-07-10","permalink":"http://54rookie.com/posts/%E5%B8%B8%E8%A7%81%E7%AD%BE%E5%90%8D%E7%AE%97%E6%B3%95/","summary":"常见签名算法 ECDSA签名 ECDSA简介: 椭圆曲线数字签名算法(ECDSA)是使用椭圆曲线密码(ECC)对数字签名算法(DSA)的模拟。ECDSA于1999年成","title":"常见签名算法"},]
[{"content":"测试一下codapi能不能用 codapi 是专门为嵌入文章、产品文档、在线课程、移动应用程序等而设计的,所以它非常轻量级。可以通过很简单的方式将其集成到自己的网页中。下面测试一下自定义的代码块功能是否可用。(test标签中的文章是用来测试网页的功能是否可用,没有阅读价值)\n我是正常的codeblock 为了不然所有的代码块都高亮显示,对代码块的渲染规则做了自定义,下面测试能否显示普通的代码块。\nprint(\u0026#34;hello python\u0026#34;)\rprint(\u0026#34;hello python\u0026#34;)\rprint(\u0026#34;hello python\u0026#34;)\r我是bash代码 测试高亮代码块格式能否使用。首先测试一种没在服务器上部署的代码类型,正常情况下,应该显示出代码高亮的样式,但是代码后面不显示运行按钮。\n# 显示当前的git配置 $ git config --list # 定义当前用户所有提交使用的作者邮箱。 $ git config --global alias.\u0026lt;alias-name \u0026lt;git-command\u0026gt; # 为git命令创建一个快捷方式(别名)。 $ git config --system core.editor \u0026lt;editor\u0026gt;\r我是java代码 测试一下代码块高亮功能和运行功能能否使用,首先测试java代码块。正常情况下,下面的java代码将被高亮,并且代码后面会出现一个 run 按钮,点击可以运行代码块中的代码。\nimport java.util.*; public class main { public static void main(string[] args){ testarraylist(); } //除了集合通用的操作外,还有以下方法 static void testarraylist(){ system.out.println(\u0026#34;-------- arraylist 的 api 测试 --------\u0026#34;); java.util.list list = new arraylist(); list.add(\u0026#34;孙悟空\u0026#34;); list.add(\u0026#34;沙和尚\u0026#34;); system.out.println(\u0026#34;0. list中,添加了三个元素后: \u0026#34; + list); list.add(1,\u0026#34;猪八戒\u0026#34;); system.out.println(\u0026#34;1. list: \u0026#34; + list); system.out.println(\u0026#34;2. list中,索引为1的元素为: \u0026#34; + list.get(1)); system.out.println(\u0026#34;3. list中,猪八戒第一次出现的索引为: \u0026#34; + list.indexof(\u0026#34;猪八戒\u0026#34;)); list.add(\u0026#34;猪八戒\u0026#34;); system.out.println(\u0026#34;4. list中,猪八戒最后一次出现的索引是: \u0026#34; + list.lastindexof(\u0026#34;猪八戒\u0026#34;)); system.out.println(\u0026#34; list.remove(1) : \u0026#34; + list.remove(1)); system.out.println(\u0026#34;5. list中,删除索引为1的元素后: \u0026#34; + list); system.out.println(\u0026#34; list.set(1,\\\u0026#34;白龙马\\\u0026#34;) : \u0026#34; + list.set(1,\u0026#34;白龙马\u0026#34;)); system.out.println(\u0026#34;6. list中,将索引为1的元素替换为暴龙马后: \u0026#34; + list); java.util.list sublist = list.sublist(0,2); system.out.println(\u0026#34;7. list.sublist(0,2) = \u0026#34; + sublist); } }\r我是python代码 测试一下代码块高亮功能和运行功能能否使用,然后测试python代码块。正常情况下,下面的python代码将被高亮,并且代码后面会出现一个 run 按钮,点击可以运行代码块中的代码。\nprint(\u0026#34;hello python\u0026#34;) print(\u0026#34;hello codapi\u0026#34;)\r我是cpp代码 测试一下代码块高亮功能和运行功能能否使用,首先测试 c++ 代码块。正常情况下,下面的 c++ 代码将被高亮,并且代码后面会出现一个 run 按钮,点击可以运行代码块中的代码。\n#include \u0026lt;stdio.h\u0026gt; #include \u0026lt;stdlib.h\u0026gt; #include \u0026lt;string.h\u0026gt; #include \u0026lt;time.h\u0026gt; #include \u0026#34;randapi.h\u0026#34; #include \u0026#34;ecdh_nist256.h\u0026#34; using namespace core; using namespace nist256; int main() { int i, res; // 初始化强随机函数 csprng rng; // crypto strong rng unsigned long ran; char raw[100]; octet raw = {0, sizeof(raw), raw}; time((time_t *)\u0026amp;ran); raw.len = 100; // fake random seed source raw.val[0] = ran; raw.val[1] = ran \u0026gt;\u0026gt; 8; raw.val[2] = ran \u0026gt;\u0026gt; 16; raw.val[3] = ran \u0026gt;\u0026gt; 24; for (i = 4; i \u0026lt; 100; i++) raw.val[i] = i; create_csprng(\u0026amp;rng, \u0026amp;raw); // initialise strong rng // 初始化强随机函数结束 printf(\u0026#34;testing ecdsa for curve nist256\\n\u0026#34;); // 待签名的消息 char *pp = (char *)\u0026#34;hello world!\u0026#34;; char m[32]; memcpy(m,pp,strlen(pp)+1); printf(\u0026#34;messaage: %s\\n\u0026#34;,m); octet m = {sizeof(m), sizeof(m), m}; // 公私钥 char s1[egs_nist256], w1[2 * efs_nist256 + 1]; octet s1 = {0, sizeof(s1), s1}; octet w1 = {0, sizeof(w1), w1}; // 签名(由2部分组成) char ds[egs_nist256], cs[egs_nist256]; octet ds = {0, sizeof(ds), ds}; octet cs = {0, sizeof(cs), cs}; // 产生随机公私钥 ecp_key_pair_generate(\u0026amp;rng, \u0026amp;s1, \u0026amp;w1); // 验证下密钥生成对不对 res = ecp_public_key_validate(\u0026amp;w1); if (res != 0) { printf(\u0026#34;ecp public key is invalid!\\n\u0026#34;); return 0; } // 公私钥的输出 printf(\u0026#34;servers private key= 0x\u0026#34;); oct_output(\u0026amp;s1); printf(\u0026#34;servers public key= 0x\u0026#34;); oct_output(\u0026amp;w1); // 签名 if (ecp_sp_dsa(hash_type_nist256, \u0026amp;rng, null, \u0026amp;s1, \u0026amp;m, \u0026amp;cs, \u0026amp;ds) != 0) { printf(\u0026#34;***ecdsa signature failed\\n\u0026#34;); return 0; } printf(\u0026#34;signature c = 0x\u0026#34;); oct_output(\u0026amp;cs); printf(\u0026#34;signature d = 0x\u0026#34;); oct_output(\u0026amp;ds); // 验签 if (ecp_vp_dsa(hash_type_nist256, \u0026amp;w1, \u0026amp;m, \u0026amp;cs, \u0026amp;ds) != 0) { printf(\u0026#34;***ecdsa verification failed\\n\u0026#34;); return 0; } else { printf(\u0026#34;ecdsa signature/verification succeeded\\n\u0026#34;); } printf(\u0026#34;if we modify the message...\\n\u0026#34;); m.val[0]=\u0026#39;a\u0026#39;; printf(\u0026#34;modified message: %s\\n\u0026#34;,m); if (ecp_vp_dsa(hash_type_nist256, \u0026amp;w1, \u0026amp;m, \u0026amp;cs, \u0026amp;ds) != 0) { printf(\u0026#34;***ecdsa verification failed\\n\u0026#34;); return 0; } else { printf(\u0026#34;ecdsa signature/verification succeeded\\n\u0026#34;); } kill_csprng(\u0026amp;rng); }\r创建 codeapi 的 template 上面的代码运行需要将所有的代码都写在代码块中,某些情况下,我们可能不需要展示所有的代码,只展示代码中的核心部分,但是整个代码必须还能够正常运行,这里通过两种方式实现了这个功能,下面分别测试这个功能是否可用。\n1. 提前定义模板 可以预定义一些模板,在代码块运行的时候之间使用模板,例如,这里预定义了 java 类 public class main{ ##code## },代码块中的部分会替换掉模板中的 ##code## 部分。\npublic static void main(string[] args){ system.out.println(\u0026#34;我是使用template main.java的java代码\u0026#34;); system.out.println(\u0026#34;运行我所需的其他代码隐藏了\u0026#34;); }\r2. 写博客时自定义模板 预定义模板的方式不够灵活,下面这种方式可以实现跟灵活的模板使用。我们可以通过下面的方式在写markdown文件的时候自定义模板的内容,例如,通过下面的语法预定义一个模板,并在下面的代码块中调用它,代码块中的部分会替换掉模板中的 ##code## 部分。\n可以使用下面框框内的语法创建codapi 的 template,\r其中 name 表示下面codapi代码中的 $template\r\u0026lt;codapi-snippet sandbox={{.type}} editor=\u0026#34;base\u0026#34; template=\u0026#34;{{$template}}\u0026#34;\u0026gt;\r\u0026lt;/codapi-snippet\u0026gt;\r---------------------------------------------------------------------\r| ```template {name=\u0026#34;temp.java\u0026#34;} |\r| public class main{ |\r| public static void main(string[] args){ |\r| ##code## |\r| } |\r| } |\r| ``` |\r---------------------------------------------------------------------\r在上述的模板定义中,模板的name被定义为 temp.java , 因此,这里创建的代码块的时候传入一个 {template=\u0026ldquo;temp.java\u0026rdquo;} 来使用这个模板即可。\nsystem.out.println(\u0026#34;我是使用template temp.java的java代码\u0026#34;); system.out.println(\u0026#34;运行我所需的其他代码隐藏了\u0026#34;);\r","date":"2024-07-03","permalink":"http://54rookie.com/posts/codapi/","summary":"测试一下codapi能不能用 Codapi 是专门为嵌入文章、产品文档、在线课程、移动应用程序等而设计的,所以它非常轻量级。可以通过很简单的方式将其集成到自己的网页中。下面测","title":"codapi test"},]
[{"content":"这是一篇发表在亚密会上的文章,随手写一下,以后再改。\n什么是匿名凭证? 匿名凭证也可以称为匿名证书,用于在保护用户隐私的情况下实现身份认证和授权。\n匿名凭证中的匿名性并不是针对凭证的颁发者,而是针对凭证的持有者,凭证持有者可以向别人证明自己拥有凭证(证书),同时不暴露凭证(证书)的内容。 匿名凭证常见的实现方法是通过承诺-签名-证明的构造范式, 通常要求采用的签名算法具备重随机化特性, 如cl系列签名、ps系列签名及结构保持签名(sps)。 匿名凭证有什么特征? 匿名凭证通常用来保护凭证持有者的隐私,或进行匿名身份认证等。以下给出匿名凭证的几个特性。\n隐私保护:用户可以证明其身份或某些属性,而不需要暴露其完整身份信息。例如,用户可以证明自己是某个俱乐部的成员,而不需要透露自己的名字。 不可追踪性:当用户多次使用凭证时,凭证的使用记录是不可追踪的。即便是凭证签发机构也无法将多次使用行为关联到同一个用户。 集合承诺 集合承诺用来对一个集合中的元素进行承诺,非正式的对其过程进行如下的描述:\n假如alice想要向bob承诺一个集合中的元素,首先通过一个初始化算法生成\u0026quot;集合承诺\u0026quot;所需要的参数。 alice发送集合a的\u0026quot;承诺c\u0026quot;给bob。(作用是保证自己不会对集合a中的元素进行思考) alice发送集合a的\u0026quot;打开承诺\u0026quot;给bob,证明自己没有更改集合a中的元素。 bob通过\u0026quot;打开承诺验证\u0026quot;检验\u0026quot;打开承诺\u0026quot;的正确性,从而检验集合a没有被更改。 alice发送集合a的子集t的\u0026quot;打开承诺\u0026quot;给bob。 bob通过\u0026quot;打开承诺验证\u0026quot;检验\u0026quot;打开承诺\u0026quot;的正确性,从而检验集合a的子集t没有被更改。 基于属性的凭证系统 基于属性的凭证系统(abc)作用如下:一个用户可以选择一个属性集,请求可信机构为该用户的这些属性颁发凭证。收到凭证的用户可以匿名的向任意验证者自己拥有凭证并且不暴露凭证的内容。匿名凭证系统中,如果不能将多个匿名凭证链接到同一个用户,我们称该系统是multi-show的。\ncl签名和ps签名 cl签名: cl03签名(基于离散对数的)\n对单个消息进行签名\n系统初始化:选择两个大素数p,q满足p=2p\u0026rsquo;+1和q=2q\u0026rsquo;+1,然后计算 n=pq\n密钥生成:随机生成三个随机数 $(a,b,c)\\in_r qr_n$ ;则私钥sk=p,公钥pk=(n,a,b,c).\n签名:生成随机数 e,s 计算v 使其满足等式$v^e=a^mb^sc \\ \\ mod\\ n$,返回签名 $\\sigma=(e,s,v)$\n注:表面上看,这里的签名好像没有使用到私钥,貌似谁都可以签名。事实上,令$m=a^mb^sc$,则$v=m^{1/e} \\ mod\\ n$,令$ed \\equiv1 \\ mod\\ \\varphi(n)$,则有$v=m^d \\ mod\\ n$。可以看出、求满足条件的v的过程实际上就是使用rsa算法对消息m进行签名的过程,且签名使用的私钥$d=1/e\\ mod\\ \\varphi(n)$。显然想要通过随机选择的e求出\u0026quot;私钥d\u0026quot;需要首先计算$\\varphi(n)=(p-1)(q-1)$,这需要使用p和q,因此只有知道私钥sk=p的人才能求出v,从而对消息进行签名。这里之所以私钥sk中只包含p是因为通过p和n可以计算出q。(我在原论文中没有找到为什么签名需要用到私钥,上述内容纯属本人猜测) 验签: 检查等式 $v^e=a^mb^sc$ 是否成立:\n对多个消息进行签名\n系统初始化:同上\n密钥生成:随机生成三个随机数$(a_1,\u0026hellip;,a_l,b,c)$ 则私钥sk=p,公钥pk=$(a_1,\u0026hellip;,a_l,b,c,n)$.\n签名:生成随机数 e,s 计算v 使其满足等式$v^e=a_1^{m_1}a_2^{m_2}·\u0026hellip;·a_l^{m_l}b^sc$,返回签名 $\\sigma=(e,s,v)$\n验签:检查等式 $v^e=a_1^{m_1}a_2^{m_2}·\u0026hellip;·a_l^{m_l}b^sc$ 是否成立\n通过承诺值对消息进行签名(我看起来像是盲签名)\n密钥生成:选择两个大素数p,q满足p=2p\u0026rsquo;+1和q=2q\u0026rsquo;+1,然后计算 n=pq。随机生成三个随机数 $(a,b,c)\\in_r qr_n$ ;则私钥sk=p,公钥pk=(n,a,b,c).\n假设用户alice想要获得bob对消息x的签名,并且不想让bob知道消息x是什么,则:\nalice首先生成随机数r,然后计算承诺$c=a^xb^r \\ mod \\ n$ 并将c发送给bob bob生成随机数r\u0026rsquo;并选择一个素数e,然后计算$v=(c \\cdot b^{r\u0026rsquo;} \\cdot c)^{1/e}$,发送(r\u0026rsquo;,e,v)给alice alice计算s=r+r\u0026rsquo;,则签名$\\sigma=(s,e,v)$就是bob对消息x的签名。 对签名的知识证明:\n略,参考原论文: a signature scheme with efficient protocols\ncl04签名(基于配对的)\n对单个消息进行签名\n系统初始化:bggen($1^\\lambda$) $\\rightarrow (p,g_1,g_2,g_t,e,p_1,p_2)$\n密钥生成:随机生成三个随机数(x,y,z)作为私钥sk,然后计算pk=($x=xp_1,y=yp_1,z=zp_1$).\n签名:生成随机数 $r\\in_rz_p$ 计算 $r=rp_2$,并计算\n$\\sigma_1=zr$ ; $\\sigma_1=yr$ ; $\\sigma_3=yz_2$; $c=(x+xym+xyrz)r$ 输出签名$\\sigma=(r,\\sigma_1,\\sigma_2,\\sigma_3,c)$ 验签:检查以下等式是否成立:\n$e(x,r)=e(p_1,\\sigma_1)$ ; $e(y,r)=e(p_1,\\sigma_2)$; $e(y,r)=e(p_1,\\sigma_3)$; $e(x,r) + e(\\sigma_2,x)^m+e(x,\\sigma_3)^r=e(p_1,c)$; 对多个消息进行签名\n系统初始化:bggen($1^\\lambda$) $\\rightarrow (p,g_1,g_2,g_t,e,p_1,p_2)$\n密钥生成:随机生成三个随机数$(x,y,{z_1,\u0026hellip;,z_l})$作为私钥sk,然后计算pk=$(x=xp_1,y=yp_1,z_i=z_ip_1)$.\n-\u0026gt; 签名:生成随机数 $r\\in_rz_p$ 计算 $r=rp_2$; 计算\n$\\sigma_{1,i}=z_ir$ ; $\\sigma_1=yr$ ; $\\sigma_{3,i}=yz_{2,i}$; $c=xr+xym_1r+xym_iz_ir$; 输出签名$\\sigma=(r,\\sigma_1,\\sigma_2,\\sigma_3,c)$; 验签:检查以下等式是否成立:\n$e(z_i,r)=e(p_1,\\sigma_{1,i})$ ; $e(y,r)=e(p_1,\\sigma_2)$; $e(y,z_{2,i})=e(p_1,\\sigma_{3,i})$; $e(x,r) + e(x,\\sigma_2)^{m_1}+e(x,\\sigma_{3,i})^{\\sum_{i=2}^mm_i}=e(p_1,c)$; 对承诺消息进行签名\n略,参考原论文:signature schemes and anonymous credentials from bilinear maps\n对签名的知识证明\n略,参考原论文:signature schemes and anonymous credentials from bilinear maps\nps签名:\n对单个消息进行签名\n系统初始化:bggen($1^\\lambda$) $\\rightarrow (p,g_1,g_2,g_t,e,g,\\widetilde g)$,定义$g_1^*=g_1{1_{g_1}}$\n密钥生成:随机生成立两个随机数(x,y)作为私钥sk,然后计算pk=($\\widetilde x=\\widetilde g^x,\\widetilde y=\\widetilde g^y$).\n签名:随机生成 $h\\in_rg_1^*$ 计算 $\\sigma_1=h,\\sigma_2=h^{x+ym}$,输出签名$\\sigma=(\\sigma_1,\\sigma_2)$\n验签:检查是否$\\sigma_1 \\neq 1_{g_1}$;检查等式$e(\\sigma_1,\\widetilde x\\cdot \\widetilde y^m)=e(\\sigma_2,\\widetilde g)$是否成立。\n签名的随机化:随机生成$t \\in_r z_p^*$,计算$\\sigma\u0026rsquo;=(\\sigma_1\u0026rsquo;,\\sigma_2\u0026rsquo;)=(\\sigma_1^t,\\sigma_2^t)$为随机化之后的签名。\n对多个消息进行签名\n系统初始化:bggen($1^\\lambda$) $\\rightarrow (p,g_1,g_2,g_t,e,g,\\widetilde g)$,定义$g_1^*=g_1{1_{g_1}}$\n密钥生成:随机生成立两个随机数$(x,y_1,\u0026hellip;,y_r)$作为私钥sk,然后计算pk=($\\widetilde x=\\widetilde g^x,\\widetilde y_1=\\widetilde g^{y_1},\u0026hellip;,\\widetilde y_r=\\widetilde g^{y_r}$).\n签名:随机生成 $h\\in_rg_1^*$ 计算 $\\sigma_1=h,\\sigma_2=h^{x+\\sum_{1}^r y_im_i}$;输出签名$\\sigma=(\\sigma_1,\\sigma_2)$\n验签:检查是否$\\sigma_1 \\neq 1_{g_1}$;检查等式$e(\\sigma_1,\\widetilde x\\cdot \\prod_{1}^r\\widetilde y^m)=e(\\sigma_2,\\widetilde g)$是否成立。\n签名的随机化:随机生成$t \\in_r z_p^*$,计算$\\sigma\u0026rsquo;=(\\sigma_1\u0026rsquo;,\\sigma_2\u0026rsquo;)=(\\sigma_1^t,\\sigma_2^t)$为随机化之后的签名。\n对承诺进行签名\nalice想要获得bob对消息m的签名,但是不想让bob知道m的值。他发送$g^m$给bob bob选择$u\\in_r z_p$,然后计算 $\\sigma_1=g^u,\\sigma_2=(g^x (g^m)^y)^u$,将签名$\\sigma=(\\sigma_1,\\sigma_2)$发送给alice alice验证等式$e(\\sigma_1,\\widetilde x\\cdot \\widetilde y^m)=e(\\sigma_2,\\widetilde g)$是否成立,成立则获得了bob对m的签名。 这里的承诺存在一个小问题,alice如果已经通过$g^m$让bob对消息m进行了签名,如果下次她又想获得m的不同签名,bob通过$g^m$会发现自己已经对该消息进行了签名,从而在一定程度上泄露消息m的隐私。为了解决这问题,下面的给出一个改进的ps盲签名。\nps对承诺签名的改进\n系统初始化:bggen($1^\\lambda$) $\\rightarrow (p,g_1,g_2,g_t,e,g,\\widetilde g)$,定义 $ g_1^* =g_1 \\backslash {1_{g_1}};g_2^*=g_2 \\backslash {1_{g_2}}$\n密钥生成:随机生成两个随机数 $(x,y)\\in_r z_p^2 , g\\in_rg_1^* ,\\widetilde g\\in_rg_2^* $ ,然后计算 $x=g^x,y=g^y \\widetilde x = \\widetilde g^x,\\widetilde y=\\widetilde g^y$。令密钥对 $sk=x,pk=(g,y,\\widetilde g,\\widetilde x,\\widetilde y)$.\n协议内容\nalice想要获得bob对消息m的签名,但是不想让bob知道m的值。他选择$t\\in_r z_p$,然后计算 $c=g^ty^m$ bob选择$u\\in_r z_p$,然后计算 $\\sigma_1\u0026rsquo;=g^u,\\sigma_2\u0026rsquo;=(xc)^u$,将签名$\\sigma\u0026rsquo;=(\\sigma_1\u0026rsquo;,\\sigma_2\u0026rsquo;)$发送给alice alice计算$\\sigma=(\\sigma_1,sigma_2)=(\\sigma_1\u0026rsquo;,\\sigma_2\u0026rsquo;/\\sigma_1\u0026rsquo;^t)$。随后,alice验证等式$e(\\sigma_1,\\widetilde x\\cdot \\widetilde y^m)=e(\\sigma_2,\\widetilde g)$是否成立,成立则获得了bob对m的签名。 上述协议也可对多个消息进行签名,具体过程参考论文。。。\n群签名\nps签名还可用于构造群签名,这是因为ps签名是可随机化的签名,并且可以提供签名的知识证明,具体过程参考论文,敲起来太累了、\n等价类上的结构保持签名(sps-eq) 1. $setup(1^k) \\rightarrow pp$:\n运行双线性参数生成函数:bggen($1^\\lambda$) $\\rightarrow (p,g_1,g_2,g_t,e,p,\\widetilde p)$\n2. $keygen(pp,1^k) \\rightarrow (sk,pk)$:\n随机生成$(x_1,\u0026hellip;,x_l)\\in_rz_p^l$,计算$(\\widetilde x_1=x_1\\widetilde p,\u0026hellip;,\\widetilde x_l= x_l\\widetilde p$。输出密钥对$sk=(x_1,\u0026hellip;,x_l)$,$pk=(\\widetilde x_1,\u0026hellip;,\\widetilde x_1)$\n3. $sign(m,sk) \\rightarrow \\sigma$:\n对于待签名消息集$m=(m_1,\u0026hellip;,m_l)$,签名者首先生成一个随机数$r\\in_r z_p^*$,然后计算$z=r\\sum_{i\\in[l]}x_im_i$ ; $r=\\frac{1}{y}p$ ; $\\widetilde r=\\frac{1}{y} \\widetilde p$。输出签名$\\sigma = (z,r,\\widetilde r)$\n4. $verify(m,\\sigma,pk) \\rightarrow 1/0$:\n验证: $\\prod_{i\\in[l]}e(m_i,x_i)=e(z,\\widetilde y)$ 和 $e(r,\\widetilde p)=e(p,\\widetilde r)$\n5. $chgrep(m,\\sigma,\\mu,pk) \\rightarrow \\sigma\u0026rsquo;$:\n给定一个随机化因子$\\mu$,选择随机数$\\psi\\in_rz_p^*$,返回:$\\sigma\u0026rsquo;=(\\psi\\mu z,\\frac{1}{\\psi}r,\\frac{1}{\\psi} \\widetilde r)$\n6. $vkey(sk,pk) \\rightarrow 1/0$:\n给定密钥对$sk=(x_1,\u0026hellip;,x_l),pk=(\\widetilde x_1,\u0026hellip;,\\widetilde x_1)$,验证公钥是否是对应私钥对应的公钥。\n集合承诺,abc系统的具体过程。 ","date":"2024-05-31","permalink":"http://54rookie.com/posts/spseq%E5%8C%BF%E5%90%8D%E5%87%AD%E8%AF%81/","summary":"这是一篇发表在亚密会上的文章,随手写一下,以后再改。 什么是匿名凭证? 匿名凭证也可以称为匿名证书,用于在保护用户隐私的情况下实现身份认证和授权。 匿名凭证中的匿名性","title":"spseq匿名凭证"},]
[{"content":"多项式承诺 1. 什么是承诺 密码学承诺方案是一个涉及两个参与方的的二段式交互协议,参与双方分别被称为承诺方和验证方。该协议共分为三个步骤:\n第一阶段被称为承诺阶段,此阶段承诺方通过$c←com(x)$计算声明x的承诺c并发送给验证方。 第二阶段被称为打开承诺阶段,此阶段承诺方发送 \u0026ldquo;承诺c所对应的声明x的证明$\\pi$\u0026ldquo;给验证方,该证明也可以称为打开承诺。 第三阶段为打开承诺验证阶段,此阶段接受方验证证明$\\pi$的合法性。如果$\\pi$合法,则认为承诺c确实是声明x对应的承诺。 承诺方案需要满足的三个基本性质:\n正确性:按照上述三个过程生成的承诺和打开承诺一定能通过成员验证。 绑定性(binding):对于任意ppt敌手,找到两个声明$x_1≠x_2$,使得$c_1=c_2$的概率可以忽略。即难以找到两个不同的声明,使得其对应的承诺相同。 隐藏性(hiding):对于任意ppt敌手,声明$x_1$和声明$x_2$的承诺$c_1,c_2$不可区分。即承诺c不泄露声明的额外信息。 2. 承诺有什么用 在生活中经常需要用到承诺,比如:\nalice和bob想要玩一个猜数字游戏,游戏规则如下: - alice从集合a={0,1}中随机选择$a\\in_r{x_0,x_1}$,然后bob输出对a的猜测b,如果b=a,则bob获胜,否则alice获胜。 - 在这个游戏当中,如果没有任何限制,那么即使bob猜出了正确的a,alice也可以声称bob猜错了,从而保证自己总是赢得游戏。如:alice选择$a=x_1$,bob在游戏中猜测$b=x_1$,此时本应bob获得游戏胜利,但是alice可以告诉bob自己选择的$a=x_0$,从而赢得游戏。导致上述结果的原因是alice可以随意更改a的值,我们可以使用承诺解决上述问题。 - 我们更改游戏规则为:alice首先选择$a \\in_r {x_0,x_1}$并计算承诺$c=com(a)$并将其发送给bob,bob输出对a的猜测b。 - 在上述游戏中,假如alice选择了$a=x_1$并计算了$c=com(a)$给bob,此时如果alice在上述游戏中想通过修改$a=x_0$来获得游戏胜利的方式是不可行的。因为bob可以计算$c\u0026rsquo;=com(a)$,然后通过验证$c\u0026rsquo;=c$是否相等验证alice是否修改了声明a。 - 由于承诺的隐藏性,bob不能从c中获得任何声明a的信息,因此bob得到c并不会增加游戏获胜的概率。 - 由于承诺的绑定性,alice不能选择两个不同的$x_0,x_1$ 使得$c=com(x_0)=com(x_1)$,因此alice不能作弊。\n信息科学中也有类似的承诺技术存在。例如,某些网站在提供下载文件时,也会提供对应文件的单向哈希值。这里,单向哈希值便是一种对文件数据的承诺(承诺阶段)。用户将文件数据下载好之后(打开承诺),检测接收到的文件数据是否有丢失或变化,如果校验通过,相当于网站兑现了关于文件数据完整性的承诺(打开承诺验证)。\nsigma协议(零知识证明中的使用) 承诺在非交互式零知识证明中发挥着关键性作用,可以保证没有秘密的敌手无法伪造合法的证明。比如在schnoor协议中,alice想要向bob证明自己拥有一个公钥$pk=sk·g$对应的私钥$sk$而不暴露$sk$的其他信息就用到了承诺。(具体参见附录1)\n秘密共享中的承诺 在一个(t,n)秘密共享中,一个dealer(秘密分发者)给n个participator(参与者)分发秘密s的share(秘密份额)$s_i$。传统的秘密共享中、dealer知晓完整的密钥s,有作恶的可能。例如对部分$p_i$发放错误的share。参与者$p_i$也可能提供非真实的share去扰乱正常的reconstruct(秘密重构)过程。使用多项式承诺则可以避免上述问题。(参见feldman承诺)\nzk-snark中的承诺 听说在zk-snark中,多项式承诺扮演着重要角色,不过我没看过这个。。\n3. 常见的承诺 关于承诺,有非常多种类,这里简单列举几个常见的承诺方式。\n哈希承诺 承诺阶段 对于一个声明$x$,承诺者计算$c=h(x)$作为承诺发给验证者 承诺打开 承诺者将声明x发送给验证者 打开承诺验证 验证者计算$c\u0026rsquo;=h(x)$,然后判断等式$c\u0026rsquo;=c$是否成立,如果成立,则验证者认为承诺$c$对应的声明确实是$x$ 基于加密算法的承诺 承诺阶段 对于一个声明$x$,承诺者计算$c=e_k(x)$作为承诺发给验证者 承诺打开 承诺者将声明x发送给验证者 打开承诺验证 验证者计算$c\u0026rsquo;=e_k(x)$,然后判断等式$c\u0026rsquo;=c$是否成立,如果成立,则验证者认为承诺$c$对应的声明确实是$x$ pederson承诺 公共参数:选择乘法群$g=z_q^*$,生成元为$g,h$,公开三元组$(g,h,q)$\n承诺阶段 对于一个声明$x$,承诺者选择一个随机数$r\\in_rz_q$计算$c=g^xh^r\\ mod\\ q$作为承诺发给验证者 承诺打开 承诺者将声明$x$和随机数$r$发送给验证者 打开承诺验证 验证者计算$c\u0026rsquo;=g^xh^r\\ mod\\ q$,然后判断等式$c\u0026rsquo;=c$是否成立,如果成立,则验证者认为承诺$c$对应的声明确实是$x$ 4. kzg多项式承诺 kzg承诺简介 首先明确的是:多项式承诺$c$的承诺对象是一个多项式$f(x)$。验证者如果对打开承诺的验证通过了,则说明承诺者确实知道声明$f(x)$。 kzg 多项式承诺(kzg polynomial commitment)也被称为卡特多项式承诺,由kate,zaverucha和goldberg在2010年提出$^{[1]}$。kzg多项式承诺的承诺值是椭圆曲线上的一个点,且打开承诺只需要发送一个固定大小的椭圆曲线上的点,而不必将整个多项式发送给验证者。\nkzg预备知识 为了学习kgz承诺需要一些必备的预备知识。列举如下:\n椭圆曲线相关知识 双线性映射相关知识 因式定理$^{[2]}$,代数基本定理 拉格朗日插值公式 参考视频: kzg的数学原理 kzg承诺的过程(单点承诺) kzg多项式承诺分为以下几个阶段,这里首先对承诺的全过程进行综述,然后再解释为什么要这么做。\n可信设置阶段: 生成一个随机数$\\alpha$,然后计算$pk=(p,\\alpha p,\\alpha^2p,\u0026hellip;,\\alpha^np)$作为公钥发布。随后将$\\alpha$永久销毁。假设承诺者想要对多项式$f(x)=a_0+a_1x+a_2x^2+\u0026hellip;+a_nx^n$进行承诺:\n承诺阶段 假设$pk=(p,\\alpha p,\\alpha^2p,\u0026hellip;,\\alpha^np)=(pk_0,pk_1,pk_2,\u0026hellip;,pk_n)$,承诺者首先计算$^{(1)}$承诺$$c=f(\\alpha)p=a_0pk_0+a_1pk_1+a_2pk_2+\u0026hellip;+a_npk_n$$ 并将其发送给验证者。(容易看出,承诺$c$是椭圆曲线上的一个点) 打开承诺 承诺者计算$q(x)=\\frac{f(x)-f(i)}{x-i}=b_0+b_1x+b_2x^2+\u0026hellip;+b_nx^n$,然后计算 \u0026ldquo;$f(x)$在$i$点取值为$f(i)$\u0026ldquo;的证明$$\\pi=q(\\alpha)p=b_0pk_0+b_1pk_1+b_2pk_2+\u0026hellip;+b_npk_n$$最后将打开承诺$(i,y=f(i),\\pi)$发送给验证者。 打开承诺验证 验证者判断等式$$e(c,p)=e(\\pi,pk_1-ip)e(p,p)^{f(i)}$$是否成立,如果成立,则接受承诺者的打开承诺$c$。也就是相信承诺者确实知道承诺$c$对应的多项式$f(x)$,且$f(x)$在$i$处的取值确实为$y=f(i)$ 。 说明 传统的打开承诺是直接将多项式$f(x)$的系数${a_0,\u0026hellip;a_n}$发送给验证者,然后验证者重新计算$c’=f(\\alpha)p=a_0pk_0+a_1pk_1+a_2pk_2+\u0026hellip;+a_npk_n$并比较等式$c\u0026rsquo;=c$是否成立,如果成立则打开承诺验证成功。这种方式的缺点是打开承诺需要暴露多项式$f(x)$,并且打开承诺需要将多项式的所有系数发送给验证者,导致通信开销与多项式的次数成正比。 kzg多点承诺 上面的介绍说明了如何证明一个多项式在某点 $i$ 的取值为$f(i)$,它意味着你可以仅靠发送单个的群元素(可以是48字节大小,例如bls12381)来证明任何次数的多项式在任意点的取值。下面我们讨论如何仅使用一个元素证明一个多项式在任意多个点处的取值。(可信设置阶段和上述单点承诺一样,不再赘述)\n承诺阶段 假设$pk=(p,\\alpha p,\\alpha^2p,\u0026hellip;,\\alpha^np)=(pk_0,pk_1,pk_2,\u0026hellip;,pk_n)$,承诺者首先计算承诺$c=f(\\alpha)p=a_0pk_0+a_1pk_1+a_2pk_2+\u0026hellip;+a_npk_n$ 并将其发送给验证者。(容易看出,承诺$c$是椭圆曲线上的一个点) 打开承诺 假设承诺者想要证明自己知道多项式$f(x)$在横坐标集合${z_0,z_1,\u0026hellip;,z_{k-1}}$处的函数值${f(z_0),f (z_1),\u0026hellip;,f(z_{k-1})}$,他首先使用拉格朗日插值法计算过k个点 ${(z_i,f(z_i)}_{i=0}^{k-1})$ 的多项式: $$i(x)=\\sum_{i=0}^{k-1}f(z_i)\\prod_{j=0,j\\neq i}^{k-1} \\frac{x-z_j}{z_i-z_j}$$\n承诺者计算零多项式 $z(x)=(x-z_0)···(x-z_{k-1})$ 并据此计算\n$$q(x)=\\frac{f(x)-i(x)}{z(x)}=c_0+c_1x+c_2x^2+\u0026hellip;+c_nx^n$$ 然后承诺者计算 \u0026ldquo;$f(x)$在k个点 $(z_i){i=0}^{k-1}$ 处的取值分别为 ${(f(z_0)){i=0}^{k-1}}$\u0026rdquo; 的证明 $$\\pi=q(\\alpha)p=c_0pk_0+c_1pk_1+c_2pk_2+\u0026hellip;+c_npk_n$$ 并将打开承诺 $((z_i,f(z_i))_{i=0}^{k-1},\\pi)$ 发送给验证者。\n打开承诺验证 验证者首先计算插值多项式$i(x)$和零多项式$z(x)$,然后使用公钥$pk$计算出$z(\\alpha),i(\\alpha)$,并判断等式 $$e(c,p)=e(\\pi,z(\\alpha)p)e(p,p)^{i(\\alpha)}$$ 是否成立,如果成立,则接受承诺者的打开承诺$c$。也就是相信承诺者确实知道承诺 $c$ 对应的多项式 $f(x)$,且 $f(x)$ 在k个点 ${(z_i){i=0}^{k-1}}$ 处的取值确实分\u0026gt; 别为 ${(f(z_0)){i=0}^{k-1}}$ 。 kzg承诺的解释 kzg承诺的打开承诺实际上是\u0026quot;承诺者想要向验证者证明自己知道承诺$c$对应的多项式$f(x)$\u0026quot;,为了不暴露$f(x)$本身,承诺者证明 \u0026ldquo;自己知道$f(x)$在横坐标$i$处的取值$y=f(i)$\u0026quot;。\n令$g(x)=f(x)-f(i)$,则上述证明等价于证明$g(x)$存在零点$i$ ; 由因式定理知,$g(x)$可分解为$g(x)=(x-i)q(x)$,即$q(x)=\\frac{f(x)-f(i)}{x-i}$,显然,只有承诺者知道$f(x)$,故只有他能计算出$q(x)$. 承诺者计算横坐标$i$处的打开承诺$\\pi=q(\\alpha)$p,并将其发送给验证者。 根据等式$g(x)=(x-i)q(x)$可知,$f(\\alpha)=(\\alpha-i)q(\\alpha)+f(i)$,故验证者可以检查等式$e(f(\\alpha)p,p)=e(((\\alpha-i)q(\\alpha)+f(i))p,p)$,也就是检查等式$e(c,p)=e(\\pi,(pk_1-ip)e(p,p))^{f(i)}$是否成立。如果成立,则打开承诺验证通过。 在kzg多点承诺中,承诺方想要向验证者证明自己知道 \u0026ldquo;$f(x)$ 在k个点 $(z_i){i=0}^{k-1}$ 处的取值确实分别为${(f(z_0)){i=0}^{k-1}}$\u0026quot;,这里的证明思路实际上上面的单点证明相同。\n首先使用拉格朗日插值公式计算出过k个点 ${(z_i,f(z_i)){i=0}^{k-1}}$ 的插值多项式 $i(x)$(这意味着 $i(z_i)=f(z_i)$)。令 $g(x)=f(x)-i(x)$,则上述证明等价于证明 $g(x)$ 存在零点 ${(z_i){i=0}^{k-1}}$ 由因式定理知,$g(x)$可分解为$$g(x)=q(x)\\prod_{i=0}^{k-1}(x-i)$$假设$z(x)=\\prod_{i=0}^{k-1}(x-i)$,则有 $q(x)=\\frac{f(x)-i(x)}{z(x)}$,显然,只有承诺者知道 $f(x)$,故只有他能计算出 $q(x)$. 承诺者计算横坐标 $i$ 处的打开承诺 $\\pi=q(\\alpha)p$,并将其发送给验证者。 后面的验证同理。 注释解释 (1)这里使用承诺$c=f(\\alpha)p$作为多项式$f(x)$的承诺是安全的。 1. 假设敌手可以寻找一个多项式$f\u0026rsquo;(x)\\neq f(x)$使得$f\u0026rsquo;(\\alpha)p=c$, 2. 那么$(f\u0026rsquo;(\\alpha)-f(\\alpha))p=0$,即函数$r(x)=f\u0026rsquo;(x)-f(x)$存在零点$\\alpha$ 。 3. 由于敌手不知道$\\alpha$的值,因此它只能令多项式$r(x)$有尽可能多的零点,从而使得$r(x)$有更大的几率存在零点$\\alpha$ 。 4. 由代数基本定理可知,在实数域上的非零n次多项式至多只有n个零点。而$f\u0026rsquo;(x)\\neq f(x)$,故$r(x)\\neq 0$。所以$r(x)$最多有n个零点。假设椭圆曲线群$g$的阶为$p=2^{128}$,那么攻击者求出满足条件的$f\u0026rsquo;(x)$的概率为$n/p=n/2^{128}$。这是一个可以忽略的概率,在现实中,可以认为攻击者不可能找到这个$f\u0026rsquo;(x)$。 (2)\n矢量承诺 尽管卡特承诺被设计成多项式承诺,但它作为矢量承诺来使用也大有用处。回忆一下,一个矢量承诺是针对矢量$a_0,\u0026hellip;,a_{n-1}$的承诺,并且允许你证明任意位置i对应$a_i$ 。我们可以使用卡特承诺的方案来重现这一场景:使p(x)为对所有的计算$p(i)=a_i$的一个多项式,我们知道这样一个多项式存在,并且可以通过拉格朗日插值来计算它:$$p(x)=\\sum_{i=0}^{k-1}a_i\\prod_{j=0,j\\neq i}^{k-1} \\frac{x-j}{i-j}$$使用这个多项式,我们可以就可以利用一个单一群元素来证明这个矢量中任意数量的元素!注意到比起默克尔树(在证明大小方面)这个方案更加高效:仅证明一个元素,默克尔证明就需要花费 log n 大小的哈希!\nfeldman承诺 承诺阶段: 对于一个多项式$f(x)=a_0+a_1x+a_2x^2+\u0026hellip;+a_{k−1}x^{k−1}$,承诺者计算承诺$c=(c_0,c_1,\u0026hellip;,c_{k-1})=(g^{a_0},g^{a_1},\u0026hellip;,g^{a_{k−1}})$ 并将其发送给验证者。\n打开承诺: 假设承诺者想要证明自己知道多项式$f(x)$在横坐标集合$\\mathbb{z}={z_0,z_1,\u0026hellip;,z_{k-1}}$处的函数值$\\mathbb{f(z)}={f(z_0),f(z_1),\u0026hellip;,f(z_{k-1})}$,他可以直接将$\\mathbb{f(z)}$发送给验证者。\n打开承诺验证: 对于集合$\\mathbb{z}$任意一点$z_i$,验证者验证$$g^{f(z_i)}=∏{j=0}^{k−1}(c_j)^{{z_i}^j} \\ mod \\ p$$ 是否成立,如果成立,则接受承诺者的打开承诺$c$。也就是相信承诺者确实知道承诺 $c$ 对应的多项式 $f(x)$,且 $f(x)$ 在k个点 ${(z_i){i=0}^{k-1}}$ 处的取值确实分别为 ${(f(z_i))_{i=0}^{k-1}}$ 。\n基于feldman承诺的vss 在普通的shamir(t,n)秘密共享之中,存在以下问题:\n密钥分发者知晓完整的密钥,有作恶的可能,例如对部分秘密持有者发放错误的分片数据。 密钥分片的持有者可能提供非真实的分片数据 当考虑存在不诚实参与方时,对于一个秘密需要有相应的算法来验证其确实是秘密的有效份额,否则不存在秘密可言。本文将介绍基于shamir密钥分享的改进机制:可验证的秘密共享(verifiable secret sharing, vss)。关于vss的方案有很多,这里直接介绍比较实用的feldman vss方案。【注:vss概念由benny chor, shafi goldwasser, silvio micali等人在1985年首次提出 】\nfeldman方案: 实际工程中使用的密钥分享都是有限域上的循环群的运算,采用公共g作为生成元。为了使得分发的秘密碎片的数据可验证,秘密分发者除了给出秘密的分片数据外,还要提供对应的系数承诺(c0,c1,\u0026hellip;)。\n符号约定: 设 p 是一个大素数,q 为 p -1 的一个大素数因子,g 属于 $𝑍_𝑝^∗$且为 q 阶元素,(p, q, g) 是公幵可知的,n 是参与者的数目,s 为要共享的密钥,k 是门限值。\n秘密分发阶段:\n选定多项式:$a(x)=a_0+a_1x+a_2x^2+\u0026hellip;+a_{k−1}x^{k−1}$,其中$a_0$代表秘密,\n计算承诺(commitment):$c_0=g^{a_0},c_1=g^{a_1},\u0026hellip;,c_{k−1}=g^{a_{k−1}}$ 以上运算是在mod p基础上的。\n将承诺 $c_i$ 和 $s_i$ 一同发送给参与者 $p_i$.【具体工程实现中,由于承诺对所以参与者都是一样的,也可先广播承诺给所有参与者,然后单独秘密发送分片,以减少消息传输量】。\n当第i个参与者收到数据碎片 $s_i$ 时,作如下验证: $$g^{s_i}=∏_{j=0}^{k−1}(c_j)^{i^j} \\ mod \\ p \\ \\ (其中i=1,2,\u0026hellip;,n)$$ 容易验证等式成立。由于承诺绑定了系数,如果分发者给出承诺不是用多项式方程真实系数,会导致验证失败。\n5. 参考文献: [1]. constant-size commitments to polynomials and their applications\n[2]. 因式定理:设f(x)为一多项式,则x-a为f(x)的因式\u0026quot;等价于f(a)=0。 [3]. 代数基本定理:代数基本定理说明,任何复系数一元n次多项式方程在复数域上至少有一根。由此推出,n次复系数多项式方程在复数域内有且只有n个根(重根按重数计算)。\n附录1 schnoor协议的具体内容如下:\n对上图进行解释如下:\n- alice:随机地选择一个标量r,然后计算出r=rg(椭圆曲线上的一点),将r发送给bob;(r实际上是一个对r的承诺) - bob:回应一个随机标量 c ;(挑战) - alice:通过计算$z=r+c·sk$,将标量 z 回应给bob;(针对挑战c的响应) - bob:然后验证$z·g=c·pk+r$ 。 首先需要知道的是:确定性算法很容易受到各种攻击,如重放攻击,穷举攻击等。因此在上述schnoor协议中,加入了随机数r,保证该协议是一个一个概率性算法。\n承诺的作用: 假设alice不发送r的承诺r给bob,协议的其他部分不变,那么将导致不知道私钥$sk$的人也能完成协议的证明。\n比如bob直接将挑战c发送给alice,alice可以计算$r = r * g - cpk \\wedge z = r$,随后将$(z,r)$发送给bob,这样bob的验证等式为:$z * g = r * g = r + cpk$这是永远成立的,这导致不知道私钥的人也能证明自己拥有私钥。 在引入承诺之后,由于alice不能修改r的值,因此收到bob的c之后,必须使用私钥sk才能计算出合法的z,从而保证了协议的不可为造性。 ","date":"2024-05-29","permalink":"http://54rookie.com/posts/%E5%A4%9A%E9%A1%B9%E5%BC%8F%E6%89%BF%E8%AF%BA/","summary":"多项式承诺 1. 什么是承诺 密码学承诺方案是一个涉及两个参与方的的二段式交互协议,参与双方分别被称为承诺方和验证方。该协议共分为三个步骤: 第一阶段被称为承诺阶段,此阶","title":"多项式承诺"},]
[{"content":"linux知识 一、基础知识 1.1 linux系统的文件结构 /bin 二进制文件,系统常规命令 /boot 系统启动分区,系统启动时读取的文件 /dev 设备文件 /etc 大多数配置文件 /home 普通用户的家目录 /lib 32位函数库 /lib64 64位库 /media 手动临时挂载点 /mnt 手动临时挂载点 /opt 第三方软件安装位置 /proc 进程信息及硬件信息 /root 临时设备的默认挂载点 /sbin 系统管理命令 /srv 数据 /var 数据 /sys 内核相关信息 /tmp 临时文件 /usr 用户相关设定\r1.2 linux系统命令行的含义 示例:root@app00:~# root //用户名,root为超级用户 @ //分隔符 app00 //主机名称 ~ //当前所在目录,默认用户目录为~,会随着目录切换而变化,例如:(root@app00:/bin# ,当前位置在bin目录下) # //表示当前用户是超级用户,普通用户为$,例如:(\u0026#34;yao@app00:/root$\u0026#34; ,表示使用用户\u0026#34;yao\u0026#34;访问/root文件夹)\r1.3 命令的组成 示例:命令 参数名 参数值\r二、基础操作 2.1 关闭系统 (1)立刻关机 shutdown -h now 或者 poweroff (2)两分钟后关机 shutdown -h 2 (3)立刻重启 shutdown -r now 或者 reboot (4)两分钟后重启 shutdown -r 2\r2.2 帮助命令(help) ifconfig --help //查看 ifconfig 命令的用法 man shutdown //打开命令说明后,可按\u0026#34;q\u0026#34;键退出\r2.3 切换用户(su) su yao //切换为用户\u0026#34;yao\u0026#34;,输入后回车需要输入该用户的密码 exit //退出当前用户\r三、目录操作 3.1 切换目录(cd) cd //切换到home目录 cd / //切换到根目录 cd /bin //切换到根目录下的bin目录 cd ../ //切换到上一级目录 或者使用命令:cd .. cd ~ //切换到home目录 cd - //切换到上次访问的目录 cd xx(文件夹名) //切换到本目录下的名为xx的文件目录,如果目录不存在报错 cd /xxx/xx/x //可以输入完整的路径,直接切换到目标目录,输入过程中可以使用tab键快速补全\r3.2 查看目录(ls) ls //查看当前目录下的所有目录和文件 ls -a //查看当前目录下的所有目录和文件(包括隐藏的文件) ls -l //列表查看当前目录下的所有目录和文件(列表查看,显示更多信息),与命令\u0026#34;ll\u0026#34;效果一样 ls /bin //查看指定目录下的所有目录和文件\r3.3 创建目录(mkdir) mkdir tools //在当前目录下创建一个名为tools的目录 mkdir /bin/tools //在指定目录下创建一个名为tools的目录\r3.3 删除目录与文件(rm) rm 文件名 //删除当前目录下的文件 rm -f 文件名 //删除当前目录的的文件(不询问) rm -r 文件夹名 //递归删除当前目录下此名的目录 rm -rf 文件夹名 //递归删除当前目录下此名的目录(不询问) rm -rf * //将当前目录下的所有目录和文件全部删除 rm -rf /* //将根目录下的所有文件全部删除【慎用!相当于格式化系统】\r3.4 修改目录(mv) mv 当前目录名 新目录名 //修改目录名,同样适用与文件操作 mv /usr/tmp/tool /opt //将/usr/tmp目录下的tool目录剪切到 /opt目录下面 mv -r /usr/tmp/tool /opt //递归剪切目录中所有文件和文件夹\r3.5 拷贝目录(cp) cp /usr/tmp/tool /opt //将/usr/tmp目录下的tool目录复制到 /opt目录下面 cp -r /usr/tmp/tool /opt //递归剪复制目录中所有文件和文件夹\r3.6 搜索目录(find) find /bin -name \u0026#39;a*\u0026#39; //查找/bin目录下的所有以a开头的文件或者目录 find /bin -size \u0026#39;-10k\u0026#39;\t//查找/bin目录下的小于10kb的文件或目录\r3.7 查看当前目录(pwd) pwd //显示当前位置路径\r四、文件操作 4.1 新增文件(touch) touch a.txt //在当前目录下创建名为a的txt文件(文件不存在),如果文件存在,将文件时间属性修改为当前系统时间\r4.2 删除文件(rm) rm 文件名 //删除当前目录下的文件 rm -f 文件名 //删除当前目录的的文件(不询问)\r4.3 编辑文件(vi,vim) vi 文件名 //打开需要编辑的文件 --进入后,操作界面有三种模式:命令模式(command mode)、插入模式(insert mode)和底行模式(last line mode) 命令模式 -刚进入文件就是命令模式,通过方向键控制光标位置, -使用命令\u0026#34;dd\u0026#34;删除当前整行 -使用命令\u0026#34;/字段\u0026#34;进行查找 -按\u0026#34;i\u0026#34;在光标所在字符前开始插入 -按\u0026#34;a\u0026#34;在光标所在字符后开始插入 -按\u0026#34;o\u0026#34;在光标所在行的下面另起一新行插入 -按\u0026#34;:\u0026#34;进入底行模式 插入模式 -此时可以对文件内容进行编辑,左下角会显示 \u0026#34;-- 插入 --\u0026#34;\u0026#34; -按\u0026#34;esc\u0026#34;进入底行模式 底行模式 -退出编辑: :q -强制退出: :q! -保存并退出: :wq ## 操作步骤示例 ## 1.保存文件:按\u0026#34;esc\u0026#34; -\u0026gt; 输入\u0026#34;:\u0026#34; -\u0026gt; 输入\u0026#34;wq\u0026#34;,回车 //保存并退出编辑 2.取消操作:按\u0026#34;esc\u0026#34; -\u0026gt; 输入\u0026#34;:\u0026#34; -\u0026gt; 输入\u0026#34;q!\u0026#34;,回车 //撤销本次修改并退出编辑 ## 补充 ## vim \u0026#43;10 filename.txt //打开文件并跳到第10行 vim -r /etc/passwd //以只读模式打 开文件\r4.4 查看文件( cat,less,more,tail ) cat a.txt //查看文件最后一屏内容 less a.txt //pgup向上翻页,pgdn向下翻页,\u0026#34;q\u0026#34;退出查看 more a.txt //显示百分比,回车查看下一行,空格查看下一页,\u0026#34;q\u0026#34;退出查看 tail -100 a.txt //查看文件的后100行,\u0026#34;ctrl\u0026#43;c\u0026#34;退出查看\r五、文件权限 5.1 权限说明 文件权限简介:\u0026lsquo;r\u0026rsquo; 代表可读(4),\u0026lsquo;w\u0026rsquo; 代表可写(2),\u0026lsquo;x\u0026rsquo; 代表执行权限(1) 括号内代表\u0026quot;8421法\u0026quot;\n##文件权限信息示例:-rwxrw-r\u0026ndash; -第一位:\u0026rsquo;-\u0026lsquo;就代表是文件,\u0026rsquo;d\u0026rsquo;代表是文件夹 -第一组三位:拥有者的权限 -第二组三位:拥有者所在的组,组员的权限 -第三组三位:代表的是其他用户的权限\n5.2 用户,用户组 getent passwd //查看当前系统中有那些用户\n5.3 文件权限( chmod ) 普通授权 chmod +x a.txt 8421法 chmod 777 a.txt //1+2+4=7,\u0026ldquo;7\u0026quot;说明授予所有权限\n六、打包与解压 6.1 说明 .zip、.rar //windows系统中压缩文件的扩展名 .tar //linux中打包文件的扩展名 .gz //linux中压缩文件的扩展名 .tar.gz //linux中打包并压缩文件的扩展名\n6.2 打包文件(tar -zcvf) tar -zcvf (打包压缩后的文件名) (要打包的文件) 参数说明: z:调用gzip压缩命令进行压缩; c:打包文件; v:显示运行过程; f:指定文件名;\n示例: tar -zcvf a.tar file1 file2,\u0026hellip; //多个文件压缩打包\n6.3 解压文件(tar -zxvf ) tar -zxvf a.tar //解包至当前目录 tar -zxvf a.tar -c /usr\u0026mdash;\u0026mdash; //指定解压的位置 unzip test.zip //解压*.zip文件 unzip -l test.zip //查看*.zip文件的内容\n七、其他常用命令 7.1 find find . -name \u0026ldquo;*.c\u0026rdquo; //将目前目录及其子目录下所有延伸档名是 c 的文件列出来 find . -type f //将目前目录其其下子目录中所有一般文件列出 find . -ctime -20 //将目前目录及其子目录下所有最近 20 天内更新过的文件列出 find /var/log -type f -mtime +7 -ok rm {} ; //查找/var/log目录中更改时间在7日以前的普通文件,并在删除之前询问它们 find . -type f -perm 644 -exec ls -l {} ; //查找前目录中文件属主具有读、写权限,并且文件所属组的用户和其他用户具有读权限的文件 find / -type f -size 0 -exec ls -l {} ; //为了查找系统中所有文件长度为0的普通文件,并列出它们的完整路径\n7.2 whereis whereis ls //将和ls文件相关的文件都查找出来\n7.3 which 说明:which指令会在环境变量$path设置的目录里查找符合条件的文件。 which bash //查看指令\u0026quot;bash\u0026quot;的绝对路径\n7.4 sudo 说明:sudo命令以系统管理者的身份执行指令,也就是说,经由 sudo 所执行的指令就好像是 root 亲自执行。需要输入自己账户密码。 使用权限:在 /etc/sudoers 中有出现的使用者 sudo -l //列出目前的权限 $ sudo -u yao vi ~www/index.html //以 yao 用户身份编辑 home 目录下www目录中的 index.html 文件\n7.5 grep grep -i \u0026ldquo;the\u0026rdquo; demo_file //在文件中查找字符串(不区分大小写) grep -a 3 -i \u0026ldquo;example\u0026rdquo; demo_text //输出成功匹配的行,以及该行之后的三行 grep -r \u0026ldquo;ramesh\u0026rdquo; * //在一个文件夹中递归查询包含指定字符串的文件\n7.6 service 说明:service命令用于运行system v init脚本,这些脚本一般位于/etc/init.d文件下,这个命令可以直接运行这个文件夹里面的脚本,而不用加上路径 service ssh status //查看服务状态 service \u0026ndash;status-all //查看所有服务状态 service ssh restart //重启服务\n7.7 free 说明:这个命令用于显示系统当前内存的使用情况,包括已用内存、可用内存和交换内存的情况 free -g //以g为单位输出内存的使用量,-g为gb,-m为mb,-k为kb,-b为字节 free -t //查看所有内存的汇总\n7.8 top top //显示当前系统中占用资源最多的一些进程, shift+m 按照内存大小查看\n7.9 df 说明:显示文件系统的磁盘使用情况 df -h //一种易看的显示\n7.10 mount mount /dev/sdb1 /u01 //挂载一个文件系统,需要先创建一个目录,然后将这个文件系统挂载到这个目录上 dev/sdb1 /u01 ext2 defaults 0 2 //添加到fstab中进行自动挂载,这样任何时候系统重启的时候,文件系统都会被加载\n7.11 uname 说明:uname可以显示一些重要的系统信息,例如内核名称、主机名、内核版本号、处理器类型之类的信息 uname -a\n7.12 yum 说明:安装插件命令 yum install httpd //使用yum安装apache yum update httpd //更新apache yum remove httpd //卸载/删除apache\n7.13 rpm 说明:插件安装命令 rpm -ivh httpd-2.2.3-22.0.1.el5.i386.rpm //使用rpm文件安装apache rpm -uvh httpd-2.2.3-22.0.1.el5.i386.rpm //使用rpm更新apache rpm -ev httpd //卸载/删除apache\n7.14 date date -s \u0026ldquo;01/31/2010 23:59:53\u0026rdquo; ///设置系统时间\n7.15 wget 说明:使用wget从网上下载软件、音乐、视频 示例:wget http://prdownloads.sourceforge.net/sourceforge/nagios/nagios-3.2.1.tar.gz //下载文件并以指定的文件名保存文件 wget -o nagios.tar.gz http://prdownloads.sourceforge.net/sourceforge/nagios/nagios-3.2.1.tar.gz\n7.16 ftp ftp ip/hostname //访问ftp服务器 mls *.html - //显示远程主机上文件列表\n7.17 scp scp /opt/data.txt 192.168.1.101:/opt/ //将本地opt目录下的data文件发送到192.168.1.101服务器的opt目录下\n八、系统管理 8.1 防火墙操作\nservice iptables status //查看iptables服务的状态 service iptables start //开启iptables服务 service iptables stop //停止iptables服务 service iptables restart //重启iptables服务 chkconfig iptables off //关闭iptables服务的开机自启动 chkconfig iptables on //开启iptables服务的开机自启动 ##centos7 防火墙操作 systemctl status firewalld.service //查看防火墙状态 systemctl stop firewalld.service //关闭运行的防火墙 systemctl disable firewalld.service //永久禁止防火墙服务\n8.2 修改主机名(centos 7)\nhostnamectl set-hostname 主机名\n8.3 查看网络\nifconfig\n8.4 修改ip\n修改网络配置文件,文件地址:/etc/sysconfig/network-scripts/ifcfg-eth0 主要修改以下配置:\ntype=ethernet //网络类型 bootproto=static //静态ip device=ens00 //网卡名 ipaddr=192.168.1.100 //设置的ip netmask=255.255.255.0 //子网掩码 gateway=192.168.1.1 //网关 dns1=192.168.1.1 //dns dns2=8.8.8.8 //备用dns onboot=yes //系统启动时启动此设置 修改保存以后使用命令重启网卡:service network restart\n8.5 配置映射\n修改文件: vi /etc/hosts 在文件最后添加映射地址,示例如下: 192.168.1.101 node1 192.168.1.102 node2 192.168.1.103 node3 配置好以后保存退出,输入命令:ping node1 ,可见实际 ping 的是 192.168.1.101。\n8.6 查看进程\nps -ef //查看所有正在运行的进程\n8.7 结束进程\nkill pid //杀死该pid的进程 kill -9 pid //强制杀死该进程\n8.8 查看链接\nping ip //查看与此ip地址的连接情况 netstat -an //查看当前系统端口 netstat -an | grep 8080 //查看指定端口\n8.9 快速清屏\nctrl+l //清屏,往上翻可以查看历史操作 8.10 远程主机 ssh ip //远程主机,需要输入用户名和密码\n九、yum命令 9.1 yum命令 9.2 systemctl 9.3 ln -s 命令 9.4 hostname 9.5 域名解析 参考文献\n转载自:https://blog.csdn.net/m0_46422300/article/details/104645072 ","date":"2024-05-10","permalink":"http://54rookie.com/posts/linux%E6%93%8D%E4%BD%9C%E6%8C%87%E4%BB%A4/","summary":"Linux知识 一、基础知识 1.1 Linux系统的文件结构 /bin 二进制文件,系统常规命令 /boot 系统启动分区,系统启动时读取的文件 /dev 设备文件 /etc 大多数配置文件 /home 普通用户的家目录 /lib","title":"linux"},]
[{"content":"初入密码界 密码学小常识: 什么是iacr:\n密码学中最著名的学术会议当属国际密码学协会(iacr,international association of cryptological research)所主办的三个⼤会了:crypto、eurocrypt、asiacrypt,即美密会、欧密会、 亚密会,其中欧密会和美密会的⽔平最⾼(亚密会的资历最浅,虽然近⼏年上升很快,但要完全赶上还需要⼀定的时间)。密码学中最重要的⽂章⼀般都会在这三个会议中发布。\n什么是dblp:\n定义: dblp(database systems and logic programming) 是计算机领域内对研究的成果以作者为核心的一个计算机类英文文献的集成数据库系统。\n简介: dblp按年代列出了作者的科研成果。包括国际期刊和会议等公开发表的论文。dblp没有提供对中文文献的收录和检索功能,国内的权威期刊及重要会议的论文缺乏一个类似的集成检索系统。dblp的文献更新速度很快,很好地反应了国内外学术研究的前沿方向。\n特征: dblp在学术界有很好的声誉,给人们带来了极大的便利,其权威性也得到了研究界的高度认可。但dblp没有提供对中文文献的收录和检索功能,国内的权威期刊及重要会议的论文缺乏一个类似的集成检索系统。dblp项目是德国特里尔大学的michael ley负责开发和维护。它提供计算机领域科学文献的搜索服务,但只储存这些文献的相关元数据,如标题,作者,发表日期等。截至2009年7月已经有超过1, 200, 000文献。和一般流行的情况不同,dblp并没有使用数据库而是使用xml存储元数据。\ndblp地址: https://dblp.org/\n参考文献:\niacr:https://blog.csdn.net/a33280000f/article/details/117929248 dblp:https://zhuanlan.zhihu.com/p/595538578 ccf-a的会议(安全相关): 密码学三大顶会:\nname description eurocrypt international conference on the theory and applications of cryptographic techniques crypto international cryptology conference. asiacrypt annual international conference on the theory and application of cryptology and information security. 网络与信息安全四大顶会:\nname description ccs acm conference on computer and communications security ndss network and distributed system security symposium s\u0026amp;p ieee symposium on security and privacy usenix security usenix security symposium 论文中常用的符号 【i.e.】是id est的缩写,翻译为\u0026quot;换句话说,那就是说\u0026quot;,目的是进一步解释前面所说的观点。\n【e.g.】是exempli gratia的缩写,翻译为\u0026quot;例如\u0026quot;,目的是用若干例子让前面说法更具体。\n【etc.】是et cetera的缩写,翻译为\u0026quot;等等\u0026quot;。表示前面的例子还没列举完,加个词\u0026quot;等等\u0026quot;。\n【viz.】 是videlicet的缩写,viz与e.g.类似,但是viz.要把它前面单词包含的项目全部列出。\n【w.r.t.】 是with respect to的缩写,翻译为\u0026quot;关于,谈到\u0026quot;。在引用文献作者时使用。\n【et al.】 是et alia的缩写,翻译为\u0026quot;等其他人\u0026quot;。它几乎都是在列文献作者时使用,把主要作者列出后,其它作者全放在et al.里面。\n参考文献: https://zhuanlan.zhihu.com/p/63640148\n密码学常用名词介绍 参考文献:https://www.jianshu.com/p/c65fa3af1c01 半诚实的模型:\n半诚实的参与方指的是遵循了协议的执行,但保存了协议的中间计算状态的参与方。对于一个程序p,半诚实的参与方使用p的输入正确的执行p的内容并将结果输出,但是他会保存程序执行过程中产生的数据。实际上,半诚实的参与方需要做的是:保存内部产生的随机数、中间变量和所有从其他参与方接收到的消息。半诚实的敌手模型的必要性在于发动主动攻击要比监听整个计算过程复杂的多,原因是,对于一个运行在计算机上程序,主动的攻击需要用一些非常复杂的程序,而单纯的获取计算过程中的数据相对比较容易(可能只需要读内存),所以半诚实敌手模型在实际的生产生活中普遍存在,这是单独考虑半诚实敌手的原因。点击了解更多\n加盐哈希: 在密码学中,是指通过在密码任意固定位置插入特定的字符串,让散列后的结果和使用原始密码的散列结果不相符,这种过程称之为“加盐”。加盐哈希早用户名密码中应用非常广泛。更多内容参考:加盐哈希\n","date":"2024-05-05","permalink":"http://54rookie.com/posts/%E7%90%86%E8%AE%BA%E7%9F%A5%E8%AF%86/","summary":"初入密码界 密码学小常识: 什么是IACR: 密码学中最著名的学术会议当属国际密码学协会(IACR,International Association of Cryptological Research)所主办的三个⼤会","title":"初入密码界"},]
[{"content":"零知识证明 零知识证明 定义:零知识证明的定义为:证明者(prover)能够在不向验证者(verifier)提供任何有用的信息的情况下,使验证者(verifier)相信某个论断是正确的。\n性质:\n完备性(completeness):只要证明者拥有相应的知识,那么就能通过验证者的验证,即证明者有足够大的概率使验证者确信。; 可靠性(soundness):如果证明者没有相应的知识,则无法通过验证者的验证,即证明者欺骗验证者的概率可以忽略。 零知识性(zero-knowledge):证明者在交互过程中仅向验证者透露是否拥有相应知识的陈述,不会泄露任何关于知识的额外信息。 举例说明:\n下面举一个零知识证明的常用例子\nalice是色盲,bob不是色盲,在bob手上有两个大小,形状完全一样的球,但这两个球的颜色不一样,一个球是蓝色的,另一个球是红色的,由于alice是色盲,所以alice无法分辨这两个球是否是一样的,bob需要向alice证明这两个球是不一样的。在这里,alice被称为验证者,他需要验证bob的陈述正确与否,bob被称为证明者,他需要证明自己的陈述(存在两个颜色不一样的球),bob需要在alice不能获得两个球的颜色的情况下,向alice证明这两个球的颜色是不一样的这个事实,这与零知识证明的定义是相符合的。\nalice当bob的面拿起两个球,左手拿蓝球,右手拿红球,然后将双手放到背后,这样bob就看不到alice手上的球了,alice在背后随机交换左右手上的球,交换完成后alice将手伸出,并询问bob两个球是否交换过位置,如果bob能看到球上的颜色,那么每次alice换过球的位置后,bob都能正确回答出alice的问题。\n第一次,alice偷偷交换了手中球的位置,然后alice问bob是否交换了球的位置,如果bob回答yes,那么alice有50%的概率相信bob是可以区分这两个球的颜色,因为bob有1/2的概率蒙对,所以alice可以在进行一次测试。如果bob回答no,那么alice可以肯定bob不能区分两个球的颜色。第二次,alice没有交换手中球的位置,然后alice问bob是否交换了球的位置。如果bob回答no,那么alice有75%的概率相信bob是可以区分两个球的颜色。\n第一次迭代后,alice可以说bob陈述的断言为真的概率为50%。如果bob第二次回答正确,那么alice可以说bob陈述为真的概率达75%。在第三次迭代后,它将是87.5%。如果连续n次bob都通过了检查,则alice有 $1-(1/2)^n$ 的概率可以认为 bob 说的是真的,这两个球的确是有红蓝两种颜色。\n零知识证明是一种基于概率的验证方式,验证者(verifier)基于一定的随机性向证明者(prover)提出问题,如果证明者都能给出正确回答,则说明证明者大概率拥有他所声称的“知识”。零知识证明并不是数学意义上的证明,因为它存在小概率的误差,欺骗的证明者有可能通过虚假的陈诉骗过验证者。换句话说,零知识证明是概率证明而不是确定性证明,但是也存在技术能将误差降低到可以忽略的值。\n知识签名:\n签名者利用数学知识和公共信息,非交互和不泄露某个秘密的情况下,向别人证明他知道这个秘密。常见的知识签名有三种:\n离散对数的知识签名 双离散对数的知识签名 离散对数e次方根的知识签名 离散对数的知识签名:(零知识证明)\n符号表示为:$sklog[\\alpha:y=g^{\\alpha}] \\pmod m$ 。\n离散对数的知识签名是针对这样一个问题:$y = g^x \\pmod n$; x 是私钥,y 是公钥,g 和 n 都是公开的系统参数,m是消息,现在需要让别别人证明自己拥有私钥 x,并且不泄露 x 的信息。 证明者事实上只需要给出一个(c,s)对符合等式 $c = h( m||y||g||(g^s) (y^c) )$ 即可。\n具体的操作过程如下:\n选择一个随机数 $r$ 计算c:根据公式 $c=h(m||y||g||g^r)$ 计算s:根据公式 $s = r - c * x$ (c, s)即为知识签名。 解释说明\n$ \\begin{align} c\u0026amp;=h(m||y||g||g^r) \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \u0026mdash; 1 式 \\\\ \u0026amp;=h(m||y||g||g^sg^{xc}) \\ \\ \\ \\ \\ \\ \u0026mdash; 2 式\\\\ \u0026amp;=h(m||y||g||g^sy^c) \\ \\ \\ \\ \\ \\ \\ \u0026mdash; 3 式\\\\ \\end{align} $\n如果知道密钥x,执行通过上面操作过程中的四个步骤就可以轻松计算出(c, s)对的值, 如果不知道密钥x,而想算出(c, s),只能通过3式计算,这在计算上是不可行的。 所以,如果能给出一个满足条件的(c, s)对,就能说明其拥有私钥x。\n\u0026ldquo;双离散对数的知识签名\u0026quot;和\u0026quot;离散对数e次方根的知识签名\u0026quot;参见参见参考文献【1】\n参考文章:\n【1】知识签名(signature of knowledge)_zhang-hui的博客-csdn博客 【2】零知识证明介绍 零知识证明的相关概念 定义: 证明者(prover)在不向验证者(verifier)提供任何与陈述相关信息的情况下使验证者(verifier)相信这个陈述是正确的。这个证明称为零知识证明。\n需要满足的满足的条件: 完备性(completeness):只要证明者拥有相应的知识,那么就能通过验证者的验证,即证明者有足够大的概率使验证者确信。; 可靠性(soundness):如果证明者没有相应的知识,则无法通过验证者的验证,即证明者欺骗验证者的概率可以忽略。 零知识性(zero-knowledge):证明者在交互过程中仅向验证者透露是否拥有相应知识的陈述,不会泄露任何关于知识的额外信息。 直观举例 交互式零知识证明: 交互式的零知识证明指的是:证明者(prover)与验证者(verifier)通过若干轮的交互,使得验证者相信证明者确实拥有某个秘密,且证明者不能得到这个秘密的任何信息。\n经典零知识证明例子之\u0026mdash;色盲游戏\nalice是色盲,bob不是色盲,在bob手上有两个大小,形状完全一样的球,但这两个球的颜色不一样,一个球是蓝色的,另一个球是红色的,由于alice是色盲,所以alice无法分辨这两个球是否是一样的,bob需要向alice证明这两个球是不一样的。在这里,alice被称为验证者,他需要验证bob的陈述正确与否,bob被称为证明者,他需要证明自己的陈述(存在两个颜色不一样的球),bob需要在alice不能获得两个球的颜色的情况下,向alice证明这两个球的颜色是不一样的这个事实,这与零知识证明的定义是相符合的。\nalice当bob的面拿起两个球,左手拿蓝球,右手拿红球,然后将双手放到背后,这样bob就看不到alice手上的球了,alice在背后随机交换左右手上的球,交换完成后alice将手伸出,并询问bob两个球是否交换过位置,如果bob能看到球上的颜色,那么每次alice换过球的位置后,bob都能正确回答出alice的问题。\n第一次,alice偷偷交换了手中球的位置,然后alice问bob是否交换了球的位置,如果bob回答yes,那么alice有50%的概率相信bob是可以区分这两个球的颜色,因为bob有1/2的概率蒙对,所以alice可以在进行一次测试。如果bob回答no,那么alice可以肯定bob不能区分两个球的颜色。第二次,alice没有交换手中球的位置,然后alice问bob是否交换了球的位置。如果bob回答no,那么alice有75%的概率相信bob是可以区分两个球的颜色。\n第一次迭代后,alice可以说bob陈述的断言为真的概率为50%。如果bob第二次回答正确,那么alice可以说bob陈述为真的概率达75%。在第三次迭代后,它将是87.5%。如果连续n次bob都通过了检查,则alice有 $1-(1/2)^n$ 的概率可以认为 bob 说的是真的,这两个球的确是有红蓝两种颜色。\n零知识证明是一种基于概率的验证方式,验证者(verifier)基于一定的随机性向证明者(prover)提出问题,如果证明者都能给出正确回答,则说明证明者大概率拥有他所声称的“知识”。零知识证明并不是数学意义上的证明,因为它存在小概率的误差,欺骗的证明者有可能通过虚假的陈诉骗过验证者。换句话说,零知识证明是概率证明而不是确定性证明,但是也存在技术能将误差降低到可以忽略的值。\n非交互式零知识证明: 交互式零知识证明协议依赖于验证者的随机尝试,需要证明者和验证者进行多次交互才能完成。非交互式零知识证明(non-interactive zero-knowledge, nizk)将交互次数减少到一次,可实现离线证明和公开验证。在区块链等零知识证明应用场景中,非交互的性质是必须的,因为在区块链系统中,不能假设双方一直在线进行交互,在区块链网络上,证明者只要向全网广播一条证明交易,网络上的矿工在将这条交易打包到区块中的时候就帮验证者完成了零知识证明的校验。\n数独游戏:\n数独是源自18世纪瑞士的一种数学游戏,是一种运用纸、笔进行演算的逻辑游戏。玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个粗线宫(3*3)内的数字均含1-9,不重复。【下图给出了一个数独游戏的题目,将空白部分填满即可赢得游戏】\nalice为了向bob证明他已经解决了一个数独难题,创建一个防篡改的机器m。alice将生成好的数独答案放入到机器m中,机器m可以向bob发送证明。alice的机器遵循以下公开可验证的协议:\n首先,alice在机器中放入尚未被解决的原始数独题目,数独中的谜题卡片三张正面朝上,例如,单元格c3具有3张正面朝上的9号卡片。【即,在3行9列的位置正面朝上放三张数字9的卡片】 则上述题目在机器m中的样子如下图:\n机器中每个位置都放了三张卡片,这是因为最后验证的时候,每个位置需要验证“行,列,九宫格”,故需要三张卡片。 接下来,alice上机器将他的答案正面朝下放置在相应的单元格上,同样也是每个单元格放三张。得到如下结果:\n最后bob向机器获取证明,机器返回给bob27个袋子:\n机器将数独中每一行9张卡片取出,并分别混淆后放入一个袋子中,一共有9行,所以9个袋子 机器将数独中每一列9张卡片取出,并分别混淆后放入一个袋子中,一共有9列,所以9个袋子 机器将数独中每个粗线宫(3*3)内卡片取出,并分别混淆后放入一个袋子中,一共有9个,所以9个袋子 bob分别对这27个袋子进行检查,如果每个袋子中的卡片都包含数字1到9,而且没有任何数字丢失或重复,那么bob可以确认alice的确解开了数独,并且bob并没有从机器返回的证明中获取任何关于数独解的知识,因为机器返回给bob袋子中的数据是被随机打乱的。 参考文献\n零知识证明介绍 sigma协议 预备知识 definition 1: (effective relation)【有效关系】\n指的是一组二元关系$r ⊆ x × y$ 其中 $x,y,r$ 为有效的可辨认的有限集合。 $y$ 中的元素被称作statements【声明】。如果$(x,y) ⊆ r$,则称 x 为 y 的witness【证据】 definition 1: (homomorphisms maps)【同态映射】\n参考文献:https://zhuanlan.zhihu.com/p/622105041\nsigma的定义 definition 2: (sigma protocol)【sigma协议】\n设 $r ⊆ x × y$是一个有效关系,则一对 $(p,v)$ 在 $r$ 上构建的sigma协议定义为:\np 是一个叫做 prover 的协议参与方,输入是一个(witness,statement)对。即$(x,y) ⊆ r$ v 是一个叫做 verifier 的协议参与方,其输入为一个 statement ,即 $y ⊆ y$ p 和 v 的三轮交互满足下面三个性质,即【正确性】【知识可靠性】和【hvzk】 p 和 v 的交互过程为:【如下图】\n首先 p 计算一个承诺(commitment) t ,将其发送给 v ; 在收到了来自 p 的消息 t 后, v 在有限的挑战空间c中随机选取一个挑战(challenge) c,并将其发送给 p ; 在接收到来自 v 的挑战 c 后, p 计算出一个响应(response) z ,将其发送给 v ; 在收到了来自 p 的消息 t 后,v 输出accept或者reject。 v 的输出由statement y 和交互中的消息 conversation$(t,c,z)$ 严格计算得出。 协议的要求是,对于所有的 $(x,y) \\in r$ ,当 $p(x,y)$ 与 $v(y)$ 交互后, $v(y)$ 总是输出accept。为了系统的安全性,挑战空间的大小应该为sup-poly(超过多项式大小)。\ndefinition 3: (correctness)【正确性】\n设$(p,v)$是关于关系$r ⊆ x × y$ 的一个sigma协议。如果对于任意的$(x,y)∈r$,验证者 v 都接受证明者 p 的证明,那么称这个sigma协议具有正确性。\n正确性显然是一个协议应该满足的最基本的是性质,如果协议不满足正确性,那么就无法达成构造这个协议最初的目的\u0026ndash;向别人证明自己拥有秘密。\ndefinition 4: (special honest verifier zero knowledge)【hvzk】\n设 $(p,v)$ 是关于关系 $r ⊆ x × y$ 的一个sigma协议,且挑战空间为 c 。如果存在一个高效的概率性算法 $sim(y,c)$(称作simulator),其输入为 $(y,c) \\in y\\times c$ 且满足:\n对于所有的输入 $(y,c) \\in y\\times c$,模拟器 $sim(y,c)$ 能够输出一对 $(t,z)$ 使得 $(t,c,z)$ 对于statement $y$ 来说是一个被 accpeted 的 conversation。 对于全体 $(x,y) \\in r$,我们随机选择 $c\u0026rsquo;\\stackrel{r}{\\longleftarrow}c$,然后用模拟器计算 $(t\u0026rsquo;,z\u0026rsquo;)\\stackrel{}{\\longleftarrow}sim(y,c)$。使得模拟器产生的 $(t\u0026rsquo;,c\u0026rsquo;,z\u0026rsquo;)$的分布与 正常运行sigma协议得到的 $(t,c,z)$ 的分布相同,我们称sigma协议 $(p,v)$是 special hvzk 。 hvzk 的一些说明:\n这个性质最开始的定义是:诚实的验证者不能从 sigma 协议中获得任何关于秘密的信息。 sim 需要 c 作为额外输出,并且就算 y 的 witness 不存在,sim 也可以输出一个被 accepted 的 conversation。 由于sim产生的 $(t\u0026rsquo;,c\u0026rsquo;,z\u0026rsquo;)$ 与正常运行sigma协议产生的 $(t,c,z)$ 在计算上不可区分,所有敌手不可能从 $(t,c,z)$ 得知到任何关于秘密 x 的信息. 这是因为, 如果敌手能从 $(t,c,z)$中得到秘密 x 的信息 , 那么由于 $(t,c,z)$ 和 $(t\u0026rsquo;,c\u0026rsquo;,z\u0026rsquo;)$ 不可区分,则这个敌手也能从 $(t\u0026rsquo;,c\u0026rsquo;,z\u0026rsquo;)$ 中得知秘密 x 的信息. 这是不可能的 , 因为 $(t\u0026rsquo;,c\u0026rsquo;,z\u0026rsquo;)$ 中根本不含由秘密 x . 上述模拟器$sim(y,c)$不具备秘密值 x ,但拥有一个能力 决定或者预知验证者的挑战值 c,这是他能模拟的关键,在随机预言机模型下的安全性证明中,这个性质具有重要的作用。 如果能够构造出一个式sim,它产生的三元组$(t,c,z)$与sigma协议产生的三元组是不可区分的。由于sim产生的三元组跟秘密并没有任何关系,且诚实的验证者不能区分两个二元组,所以诚实的验证者不能从sigma协议的三元组中得到任何秘密的信息。【否则他可以根据这个秘密信息区分两个三元组】 definition 5: (knowledge soundness)【知识可靠性】\n设$(p,v)$是关于关系$r ⊆ x × y$ 的一个sigma协议。如果存在一个高效的确定性的算法$ext$【称作一个witness extractor】使得:\n给定一个statement $y$,以及两个关于 $y$ 被 accepted 的 conversation $(t,c,z) 和 (t,c\u0026rsquo;,z\u0026rsquo;)$,并且$c \\ne c\u0026rsquo;$,如果存在提取器 $ext$ 能够输出 $x \\in x$使得 $(x,y) \\in r$( x 是 y 的witness),即存在一个提取器 $ext$ 使得 $ext((t,c,z),(t,c\u0026rsquo;,z\u0026rsquo;))=x$;那么我们称 sigma 协议满足 knowledge soundness【知识可靠性】。\nknowledge soundness的一些说明:\n这个性质保证最开始定义是:只有拥有秘密的人才能给出一个能通过验证的零知识证明。 这个性质主要约束了恶意的 prover p,保证 verifier v 能通过交互推出来 p 到底有没有骗自己;换句话说,如果证明方 p 没有 witness $x$,那么 p 无法说服 v 相信他确实有证据 $x$。因为 v 可以选择两个不同的挑战 c 和 c\u0026rsquo; 让 p 给处两个响应 z 和 z\u0026rsquo; ,如果 p 不知道 x , 那么他无法做到这一点。 补充说明\n其实 hvzk 和 soundness 最初并不和模拟器与提取器相关,hvzk就是要求sigma协议中,诚实验证者不能获得秘密的任何知识,soundness则要求必须拥有秘密的人才能生成符合验证条件的协议内容。 为了能够在理论上证明 sigma 协议的安全性,引入了模拟器和提取器,使用模拟器充当安全证明中挑战者的模拟方案,挑战者结合分叉引理和提取器求出秘密 x 解决困难问题,从而构造可证明安全的sigma协议。通过随机预言机,还可以构造出可证明安全的非交互式零知识证明,数字签名等。\nsigma协议的例子 下面的协议中,只有schnorr协议给出了三个性质的证明,其它证明可去参考文献【5】中查看。\nschnorr协议过程:\n设 $\\mathbb{g}$是一个阶为素数 q 的循环群,其生成元为$g \\in \\mathbb{g}$ 。设证明人 p 保存私钥$\\alpha \\in \\mathbb{z}_q$,对应的公钥匙为 $u = g^{\\alpha} \\in \\mathbb{g}$ 。 p 需要向 v 证明其拥有$\\alpha$。这里 $\\alpha$是 witness, $u=g^{\\alpha}$是 statement。schnorr 身份认证协议过程为:【如下图】\np 计算$\\alpha_t \\stackrel{r}{\\longleftarrow}\\mathbb{z}_q,\\ u_t\\stackrel{r}{\\longleftarrow}g^{α_t}$,并将 $u_t$发送给 v ;\t【$commitment=u_t$】 v 计算$c\\stackrel{r}{\\longleftarrow}c ,\\ \\ \\ c \\subset \\mathbb{z}_q$并将 c 发送给 p ;\t【$challenge=c$】 p 计算$\\alpha_z \\leftarrow \\alpha_t + \\alpha c$,并将$\\alpha_z$发送给 v ;\t【$response=\\alpha_z$】 v 检查如果 $g^{a_z} = u_t \\cdot u^c$ ;v 输出accept,否则输出reject。 正确性:\n正确性显然成立,这里不再赘述。\nhvzk:\n证明 hvzk 在于如何构建一个产生 conversation 并被 verifier 接受的 sim(u) ,且由 sim 产生出来的conversation 中元素的分布和正常 p,v 交互产生的 conversation 中元素的概率分布相同。如果我们设 vk = u ,则 sim 计算:$α_z \\stackrel{r}{\\leftarrow} z_q; c \\stackrel{r}{\\leftarrow} c; u_t ←g^{α_z}/u^c$。 在真实的交互过程中, c 和 $\\alpha_z$分别在挑战空间 c 和 $\\mathbb{z}_q$ 中服从均匀分布,且 $c,\\alpha_z$ 相互独立。此外 $u_t$是由$g^{\\alpha_z}=u_t \\cdot u^c$唯一确定的,因此可以明显看出, sim 产生的 conversation 中元素的概率分布同正常交互所产的 conversation 中元素的概率分布相同。\nknowledge soundness:\n假设攻击者对于同一个承诺 u 可以产生两个被 accepted 的 conversation$(u_t,c,\\alpha_z)$ 和 $(u_t,c\u0026rsquo;,\\alpha\u0026rsquo;_z)$且他们满足$c \\neq c\u0026rsquo;$,则有 $ g^{\\alpha_z}=u_t \\cdot u^c$ 和 $g^{\\alpha\u0026rsquo;_z}=u_t \\cdot u^{c\u0026rsquo;}$成立,将两式相除得 $g^{\\delta \\alpha}= u^{\\delta c}$,其中 $\\delta \\alpha = \\alpha_z - \\alpha_z\u0026rsquo;$;$\\delta c = c-c\u0026rsquo;$。由于 $ \\delta c \\ne 0$ ,且群 $\\mathbb{g}$ 是素数阶得循环群,因此有 $1/ \\delta c \\in \\mathbb{g}$,则有 $g^{\\delta z/\\delta c} = u$,故我们得到了离散对数问题 $dlog_gu$ 的解为 $\\alpha = \\frac{\\delta z}{\\delta c} $ 所以 schnorr\u0026rsquo;s protocol满足knowledge soundness。\nokamoto协议过程\n设$\\mathbb{g}$是一个阶为素数 q 的循环群,其生成元为 $g \\in \\mathbb{g}。 h \\in \\mathbb{g}$是群中任意一个元素。okamoto’s protocol中的关系为:$r={ (\\alpha,\\beta),u) \\in \\mathbb{z}^2_q \\times \\mathbb{g} : g^{\\alpha}h^{\\beta}=u }$\n假设$commitment=u_t$,$challenge=c$且$response=(\\alpha_z,\\beta_z)$,则协议的过程如下图:其中 $c \\subset \\mathbb{z}_q$。\nthe chaum-pedersen协议过程\n该协议基于dh-triples。设$\\mathbb{g}$是一个阶为素数 q 的循环群,其生成元为 $g \\in \\mathbb{g}$。对于 $(\\alpha,\\beta,\\gamma) \\in \\mathbb{g}$如果$\\alpha \\beta = \\gamma$我们就说$(g^\\alpha,g^\\beta,g^\\gamma)$是一个dh-triples【dh三元组】。等价的,如果$(u,v,w)$是一个dh-tripes,当且仅当存在一个$\\beta \\in \\mathbb{z_q}$ 使得$v= g^\\beta , w = u^\\beta$ 。在chaum-pedersen 协议中的关系 r为:$r:= { (\\ \\beta,(u,v,w)\\ ) \\in \\mathbb{z}_q \\times \\mathbb{g}^3 : v=g^\\beta ,w=u^\\beta }$;\n假设$commitment=(w_t,v_t)$,$challenge=c$ 且 $response=\\beta_z$,则协议的过程为:【其中$c \\subset \\mathbb{z}_q$】\n线性sigma协议 线性sigma协议:\n上述schnorr, okamoto, 和 chaum- pedersen 协议都是一种更加一般的 sigma protocol 的特例。这种更加一般的 sigma protocol 是要证明元素之间的线性关系。设$\\mathbb{g}$是一个阶为素数 q 的循环群,它的生成元为$g \\in \\mathbb{g}$我们考虑一个布尔函数 $\\phi$:\n$$\\phi (x_1,x_2,\u0026hellip;,x_n):={u_1=\\prod_{j=1}^{n}g_{1j}^{x_j}\\ \\wedge \\ u_2=\\prod_{j=1}^{n}g_{2j}^{x_j} \\wedge \u0026hellip; \\wedge u_m=\\prod_{j=1}^{n}g_{mj}^{x_j} }$$\n在函数 $\\phi$ 中, $g_{ij} ,u_i \\in \\mathbb{g}$。这些群元素一部分可以是系统参数甚至是常量,另一些元素是针对函数的特殊变量。$x_i \\in \\mathbb{z}_q$是函数$\\phi$的入参,当函数中的所有等式成立则$\\phi$返回true。对于这样函数的一个特定的类 f ,我们可以定义一个关系:\n$r:= {((\\alpha_1,\u0026hellip;,\\alpha_n),\\phi) \\in \\mathbb{z}_q^n \\times f: \\phi(\\alpha_1,\u0026hellip;,\\alpha_n)= true }$ 在r中,一个函数$\\phi$是一个 statement,而一个使得这个函数$\\phi$为 true 的 $(\\alpha_1,\u0026hellip;,\\alpha_n) \\in \\mathbb{z}_q^n$ 是这个函数 $\\phi$ 的 witness 。而我们称这样的协议为 linear protocols 的原因在于如果我们对函数$\\phi$中的等式取对数可以得到: $$dlog_g({u_j})= \\sum_{j=1}^n x_i \\cdot dlog_g(g_{ij}) \\ \\ \\ \\ \\ (i=1,\u0026hellip;,m)$$\n对于一般 linear sigma protocol 的过程为:\n补充:\n事实上,schnoor协议,okamoto协议和the chaum-pedersen协议都是线性sigma协议的一种特例,我们详细说明如下:\n在上述 schnorr‘s protocol 中\n$p((\\alpha_1,\u0026hellip;,\\alpha_n),\\phi)$中,n = 1,$\\phi(x):= {u=g^x}$ $\\alpha_{tj}$中,j = 1 ; $u_{tm}$中,m = 1 ; 【$u_{tm}$事实上就是承诺$\\phi(\\alpha_{t1})$】 将上面的条件代入即可得到 schnorr协议 在上述 okamoto\u0026rsquo;s protocol 中\n$p((\\alpha_1,\u0026hellip;,\\alpha_n),\\phi)$中,n = 2 ; $\\phi(x,y) := {u=g^xh^y}$ $\\alpha_{tj}$中,j = 2 ; $u_{tm}$中,m = 1 ;\t【$u_{tm}$事实上就是承诺$\\phi(\\alpha_{t1},\\alpha_{t2})$】 将上面的条件代入即可得到 okamoto 协议 the chaum-pedersen protocol中\n$p((\\alpha_1,\u0026hellip;,\\alpha_n),\\phi)$中,n = 1 $\\phi(x) := { v=g^x \\wedge w=u^x}$ $\\alpha_{tj}$中,j = 1 ; $u_{tm}$中,m = 2 ;\t【$u_{tm}$事实上就是承诺$\\phi(\\alpha_{t1})$】 将上面的条件代入即可得到 the chaum-pedersen 协议 对于一般的 linear protocol 是一个关于关系 r 的 sigma协议并且满足knowledge soundness以及special hvzk。\n基于同映射的sigma协议 基于同态映射的sigma协议 我们可以利用群同态来更加清晰高效的来描述到目前为止的介绍的所有sigma协议。设$\\mathbb{h}_1,\\mathbb{h}_2$是两个不知道阶的交换群,以及一个同态映射$\\varphi: \\mathbb{h}_1 \\to \\mathbb{h}_2$。为了表达的方便, 群$\\mathbb{h}_1$中的运算为群加法, 群$\\mathbb{h}_2$中的运算为群乘法。设$u \\in \\mathbb{h}_2$,证明者需要向验证着证明他知道在映射$\\varphi$下 u 的原象。在这种sigma protocol中,关系为:\n$$r:={(\\alpha,(u,\\varphi)) \\in \\mathbb{h}_1 \\times (\\mathbb{h}_2 \\times f):\\varphi(\\alpha)=u}$$\n其中$\\alpha \\in \\mathbb{h}_1$是映射 $\\varphi$ 对于 $u \\in \\mathbb{h}_2$ 的原象。协议的过程如下:\n在$|\\mathbb{h}_1| \\times |\\mathbb{h}_2|$最小素因子至少为 |c| 的情况下,基于同态映射的sigma protocol满足knowledge soundness以及special hvzk。\nschnorr 协议中\n$\\mathbb{h}_1 := \\mathbb{z}_q,\\ \\mathbb{h}_2 := \\mathbb{g},and \\ \\ \\ \\ \\varphi_1(x):=(g^x)$ $r:={(\\alpha,(u,\\varphi)) \\in \\mathbb{h}_1 \\times (\\mathbb{h}_2 \\times f):\\varphi(\\alpha)=u}$ okamoto协议中\n$\\mathbb{h}_1 := \\mathbb{z}_q^2,\\ \\mathbb{h}_2 := \\mathbb{g},and \\ \\ \\ \\ \\varphi_1(x,y):=(g^xh^y)$ $r:={((\\alpha,\\beta),(u,\\varphi)) \\in \\mathbb{h}_1^2 \\times (\\mathbb{h}_2 \\times f):\\varphi(\\alpha,\\beta)=u}$ the chaum-pedersen协议中\n$\\mathbb{h}_1 := \\mathbb{z}_q, \\mathbb{h}_2 := \\mathbb{g}^2,and \\ \\ \\ \\ \\varphi_2(x):=(g^x,u^x)$ $r:={(\\alpha,(u_1,u_2,\\varphi)) \\in \\mathbb{h}_1 \\times (\\mathbb{h}_2^2 \\times f):\\varphi(\\alpha)=(u_1,u_2}$ 一般linear sigma protocol中,对应于同态映射的表示方式为:\n$\\mathbb{h}_1 := \\mathbb{z}_q^n$ , $\\mathbb{h}_2:=\\mathbb{g}^m $ 并且 $\\varphi_3(x_1,\u0026hellip;,x_n) := (\\prod_{j=1}^ng_{1j}^{x_j},\u0026hellip;,\\prod_{j=1}^ng_{mj}^{x_j})$ fait-shamir启发式 定义 知识签名【sok】 定义:\n签名者利用数学知识和公共信息,非交互和不泄露某个秘密的情况下,向别人证明他知道这个秘密。常见的知识签名有三种:\n离散对数的知识签名 双离散对数的知识签名 离散对数e次方根的知识签名 离散对数的知识签名:(零知识证明)\n符号表示为:$sklog[\\alpha:y=g^{\\alpha}] \\pmod m$ 。\n离散对数的知识签名是针对这样一个问题:$y = g^x \\pmod n$; x 是私钥,y 是公钥,g 和 n 都是公开的系统参数,m是消息,现在需要让别别人证明自己拥有私钥 x,并且不泄露 x 的信息。 证明者事实上只需要给出一个(c,s)对符合等式 $c = h( m||y||g||(g^s) (y^c) )$ 即可。\n具体的操作过程如下:\n选择一个随机数 $r$ 计算c:根据公式 $c=h(m||y||g||g^r)$ 计算s:根据公式 $s = r - c * x$ (c, s)即为知识签名。 解释说明\n$ \\begin{align} c\u0026amp;=h(m||y||g||g^r) \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \u0026mdash; 1 式 \\\\ \u0026amp;=h(m||y||g||g^sg^{xc}) \\ \\ \\ \\ \\ \\ \u0026mdash; 2 式\\\\ \u0026amp;=h(m||y||g||g^sy^c) \\ \\ \\ \\ \\ \\ \\ \u0026mdash; 3 式\\\\ \\end{align} $\n如果知道密钥x,执行通过上面操作过程中的四个步骤就可以轻松计算出(c, s)对的值, 如果不知道密钥x,而想算出(c, s),只能通过3式计算,这在计算上是不可行的。 所以,如果能给出一个满足条件的(c, s)对,就能说明其拥有私钥x。\n拓展形式:\n离散对数的知识签名是针对这样一个问题:$y_1 = g_1^x\\ mod(n);y_2 = g_2^x\\ mod(n)$; x 是秘密值,$y_1,y_2$是公开的。$g_1,g_2$ 和 n 都是公开的系统参数,m是消息,现在需要向别人证明自己拥有秘密值 x ,且这个 x满足$y_1 = g_1^x\\ mod(n);y_2 = g_2^x\\ mod(n)$,并且证明过程中不泄露 x 的信息。\n这里只需要证明者给出一个(c,s)对,符合以下的等式要求即可:\n$c = h(\\ m||y||g||g_1^s y_1^c||g_2^sy_2^c \\ )$ 具体的操作过程如下:【证明过程如上】 选择一个随机数 $r$ 计算c:根据公式 $c=h(\\ m||y||g||g_1^r||g_2^r \\ )$ 计算s:根据公式 $s = r - c * x$ (c,s)即为知识签名。 更一般的形式:\n离散对数的知识签名:$y_1 = g_1^{x_1} \\ mod(n);\\ \u0026hellip;\\ ;y_m = g_m^{x_m} \\ mod(n)$; $x_i\\ (i=1,2,\u0026hellip;,m)$ 是秘密值,并且$y_i\\ (i=1,2,\u0026hellip;,m)$是公开的。$g_i\\ (i=1,2,\u0026hellip;,m)$ 和 n 都是公开的系统参数,m是消息,现在需要向别人证明自己拥有秘密值 $x_i\\ (i=1,2,\u0026hellip;,m)$ ,且$x_i$满足$y_i = g_i^{x_i} \\ mod(n) \\ (i=1,2,\u0026hellip;,m)$,并且证明过程中不泄露$x_i\\ (i=1,2,\u0026hellip;,m)$ 的信息。\n这里只需要证明者给出一个$(c,s_1,s_2,\u0026hellip;,s_m)$对,符合以下的等式要求即可:\n$c = h(\\ m||y||g||g_1^s y_1^c||\u0026hellip;||g_m^sy_m^c \\ )$ 具体的操作过程如下:【证明过程如上】 选择随机数 $r_1,r_2,\u0026hellip;,r_m$ 计算c:根据公式 $c=h(\\ m||y||g||g_1^{r_1}||\u0026hellip;||g_m^{r_m} \\ )$ 计算s:根据公式 $s_i = r_i - c * x_i \\ \\ (i=1,2,\u0026hellip;,m)$ 则$(c,s_1,s_2,\u0026hellip;,s_m)$即为知识签名。 zk-snark 参考文献:\nhttps://blog.csdn.net/qq_35739903/article/details/119455825 https://zhuanlan.zhihu.com/p/38205067 参考文献 【1】https://zhuanlan.zhihu.com/p/144899541 【2】https://zhuanlan.zhihu.com/p/622105041 【3】https://zhuanlan.zhihu.com/p/152065162 【4】http://blockchain.whu.edu.cn/uploads/soft/201105/1_1954088811.pdf【pdf】 【5】https://zhuanlan.zhihu.com/p/343756241【讲的很好】 【6】https://zhuanlan.zhihu.com/p/144899541【讲的较详细】 补充: sigma协议在其它资料中的表述 sigma协议簇: sigma协议簇是指一类的协议,这些协议满足一些指定的要求。值得注意的是,sigma协议簇中的协议都是零知识证明。也就是满足sigma协议簇的协议一定是一个零知识证明。\n二元关系(预备知识): 例如:实数中的关系“大于”可记作 $r={(x,y)|x,y是实数且x\u0026gt;y}$ 其中 r 表示这个关系,它是一个集合,集合中的元素有二元组(x,y)组成 r 中的元素是所有满足 x,y 是实数且 x\u0026gt;y 的二元组 (x,y) 其它二元关系的例子(零知识证明常用到的) sigma协议内容: 以sigma协议为标准构造零知识证明 可以按照以下步骤证明该问题: 证明过程如图所示: 说明:\n该协议是满足sigma协议的三个性质的。因此他是一个零知识证明 sigma协议的扩展 or证明:\nor证明的性质:\n","date":"2024-04-05","permalink":"http://54rookie.com/posts/%E9%9B%B6%E7%9F%A5%E8%AF%86%E8%AF%81%E6%98%8E/","summary":"零知识证明 零知识证明 定义:零知识证明的定义为:证明者(prover)能够在不向验证者(verifier)提供任何有用的信息的情况下,使验证者(verifier)","title":"零知识证明"},]
[{"content":"","date":"0001-01-01","permalink":"http://54rookie.com/archive/","summary":"","title":"archive"},]
[{"content":"\r🌞 分类 one 进制转换器 工具大全 百度 🔨 实用工具 进制转换器 工具大全 百度 📑 分类 three 进制转换器 工具大全 百度 🔖 标签 bookmarks bookmark item one https://bookmark-item-one.com bookmark item two https://bookmark-item-two.com bookmark item three https://bookmark-item-three.com ","date":"0001-01-01","permalink":"http://54rookie.com/nav/","summary":"🌞 分类 ONE 进制转换器 工具大全 百度 🔨 实用工具 进制转换器 工具大全 百度 📑 分类 THREE 进制转换器 工具大全 百度 🔖 标签 BOOKMARKs bookmark item one https://bookmark-item-one.com bookmark item two https://bookmark-item-two.com bookmark item three https://bookmark-item-three.com","title":"nav"},]
[{"content":"","date":"0001-01-01","permalink":"http://54rookie.com/search/","summary":"","title":"search"},]
✖