# 区块链之密码学 **Published by:** [leaf](https://paragraph.com/@leaf-6/) **Published on:** 2022-12-07 **URL:** https://paragraph.com/@leaf-6/mFrRo4rACaYmLIXhex0d ## Content 密码散列函数是一种纯确定性函数,它将输入从大空间映射到固定集中的输出。这些输出通常称为输入摘要。例如,输入可以是本书序言的全部文本,它的摘要可以是从128位值空间中的十六进制 在不使用形式化的情况下,应该使用安全的散列函数。这意味着几乎不可能找到产生相同摘要的两种不同的输入。哈希函数也应该是不可逆转的.如果只给文摘,就不可能找到产生文摘的输入。同样,对输入的圈套变化应该会对输出摘要产生很大的变化--两个类似的输入应该有非常不同的摘要。哈希函数也应该相对较快地从它们的输入中进行计算,因此验证输入匹配及其摘要是一项简单的任务。 哈希数值是保持区块链完整性的核心,也是工作证明共识机制的基础。我们将在几个页面中看到这两种用途。 公钥密码学 公钥密码可以通过许多不同的算法实现,其中最流行的算法之一是RiVest-Shamir-Adleman(更著名的名字是RSA),然而,它依赖于椭圆曲线数字签名算法(ECDSA)。这算法还允许恢复公钥,给定消息及其签名。 有了这两个密码学概念,我们现在终于可以开始学习如何构建块链了。 区块链是一种分散的、不可磨灭的公共数字分类账。我们可以把ablockehain看作一个分布式数据库,在这个数据库中,一旦一个记录被确认,它就永远不能被删除或修改,并且没有一个权威机构可以控制这个数据库,这个数据库在对等网络中的所有节点上都被复制。实际上存储在数据库中的内容可能会有所不同;它可以是货币、资产注册,甚至是可执行的代码。 在块链中,每个状态更改都是事务的一部分。将事务看作是来自全局数据库中的用户的解剖写入操作,该操作可能会更改一个或多个数组。网络中的任何用户都可以提交要执行的事务。 如何处理传输是块链状态传递关系的一部分,通过处理它接收的每个事务,块链从一种状态过渡到另一种状态。例如,管理货币的块链可能处理两个帐户之间的货币转移:它减少发送方的余额,并将收件人的余额增加相同的金额。其他块链甚至允许事务在链上创建和执行完整的程序 当块被添加到块链时,它通过对等网络传播到所有节点。每个节点将重新执行块中的所有事务,以检查它们是否有效,如果它们注意到任何非法更改,则拒绝该块。这意味着每个事务实际上在整个nettwverk中每一个单字节执行一次。这允许块链完全分散,因为每个节点都检查所有正在运行的事务,但这是有代价的:混合开销限制了可以运行的事务的数量。 注意:公共区块链不要求用户注册。他们只需创建一个新的密钥对,就可以开始对事务进行签名以参与网络,但是,他们可能要求用户拥有与区块链相关联的货币来处理他们的事务。 事务以块的形式分批处理,然后将它们链接在一起形成实际的块链。这些块构成了块链的历史,每个块打包了一组改变其状态的事务。在每个块中选择和排序事务的方式取决于块链协商一致规则,我们将在后面的页面中看到这些规则。 当块被添加到块链时,它通过对等网络传播到所有节点。每个节点将重新执行块中的所有事务,以检查它们是否有效,如果它们注意到任何非法更改,则拒绝该块。这意味着每个事务实际上在整个nettwverk中每一个单字节执行一次。这允许块链完全分散,因为每个节点都检查所有正在运行的事务,但这是有代价的:混合开销限制了可以运行的事务的数量。 事务以块的形式分批处理,然后将它们链接在一起形成实际的块链。这些块构成了块链的历史,每个块打包了一组改变其状态的事务。在每个块中选择和排序事务的方式取决于块链协商一致规则,我们将在后面的页面中看到这些规则。 当块被添加到块链时,它通过对等网络传播到所有节点。每个节点将重新执行块中的所有事务,以检查它们是否有效,如果它们注意到任何非法更改,则拒绝该块。这意味着每个事务实际上在整个nettwverk中每一个单字节执行一次。这允许块链完全分散,因为每个节点都检查所有正在运行的事务,但这是有代价的:混合开销限制了可以运行的事务的数量。 当块被添加到块链时,它通过对等网络传播到所有节点。每个节点将重新执行块中的所有事务,以检查它们是否有效,如果它们注意到任何非法更改,则拒绝该块。这意味着每个事务实际上在整个nettwverk中每一个单字节执行一次。这允许块链完全分散,因为每个节点都检查所有正在运行的事务,但这是有代价的:混合开销限制了可以运行的事务的数量。 网络每秒处理。换句话说,区块是以交易数量下放为交换的。 Glven这一高成本处理一个区块链的变更,所有的交易费用都要支付。这种费用通常是以一种原生于区块链的货币支付的(比特币网络中的比特币,或者Eretun中的以太)。不管你是这项费用的受益者,我们将在几页中看到,该费用的目标是防止攻击者将需要由每个节点处理的事务淹没网络,并为向链添加新块的节点提供奖励。 BloekChains通过在每个街区上保存他们整个历史的摘要来抵抗变化。该链中的每个块都是通过在其自身事务上计算的散列以及前一个块的哈希来标识的如何构造块。每个块都由前一个块的散列以及其所有OUN trarsaction标识。我们将在下一节中看到“现在”的角色。使用此方案,对链中任何块上的任何事务的任何更改都将导致所有后续哈希的更改,从而使任何修改变得微不足道。例如,如果攻击者试图更改十个街区前发生的事务,那么不仅该块的摘要会发生变化,而且下一个块的摘要也会发生变化(因为它是基于前一个块哈希计算的),并且一直到链的头但是,为了防止攻击者修改块链并在网络中分发虚假副本,这种机制必须对攻击者重新生成所有块进行区分。这就是工作证明的来源。 交易是有序的,并包括在区块链的区块将取决于衰变的Igorifhn的网络。既然我们所处理的是一个分散化的数据库,我们需要一种让所有参与者都同意如何给粉笔增加变化的方法。例如,如果一个卖家在区块链上提供一项资产,而两个买家争相抢购,一个分散的网络怎么能决定谁是第一个呢?更糟糕的是,如何阻止卖方告诉这两位卖家他们两次进行了购买和现金交易?“我们需要一种方法来确定交易是如何按顺序选择和订购的,以保持区块链的单一状态。换句话说,我们需要一种方法,在哪些区块上添加区块。” 许多公共区块,如比特币或Etalum,都依赖一种被称为“工作证明”的共识算法。工作证明是用于执行计算的大量CPUJ循环的密码证明;在这种情况下,基于块计算一个困难的数字。为了将一个块添加到块链中,它必须伴随着它的证明获得添加块的节点将得到回报,以回报他们的努力。完成这个角色的节点被称为“矿工”,每当添加一个新的块时,他们都会竞相添加下一个来获取相应的奖励。注意,这些证明的机制实际上很简单。链中的标识符前端块是一个散列,它包括前一个块的标识符、块中的所有事务和一个非ct。通过更改当前值,计算块的摘要将完全不同。为了给链添加一个新的块,偷窃者必须有一个特定的结构(从N个零开始)。由于预测摘要会是什么样子并不是被动的,矿工只能反复计算分块,同时更改时值,直到他找到一个符合要求的摘要为止。这需要很多尝试,因此被认为是工作的证明. 请记住,整个基础设施运行在对等分散网络之上。这允许节点按自己的意愿运行和离开网络,而不需要requlringa集中式服务器。在这里,Prool-ol-Work算法为新节点提供了一种方法,可以知道哪一个是实际的链:他们只需要寻找具有最大累积计算能力的链。 这也防止了恶意参与者仅仅更改链上的记录并重新计算所有后续的块哈希,就像我们在上一节中讨论的那样。要做到这一点,攻击者将需要解决所有来自被攻击的块的所有工作的证据,这需要比网络中的其他矿工更强的计算能力, 注除了工作证明外,还有其他协商一致的机制。在……里面 以证明权威和利害关系证明作为建立更快和更小的链的备选方案。 衰老算法与网络的最终性密切相关。当我们知道它已经包含在区块链中并且不会改变的时候,我们说反交换是最终的。在最近的块中添加的事务远未被认为是最终的:如果一个矿工设法在一行中挖掘两个块,从EXT开始到最后,它们可能会生成一个新的链,以替换最新的块,而不包括该事务。 这被称为重组,这在工作证明链中并不少见.要知道一个交易是最终的,我们需要在包含它的交易之上等待几个块被开采,以确保它不会改变。块的数量将取决于特定的链和我们需要多少信心。 论吞吐量通过设计来解决一个工作的证明在计算上是昂贵的.这本身就强制执行对块链吞吐量的依赖,在每次添加大量事务时,强制解决一个dificult难题。然而,还有另一个原因限制了每秒添加到链中的事务:可验证性。为了保持块链的分散性,网络中的每个节点都需要能够验证每个事务是合法的,并按照既定的规则执行。如果网络每秒接受大量事务,那么只有强大的设备。 将能够验证该链,将网络遗漏给任何无法访问必要硬件的用户。因此,低吞吐量与保证对区块链的公共访问有关。 特别是,Etalum被设计为每秒钟处理大约15个事务。注意,在Etalum中事务可能比较复杂,因为它们可以执行任意的计算,因此这个上限实际上与需要多少cffort有关,需要Torun并验证每个块的事务。 请注意,这几个事务每秒在网络中的所有用户和应用程序之间共享。即使对于一个传统的Web应用程序来说,这也是一个非常低的限制。我们将在第8章中看到围绕这一限制的一些方法。 从比特币到以太坊到目前为止,WWE已经将一个块数据作为一个公共数据库,但我们还没有深入了解该数据库可能包含的内容。第一个著名的区块链是用来跟踪拥有的一个数字电流,比特币。 我们今天所理解的区块链,大部分是在2008年由SatoshiNakamotoR在他的“比特币:对等电子现金系统”论文中介绍的?这张纸既短又易读,而且它包装了今天使用的大部分区块链概念。它引入了一个“纯粹的点对点版本的电子现金,没有任何集中所有者或发行人。 总而言之,比特币区块链是一个公共分散数据库,它跟踪用户比特币的平衡,并支持资金从一个审计机构转移到另一个审计中心的交易,这是一个深度集中的电子支付平台的实现。值得一提的是,除了普通的传输,比特币还支持有限的脚本语言。这种语言允许构造,例如Timeelocks,它限制执行fom到将来某一段时间,或者多重签名。交易,需要多个帐户同意才能移动资产。用这种语言可以建立的东西仍然是有限的。正是为了支持网络中的仲裁计算而提出的。密码学的历史悠久,古时候主要应用于军事机密的传送,如“口令”,“暗号”等。在1970年之前,密码学的应用范畴大部分还是在政府层面,直到标准加密系统-数据加密标准和非对称加密算法的发明,密码学才逐步被深入应用在各个领域。密码学的发展历程密码学的发展大致可以分为三个阶段:古典密码学->现代密码学->公钥密码学 1.古典密码学:这阶段的核心密码学思想主要为代替和置换。代替就是将明文每个字符替换成另外一种字符产生密文,接收者根据对应的字符替换密文就得到明文了。置换就是将明文的字符顺序按照某种规则打乱。 2.现代密码学:这阶段的发展主要是对称加密算法。对称加密是发送方使用某种公开的算法使用密钥对明文进行加密,接收方使用之前发送方给予的密钥对密文进行解密得到明文。 3.公匙密码学:这个阶段的发展主要是非对称加密算法。非对称加密的原理是公钥加密,私钥解密。它的实现过程是A通过某种算法产生一对密钥,分别是公钥和私钥,然后将公钥公开。B想发送信息给A,就使用A的公钥对明文进行加密产生密文并发送给A。A接收到密文后,用自己的私钥对密文进行解密,得到明文。非对称加密解密的示意图如下非对称加密涉及到了:公钥和私钥 特点是:特性1:使用公钥加密的数据只有私钥才能解密,公钥自己是解密不了的。特性2:使用私钥加密的数据只有公钥才能解密,私钥自己是解密不了的。服务端同时持有公钥和私钥(不会给任何人)。服务端要跟谁通信就把自己的公钥给它。使用非对称加密的交互的过程如下:客户端先拿到服务端的公钥,然后使用这个公钥加密数据,再把加密后的数据发送给服务端,由于上面说的特性1、2,这时只有服务端才能正确的解密出数据。 密码学在区块链的应用非常广泛,可分为3类:对称加密算法、非对称加密算法和哈希散列算法。常见的方法有: Merkle tree 哈希树算法,椭圆曲线算法,SHA-256算法,Base58编码。作用有:通过hash算法快速查找;对明文进行加解密;对信息进行签名以及验证;产生数字证书;生成账户地址等。 对称加密算法是应用较早的加密算法,技术成熟。 1 对称加密的使用过程 在对称加密算法中,数据发信方将明文(原始数据)和加密密钥一起经过特殊加密算法处理后,使其变成复杂的加密密文发送出去。收信方收到密文后,若想解读原文,则需要使用加密用过的密钥及相同算法的逆算法对密文进行解密,才能使其恢复成可读明文。在对称加密算法中,使用的密钥只有一个,发收信双方都使用这个密钥对数据进行加密和解密,这就要求解密方事先必须知道加密密钥。 2 对称加密的特点 对称加密算法的特点是算法公开、计算量小、加密速度快、加密效率高。不足之处是,交易双方都使用同样钥匙,安全性得不到保证。此外,每对用户每次使用对称加密算法时,都需要使用其他人不知道的惟一钥匙,这会使得发收信双方所拥有的钥匙数量呈几何级数增长,密钥管理成为用户的负担。假设两个用户需要使用对称加密方法加密然后交换数据,则用户最少需要2个密钥并交换使用,如果企业内用户有n个,则整个企业共需要n×(n-1) 个密钥,密钥的生成和分发将成为企业信息部门的恶梦。对称加密算法的安全性取决于加密密钥的保存情况,但要求企业中每一个持有密钥的人都保守秘密是不可能的,他们通常会有意无意的把密钥泄漏出去——如果一个用户使用的密钥被入侵者所获得,入侵者便可以读取该用户密钥加密的所有文档,如果整个企业共用一个加密密钥,那整个企业文档的保密性便无从谈起。 对称加密算法在分布式网络系统上使用较为困难,主要是因为密钥管理困难,使用成本较高。而与公开密钥加密算法也就是非对称加密算法比起来,对称加密算法能够提供加密和认证,却缺乏了签名功能,使得使用范围有所缩小。 3 对称加密的划分 对称加密分为序列密码和分组加密。 序列密码,也叫流加密(stream cyphers),依次加密明文中的每一个字节。加密是指利用用户的密钥通过某种复杂的运算(密码算法)产生大量的伪随机流,对明文流的加密。解密是指用同样的密钥和密码算法及与加密相同的伪随机流,用以还原明文流。 分组密码,也叫块加密(block cyphers),一次加密明文中的一个块。是将明文按一定的位长分组,明文组经过加密运算得到密文组,密文组经过解密运算(加密运算的逆运算),还原成明文组。 序列密码,也叫流密码,是利用种子密钥通过密钥流生成器产生与明文长度一致的伪随机序列,该随机序列与明文进行某种算法相结合产生的密文的一种密码算法。 使用序列密码对某一消息m执行加密操作时,都是按字进行加密的,一般是先将m分成连续的字符,m=m1m2m3…;然后使用密钥流k=k1k2k3...中的第i个字符ki对明文消息的第i字符mi执行加密变换,i=1,2,3...;所有的加密输出连接在一起就构成了对m执行加密后的密文。解密需要和加密同步进行,所以使用序列密码,发送方和接收方需要对明文或密文在信息的同一位置进行操作。 加解密过程示意图如下:序列密码具有实现简单、便于硬件实施、加解密处理速度快、没有或只有有限的错误传播等特点,但是因为这类密码主要运用在军事,政治机密机构上,因此它的研究成果较少有公开。目前可以公开在其他领域应用的算法有RC4,SEAL,A5等。一次一密加密一次一密加密是1917年Mauborgne和Vernam联合提出的一种理想加密方案,它要求对明文消息逐字符加密,每个字符加密都是独立的,加密的密钥是与明文长度一致的毫无规则随机的密钥序列,这个密钥序列就是一次一密乱码本。在使用时,密文发送方和接收方手里头都拥有一个一模一样的乱码本,该乱码本是由双方协商确定的一个足够长的随机密钥序列。发送方对明文进行加密时,用到的密钥序列是来自乱码本发送消息长度的最前面的一段,加密完后,立马对用过的密钥序列进行销毁。接收方收到密文后,用乱码本的密钥序列依次对密文进行解密,得到明,同时也要把用过的密钥序列进行销毁,不再使用。 一次一密的乱码本被认为是无条件安全的密码体制,即是一种不可攻破的密码体制。窃听者得到密文信息,根本没有可能对其进行解密,因为加密的密钥是完全随机的,毫无规律,攻击者没有任何信息对它进行密文分析。但前提是一次一密乱码本不能泄漏。 一次一密概念的提出对序列密码的产生提供了方向。正是基于这个概念,越来越多的序列密码算法不断产生。序列密码的结构序列密码的结构可细分为同步流密码和自同步流密码。同步流密码指它的密钥流的产生与明文无关,而是通过某种独立的随机方法产生的伪随机数字流。自同步流密码也叫异步流密码,它与同步流密码相反,密钥流的产生与明文有关,具体是后一个密钥字的产生与前一个明文加密后的字有关。自同步密码这个特性,使得它非常难以研究,所以大部分序列密码的研究都集中在同步密码上。同步流密码同步流密码产生密码流的过程分为两部分,一个是密钥流产生器,另一个是加密变换器。 加密过程表达式是:ci=E(ki,mi),参数都是字节数组的单个元素。解密过程和加密过程必须同步,表达式是一个。因为密钥流的产生每次都是不一样的。所以加密时,每次产生的密钥流元素先缓存到寄存器中,等解密用完这个元素以后再继续进行加密。整个过程有点类似tcp协议。目前最为常用的流密码体制是有限域GF(2)上的二元加法流密码,其加密变换可表示为ci=ki⊕mi。 特点: 在同步流密码中,发送方和接收方必须是同步的,即双方使用同样的密钥,对同一位置进行操作。一旦密文字符在传输中出现丢失,损坏或者删除,那么解密将失败 2)无错误传播 密文字符在传输过程中被修改,只是对该字符产生影响,并不影响其他密文字符的解密。 3)主动攻击性破坏同步性 作为同步要求的结果,主动攻击者对传输中的密文字符进行重放,插入,删除等破坏操作,直接会造成加解密过程的同步性。所以在使用时,需要借助其他密码学技术对传输的密文进行认证和完整性的验证操作。 2.2 自同步流密码 自同步密码的密钥流的产生不独立于明文流和密文流,通常第i个密钥字的产生不仅与主密钥有关,而且与前面已经产生的若干个密文字有关。 特点: 1)自同步 发送方在传输密文流过程中,某些密文字符被攻击,接收方的解密只是在这些被攻击过的密文与发送方不同步,而其他密文流解密同步不会有问题。 2)有限的错误传播 接收方的解密只是对攻击过的i个密文字符有影响,而对其他密文流不会有问题。所以产生的明文至多有i个错误。 3)主动攻击破坏当前的同步性 4)明文统计扩算 每个明文字符都会影响其后的整个密文,即明文的统计学特性扩散到了密文中。因此,自同步流密码在抵抗利用明文冗余而发起的攻击方面要强于同步流密码。 2.3 密钥流生成器 流密钥的重要部分是密钥流生成器。理想的密钥流生成器是生成完全随机的密钥流,但实际中因为它是根据用户的私钥通过一定的算法产生的,不可能做到真正的随机,所以产生的密钥流是伪随机序列。 一个密钥流生成器通常由一个线形反馈移位寄存器(LFSR)和一个非线形组合部分构成。线形反馈移位寄存器可以称为驱动部分。其工作原理是将驱动部分在j时刻的状态变量x作为输入,输入到非线形组合部分f,将f(x)作为当前时刻的kj。驱动部分负责提供非线形组合部分使用的序列,而非线形部分以各时刻移位寄存器的状态组合出密钥序列j时刻的值kj。通俗讲就是驱动部分内部的变量是不断变化的,在每个不同时刻的值都是不一样的,它不断向非线形组合部分输入变量x的不同时刻的值,非线形组合部分接收到此时刻的x,通过函数f产生当前的密钥流字节。 2.4 反馈移位寄存器 反馈移位寄存器是流密码产生密钥流的一个重要组成部分,GF(2)上一个n级反馈移位寄存器由n个二元存储器和一个反馈函数f(a1,a2,a3,a4,...,an)组成,n级反馈移位寄存器如下图所示。每一存储器称为移位寄存器的一级,在任一时刻,这些级的内容构成反馈位移寄存器的状态,在每一状态对应GF(2)上一个维向量,总共有2^n 种可能的状态。每一时刻的状态可用长为n的序列a1,a2,a3,...,an或者n维向量(a1,a2,a3,...,an)表示,其中ai是当前时刻第i级存储器的内容。初始状态由用户确定,当第i个移位时钟脉冲到来时,每一级存储器ai都将其内容向下一级ai-1传递,反馈函数f(a1,a2,a3,a4,...,an)根据寄存器当前的状态计算出下一时刻的an。反馈函数是一个n元布尔函数,即n个变量a1,a2,a3,...,an可以分别独立地取0和1两个可能的值,函数中的运算有逻辑与、逻辑或、逻辑补等运算,最后的函数值为0或1.RC4算法RC4加密算法是RSA三人组中的头号人物Ron Rivest在1987年设计的密钥长度可变的流加密算法簇。该算法的速度可以达到DES加密的10倍左右,且具有很高级别的非线性。1994年9月,它的算法被发布在互联网上。由于RC4算法加密是采用的xor,所以,一旦子密钥序列出现了重复,密文就有可能被破解。RC4作为一种老旧的验证和加密算法易于受到黑客攻击,现在逐渐不推荐使用了。 RC4算法的实现非常简单,使用从1到256个字节(8到2048位)可变长度密钥初始化一个256个字节的状态向量S,S的元素记为S[0],S[1],S[2],...,S[255],S先初始化为S[i]=i。以后自始至终都包含从0到255的所有8比特数,只是对它进行置换操作。每次生成的密钥字节ki由S中256个元素按一定方法选出一个元素而生成。每生成一个密钥字节,S向量中元素会进行一次置换操作。则RC4算法分为两部分:初始化S和密钥流的生成,其中密钥流的生成过程中每次产生的密钥字与对应明文的元素进行异或运算得到密文字 1.1 初始化S 生成S的步骤如下: 1)声明一个长度为256的字节数组,并给S中的元素从0到255以升序的方式填充,即S[0]=0,S[1]=1,S[2]=2,...,S[255]=255。 2)j:=0 3)对于0<=i<=255,循环下边两个方法: j = (j + S[i] + int(K[i%keylen])) % 256 S[i], S[j]=S[j], S[i] 1.2 密钥流的生成 步骤如下:步骤如下: 1)i=0;j=0 2)i = (i + 1) % 256 3)j = (j + S[i]) % 256 4)S[i], S[j]=S[j], S[i] 5)输出密钥字key = S[(S[i]+S[j])%256] 1.3 RC4的安全性 由于RC4算法加密采用的是异或方式,所以,一旦子密钥序列出现了重复,密文就有可能被破解,但是目前还没有发现密钥长度达到128位的RC4有重复的可能性,所以,RC4也是目前最安全的加密算法之一。 1.4 RC4加密过程 简单介绍下RC4的加密过程: 1)利用自己的密钥,产生密钥流发生器 2)密钥流发生器根据明文的长度产生伪随机序列 3)伪随机序列每个位元素与明文对应的位元素进行异或运算,生成密文 示意图:golang实现RC4加密:package main import "fmt" func main() { data := []byte("helloworld"); output:=make([]byte,len(data)) fmt.Printf("明文:%s\n", data); K := []byte("qwuoaknfabbalafbj"); keylen := len(K); SetKey(K, keylen); output=Transform(output, data, len(data)); fmt.Printf("密文: %x\n", output); SetKey(K, keylen); output1:=make([]byte,len(data)) output1=Transform(output1, output, len(data)); fmt.Printf("解密后明文:%s", output1); } var S = [256]int{} //初始化S盒 func SetKey(K []byte, keylen int) { for i := 0; i < 256; i++ { S[i] = i; } j := 0; for i := 0; i < 256; i++ { j = (j + S[i] + int(K[i%keylen])) % 256; S[i], S[j]=S[j], S[i]; } } //生成密钥流 func Transform(output []byte, data []byte, lenth int)[]byte { i := 0; j := 0 output=make([]byte,lenth) for k := 0; k < lenth; k++ { i = (i + 1) % 256; j = (j + S[i]) % 256; S[i], S[j]=S[j], S[i]; key := S[(S[i]+S[j])%256]; //按位异或操作 output[k] = uint8(key)^data[k]; } return output } 利用goland封装好的方法实现RC4加密,但是没有解密package main import ( "fmt" "crypto/rc4" ) func main() { var key []byte = []byte("fd6cde7c2f4913f22297c948dd530c84") //初始化用于加密的KEY rc4obj, _ := rc4.NewCipher(key) //返回 Cipher str := []byte("helloworld") //需要加密的字符串 plaintext := make([]byte, len(str)) // rc4obj.XORKeyStream(plaintext, str) //XORKeyStream方法将src的数据与秘钥生成的伪随机位流取XOR并写入dst。 //plaintext就是你加密的返回过来的结果了,注意:plaintext为base-16 编码的字符串,每个字节使用2个字符表示 必须格式化成字符串 stringinf := fmt.Sprintf("%x\n", plaintext) //转换字符串 fmt.Println(stringinf) } 分组密码,也叫块加密,英文Block Cyper,一般先对明文m进行填充得到一个长度是固定分组长度s的整数倍明文串M;然后将M划分成一个个长度为s的分组;最后对每个分组使用同一个密钥执行加密变换。比较常见的算法有AES;DES;3DES。 分组密码中,无论是明文块还是密文块,块与块之间都有一些逻辑运算关系,这些关系即为运算的模式。 比较常见的分组密码运算的五种模式: Electronic Code Book(ECB)电子密码本模式Cipher Block Chaining(CBC)密码分组链接模式Cipher Feedback Mode(CFB)加密反馈模式Output Feedback Mode(OFB)输出反馈模式Counter mode(CTR)计数器模式 目前推荐使用的是CBC模式和CTR模式,其它模式较少使用或不推荐使用。1. ECB模式 ECB又称电子密码本模式,英文全称是Electronic codebook,是最基本的块密码加密模式,加密前根据加密块大小(如AES为128位)分成若干块,如果最后一块不足128位,使用填充(具体看算法,默认是0x00),之后将每个块使用相同的密钥单独加密得到密文块,然后将密文块连在一起就得到密文了。解密同理。 下图展示ECB模式加解密的过程: 加密过程:解密过程:由此得知相同的明文内容将永远加密成相同的密文,而且密文的格式和明文也相同。这是很不安全的,尤其是传输图片或明文内容重复很多的情况下。由于所有分组的加密方式一致,明文中的重复内容会在密文中有所体现,因此难以抵抗统计分析攻击。还有因为明文和密文的内容顺序一致,攻击者很容易破坏密文。攻击者在密文传输过程中截获,并对密文内容次序打乱,接收密文信息者得到的密文就不可能解密成原本的明文信息了。这也是ECB模式很少使用的原因。 特点: 1.操作简单,易于实现,有利于并行计算,误差不会被传送; 2.不能隐藏明文的模式; 3.可能对明文进行主动攻击;CBC模式CBC又称密文分组链接模式,英文全称是Cipher Block Chaining,之所以叫这个名字,是因为密文分组像链条一样相互连接在一起。 在CBC模式中,每个明文块先与前一个密文块进行异或后,再进行加密。在这种方法中,每个密文块都依赖于它前面的所有明文块。同时,为了保证每条消息的唯一性,在第一个块中需要使用初始化向量。 若第一个块的下标为1,则CBC模式的加密过程为: Ci = Ek (P ⊕ Ci-1), C0 = IV. 而其解密过程则为: Pi = Dk (Ci) ⊕Ci-1, C0 = IV. CBC模式运算过程示意图CBC算法优点: 明文的重复排列不会反映在密文中支持并行计算(仅解密)能够解密任意密文分组 CBC算法缺点:对包含某些错误比特的密文进行解密时,第一个分组的全部比特以及后一个分组的相应比特会出错加密不支持并行计算CFB又称密文反馈,英文全称为Cipher feedback。模式类似于CBC,可以将块密码变为自同步的流密码;工作过程亦非常相似。需要使用一个与块的大小相同的移位寄存器,并用IV将寄存器初始化。然后,将寄存器内容使用块密码加密,然后将结果的最高x位与平文的x进行异或,以产生密文的x位。下一步将生成的x位密文移入寄存器中,并对下面的x位平文重复这一过程。解密过程与加密过程相似,以IV开始,对寄存器加密,将结果的高x与密文异或,产生x位平文,再将密文的下面x位移入寄存器。 与CBC相似,明文的改变会影响接下来所有的密文,因此加密过程不能并行化;而同样的,与CBC类似,解密过程是可以并行化的。 CFB模式运算过程示意图CFB模式的优点:不需要填充(padding)支持并行计算(仅解密)能够解密任意密文分组CFB模式的缺点: 加密不支持并行计算对包含某些错误比特的密文进行解密时,第一个分组的全部比特以及后一个分组的相应比特会出错不能抵御重放攻击OFB:将分组密码作为同步序列密码运行,和CFB相似,不过OFB用的是前一个n位密文输出分组反馈回移位寄存器,OFB没有错误扩散问题。 输出反馈模式(Output feedback, OFB)可以将块密码变成同步的流密码。它产生密钥流的块,然后将其与平文块进行异或,得到密文。与其它流密码一样,密文中一个位的翻转会使平文中同样位置的位也产生翻转。这种特性使得许多错误校正码,例如奇偶校验位,即使在加密前计算而在加密后进行校验也可以得出正确结果。 每个使用OFB的输出块与其前面所有的输出块相关,因此不能并行化处理。然而,由于平文和密文只在最终的异或过程中使用,因此可以事先对IV进行加密,最后并行的将平文或密文进行并行的异或处理。 可以利用输入全0的CBC模式产生OFB模式的密钥流。这种方法十分实用,因为可以利用快速的CBC硬件实现来加速OFB模式的加密过程。 加密过程:OFB模式的优点: 不需要填充(padding)可事先进行加密、解密的准备加密、解密使用相同结构对包含某些错误比特的密文进行解密时,只有铭文中相应的比特会出错OFB模式的缺点:不支持并行运算主动攻击这反转密文分组中的某些比特时,明文分组中相对应的比特也会被反转CTR模式计数模式(CTR模式)加密是对一系列输入数据块(称为计数)进行加密,产生一系列的流密码,流密码与明文异或得到密文,同样解密就是流密码与密文异或得到明文。 数据块是加密之前通过将逐次累加的计数器产生不同的比特序列,它是由nonce和counter(分组序号)构成的。CTR计数器,长度是128比特(16字节)。前8个字节是叫做nonce的初始值,这个值每次加密都不相同。后8个字节则是分组序号,也就是不断+1得到的值。nonce的作用是让数据块内容复杂化。如果没有nonce,只有counter,数据块过于单一。Golang里封装的计数器实现与这里讲的有些许不同,首先初始化一个长度为BLOCK.SIZE()的初始向量iv,然后iv最后一个字节通过计数器逐组递增,同样也会产生分组加密之前不同的数据块。 加密的过程就是生成一个初始的计数器。假设有8个分组,就通过初始计数器不断+1得到8个计数器值,每个计数器值再加密得到密钥流,每个密钥流和对应分组明文异或得到密文。所以它的加密过程相当于一次一密。 CTR模式中可以以任意顺序对分组进行加密和解密,因为在加密和解密时需要用到的“计数器”的值可以由nonce和分组序号直接计算出来。这就意味着能够实现并行计算。在支持并行计算的系统中,CTR模式的速度是非常快的。 下图展示CTR模式的加解密的过程: 加密过程:unc AesCTR_Encrypt(plainText,key[]byte)[]byte{ //判断用户传过来的key是否符合16字节,如果不符合16字节加以处理 keylen:=len(key) if keylen==0{ //如果用户传入的密钥为空那么就用默认密钥 key=[]byte("wumansgygoaescbc") //默认密钥 }else if keylen>0&&keylen<16{ //如果密钥长度在0到16之间,那么用0补齐剩余的 key=append(key,bytes.Repeat([]byte{0},(16-keylen))...) }else if keylen>16{ key=key[:16] } //1.指定使用的加密aes算法 block, err := aes.NewCipher(key) if err!=nil{ panic(err) } //2.不需要填充,直接获取ctr分组模式的stream // 返回一个计数器模式的、底层采用block生成key流的Stream接口,初始向量iv的长度必须等于block的块尺寸。 iv := []byte("wumansgygoaesctr") stream := cipher.NewCTR(block, iv) //3.加密操作 cipherText := make([]byte,len(plainText)) stream.XORKeyStream(cipherText,plainText) return cipherText } func AesCTR_Decrypt(cipherText,key []byte)[]byte{ //判断用户传过来的key是否符合16字节,如果不符合16字节加以处理 keylen:=len(key) if keylen==0{ //如果用户传入的密钥为空那么就用默认密钥 key=[]byte("wumansgygoaescbc") //默认密钥 }else if keylen>0&&keylen<16{ //如果密钥长度在0到16之间,那么用0补齐剩余的 key=append(key,bytes.Repeat([]byte{0},(16-keylen))...) }else if keylen>16{ key=key[:16] } //1.指定算法:aes block, err:= aes.NewCipher(key) if err!=nil{ panic(err) } //2.返回一个计数器模式的、底层采用block生成key流的Stream接口,初始向量iv的长度必须等于block的块尺寸。 iv := []byte("wumansgygoaesctr") stream := cipher.NewCTR(block, iv) //3.解密操作 plainText := make([]byte,len(cipherText)) stream.XORKeyStream(plainText,cipherText) return plainText } CTR模式的优点: 不需要填充(padding)可事先进行加密、解密的准备加密、解密使用相同的结构对包含某些错误比特的密文进行解密时,只有明文中相对应的比特会出错支持并行计算(加密、解密) CTR模式的缺点:主动攻击者反转密文分组中的某些比特时,明文分组中对应的比特也会被反转没有错误传播,不适合用于数据完整性认证DES算法DES算法为密码体制中的对称密码体制,又被称为美国数据加密标准,是1972年美国IBM公司研制的对称密码体制加密算法,英文全称是Data Encryption Standard。于1973年5月被美国采纳为联邦信息处理标准。该标准每5年审查一次。因为DES的安全性出现问题,同时AES的出现,美联邦在1994年1月取消了DES作为联邦加密标准。DES加密不断被破解,其中用时最短的时间是22小时15分钟,所以DES算法现在应用越来越少了。 DES是以64比特的明文为一个单位来进行加密的,超过64比特的数据,要求按固定的64比特的大小分组。每组64比特的明文加密得到同样长度的密文。DES的密钥长度为64位。加密运算时实际用到的密钥长度是56位,原密钥舍弃掉8位比特,分别是每隔8位的比特,即原密钥的第8位,第16位,......,第64位。而舍弃掉的这8位比特作用是校验奇偶性的。这8个比特的定义如下:若其前面7个比特中有奇数个1,则该比特为0,反之为1。 DES是一个迭代分组密码,在对明文加密之前先对明文进行补长,使补长后明文的比特长度模64为0,再按照每组64比特分组。依次对分组密文进行加密,最终把加密后的结果拼接一起,得到密文。 每组64位的输入数据块m的加密过程如下:首先m经过初始置换IP得到m0 ;将m0分成左右各为32比特两部分,记为m0 = L0 R0 ;对L0和R0进行16轮迭代运算加密,得到L16和R16;再对L16R16进行初始置换IP的逆初始置换IP^-1 ,得到该分组输入块的密文。 DES是一个迭代分组密码,在对明文加密之前先对明文进行补长,使补长后明文的比特长度模64为0,再按照每组64比特分组。依次对分组密文进行加密,最终把加密后的结果拼接一起,得到密文。 每组64位的输入数据块m的加密过程如下:首先m经过初始置换IP得到m0 ;将m0分成左右各为32比特两部分,记为m0 = L0 R0 ;对L0和R0进行16轮迭代运算加密,得到L16和R16;再对L16R16进行初始置换IP的逆初始置换IP^-1 ,得到该分组输入块的密文。 DES加密总体框架如下图框架如下图初始置换IP是将一个64比特的消息中的各个比特进行换位,目的是让消息中的各个比特的顺序错乱。设m=m1m2...m64,根据初始置换IP表进行置换。初始置换IP表里的元素代表m的第几位。置换原则是按行依次对m的每个比特进行替换,比如m1置换后是m58,m2置换后是m50,以此类推,最后的m64置换后是m7。经过置换后的分组明文块由两部分组成,记为L0R0。 初始置换IP表:16轮迭代运算 迭代运算的结构是Feistel。在Feistel结构中,加密的每个过程称为轮,全过程就是若干轮的加密运算。DES第16轮加密运算与前15轮不一样,过程分为两步:1.和前15轮一样得到L16和R16;2.将得到的L16和R16两部分整体进行互换,得到最终的L16R16。 每一轮的运算规则如下: Li = Ri-1 Ri = Li-1 ⊕ f(Ri-1 , ki) 其中L0和R0已知;⊕表示两个比特串按位异或,f是一个非线形函数,ki是由密钥按照一定规则每一轮生成的长度均为48位的比特串。 具体的迭代过程如下图所示//PC-1置换表 private int[] PC1={ 57,49,41,33,25,17,9, 1,58,50,42,34,26,18, 10,2,59,51,43,35,27, 19,11,3,60,52,44,36, 63,55,47,39,31,23,15, 7,62,54,46,38,30,22, 14,6,61,53,45,37,29, 21,13,5,28,20,12,4}; //PC-2置换表 private int[] PC2={ 14,17,11,24,1,5,3,28, 15,6,21,10,23,19,12,4, 26,8,16,7,27,20,13,2, 41,52,31,37,47,55,30,40, 51,45,33,48,44,49,39,56, 34,53,46,42,50,36,29,32}; //循环左移次数表 private int[] leftTable = {1,1,2,2,2,2,2,2,1,2,2,2,2,2,2,1}; package symmetricipher; /** * @description: 代码实现Des算法加解密 * @author sakura * @date 2019年3月25日 下午12:52:21 */ /* * 1.主要的一个迭代公式 Li=Ri Ri = Li-1 ⊕F(Li-1,Ki) * 2.整体可以分为 加解密运算 F函数的处理 子密钥的产生 * 3.子秘钥产生:64位经过PC-1密钥置换成56位 分为Ci Di左右各28为位 然后根据循环左移表来左移 最后经过PC-2置换成48位的密钥Ki * 4.F函数的处理:Li-1(32位)经过E盒扩展成48位; 48位的Li-1与 子秘钥Ki进行异或 ; * 异或的结果经过S盒(8个盒子 6进4出)生成32位;32位再经过P盒转换成最后32位F函数处理后的结果 * 5.加解密运算这边:先将明文做一个IP置换,然后将64位分成左右32位L0,R0 然后开始迭代 ;到第16次,做IP逆置换生成最终的密文 * * 6.解密运算: * 加密反过来 * */ public class DES { //初始IP置换 private int[] IP={ 58,50,42,34,26,18,10,2, 60,52,44,36,28,20,12,4, 62,54,46,38,30,22,14,6, 64,56,48,40,32,24,16,8, 57,49,41,33,25,17,9,1, 59,51,43,35,27,19,11,3, 61,53,45,37,29,21,13,5, 63,55,47,39,31,23,15,7}; //IP逆置换 private int[] IP1={ 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}; //E扩展 private int[] ETable={ 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}; //P置换 private int[] P={ 16,7,20,21,29,12,28,17, 1,15,23,26,5,18,31,10, 2,8,24,14,32,27,3,9, 19,13,30,6,22,11,4,25}; //S盒 private static final int[][][] SBox = { { { 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 } }, { { 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 } }, { { 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 } }, { { 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 } }, { { 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 } }, { { 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 } }, { { 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 } }, { { 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 } } }; //PC-1置换表 private int[] PC1={ 57,49,41,33,25,17,9, 1,58,50,42,34,26,18, 10,2,59,51,43,35,27, 19,11,3,60,52,44,36, 63,55,47,39,31,23,15, 7,62,54,46,38,30,22, 14,6,61,53,45,37,29, 21,13,5,28,20,12,4}; //PC-2置换表 private int[] PC2={ 14,17,11,24,1,5,3,28, 15,6,21,10,23,19,12,4, 26,8,16,7,27,20,13,2, 41,52,31,37,47,55,30,40, 51,45,33,48,44,49,39,56, 34,53,46,42,50,36,29,32}; //循环左移次数表 private int[] leftTable = {1,1,2,2,2,2,2,2,1,2,2,2,2,2,2,1}; //加密轮数16轮 private static final int LOOP = 16; private String[] keys = new String[LOOP]; private String[] pContent; private String[] cContent; private int originLength; //初始明文长度 //16个子密钥 private int[][] subKey = new int[16][48]; //存储16次的子密钥 private String content; private int pOriginLegth; //明文初始长度? //构造函数 public DES(String key, String content) { this.content = content; pOriginLegth = content.getBytes().length; generateSubKey(key); } //主函数入口 public static void main(String[] args) { String plainText = "SakuraOne"; System.out.println("明文: \n" + plainText); String key = "IAMKEY"; DES des = new DES(key,plainText); byte[] c = des.group(plainText.getBytes(), true);//加密 System.out.println("密文:\n" + new String(c)); byte[] p = des.group(c, false); //解密 byte[] pd = new byte[plainText.getBytes().length]; System.arraycopy(p, 0, pd, 0, plainText.getBytes().length); System.out.println("解密后的明文:\n" + new String(pd)); } /** *拆分分组 */ public byte[] group(byte[] plainText, boolean decryption) { //填充明文长度为64位的整数 originLength = plainText.length; int gNum; int rNum; gNum = originLength/8; rNum = 8-(originLength-gNum*8); byte[] pPadding; if(rNum<8) { pPadding = new byte[originLength+rNum]; System.arraycopy(plainText, 0, pPadding, 0, originLength); for(int i=0; i<rNum; i++) { pPadding[originLength+1]=(byte)rNum; } }else { pPadding = plainText; } gNum = pPadding.length/8; byte[] groupPT = new byte[8]; //64位分组单位 byte[] resultData = new byte[pPadding.length]; for(int i=0; i<gNum; i++) { System.arraycopy(pPadding, i*8, groupPT, 0, 8); System.arraycopy(encryptUnit(groupPT, subKey, decryption), 0, resultData, i*8, 8); } //如果是解密 这里感觉什么也没有做呢?? if(decryption == false) { byte[] pResultData = new byte[pOriginLegth]; System.arraycopy(resultData, 0, pResultData, 0, pOriginLegth); return pResultData; } return resultData; } /** *加密一个64位分组 * */ public byte[] encryptUnit(byte[]unit, int keysArray[][], boolean decryption) { //得到明文的01字符串 StringBuilder sb = new StringBuilder(); for(int i=0; i<8; i++) { String tmpBit = Integer.toBinaryString(unit[i] & 0xff); while(tmpBit.length()%8!=0) { tmpBit="0"+tmpBit; } sb.append(tmpBit); } //将明文01字符串转换为数字01存放在数组中 int[] pBit = new int[64]; String pStr = sb.toString(); for(int i=0; i<64; i++) { int bit = Integer.valueOf(pStr.charAt(i)); if(bit == 48) { bit = 0; }else if(bit == 49){ bit = 1; }else { System.out.println("To bit error"); } pBit[i] = bit; } /*=========IP置换==========*/ int[] pIP = new int[64]; for(int i=0; i<64; i++) { pIP[i] = pBit[IP[i]-1]; } //加密 if(decryption) { //迭代16次 for(int i=0; i<16; i++) { loop(pIP, i, decryption, keysArray[i]); } }else { //解密 反向迭代 for(int i=15; i>-1; i--) { loop(pIP, i, decryption, keysArray[i]); } } /*===========IP逆置换=============*/ int[] c = new int[64]; for(int i=0; i<IP1.length; i++) { c[i] = pIP[IP1[i]-1]; } byte[] cByte = new byte[8]; for(int i=0; i<8; i++) { cByte[i] = (byte)((c[8*i]<<7)+(c[8*i+1]<<6)+(c[8*i+2]<<5)+(c[8*i+3]<<4)+(c[8*i+4]<<3)+(c[8*i+5]<<2)+(c[8*i+6]<<1)+(c[8*i+7])); } return cByte; //最终的密码字节数组 } //依次迭代过程 public void loop(int[] median, int times, boolean decryption, int[]keyArray ) { int[] l0 = new int[32]; int[] r0 = new int[32]; int[] l1 = new int[32]; int[] r1 = new int[32]; int[] f = new int[32]; //调用F函数后生成的结果 System.arraycopy(median, 0, l0, 0, 32); System.arraycopy(median, 32, r0, 0, 32); l1 = r0; f = fFunction(r0, keyArray); //调用F函数 for(int i=0; i<32; i++) { r1[i] = l0[i]^f[i]; //ri = li-1 ^ f[i] if(((decryption==false) && (times==0)) || ((decryption==true) && (times==15))) { median[i] = r1[i]; median[i+32] = l1[i]; }else { median[i] = l1[i]; median[i+32] = r1[i]; } } } /** * F函数 */ public int[] fFunction(int[] rContent, int[] key) { int[] result = new int[32]; int[] rXORkey = new int[48]; //ri扩展 与 keyi异或 for(int i=0; i<ETable.length; i++) { rXORkey[i] = rContent[ETable[i]-1]^key[i]; } /*=============S-box替换 将48位变成32位==============*/ int[][] s= new int[8][6]; int[] sAfter = new int[32]; for(int i=0; i<8; i++) { System.arraycopy(rXORkey, i*6, s[i], 0, 6); int r = (s[i][0]<<1)+s[i][5]; //横坐标 int c = (s[i][1]<<3) + (s[i][2]<<2) + (s[i][1]<<1) + s[i][4]; //纵坐标 String str = Integer.toBinaryString(SBox[i][r][c]); while(str.length() < 4) { str = "0"+str; } for(int j=0; j<4; j++) { int p=Integer.valueOf(str.charAt(j)); if(p==48) { p=0; }else if(p==49) { p=1; }else { System.out.println("To bit error!"); } sAfter[4*i+j] = p; } } /*===============P盒替换=====================*/ for(int i=0; i<P.length; i++) { result[i] = sAfter[P[i]-1]; } return result; } /** * description:生成子密钥 * * @param key 密钥 * */ public void generateSubKey(String key) { //当key的长度小于64位时要扩展至64位 while(key.length()<8) { key = key + key; } key = key.substring(0, 8); //将字符密钥转换成二进制形式 byte[] keys = key.getBytes(); int[] kBit = new int[64]; for(int i=0; i<8; i++) { //每个字节即每8位&0000 0000 String kStr = Integer.toBinaryString(keys[i] & 0xff); //补齐8位 if(kStr.length()<8) { for(int t=0; t<8-kStr.length(); t++) { kStr = "0" + kStr; } } //将01字符串转换成二进制01 for(int j=0; j<8; j++) { int p = Integer.valueOf(kStr.charAt(j)); if(p == 48) { p=0; }else if(p == 49) { p=1; }else { System.out.println("To bit error!"); } kBit[i*8+j] = p; } } //得到kBit 初始化的64位密钥 然后进行PC-1压缩成56位 /*==============PC-1压缩===============*/ int[] kNewBit = new int[56]; for(int i=0; i<PC1.length; i++) { kNewBit[i] = kBit[PC1[i]-1]; } /*================初始密钥分组=============*/ int[] c0 = new int[28]; int[] d0 = new int[28]; System.arraycopy(kNewBit, 0, c0, 0, 28); System.arraycopy(kNewBit, 28, d0, 0, 28); //生成16个子密钥 for(int i=0; i<16; i++) { int[] c1 = new int[28]; int[] d1 = new int[28]; /*============ci、di分别循环左移===========*/ if(leftTable[i] == 1) { System.arraycopy(c0, 1, c1, 0, 27); c1[27]=c0[0]; System.arraycopy(d0, 1, d1, 0, 27); d1[27]=d0[0]; }else if(leftTable[i] == 2) { System.arraycopy(c0, 2, c1, 0, 26); c1[26]=c0[0]; c1[27]=c0[1]; System.arraycopy(d0, 2, d1, 0, 26); d1[26]=d0[0]; d1[27]=d0[1]; }else { System.out.println("leftTable error!"); } /*================ci、di合并 PC-2压缩置换=============*/ int[] tmp = new int[56]; System.arraycopy(c1, 0, tmp, 0, 28); System.arraycopy(d1, 0, tmp, 28, 28); for(int j=0; j<PC2.length; j++) { subKey[i][j] = tmp[PC2[j]-1]; } c0 = c1; d0 = d1; } } } 非线形函数f f函数的参数有两个变量,一个是32比特的Ri-1,另一个是48比特的ki,输出的结果为32比特。具体执行如下图过程是: <1> Ri-1是每轮迭代运算初始值的右半部分。它的长度为32位。通过扩展置换E(表1.2.3)扩展成一个48比特的串; 扩展置换E表里的数字都是32比特串的第几位。通过表可以看出32比特串分成8组,每组4位,然后将每组的4位前后根据表扩展,如第一组前面添加32比特串的第32位比特,后边添加32位比特串的第5位比特,其他依次类推。 <2> 48比特的串与长度一致的ki进行异或运算 <3> 将<2>步得到的48比特串分成8个6比特的串,即为A1A2A3A4A5A6A7A8 <4> 将A1,A2,A3,A4,A5,A6,A7,A8分别作为8个S盒的输入,查表(表1.2.4)得到输出B1,B2,B3,B4,B5,B6,B7,B8; 每个S盒都是将6比特消息映射成一个4比特的消息。设Si盒的输入位6比特串x=x1x2x3x4x5x6,将x1x6转换成10进制的0~3的某个数,它对应表中的行数,将x2x3x4x5转换成0~15的10进制的某个数,它作为表的列号,利用行号和列号查询对应S盒表得到一个整数,将该整数转换成二进制就是输出结果。例如S1盒的输入是110011,则行号是11(第3行),列号是1001(第9列),查表得到整数11,再转换成二进制为1011,这就是1盒的输出结果。 <5> 将8个S盒的输出拼接一起得到B1B2B3B4B5B6B7B8,再将这32比特的串使用置换运算P(表1.2.5)得到最后的结果,也就是每轮函数f的输出。 置换运算P:P盒置换将每一位输入位映射到输出位。任何一位都不能被映射两次,也不能被略去。映射规则是,参照置换P表,将32位的输入的第16位放在第一位,第七位放在第二位,第二十位放在第三位,以此类推. 置换P 16,7,20,21,29,12,28,17,1,15,23,26,5,18,31,10 2,8,24,14,32,27,3,9,19,13,30,6,22,11,4,25 1.3 逆初始置换IP^-1 逆置换是初始置换的逆运算。参照表1.2.6,从初始置换规则中可以看到,原始数据的第1位置换到了第40位,第2位置换到了第8位。则逆置换就是将第40位置换到第1位,第8位置换到第2位。以此类推,逆置换规则如下。 表1.2.6: 逆初始置换IP^-1 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 2 DES算法解密过程 加密和解密使用相同的算法。加密和解密唯一不同的是秘钥的次序是相反的。就是说如果每一轮的加密秘钥分别是K1、K2、K3...K16,那么解密秘钥就是K16、K15、K14...K1。为每一轮产生秘钥的算法也是循环的。加密是秘钥循环左移,解密是秘钥循环右移。解密秘钥每次移动的位数是:0、1、2、2、2、2、2、2、1、2、2、2、2、2、2、1。具体不做讲解。但是要注意一点,解密的结果并不一定是我们原来的加密数据,可能还含有你补得位,一定要把补位去掉才是你的原来的数据。 3 DES算法特点 1、分组加密算法: 以64位为分组。64位明文输入,64位密文输出。 2、对称算法: 加密和解密使用同一秘钥 3、有效密钥长度为56位 秘钥通常表示为64位数,但每个第8位用作奇偶校验,可以忽略。 4、代替和置换 DES算法是两种加密技术的组合:混乱和扩散。先替代后置换。 5、易于实现 DES算法只是使用了标准的算术和逻辑运算,其作用的数最多也只有64 位,因此用70年代末期的硬件技术很容易实现 golang实现DES_CBC模式加解密package main import ( "crypto/cipher"//密码 "crypto/des" "encoding/base64"//将对象转换成字符串 "fmt" "bytes" ) //DES加密的方法 func MyDesEncrypt(origData,key[]byte) { //生成加密块 block,_:=des.NewCipher(key) //按照blocksize的长度padding origData =PKCS5Padding(origData,des.BlockSize) //设置加密方式 blockMode:=cipher.NewCBCEncrypter(block,key) //创建明文长度的字节数组 crypted :=make([]byte,len(origData)) //加密明文 blockMode.CryptBlocks(crypted,origData) //将字节数组转换成字符串 fmt.Println(base64.StdEncoding.EncodeToString(crypted)) } //明文补码 func PKCS5Padding(ciphertext []byte,blockSize int) []byte { padding:=blockSize-len(ciphertext)%blockSize padtext := bytes.Repeat([]byte{byte(padding)},padding)//补码过程 return append(ciphertext,padtext...) } //实现去补码 func PKCS5UnPadding(origData []byte)[]byte { length:=len(origData) unpadding:=int(origData[length-1]) return origData[:(length-unpadding)] } //DES解密方法 func MyDESDecrypt(data string,key []byte) { //将字符串转换成字节数组 crypted,_:=base64.StdEncoding.DecodeString(data) //将字节密钥转换成block块 block,_:=des.NewCipher(key) //设置解密方式 blockMode:=cipher.NewCBCDecrypter(block,key) //创建秘文大小的数组变量 origData:=make([]byte,len(crypted)) //解密秘文到数组origData中 blockMode.CryptBlocks(origData,crypted) origData=PKCS5UnPadding(origData) fmt.Println(string((origData))) } func main() { fmt.Println("hello world") //声明一个密钥,利用此密钥实现明文的加密和解密 key :=[]byte("12345698") MyDesEncrypt([]byte("hello world " ),key) MyDESDecrypt("NIJWb9F1DO11q08fSnB/HA==",key) 3DES算法3DES,或叫3重DES,英文全称是triple-DES,是普通DES的升级改进版。在AES未出现之前,DES加密慢慢被发现存有较大的安全性,为此3DES作为过渡期的重要对称加密诞生了。1999年,NIST将3-DES指定为过渡的加密标准。 3DES并不是一个全新的加密算法,它可以被认为是DES系列的加密范畴。DES的密钥长度是8个字节,由于长度较短,较容易被暴力破解。增加密钥的长度成为提高DES安全性的重大突破口。密钥长度增加至2倍,也就是2DES(双重DES),但这个算法存在一种中间相遇攻击隐患,对其安全性构成了威胁,所以实际应用中,很少或不推荐双重DES。密码长度增加至3倍,也就是3DES。该算法不仅很大提高了DES的安全性,而且还可以抵抗中间相遇攻击。到目前为止,还没有相关它被暴力破解或其它安全性受到威胁的信息。尽管已经公布了高级加密标准AES,但是目前3DES还被当作一个安全有效的加密算法在使用。 1. 3DES算法的原理及加解密过程 密钥长度为192bit(也就是24字节),加密过程是进行3次DES加密或解密的密码算法叫3DES。 由于当时DES算法的应用较多,所以设计3DES不得不考虑与DES的兼容问题,也就是2者之间可以混用,3DES加密,DES能够解密,DES加密,3DES能够解密。最终IBM公司设计出来了合理方案,将第2重加密过程改为解密过程,整体的加密过程是加密-->解密-->加密,当3DES的密钥是DES密钥的3次重复时,两者完全兼容,此时的3DES实际只有最后一重加密是有效的。如果3DES的密钥不是DES密钥的3次重复,此时两者不存在兼容,3DES的第二重解密实际上也是加密过程,只不过用的DES的解密算法而已。 3DES加密解密过程首先对输入的私钥平均分成3组,每组密钥对应一重DES算法,其具体实现如下:Golang实现3DES_CBC模式加解密package main import ( "bytes" "crypto/des" "crypto/cipher" "fmt" ) //补码 func PKCS5Padding(ciphertext []byte, blocksize int)[]byte { //求得补码的长度x padding := blocksize-len(ciphertext)%blocksize //将x转换成字节,并创建一个长度为x,元素都为x的切片 padtext := bytes.Repeat([]byte{byte(padding)},padding) //返回补码后要加密的明文 return append(ciphertext,padtext...) } //去码 func PKCS5UnPadding(origData []byte)[]byte { //求得加密时补码的长度,长度就等于密文最后元素的10进制数字 length:=len(origData) unpadding:=int(origData[length-1]) //返回去码之后要进行解密的密文 return origData[:(length-unpadding)] } //3DES加密 ////3DES的密钥长度必须为24位 func TripleEncrypt(origData []byte,key[]byte)[]byte { //通过调用3des库里方法产生分组密钥块 block,_:=des.NewTripleDESCipher(key) //补码 origData = PKCS5Padding(origData,block.BlockSize()) //设置加密模式,此处用CBC模式 blockMode:=cipher.NewCBCEncrypter(block,key[:8]) //创建密文数组,加密 crypted:=make([]byte,len(origData)) //加密 blockMode.CryptBlocks(crypted,origData) return crypted } //解密 func TrileDesDecrypt(crypted, key []byte)[]byte { //设置分组的密钥块 block,_:=des.NewTripleDESCipher(key) //设置解密模式 blockMode:=cipher.NewCBCDecrypter(block,key[:8]) //创建切片 origData :=make([]byte,len(crypted)) //解密 blockMode.CryptBlocks(origData,crypted) //去码得到原文 origData=PKCS5UnPadding(origData) return origData } func main() { fmt.Println("hello world") var key=[]byte("123456789012345678901239") var encirtcode =TripleEncrypt([]byte("hello world"),key) var decryptcode=TrileDesDecrypt(encirtcode,key) fmt.Printf("%x\n",encirtcode) fmt.Println(string(decryptcode)) } AES算法 AES英文全称是Advanced Encryption Standard,中文是高级加密标准。它的出现就是为了替代之前的加密标准DES。1997年1月2号,美国国家标准技术研究所(National Institute of Standards and Technology: NIST)发起征集高级加密标准算法的活动,目的是重新确立一种新的分组密码代替DES,成为新的美联邦信息处理的标准。该活动得到了全世界很多密码工作者的响应,先后有很多人提交了自己设计的算法。最终获胜的是由两位比利时的著名密码学家Joan Daemen和Vincent Rijmen设计的Rijndael算法。2001年11月,NIST正式公布该算法,命名为AES算法。 1. AES算法原理 AES算法采用分组密码体制,即AES加密会首先把明文切成一段一段的,而且每段数据的长度要求必须是128位16个字节,如果最后一段不够16个字节,需要用Padding来把这段数据填满16个字节,然后分组对每段数据进行加密,最后再把每段加密数据拼起来形成最终的密文。AES算法的密钥长度可以有三种,分别是128位,256位,512位。 AES算法的加密过程使用了四个变换:字节替换变换(SubBytes)、行移位变换(SiftRows)、列混淆变换(MixColumns)和轮密钥加变换(AddRoundKey)。解密过程用了这四个变换的逆操作,分别是逆字节替换变换(InvSubBytes)、逆行移位变换(InvShiftRows)、逆列混淆变换(InvMixColumns)和轮密钥加变换。这里说明一下,轮密钥加变换的逆运算就是它本身,所以名字就通用一个。 1.1 字节替换变换 字节替换变换是一个非线形变换。输入的任意字节我们看作是有限域GF(2^8 )的元素,也就是这些字节都会在这个有限域内找到。在这个有限域内任何元素通过映射运算都会找到与之对应的元素,而且他们之间映射是可逆的。根据这个映射关系,制作了一个S盒对照表。根据这个表我们会很容易的查找对应的映射元素。如果输入的字节为xy,查找S盒中的第x行盒第y列找到对应的值,将其输出替换xy输出。例如1D,替换之后就是A4。S盒的制作方法这里不做讲解。 下表就是AES的S盒:注:S盒中的元素都是16进制,字母大写表示。 1.2 逆字节替换变换 逆字节替换变换是字节替换变换的逆变换。字节替换变换的映射运算是可逆的,所以根据映射逆运算也制作了一张逆S盒的对照表。查找方法与字节替换变换方法一样。例如A4,替换之后就是1D。逆S盒的制作方法这里也不做讲解。 下表就是AES的逆S盒: 注:逆S盒中的元素都是16进制,字母大写表示。 1.3 行移位变换 行移位的功能是实现一个4x4矩阵内部字节之间的置换。AES算法的明文分组要求是每组的字节长度为16,就是因为能够刚好转换成4x4矩阵。 行移位的过程:第一行保持不变,第二行循环左移1个字节,第三行循环左移2个字节,第四行循环左移3个字节。 1.4 逆行移位变换 逆向行移位即是相反的操作。即第一行保持不变,第二行循环右移1个字节,第三行循环右移2个字节,第四行循环右移3个字节。 列混淆变换将状态矩阵中的每一列视为系数在GF(2^8 )上的次数小于4的多项式与同一个固定的多项式a(x)进行模多项式m(x)=x^4 +1的乘法运算。在AES中,a(x)={03}x^3 +{01}x^2 +{01}x+{02}。 1.6 逆列混淆变换 逆列混淆变换是列混淆变换的逆,它将状态矩阵中的每一列视为系数在GF(2^ 8)上的次数小于4的多项式与同一个固定的多项式a^-1 (x)进行模多项式m(x)=x^4 +1的乘法运算。a^-1 (x)={0B}x^3 +{0D}x^2 +{09}x+{0E}。 1.7 轮密钥加变换 任何数和自身的异或结果为0。加密过程中,每轮的输入与轮密钥异或一次;因此,解密时再异或上该轮的密钥即可恢复输入。 1.8 密钥扩展算法 AES加密的每一轮用到的密钥都是不一样的。AES密钥扩展算法的输入值是4个字(16字节),输出值是一个由44个字组成(176字节)的一维线性数组。 2. AES加密过程 AES的解密就是加密的逆运算,所以这里我只讲解AES加密的过程。一般AES的每组明文加密需要重复10轮加密才能完成,所有分组的明文经过10轮加密后,拼接一起就是最后的密文。下面我只讲解一轮的加密过程。 每一轮的加密过程:将明文分成N组,每组长度为128比特,也就是16个字节;将分组明文字节输出转换成16进制(字母大写输出);该轮分组将分组明文进行字节替换;字节替换后,转换成4X4矩阵,然后进行行移位变换将列混淆变换后的4X4矩阵,再进行列混淆变换,输出处理后的字节数组;轮密钥加:经过第6步后的字节数组与轮密钥进行XOR异或运算得到新的字节数组;第7部回到第4步AES的安全性 AES加密算法中,每轮使用不同的常数消除了密钥的对称性;使用了非对称性的密钥扩展算法消除了相同密钥的可能性;加密和解密使用不同的变换,从而消除了弱密钥和半弱密钥存在的肯能性。经过验证,AES加密算法能有效地抵抗现有的攻击,如差分攻击、相关密钥攻击、插值攻击等。 4. golang实现AES_CFB加解密 AES_CFB加解密代码实现:package main import ( "fmt" "crypto/aes" "io" "crypto/rand" "crypto/cipher" "encoding/base64" ) //通过CFB模式,进行AES加密 //加密 func AESEncrypt(plaintext []byte, key []byte) []byte { //分组密钥,key字节的长度必须是16或24,或32;密钥的长度可以使用128位、192位或256位;位只有两种形式0和1,而字节是有8个位组成的。可以表示256个状态。1字节(byte)=8位(bit) block,_:=aes.NewCipher(key) //block 是个*aes.aesCipherGCMiv 类型 包含encode 和decode 这两个code类型是[]uint32, n。 n=BlockSize+28,n是新创建enc和dec的长度 //创建数组,目的是存储你接下来加密的密文 ciphertext:=make([]byte,aes.BlockSize+len(plaintext)) //设置内存空间可读,类似在明文前面加入一个长度16切片,用于被读取内存流 iv :=ciphertext[:aes.BlockSize] //[]uint8 //[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] //读内存流,把rand.reader随机读取的内存流数据复制到iv里 io.ReadFull(rand.Reader,iv)//n是iv的长度 //iv=[127 78 10 244 97 152 178 224 62 49 156 74 239 99 211 94]每次不一样,包含时间戳应该 //设置加密模式,返回一个流,也就是把iv放到block里,返回stream流 //下边方法将iv的数copy到block里的next字段的字节数组里 stream :=cipher.NewCFBEncrypter(block,iv) //&{0xc42007e2a0 [127 78 10 244 97 152 178 224 62 49 156 74 239 99 211 94](输出和读出的iv一样) [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] 16 false} //拿着你的密文进行异或运算,加密利用ciphertext[:aes.BlockSize]与明文进行异或,overlap使部分重叠 stream.XORKeyStream(ciphertext[aes.BlockSize:],plaintext) return ciphertext } //解密 func AesDecrypt(ciphertext []byte,key []byte)[]byte { block,_:=aes.NewCipher(key) iv:=ciphertext[:aes.BlockSize] ciphertext=ciphertext[aes.BlockSize:] //设置解密方式 stream :=cipher.NewCFBDecrypter(block,iv) //解密 stream.XORKeyStream(ciphertext,ciphertext) return ciphertext } func main() { fmt.Println("hello world") var encryptcode = AESEncrypt([]byte("abc"),[]byte("123456789abcdejg")) var decryptcode =AesDecrypt( encryptcode,[]byte("123456789abcdejg")) fmt.Println(string(decryptcode)) fmt.Println(base64.StdEncoding.EncodeToString(encryptcode)) } golang实现AES_CTR加解密 AES_CTR加解密代码实现package main import ( "bytes" "crypto/aes" "crypto/cipher" "fmt" ) func main() { str:=[]byte("helloworld") key:=[]byte("12345678qwertyui") encrypt:=AesCTR_Encrypt(str,key) fmt.Printf("%x\n",encrypt) decrypt:=AesCTR_Decrypt(encrypt,key) fmt.Println(string(decrypt)) } func AesCTR_Encrypt(plainText, key []byte) []byte { //判断用户传过来的key是否符合16字节,如果不符合16字节加以处理 keylen := len(key) if keylen == 0 { //如果用户传入的密钥为空那么就用默认密钥 key = []byte("Abskjeqiu1234567") //默认密钥 } else if keylen > 0 && keylen < 16 { //如果密钥长度在0到16之间,那么用0补齐剩余的 key = append(key, bytes.Repeat([]byte{0}, (16 - keylen))...) } else if keylen > 16 { key = key[:16] } //1.指定使用的加密aes算法 block, err := aes.NewCipher(key) if err != nil { panic(err) } //2.不需要填充,直接获取ctr分组模式的stream // 返回一个计数器模式的、底层采用block生成key流的Stream接口,初始向量iv的长度必须等于block的块尺寸。 iv := []byte("wumansgygoaesctf") stream := cipher.NewCTR(block, iv) //3.加密操作 cipherText := make([]byte, len(plainText)) stream.XORKeyStream(cipherText, plainText) return cipherText } func AesCTR_Decrypt(cipherText, key []byte) []byte { //判断用户传过来的key是否符合16字节,如果不符合16字节加以处理 keylen := len(key) if keylen == 0 { //如果用户传入的密钥为空那么就用默认密钥 key = []byte("Abskjeqiu1234567") //默认密钥 } else if keylen > 0 && keylen < 16 { //如果密钥长度在0到16之间,那么用0补齐剩余的 key = append(key, bytes.Repeat([]byte{0}, (16 - keylen))...) } else if keylen > 16 { key = key[:16] } //1.指定算法:aes block, err := aes.NewCipher(key) if err != nil { panic(err) } //2.返回一个计数器模式的、底层采用block生成key流的Stream接口,初始向量iv的长度必须等于block的块尺寸。 iv := []byte("wumansgygoaesctf") stream := cipher.NewCTR(block, iv) //3.解密操作 plainText := make([]byte, len(cipherText)) stream.XORKeyStream(plainText, cipherText) return plainText } //输出 40dbbf4ab999bb36d0d5 helloworld 非对称加密也叫公钥密码。 1976年Diffie和Hellman首次提出了一种全新的加密思想,公钥密码体制思想。在当时几乎所有的密码体制都是对称密码体制,原理都是基于替换和置换这些较简单方法。公钥密码体制完全与之不同,它是非对称的,有两个不同的密钥,分别是公钥和私钥,加密的原理也不是之前的简单置换或替换,而是一些复杂的数学函数。这些数学函数都是基于数学难题。其所依据的难题一般分为三类:大整数分解问题类、离散对数问题类、椭圆曲线类。有时也把椭圆曲线类归为离散对数类。公钥密码体制是一次革命性的变革,突破了原有的密码体制模式,它解决了传统密码体制的两个大难题:密钥分配和数字签名。 1 非对称加密的概述 传统密码体制用的都是一个密钥,发送方传输密钥给接收方成本很高,而且风险很大。接收方收到的密文如果在传输过程中被修改,接收方无法判断密文的真伪性。公钥体制完美地解决了上述问题。它有一对密钥,一个是公钥,完全公开,任何人都可以收到该密钥;另一个是私钥,自己保存,不需要告诉任何人。通过公开的公钥是无法计算出私钥的,所以私钥是安全的。发送方A用公钥对明文进行加密,接收方B用对应的私钥进行解密。为保证传输密文的完整性和消息来源的准确性,需要对密文进行数字签名。A对密文用自己的私钥进行再次加密,此过程叫数字签名;B接收到密文用该私钥对应的公钥进行解密,此过程叫验签。 所以公钥密码体制可以分为两个模型:加密解密模型和签名验签模型。两个模型可以独立使用,也可以一起混用。具体按照自己的应用场景使用,一般情况下发送的密文都是需要进行数字签名的,发送的内容包括密文和签名两部分。接受者先进行验签,验签通过后,再进行解密。 非对称加密的方式有很多,以下讲解RSA,DSA,ECDSA这三种加密方式。公钥密码体制的要求公钥密码体制要想实现必须满足以下要求: 1.产生一对密钥对,即公私钥对,在计算上是容易的; 2.通过公钥对明文进行加密,在计算上是容易的; 3.通过私钥对密文进行解密,在计算上是容易的; 4.已知公钥,无法计算出私钥; 5.已知公钥和密文,无法计算出明文; 6.加密和解密的顺序可以交换。 目前满足以上要求,建立公钥密码体制基于的困难问题有较多,我只分析以下两种常用的: 1.大整数分解问题 若已知两个大素数p和q,求n=pq是很容易的,但是已知n,求p和q是几乎不可能的,这就是大整数分解问题。 2.离散对数问题 先了解两个概念,阶和原根。 设m > 1 且 (a, m) = 1, 则使得a^t ≡ 1 mod m成立的最小的正整数t称为a对模m的阶, 记为δm(a)。 原根,是一个数学符号。设m是正整数,a是整数,若a模m的阶等于φ(m),则称a为模m的一个原根。里面提到的φ(m)是m质因数的个数。 给定一个公式a^t mod b ≡ c,其中a是b的原根,b是一个超大的素数,c是小于b大于0的正整数。问题是已知a,t,b求c很容易,但是已知a,b,c求t非常困难。这就是离散对数问题。 举个例子(b取个小值):根据给定的t求 3^t mod 17很容易。t=1时,得3;t=2时,得9;t=3时,得10,等等最终的结果都是在小于17大于0的正整数。但是现在3^t mod 17≡12,求t。求解过程非常困难,而且满足条件的t不计其数。这里用的是17,如果换成很大的数,那几乎没有可能求解出来真正的t。RSA算法RSA是目前使用最广泛的公钥密码体制之一。它是1977年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的。当时他们三人都在麻省理工学院工作。RSA就是他们三人姓氏开头字母拼在一起组成的。 RSA算法的安全性基于RSA问题的困难性,也就是基于大整数因子分解的困难性上。但是RSA问题不会比因子分解问题更加困难,也就是说,在没有解决因子分解问题的情况下可能解决RSA问题,因此RSA算法并不是完全基于大整数因子分解的困难性上的。 1. RSA算法描述 1.1 RSA产生公私钥对 具体实例讲解如何生成密钥对 1.随机选择两个不相等的质数p和q。 alice选择了61和53。(实际应用中,这两个质数越大,就越难破解。) 2.计算p和q的乘积n。 n = 61×53 = 3233 n的长度就是密钥长度。3233写成二进制是110010100001,一共有12位,所以这个密钥就是12位。实际应用中,RSA密钥一般是1024位,重要场合则为2048位。 3.计算n的欧拉函数φ(n)。称作L 根据公式φ(n) = (p-1)(q-1) alice算出φ(3233)等于60×52,即3120。 4.随机选择一个整数e,也就是公钥当中用来加密的那个数字 条件是1< e < φ(n),且e与φ(n) 互质。 alice就在1到3120之间,随机选择了17。(实际应用中,常常选择65537。) 5.计算e对于φ(n)的模反元素d。也就是密钥当中用来解密的那个数字 所谓"模反元素"就是指有一个整数d,可以使得ed被φ(n)除的余数为1。ed ≡ 1 (mod φ(n)) alice找到了2753,即17*2753 mode 3120 = 1 6.将n和e封装成公钥,n和d封装成私钥。 在alice的例子中,n=3233,e=17,d=2753,所以公钥就是 (3233,17),私钥就是(3233, 2753)。 1.2 RSA加密 首先对明文进行比特串分组,使得每个分组对应的十进制数小于n,然后依次对每个分组m做一次加密,所有分组的密文构成的序列就是原始消息的加密结果,即m满足0<=m 1; x:x < q,x为私钥 ; y:y = g^x mod p ,( p, q, g, y )为公钥; H( x ):One-Way Hash函数。DSS中选用SHA( Secure Hash Algorithm )。 p, q, g可由一组用户共享,但在实际应用中,使用公共模数可能会带来一定的威胁。 签名及验证协议: 1.P产生随机数k,k < q; 2.P计算 r = ( g^k mod p ) mod q s = ( k^(-1) (H(m) xr)) mod q 签名结果是( m, r, s )。 3.验证时计算 w = s^(-1)mod q u1 = ( H( m ) w ) mod q u2 = ( r w ) mod q v = (( g^u1 * y^u2 ) mod p ) mod q 若v = r,则认为签名有效。golang实现DSA签名及验证package main import ( "crypto/dsa" "crypto/rand" "fmt" ) //作用1 确保传递数据的完整性 2 确保数据的来源 func main() { //DSA专业做签名和验签 var param dsa.Parameters//结构体里有三个很大很大的数bigInt //结构体实例化 dsa.GenerateParameters(&param,rand.Reader,dsa.L1024N160)//L是1024,N是160,这里的L是私钥,N是公钥初始参数 //通过上边参数生成param结构体,里面有三个很大很大的数 //生成私钥 var priv dsa.PrivateKey//privatekey是个结构体,里面有publickey结构体,该结构体里有Parameters字段 priv.Parameters=param //通过随机读数与param一些关系生成私钥 dsa.GenerateKey(&priv,rand.Reader) //通过私钥生成公钥 pub:=priv.PublicKey message:=[]byte("hello world") //r,s是两个整数,通过私钥给message签名,得到两个随机整数r,s r,s,_:=dsa.Sign(rand.Reader,&priv,message)//利用公钥验签,验证r,s b:= dsa.Verify(&pub,message,r,s) if b==true{ fmt.Println("验签成功") }else { fmt.Println("验证失败") } } 哈希(hash)算法哈希函数是密码学中的一个重要分支,该函数是一类数学函数,它可以在有限的合理时间内,将任意长度的消息变换成固定长度的二进制串,且不可逆,这个输出值就是哈希值,也叫散列值或消息摘要。以hash函数为基础的hash算法,在数字签名,实现数据完整性,merkle树数据存储和检索等方面有着广泛的应用。 在比特币系统中使用了两个密码学hash函数,一个是SHA256,另一个是ripemd160。ripemd160主要用于生成比特币地址,SHA256是比特币链上几乎所有加密算法的hash函数。 1. 技术原理 hash函数也叫散列函数,杂凑函数。它是一种单向密码机制,也就是只能加密,而不能解密。数学表达式可以为:h=H(m),其中H是哈希函数,m是要加密的信息,h是输出的固定长度的哈希值。运算过程是设定一个初始向量,对消息补长到算法要求长度,将补长后的消息拆分成N份数据块,N份数据块与初始向量通过hash算法进行迭代循环运算,最终得到固定长度的hash值。 hash函数具有以下特点: 压缩性:对任意长度的信息加密成固定长度的hash值;单向性:hash函数的数学原理没有逆运算,所以不能将hash值转换成加密前的信息;抗碰撞性:hash函数的运算过程相当复杂,包含多种数学运算和大量变量循环运算,要满足两个不同的消息产生相同的hash值几乎不可能发生;高灵敏性:任何微小的输入都有可能对输出产生巨大的影响。 典型的hash函数有两类:消息摘要算法(MD5)和安全散列算法(SHA)。2. hash碰撞 理想的hash函数对于不同的输入得到两个不同的hash值。在实际中,如果存在两个不同的信息m,m'使H(m)=H(m'),那么就称m和m'是该函数的一个碰撞。简言之,hash碰撞是指两个不同的消息在同一个哈希函数作用下,产生两个相同的哈希值。 为了保证数据安全性和不可篡改性,实际hash算法要足够复杂使其有很强的hash抗碰撞性。 hash抗碰撞性分为两种:一种是弱抗碰撞性,即指定的消息x和函数H,去求消息y,使H(x)=H(y)在计算上是不可行的;另一个是强抗碰撞性,即给定函数H,对于任意一对不同的消息x和y,使得H(x)=H(y)在计算上也是不可行的。 SHA256 SHA是一个密码散列函数家族,是英文Secure Hash Algorithm的缩写。由美国国家安全局(NSA)所设计,并由美国国家标准与技术研究院(NIST)发布。SHA家族目前有三个系列:SHA-1,SHA-2,SHA-3。因为SHA-1已经被计算出能够被破解,所以现在几乎不再使用。SHA-3是2012年产生的算法,也叫Keccak算法,在以太坊公链中主要使用。SHA-2是当前使用最广泛的算法,尤其是比特币一代的公链。 SHA算法有如下特性:1.不可以从消息摘要中复原信息;2.两个不同的消息不会产生同样的消息摘要。 SHA256是目前区块链加密算法中最基础也是应用最多的算法。它是SHA-2算法系列的最具代表性的加密算法。了解和熟练运用SHA256是区块链技术人才的最基本要求。 1. SHA256的算法原理SHA-256是指对于任意小于2^64 位长度(按bit计算)的消息,以512位的分组为单位进行处理,最终产生一个32个字节长度数据的一种加密算法。产生的数据称作消息摘要。因为消息摘要的唯一性和确定性,所以可以用来验证数据在传输过程中是否发生改变,即验证其完整性。1.1 运算单位 SHA算法过程的处理单位是位。本文中,一个“字”(Word)是32位,而一个“字节”(Byte)是8位。比如,字符串“abc”可以被转换成一个位字符串:01100001 01100010 01100011。它也可以被表示成16进制字符串:0x616263. 1.2 补位 将消息转换成二进制串,在后边添加一个“1”和若干个“0”,使其长度模512余数为448。以信息“abc”为例显示补位的过程。 原始信息:01100001 01100010 01100011 补位第一步:0110000101100010 01100011 1 首先补一个“1” 补位第二步:0110000101100010 01100011 10…..0 然后补423个“0” 1.3 消息填充 将补位过的信息再追加一个64位的消息长度信息,使得填充完成后的消息长度正好是512位的整数倍。追加的64位的消息长度信息是原始消息的位长,填充完成的消息会被分成512位的消息分组。 1.4 初始向量 SHA256是一个Merkle-Damgard结构的迭代哈希函数,进行第一次运算的时候需要一个初始向量。该向量在整个运算过程中是一个变量。SHA256的初始变量是取自然数前8个素数(2,3,5,7,11,13,17,19)的平方根的小数部分前32bit的值。如2的平方根取小数部分是:0.414213562373095048...,转换成二进制取前32bit值是:10110111111100101010000101001010,然后将其转换成16进制的值是:0x6a09e667。同样我们也会得到其他素数的初始变量。这些初始变量存储于8个寄存器A、B、C、D、E、F、G和H中,分别是: A= H0 = 0x6a09e667 B= H1 = 0xbb67ae85 C= H2 = 0x3c6ef372 D= H3 = 0xa54ff53a E= H4 = 0x510e527f F= H5 = 0x9b05688c G= H6 = 0x1f83d9ab H= H7 = 0x5be0cd19 1.5 使用的64个常量 在SHA256算法中,用到64个常量,这些常量是对自然数中前64个素数的立方根的小数部分取前32bit而来。其作用是提供了一个64位随机串集合,用于被随机选取作为改变每次消息块运算初始向量函数的参数。这样每次消息块加密运算时,输入的初始向量都是没有任何规则的。这64个常量如下: 428a2f98 71374491 b5c0fbcf e9b5dba5 3956c25b 59f111f1 923f82a4 ab1c5ed5 d807aa98 12835b01 243185be 550c7dc3 72be5d74 80deb1fe 9bdc06a7 c19bf174 e49b69c1 efbe4786 0fc19dc6 240ca1cc 2de92c6f 4a7484aa 5cb0a9dc 76f988da 983e5152 a831c66d b00327c8 bf597fc7 c6e00bf3 d5a79147 06ca6351 14292967 27b70a85 2e1b2138 4d2c6dfc 53380d13 650a7354 766a0abb 81c2c92e 92722c85 a2bfe8a1 a81a664b c24b8b70 c76c51a3 d192e819 d6990624 f40e3585 106aa070 19a4c116 1e376c08 2748774c 34b0bcb5 391c0cb3 4ed8aa4a 5b9cca4f 682e6ff3 748f82ee 78a5636f 84c87814 8cc70208 90befffa a4506ceb bef9a3f7 c67178f21.6 运算过程 运算过程简单描述如下: 创建8个变量a,b,c,d,e,f,g,h,并分别赋值初始向量对应的值;将原始消息补位和填充后,分为N个512bit的消息块M(i);运算M有个大的循环,形如: For i =1 to N;大循环里面有个64次的循环,用于改变8个变量,并将最终改变后的8个变量作为下一次大循环的参数;大循环最后得到的a,b,c,d,e,f,g,h拼接在一起就是最后的长度为256位的消息摘要。下边分析一下64次的循环里面的具体函数,伪代码如下: For t = 0 to 63 T1 = (h +(∑1(e) + CH(e,f,g) + Kt + Wt)mod2^32 T2 = ∑0(a) + MAJ(a,b,c)mod2^32 h = g g = f f = e e = (d + T1)mod2^32 d = c c = b b = a a = (T1 + T2)mod2^32 其中∑1(e)和∑0(a)分别是e和a的位移异或函数,表达式不做展开;与异或运算函数;MAJ(a,b,c)是a,b,c之间异或运算加法运算函数,表达式不做展开,;T1和T2是每一步生成的两个临时变量;Kt是每次循环从随机串里随机选取的数值;Wt是对输入的消息块的处理函数。消息块的处理:每个消息块分解为16个32-bit的big-endian的字,记为w[0], …, w[15]。也就是说,前16个字直接由消息的第i个块分解得到,其余的字由如下迭代公式得到,这样Wt的表达式如下表示:Wt=w[t],0<=t<=16 Wt=σ1(Wt−2)+Wt−7+σ0(Wt−15)+Wt−16,16<=t<=63 最后一次循环所产生的八个字合起来即是第i个块对应到的散列字符串Hi就是sha256加密后的散列值。 2. SHA256在区块链的应用及代码实现 SHA256从出现到现在,目前被证明是很安全的,其在区块链上的应用最为广泛,如比特币的挖矿算法,产生账户地址和区块产生hash,以及以太坊区块生成hash等等。 它的使用非常简单,golang库里已经封装好了SHA256的具体算法,所以我们在使用时直接调用方法就可以。函数hash.Sha256的参数类型是字节切片,返回值也是字节切片,在使用时需要注意。 Go代码实现SHA256有两种方法,原理和输出都一样,代码如下:package main import ( "github.com/nebulasio/go-nebulas/crypto/hash" "fmt" "encoding/hex" "crypto/sha256" ) func main() { a:="helloworld" //方法1:一个方法直接输出 hash:=hash.Sha256([]byte(a)) fmt.Println(hex.EncodeToString(hash)) sha256.New() //方法2:按步骤一步步输出 h := sha256.New() //创建sha256算法 h.Write([]byte(a)) //用sha256算法对参数a进行加密,得到8个变量 hash1 := h.Sum(nil) //将8个变量相加得到最终hash fmt.Println(hex.EncodeToString(hash1)) } //输出 936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af 936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af 数字签名数字签名在信息安全,包括身份认证、数据完整性、不可否认性以及匿名性有着重要应用,是现代密码学的重要分支。签名隶属于公钥密码学。签名过程:发送方用自己的私钥对发送信息进行所谓的加密运算,得到一个hash值,该hash值就是签名。使用时需要将签名和信息发给接收方。接受者用发送方公开的公钥和接收到的信息对签名进行验证,通过认证,说明接收到的信息是完整的、准确的,否则说明消息来源不对。 简单描述数字签名:私钥签名;公钥验签 1 普通签名 签名只是用一把私钥,并且是发送方自己进行地签名动作,这类签名就是普通签名。常用的签名方法有较多,包括RSA数字签名、DSS数字签名、ElGamal数字签名、ECDSA数字签名等等。其中RSA和ECDSA签名已经在加密算法中有讲解。区块链项目中最常用的签名方法是ECDSA数字签名。签名和验签的原理不作讲解,感兴趣可以翻阅之前的章节。 因为ECDSA数字签名在区块链的重要性,我接下来只对它进行讲解如何应用。 下边代码展示简单地用ECDSA签名对数据BLOCK进行签名,上链之前需要先进行验证,验证通过之后才能上链。具体解析在代码中有说明。 过程: 1.通过ecdsa.GenerateKey产生一私钥;输出的私钥是指针类型; 2.通过私钥产生公钥; 3.对数据BLOCK进行hash运算,实际公链中就是挖矿过程; 4.为了可以给任何长度的data进行签名,我们创建自己的签名方法; 5.验证数据是否合法,也就是通过公钥对签名进行验证,通过后才能对数据进行上链动作。package main import ( "bytes" "encoding/binary" "log" "time" "crypto/sha256" "fmt" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "math/big" ) //简单的区块链块结构 type Block struct { //1. 区块高度 Height int64 //2. 上一个区块HASH PrevBlockHash []byte //3. 交易数据 Data []byte //4. 时间戳 Timestamp int64 //5. 实际通过挖矿得到Hash Hash []byte // 6. 随机数Nonce Nonce int64 } func main() { //调用底层函数,产生私钥 prk,_:=ecdsa.GenerateKey(elliptic.P256(),rand.Reader) //生成公钥 pubkey:=prk.PublicKey //需要上链的数据 data:=[]byte("helloworld") //手动创建一个区块信息,实际中,通过交易来触发自动创建block block:=&Block{2,nil,data,time.Now().Unix(),nil,0} //将block信息内容拼接成字节数组 blockbytes:=prepareData(block) //对block进行hash运算,实际中是挖矿过程 blockHash:=sha256.Sum256(blockbytes)//签名 signatrue,_:=Sign(blockHash[:],prk) //验证,如果通过就进行上链处理,否则rollback if Verify(blockHash[:],signatrue,&pubkey){ fmt.Println("该block合法,可以上链") }else { fmt.Println("该Block不合法,rollback") } } //数据拼接 func prepareData(block *Block) []byte { Block:=block data := bytes.Join( [][]byte{ Block.PrevBlockHash, Block.Data, IntToHex(Block.Timestamp), Block.Data, IntToHex(int64(Block.Nonce)), IntToHex(int64(Block.Height)), }, []byte{}, ) return data } // 将int64转换为字节数组 func IntToHex(num int64) []byte { buff := new(bytes.Buffer) err := binary.Write(buff, binary.BigEndian, num) if err != nil { log.Panic(err) } return buff.Bytes() } func Sign(data []byte, privkey *ecdsa.PrivateKey) ([]byte, error) { // 对要签名的信息进行sha256散列,生成一个长度为32的字节数组 digest := sha256.Sum256(data) // 通过椭圆曲线方法对散列后的明文进行签名,返回两个big.int类型的大数 r, s, err := ecdsa.Sign(rand.Reader, privkey, digest[:]) if err != nil { return nil, err } //将大数转换成字节数组,并拼接起来,形成签名 signature := append(r.Bytes(), s.Bytes()...) return signature, nil } // 通过公钥验证签名func Verify(data, signature []byte, pubkey *ecdsa.PublicKey) bool { // 将明文转换成字节数组 digest := sha256.Sum256(data) //声明两个大数r,s r := big.Int{} s := big.Int{} //将签名平均分割成两部分切片,并将切片转换成*big.int类型 sigLen := len(signature) r.SetBytes(signature[:(sigLen / 2)]) s.SetBytes(signature[(sigLen / 2):]) //通过公钥对得到的r,s进行验证 return ecdsa.Verify(pubkey, digest[:], &r, &s) } 1991年,Chaum和Van Heyst首次提出了群签名的概念。群签名允许一个群体中的任意一个成员以匿名的方式代表整个群体对消息进行签名。 群签名需要有一个集体,一般是公司。管理员通过认证添加群签名的成员,每个成员的签名都代表集体。利用群签名可以很好的隐藏组织结构。例如,一个公司的职员可以利用群签名方案代表公司进行签名,验证者(可能是公司顾客)只需要利用公司的群公钥进行签名的合法性验证。验证者并不知道该签名是由哪个职员所签名的。当发生争议时,群管理员可以识别出实际的签名者。当然,群签名还可以应用于电子投票、电子投标和电子现金等。 如何实现群签名呢? 入群:群成员在入群之前都会向群管理进行申请入群,通过后,申请人会和群管理员达成交互式协议,该协议可生成群成员的私钥。群管理员对该密钥进行签名,并颁发给申请人,完成入群。群成员群管理员将群公钥对外公开。签名:群成员通过自己的群密钥和群公钥对消息进行签名,得到群签名。验证:通过输入群公钥和群签名用公开的某种验证方法进行验证,返回值只有真假,验证者无法计算得到签名者是群公钥里的具体人员,只知道该签名者属于群公钥里的,可以代表群体。追踪:群管理员可以通过群签名得到具体是哪个群成员进行签名的。群签名是一个中心化的签名结构,该结构的算法都是群管理员定的,造成签名者的隐私没有做到真正的保障。 所以总结群签名有三点: 1.只有群体中的合法成员才能代表整个群体进行签名; 2.接收者可以用群公钥验证群签名的合法性,但不知道该群签名是群体具体哪个成员所签;、 3.在发生争议时,群管理员可以识别出实际的签名者。 3 环签名 环签名由Rivest,shamir和Tauman三位密码学家在2001年首次提出。因签名中参数Ci(i=1,2,…,n)根据一定的规则首尾相接组成环状而得名。其实就是实际的签名者用其他可能签名者的公钥产生一个带有断口的环,然后用私钥将断口连成一个完整的环。 环签名可以认为是不可追踪的群签名,实际的操作过程都是自己完成的,没有群管理员。 签名者是某个群体的成员,他把其他群体的公钥拿来并加上自己的公钥组合成一个群公钥,然后用自己的私钥和群公钥对要发布的信息进行环签名。接收者接收到群公钥、签名和信息,然后对签名进行验证,如果签名来自群公钥,那么验证成功,否则消息不合法。 群公钥中签名者的公钥只是迷惑作用,并不对数据有任何操作。 形成的签名因为是每个其它群公钥产生的数据和自己用私钥产生的数据组成 的环,自己的数据隐藏环中,攻击者很难找到签名者的公钥。所以签名者的隐私很好地被保护起来了。 4 代理签名 1996年,Mambo等人首次提出了代理签名的概念。在代理签名方案中,允许一个原始签名者把他的签名权利委托给一个称为代理签名者的人,然后代理签名者就可以代表原始签名者进行签名。代理签名者可用于需要委托权力的密码协议中,如电子现金、移动代理和移动通信等。 5 盲签名 1982年Chaum首次提出了盲签名的概念。盲签名允许使用者获得一个消息的签名,而签名者既不知道该消息的内容,也不知道该消息的签名。盲签名可用于需要提供匿名性的密码协议中,如电子投票和电子现金。 一个盲签名方案由以下部分组成。 1)消息盲化:使用者利用盲因子对要签名的信息进行盲化处理,然后将盲化后的消息发送给签名者。 2)盲消息签名:签名者对盲化后的消息进行签名,因此他并不知道真实消息的具体内容。 3)恢复签名:使用者除去盲因子,得到真实消息的签名。 6 零知识证明 零知识证明:实质上是一种涉及两方或更多方的协议,即两方或更多方完成一项任务所需采取的一系列步骤。证明者向验证者证明并使其相信自己知道或拥有某一消息,但证明过程不能向验证者泄漏任何关于被证明消息的信息。简单理解,就是证明者能够在不向验证者提供任何有用信息的情况下,让验证者相信你。举个例子:小明在逛商场时,把钱包弄丢了。广播播报:有人捡到一个钱包,在前台,请失主认领。问题来了,小明如何向前台证明这个钱包一定是自己的呢?不是自己冒领?这个案例,就是零知识证明的完美运用。第一:前台为了避免人冒领,不会向小明泄露有关钱包的任何信息。 第二:小明必须提供准确无误的信息,证明钱包是自己的。 比如:浅谈问小明几个问题: 1、钱包的大小、颜色、品牌是什么? 2、钱包里有多少钱?有无证件?等等 如果小明全答对了,小明就是失主。 这种信息验证的手段,就是零知识证明。比较系统地讲解了密码学的发展和实现原理,着重在对称密码和公钥密码章节。而对于古典密码学内容没有展开,如凯撒密码,棋盘密码等。文中对于讲解的密码算法都有Go语言实现代码,能够让学习者不仅懂得算法的底层实现的数学原理,而且还能在实际中灵活的运用,尤其是在区块链技术中。 密码学是一个一直都在发展,一直都在被挑战的学科。当前的密码体系是公钥密码。它是一套很成熟的密码体制,应用在方方面面,如信息加密、数字签名、数字证书、加密货币等等。但没有一种算法是长久的,总会被更先进的密码技术所替代。量子密码学是个新方向,它在存在无时无刻都在威胁着现有的密码体系。未来的密码学体系应该是量子密码。 ## Publication Information - [leaf](https://paragraph.com/@leaf-6/): Publication homepage - [All Posts](https://paragraph.com/@leaf-6/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@leaf-6): Subscribe to updates