JAVA与密码学

学习资料整理

1 PKI -- 公共密钥基础设施

1.1 PKI 的定义

PKI(Public Key Infrastructure)是一种遵循既定标准的,采用密码技术为网上安全通信提供一整套安全服务的基础平台,能够为所有网络应用提供信息加密和数字签名等密码服务及所必须的密钥与证书管理体系。也就是能够对公私钥对进行管理,支持身份验证、机密性、完整性以及不可否认性服务的具有普适性的信息安全基础设施。

公钥基础设施是一个包括 硬件软件、人员、策略和规程的集合,用来实现基于 公钥密码体制密钥证书的产生、管理、存储、分发和撤销等功能。
PKI体系是计算机软硬件、权威机构及应用系统的结合。它为实施电子商务、电子政务、办公自动化等提供了基本的安全服务,从而使那些彼此不认识或距离很远的用户能通过信任链安全地交流。
PKI系统的组成

一个典型的PKI系统包括PKI策略、软硬件系统、证书机构CA、注册机构RA、证书发布系统和PKI应用等。

  • PKI安全策略

建立和定义了一个组织信息安全方面的指导方针,同时也定义了密码系统使用的处理方法和原则。它包括一个组织怎样处理密钥和有价值的信息,根据风险的级别定义安全控制的级别。

一般情况下,在PKI中有两种类型的策略:

一是证书策略,用于管理证书的使用,比如,可以确认某一CA是在Internet上 的公有CA,还是某一企业内部的私有CA;

另外一个就是CPS(Certificate Practice Statement)。一些由商业证书发放机 构(CCA)或者可信的第三方操作的PKI系统需要CPS。这是一个包含如何在实践中增强和支持安全策略的一些操作过程的详细文档。它包括CA是如何建立 和运作的,证书是如何发行、接收和废除的,密钥是如何产生、注册的,以及密钥是如何存储的,用户是如何得到它的等等。

  • 证书机构CA

证书机构CA是PKI的信任基础,它管理公钥的整个生命周期,其作用包括:**发放证书、规定证书的有效期和通过发布证书废除列表(CRL)**确保必要时可以废除证书。

  • 注册机构RA

注册机构RA提供用户和CA之间的一个接口,它获取并认证用户的身份,向CA提出证书请求。它主要完成收集用户信息和确认用户身份的功能。这里指的用户,是指将要向认证中心(即CA)申请数字证书的客户,可以是个人,也可以是集团或团体、某政府机构等。

注册管理一般由一个独立的注册机构(即RA)来承担。它**接受用户的注册申请,审查用户的申请资格 ,并决定是否同意CA给其签发数字证书 。注册机构并不给用户签发证书,而只是对用户进行资格审查。因此,RA可以设置在直接面对客户的业务部门,如银行的营业部、机构认识部门等。当然,对于一个规模较小的PKI应用系统来说,可把注册管理的职能由认证中心CA来完成,而不设立独立运行的RA。但这并不是取消了PKI的注册功能,而只是将其作为CA的一项功能而已。PKI国际标准推荐由一个独立的RA来完成注册管理的任务,可以增强应用系统的安全。**

  • 证书发布系统

证书发布系统负责证书的发放,如可以通过用户自己,或是通过目录服务器发放。目录服务器可以是一个组织中现存的,也可以是PKI方案中提供的。

  • PKI的应用

PKI的应用非常广泛,包括应用在web服务器和浏览器之间的通信、电子邮件、电子数据交换(EDI)、在Internet上的信用卡交易和虚拟私有网(VPN)等。

通常来说,CA是证书的签发机构,它是PKI的核心。众所周知,构建密码服务系统的核心内容是如何实现密钥管理。公钥体制涉及一对密钥(即私钥和公钥),私钥只由用户独立掌握,无须在网上传输,而公钥则是公开的,需要在网上传送,故公钥体制的密钥管理主要是针对公钥的管理问题,较好的方案是数字证书机制。

一个简单的PKI系统包括证书机构CA、注册机构RA和相应的PKI存储库。

* CA用于签发并管理证书;

* RA可作为CA的一部分,也可以独立,其功能包括个人身份审核、CRL管理、密钥产生和密钥对备份等;

* PKI存储库包括LDAP目录服务器和普通数据库,用于对用户申请、证书、密钥、CRL和日志等信息进行 存储和管理,并提供一定的查询功能。
LDAP目录服务器(了解)

目录服务器

目录是一个专门为搜索和浏览而设计的数据库,它也支持简单的插入、删除、修改功能。可以把它理解为我们传统使用的关系型数据库,但是他与我们的关系型数据库有着本质的区别,目录的存储结构类似于linux文件系统,它的结构一颗树,由于它是为浏览和搜索而设计的,它的查询速度很快,相反插入速度较慢,它也不支持事务和回滚以及复杂的插入、更新功能。目录服务器可像关系型数据库一样对外提供数据服务,它可以是单机或集群式的。在集群式的架构中每个机器都拥有一致的数据备份。

目录服务是由目录数据库和一套访问协议组成的系统。类似以下的信息适合储存在目录中:

  • 企业员工信息,如姓名、电话、邮箱等;

  • 公用证书和安全密钥;

  • 公司的物理设备信息,如服务器,它的IP地址、存放位置、厂商、购买时间等;

LDAP是轻量目录访问协议(Lightweight Directory Access Protocol)的缩写,LDAP是从X.500目录访问协议的基础上发展过来的,目前的版本是v3.0。

可以使用Java构建。

1.2 CA

1.2.1 数字证书基础

数字证书是一种数字标识,可以说是Internet上的安全护照或身份证明,数字证书提供的是网络上的身份证明。

数字证书是一个经 证书授权中心 数字签名的 包含公开密钥拥有者信息和公开密钥的文件

最简单的证书包含一个公开密钥名称以及证书授权中心的数字签名

一般情况下证书中还包括密钥的有效时间发证机关(证书授权中心)的名称,该证书的序列号等信息,证书的格式遵循ITUT X.509国际标准。

1.2.2 证书格式

在Internet网络中,应用程序使用的证书都来自不同的厂商或组织,为了实现可交互性,要求证书能够被不同的系统识别,符合一定的 格式,并实现标准化。X.509为证书及其CRL格式提供了一个标准。但X.509本身不是Internet标准,而是国际电联ITU标准,它定义了一个开放的框架,并在一定的范围内可以进行扩展

X.509 目前有三个版本:V1、V2和V3,其中V3是在V2的基础上加上扩展项后的版本。

为了适应新的需求ISO/IEC和ANSI X9发展了X.509 V3版本证书格式,该版本证书通过增加标准扩展项对V1和V2证书进行了扩展。另外,根据实际需要,各个组织或团体也可以增加自己的私有扩展。

X.509 V1和V2证书所包含的主要内容如下:

  • 证书版本号(Version):版本号指明X.509证书的格式版本,现在的值可以为0、1、2,也为将来的版本进行了预定义。

  • 证书序列号(SerialNumber):序列号指定由CA分配给证书的唯一的数字型标识符。当证书被取消时,实际上是将此证书的序列号放入由CA签发的CRL中,这也是序列号唯一的原因。

  • 签名算法标识符(Signature):签名算法标识用来指定由CA签发证书时所使用的签名算法。算法标识符用来指定CA签发证书时所使用的公开密钥算法和hash算法,须向国际知名标准组织(如ISO)注册。

  • 签发机构名(Issuer):此域用来标识签发证书的CA的X.500 DN名字。包括国家、省市、地区、组织机构、单位部门和通用名。

  • 有效期(Validity):指定证书的有效期,包括证书开始生效的日期和时间以及失效的日期和时间。每次使用证书时,需要检查证书是否在有效期内。

  • 证书用户名(Subject):指定证书持有者的X.500唯一名字。包括国家、省市、地区、组织机构、单位部门和通用名,还可包含email地址等个人信息等

  • 证书持有者公开密钥信息(subjectPublicKeyInfo):证书持有者公开密钥信息域包含两个重要信息:证书持有者的公开密钥的值;公开密钥使用的算法标识符。此标识符包含公开密钥算法和hash算法。

  • 签发者唯一标识符(Issuer Unique Identifier):签发者唯一标识符在第2版加入证书定义中。此域用在当同一个X.500名字用于多个认证机构时,用一比特字符串来唯一标识签发者的X.500名字。可选。

  • 证书持有者唯一标识符(Subject Unique Identifier):持有证书者唯一标识符在第2版的标准中加入X.509证书定义。此域用在当同一个X.500名字用于多个证书持有者时,用一比特字符串来唯一标识证书持有者的X.500名字。可选。

  • 签名值(Issuer's Signature):证书签发机构对证书上述内容的签名值。

X.509 V3 证书是在v2的基础上一标准形式或普通形式增加了扩展项,以使证书能够附带额外信息。标准扩展是指由X.509 V3版本定义的对V2版本增加的具有广泛应用前景的扩展项,任何人都可以向一些权威机构,如ISO,来注册一些其他扩展,如果这些扩展项应用广泛,也许以后会成为标准扩展项。

CRL——证书废除列表(吊销列表)

证书废除列表CRL(Certificate revocation lists,又称证书黑名单)为应用程序和其它系统提供了一种检验证书有效性的方式。任何一个证书废除以后,证书机构CA会通过发布CRL的方式来通知各个相关方

目前,同X.509 V3证书对对应的CRL为 X.509 v2 CRL,其所包含的内容格式如下:

  • CRL的版本号:0表示X.509 V1 标准;1表示X.509 V2 标准;目前常用的是同X.509 V3证书对应的CRL V2版本。

  • 签名算法:包含算法标识算法参数,用于指定证书签发机构用来对CRL内容进行签名的算法。

  • 证书签发机构名:签发机构的DN名,由国家、省市、地区、组织机构、单位部门和通用名等组成。

  • 此次签发时间:此次CRL签发时间,遵循ITU-T X.509 V2标准的CA在2049年之前把这个域编码为UTCTime类型,在2050或2050年之后年之前把这个域编码为GeneralizedTime类型。

  • 下次签发时间:下次CRL签发时间,遵循ITU-T X.509 V2标准的CA在2049年之前把这个域编码为UTCTime类型,在2050或2050年之后年之前把这个域编码为GeneralizedTime类型。

  • 用户公钥信息:其中包括废除的证书序列号和证书废除时间。废除的证书序列号是指要废除的由同一个CA签发的证书的一个唯一标识号,同一机构签发的证书不会有相同的序列号。

  • 签名算法:对CRL内容进行签名的签名算法

  • 签名值:证书签发机构对CRL内容的签名值

CRL中还包含扩展域和条目扩展域。CRL扩展域用于提供与CRL有关的额外信息部份,允许团体和组织定义私有的CRL扩展域来传送他们独有的信息;

证书的存放方式

数字证书作为一种电子数据格式,可以直接从网上下载(PKI用户可自主下载),也可以通过其他方式。

  • IC卡存放用户证书。即把用户的数字证书写到IC卡中,供用户随身携带。

  • 直接存放在磁盘或自己的终端上。户将从CA申请来的证书下载或复制到磁盘或自己的PC机或智能终端上,当用户使用自时,直接从终端读入即可。

1.2.3 CA框架模型

证书机构CA用于创建和发布证书,它通常为一个称为安全域(security domain)的有限群体发放证书。

创建证书的时候,CA系统首先获取用户的请求信息,其中包括用户公钥(如果用户端是个人使用或者测试用,则公钥一般由用户端产生,如电子邮件程序或浏览器等 或者使用第三方开发的具有独立CSP的智能终端如USBkey),CA将根据用户的请求信息产生证书,并用自己的私钥对证书进行签名。其他用户、应用程序或实体将使用CA的公钥对证书进行验证。如果一个CA系统是可信的,则验证证书的用户可以确信,他所验证的证书中的公钥属于证书所代表的那个实体。

CA 还负责维护和发布证书废除列表CRL(又称为证书黑名单)。

当一个证书,特别是其中的公钥因 为其他原因无效时(不是因为到期),CRL提供了一种通知用户和其他应用的中心管理方式。CA系统生成CRL以后,要么是放到LDAP服务器中供用户查询 或下载,要么是放置在Web服务器的合适位置,以页面超级连接的方式供用户直接查询或下载。(通知到PKI用户公钥已失效)

  • 安全服务器

安全服务器面向普通用户,用于提供证书申请、浏览、证书撤消列表以及证书下载等安全服务。

安全服务器与用户的的通信采取安全信道方式(如SSL 的方式,不需要对用户进行身份认证)。用户首先得到安全服务器的证书(该证书由CA颁发), 然后用户与服务器之间的所有通信,包括用户填写的申请信息以及 浏览器生成的公钥均以安全服务器的密钥进行加密传输,只有安全服务器利用自己的私钥解密才能得到明文 ,这样可以防止其他人通过窃听得到明文。从而保证了证书申请和传输过程中的信息安全性。

  • CA 服务器

CA服务器使整个证书机构的核心,负责证书的签发。CA首先产生自身的私钥和公钥(密钥长度至少为1024位),然后生成数字证书,并且将数字证书传输给安全服务器。CA还负责为操作员、安全服务器以及注册机构服务器生成数字证书。安全服务器的数字证书和私钥也需要传输给安全服务器。CA服务器是 整个结构中最为重要的部分,存有CA的私钥以及发行证书的脚本文件 ,出于安全的考虑,应将CA服务器与其他服务器隔离,任何通信采用人工干预的方式,确保 认证中心的安全。

  • 注册机构RA

登记中心服务器面向登记中心操作员,在CA体系结构中起承上启下的作用,一方面向CA转发安全服务器传输过来的证书申请请求,另一方面向LDAP服务器和安全服务器转发CA颁发的数字证书和证书撤消列表。

  • LDAP服务器

LDAP服务器提供目录浏览服务,负责将注册机构服务器传输过来的用户信息以及数字证书加入到服务器上。这样其他用户通过访问LDAP服务器就能够得到其他用户的数字证书。

  • 数据库服务器

数据库服务器是认证机构中的核心部分,用于认证机构中数据(如密钥和用户信息等)、日志合统计信息的存储和管理。

证书的申请和撤销

证书的申请有两种方式,一是在线申请,另外一个就是离线申请。在线申请就是通过浏览器或其他应用系统通过在线的方式来申请证书,这种方式一般用于申请普通用户证书或测试证书。离线方式一般通过人工的方式直接到证书机构证书受理点去办理证书申请手续,通过审核后获取证书,这种方式一般用于比较重要的场合,如服务器证书和商家证书等。下面讨论的主要是在线申请方式。

当证书申请时,用户使用浏览器通过Internet访问安全服务器,下载CA的数字证书(又叫做根证书),然后注册机构服务器对用户进行身份审核,认可后便批准用户的证书申请,然后操作员对证书申请表进行数字签名,并将申请及其签名一起提交给CA服务器。

CA 操作员获得注册机构服务器操作员签发的证书申请,发行证书或者拒绝发行证书,然后将证书通过硬拷贝的方式传输给注册机构服务器。注册机构服务器得到用户的 证书以后将用户的一些公开信息和证书放到LDAP服务器上提供目录浏览服务,并且通过电子邮件的方式通知用户从安全服务器上下载证书。用户根据邮件的提示 到指定的网址下载自己的数字证书,而其他用户可以通过LDAP服务器获得他的公钥数字证书。

证书申请的步骤如下:

  1. 用户申请

用户首先下载CA的证书,又叫根证书,然后在证书的申请过程中使用SSL安全方式与服务器建立连接用户填写个人信息,浏览器生成私钥 和公钥对,将私钥保存客户端特定文件中,并且要求用口令保护私钥同时将公钥和个人信息提交给安全服务器安全服务器将用户的申请信息传送给注册机构服务器。

SSL/TLS----网页加密解密

  1. 注册机构审核

用户与注册机构人员联系,证明自己的真实身份,或者请求代理人与注册机构联系

注册机构操作员利用自己的浏览器与注册机构服务器建立SSL安全通信,该 服务器需要对操作员进行严格的身份认证,包括操作员的数字证书、IP地址,为了进一步保证安全性,可以设置固定的访问时间。操作员首先查看目前系统中的申 请人员,从列表中找出相应的用户,点击用户名,核对用户信息,并且可以进行适当的修改,如果操作员同意用户申请证书请求,必须对证书申请信息进行数字签名。操作员也有权利拒绝用户的申请。操作员与服务器之间的所有通信都采用加密和签名,具有安全性、抗否认性,保证了系统的安全性和有效性。

  1. CA发行证书

注册机构RA通过硬拷贝 的方式向CA传输用户的证书申请 与操作员的数字签名 ,CA操作员查看用户的详细信息,并且验证操作员的数字签名,如果签名验证通 过,则同意用户的证书请求,颁发证书。然后CA将证书输出。如果CA操作员发现签名不正确,则拒绝证书申请 , CA颁发的数字证书中包含关于用户及CA自 身的各种信息,如:能唯一标识用户的姓名及其他标识信息,如个人的email地址;证书持有者的公钥。公钥用于为证书持有者加密敏感信息、签发个人证书的 认证机构的名称、个人证书的序列号和个人证书的有效期(证书有效起止日期)等

  1. 注册机构证书转发

注册机构RA操作员从CA处得到新的证书,首先将证书输出到LDAP目录服务器以提供目录浏览服务,最后操作员向用户发送一封电子邮件,通知用户证书已经 发行成功,并且把用户的证书序列号告诉用户到指定的网址去下载自己的数字证书。并且告诉用户如何使用安全服务器上的LDAP配置,让用户修改浏览器的客户 端配置文件以便访问LDAP服务器,获得他人的数字证书。

  1. 用户证书获取

用户使用证书申请时的浏览器到指定的网址,键入自己的证书序列号,服务器要求用户必须使用申请证书时的浏览器,因为浏览器需要用该证书相应的私钥去验证数字证书。只有保存了相应私钥的浏览器才能成功下载用户的数字证书。

这时用户打开浏览器的安全属性,就可以发现自己已经拥有了CA颁发的数字证书,可以利用该数字证书与其他人以及web服务器(拥有相同CA颁发的证书)使用加密、数字签名进行安全通信。

认证中心还涉及到CRL的管理。用户向特定的操作员(仅负责CRL的管理)发一份加密签名的邮件,申明自己希望撤消证书。操作员打开邮件,填写CRL注册 表,并且进行数字签名,提交给CA,CA操作员验证注册机构操作员的数字签名,批准用户撤消证书,并且更新CRL,然后CA将不同格式的CRL输出给注册 机构,公布到安全服务器上,这样其他人可以通过访问服务器得到CRL。

证书撤销流程步骤如下:
  1. 用户向注册机构操作员CRL Manager发送一封签名加密的邮件,声明自己自愿撤消证书。

  1. 这册机构同意证书撤消,操作员键入用户的序列号,对请求进行数字签名。

  1. CA查询证书撤消请求列表,选出其中的一个,验证操作员的数字签名,如果正确的话,则同意用户的证书撤消申请,同时更新CRL列表,然后将CRL以多种格式输出。

  1. 注册机构转发证书撤消列表。操作员导入CRL,以多种不同的格式将CRL公布于众。

  1. 用户浏览安全服务器,下载或浏览CRL。

在一个PKI,特别是CA中,信息的存储是一个非常核心的问题,它包括两个方面:

一是CA服务器利用数据库来备份当前密钥和归档过期密钥,该数据库需高度安全和机密,其安全等级同CA本身相同;

另外一个就是目录服务器,用于分发证书和CRL,一般采用LDAP目录服务器。

密钥管理

密钥管理也是PKI(主要指CA)中的一个核心问题 ,主要是指密钥对的安全管理,包括密钥产生、密钥备份、密钥恢复和密钥更新等。

1. 密钥产生

密钥对的产生是证书申请过程中重要的一步,其中产生的私钥由用户保留,公钥和其他信息则交于CA中心进行签名,从而产生证书。根据证书类型和应用的不同,密钥对的产生也有不同的形式和方法。对普通证书和测试证书,一般由浏览器或固定的终端应用来产生,这样产生的密钥强度较小,不适合应用于比较重要的安全网络交易。而对于比较重要的证书,如商家证书和服务器证书等,密钥对一般由专用应用程序或CA中心直接产生,这样产生的密钥强度大,适合于重要的应用场合。 另外,根据密钥的应用不同,也可能会有不同的产生方式。比如签名密钥可能在客户端或RA中心产生,而加密密钥则需要在CA中心直接产生(一种方式)

2. 密钥备份和恢复

在一个PKI系统中,维护密钥对的备份至关重要,如果没有这种措施,当密钥丢失后,将意味着加密数据的完全丢失,对于一些重要数据,这将是灾难性的。所以,密钥的备份和恢复也是PKI密钥管理中的重要一环。

使用PKI的企业和组织必须恩能够得到确认:即使密钥丢失,受密要加密保护的重要信息也必须能够恢复,并且不能让一个独立的个人完全控制最重要的主密钥,否则将引起严重后果。

企业级的PKI产品**至少应该支持用于加密的安全密钥的存储、备份和恢复 **。密钥一般用口令进行保护,而口令丢失则是管理员最常见的安全疏漏之一。所以,PKI产品应该能够备份密钥,即使口令丢失,它也能够让用户在一定条件下恢复该密钥,并设置新的口令。

例如,在某些情况下用户可能有多对密钥,至少应该有两个密钥:一个用于加密,一个用于签名。签名密要不需要备份,因为用于验证签名的公钥(或公钥证书)广泛发布,即使签名私钥丢失,任何用于相应公钥的人都可以对已签名的文档进行验证。 但PKI系统必须备份用于加密的密钥对,并允许用户进行恢复,否则,用于解密的私钥丢失将意味着加密数据的完全不可恢复。

另外,使用PKI的企业也应该考虑所使用密钥的生命周期,它包括密钥和证书的有效时间,以及已撤销密钥和证书的维护时间等。

3. 密钥更新

对每一个由CA颁发的证书都会有有效期,密钥对生命周期的长短由签发证书的CA中心来确定,各CA系统的证书有效期限有所不同,一般大约为2-3年。

当用户的私钥被泄漏或证书的有效期快到时,用户应该更新私钥。这时用户可以废除证书,产生新的密钥对,申请新的证书。

证书的使用

在实际应用中,为了验证信息的数字签名,用户首先必须获取信息发送者的公钥证书 ,以及一些额外需要的证书(如CA证书等,用于验证发送者证书的有效性)。

证书的获取可以有多种方式,如发送者发送签名信息时附加发送自己的证书,或以另外的单独信息发送证书,或者可以通过访问证书发布的目录服务器来获得,或者直接从证书相关的实体处获得。在一个PKI体系中,可以采取某种或某几种上述方式获得证书。

证书的持有者可以是个人用户、企事业单位、商家、银行等。无论是哪一方,在使用证书验证数据时,都遵循同样的验证流程。一个完整的验证过程有以下几步:

  1. 将客户端发来的数据解密 (如解开数字信封)。

  1. 将解密后的数据分解成原始数据,签名数据和客户证书三部分。

  1. 用CA根证书验证客户证书的签名完整性。

  1. 检查客户证书是否有效 (当前时间在证书结构中的所定义的有效期内)。

  1. 检查客户证书是否作废 (OCSP方式或CRL方式)。

  1. 验证客户证书结构中的证书用途。

  1. 客户证书验证原始数据的签名完整性。

PCKS----公开密钥密码学标准

*CA中心普遍采用的规范是X.509系列和PKCS系列*

PKCS 是由美国RSA安全公司及其合作伙伴指定的一组公钥密码学标准,其中包括证书申请、证书更新、证书作废列表发布、扩展证书内容及数字签名、数字信封的格式等方面的一系列相关协议。 到1999年底,PKCS已经公布了以下标准:

PKCS#1:RSA加密标准或规范,定义RSA公开密钥算法如何进行加密和签名,主要用于组织PKCS#7中所描述的数字签名和数字信封。

该标准类似于国密算法体系中的《GMT 0009-2012 SM2密码算法使用规范》,该国密规范主要内容有:

  • 规范了sm2算法的使用方法

  • 密钥结构

  • 密钥产生

  • 签名运算

  • 加密运算

  • 密钥协商

  • 规范了私钥的保护结构(用于加密密钥)

  • 规范了用户ID的标称值。

  • 密钥结构

  • 密钥产生

  • 签名运算

  • 加密运算

  • 密钥协商

PKCS#3:定义Diffie-Hellman密钥交换协议;

PKCS#5:描述一种利用从口令派生出来的安全密钥加密字符串的方法。使用MD2或MD5从口令中派生密钥,并采用DES-CBC模式加密。主要用于加密从一个计算机传送到另一个计算机的私人密钥,不能用于加密消息。

PKCS#6:描述了公钥证书的标准语法,主要描述X509证书的扩展格式

PKCS#7:定义一种通用的消息语法,包括数字签名和加密等用于增强的加密机制,PKCS#7与PEM兼容,所以不需要其他密码操作 ,就可以将加密的消息转换成PEM消息。

该标准类似于国密体系中的《GMT 0010-2012 SM2密码算法加密签名消息语法规范》,这个国密标准的主要内容:

  • 规范了加密签名消息语法

  • 签名消息

  • 数字信封消息

  • 带签名的数字信封消息

  • 加密消息

  • 密钥协商交换消息

  • 规范了公钥和私钥的语法

PKCS#8:描述私有密钥信息格式,该信息包括公开密钥算法的私钥密钥 及可选的属性集等;

PKCS#9:定义一些用于PKCS#6证书扩展、PKCS#7数字签名和PKCS#8私钥加密信息的属性类型;

PKCS#10:描述证书请求语法;

PKCS#11:称为Cyptoki,定义了一套独立于技术的程序设计接口,用于智能卡和PCMCIA卡之类的加密设备;

PKCS#12:描述个人信息交换语法标准。描述了将用户公钥、私钥证书和其他相关信息打包的语法;

PKCS#13:椭圆曲线密码 体制标准

PKCS#14:伪随机数 形成标准

PKCS#15:密码令牌信息格式标准

数字证书扩展名

数字签名

先单向散列,再使用私钥加密

数字签名 (又称公钥数字签名、电子签章)是一种类似写在纸上的普通的物理签名,但是使用了公钥加密领域的技术实现,用于鉴别数字信息的方法。一套数字签名通常定义两种互补的运算,一个用于签名,另一个用于验证。简单地说,所谓数字签名就是附加在数据单元上的一些数据,或是对数据单元所作的密码变换。 这种数据或变换允许数据单元的接收者用以确认数据单元的来源和数据单元的完整性并保护数据,防止被人进行伪造,它是对电子形式的消息进行签名的一种方法。

公钥加密系统里面,使用公钥加密,就可以使用私钥来解密;使用私钥加密,也可以使用公钥来解密。消息加密利用的是前者,数字签名利用的是后者。在数字签名的过程中,对原文的保密性没有要求,所以加密、解密这样的名词在这个场景中并不准确,用签名解签会更合适。

数字签名是非对称加密哈希算法的应用。

数字签名的文件的完整性是很容易验证,而且数字签名具有不可抵赖性 。

普通数字签名算法有RSA、ElGamal、Fiat-Shamir、Guillou- Quisquarter、Schnorr、Ong-Schnorr-Shamir数字签名算法、Des/DSA, 椭圆曲线数字(ECC)签名算法和有限自动机数字签名算法等。特殊数字签名有盲签名、代理签名、群签名、不可否认签名、公平盲签名、门限签名、具有消息恢复功能的签名等,它与具体应用环境密切相关

数字签名过程

用到的技术:哈希算法、RSA非对称加密,国密SM2

假如小明要给小红发一个文件,那么怎样才能保证小红收到的文件是小明发的并且文件没有被篡改呢?

发送方(签名):

  • 小明告诉小红自己的公钥,并且私钥只有小明自己一个人知道

  • 小明对数据M 计算哈希运算,得到摘要 D

  • 小明使用RSA私钥对摘要D进行加密,得到签名S

  • 将数据M 和签名S 一起发送给小红

接收方(验签):

  • 小红收到消息后,首先对数据M 使用跟小明一样的哈希算法,得到摘要 D'

  • 使用小明的RSA公钥对签名S进行解签,得到D''

  • 如果 D' 和 D'' 相同,那么证明 数据M 确实是小明发出的,并且文件内容没有被篡改过

公钥加密与数字签名的一般性区别
  • 公钥加密一般是任何人都可加密,只有私钥解密;而数字签名只有私钥用来签名,任何人都可以使用公钥来验签(不可否认性,私钥只有签名者拥有)

  • 公钥加密过程是为了保证信息的机密性,而数据签名则是为了保证信息的完整性和不可否性

1.3 数字信封----双层加密体系

信息发送者首先利用随机产生的对称密码加密信息,再利用接收方的公钥加密对称密码,被公钥加密后的对称密码被称之为数字信封

常见对称密钥算法

1. DES

以64位为分组。64位明文输入,64位密文输出。

算法特点

优点:效率高,算法简单,系统开销小,适合加密大量数据,明文长度和密文长度相等

缺点:需要以安全方式进行秘钥交换,秘钥管理复杂分组比较短、密钥太短、密码生命周期短、运算速度较慢。

用途

  • cookie: 可以使用DES来将用户的id和登录时间加密为uid和lid,存在cookie中,然后在拦截器中解密出来验证uid和lid的正确性,从而实现用户通行许可的验证。

  • 可用于大量数据的加密

2. 3DES

3DES(或称为Triple DES)是三重数据加密算法(TDEA,Triple Data Encryption Algorithm)块密码的通称。它相当于是对每个数据块应用三次DES加密算法

由于计算机运算能力的增强,原版DES密码的密钥长度变得容易被暴力破解;3DES即是设计用来提供一种相对简单的方法,即通过增加DES的密钥长度来避免类似的攻击,而不是设计一种全新的块密码算法

原理:

设Ek()和Dk()代表DES算法的加密和解密过程,K代表DES算法使用的密钥,M代表明文,C代表密文:

3DES加密过程为:C=Ek3 (Dk2 (Ek1(M) ) )

3DES解密过程为:M=Dk1 (Ek2 (Dk3(C) ) )

算法特点

密钥长度的增加

优点:它以DES为基本模块,通过组合分组方法设计出分组加密算法。比起最初的DES,3DES更为安全。

缺点: 效率低,资源消耗大。

Demo:

package com.lett.symenc.improve;

import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

/**
 * @author lett
 * @date 2020/07/29 9:59
 */
public class Des3Test {
    /**
     * JDK中 3DES 4种不同的模式
     * DESede/CBC/NoPadding (112/168)
     * DESede/CBC/PKCS5Padding (112/168)
     * DESede/ECB/NoPadding (112/168)
     * DESede/ECB/PKCS5Padding (112/168)
     */
    private static final String algorithm = "DESede/ECB/PKCS5Padding";

    public static void main(String[] args) {
        //no padding模式下必须是8字节的倍数,也可手动填充
        String input = "12345678";

        KeyGenerator keyGenerator = null;
        try {
            keyGenerator = KeyGenerator.getInstance("DESede");
        } catch (NoSuchAlgorithmException e) {
            System.out.println("密钥生成过程出现错误!");
            e.printStackTrace();
        }

        keyGenerator.init(168);
        SecretKey secretKey = keyGenerator.generateKey();
        byte[] key = secretKey.getEncoded();
        System.out.println(key.length);
        String s = null;
        String result = null;

//        IvParameterSpec iv = get8ByteIV();
//
//        try {
//            s  = encryptionIV(input, key,iv);
//            result = decryptIV(s, key,iv);
//        } catch (Exception e) {
//            System.out.println("加解密过程出现错误!");
//            e.printStackTrace();
//        }

        try {
            s = enc(input,key);
            result = dec(s,key);
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println(s);
        System.out.println(result);

    }

    /**
     * CBC模式 3DES加密
     * @param string 明文
     * @param key 密钥
     * @param ivParameterSpec 8字节向量
     * @return Base64后的密文
     * @throws Exception
     */
    public static String encryptionIV(String string,byte [] key,IvParameterSpec ivParameterSpec)  throws Exception{
        Cipher cipher = Cipher.getInstance(algorithm);
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, "DESede");
        cipher.init(Cipher.ENCRYPT_MODE,secretKeySpec,ivParameterSpec);
        byte[] result = cipher.doFinal(string.getBytes());
        return Base64.encode(result);
    }

    /**
     * CBC模式 3DES解密
     * @param string Base64后的密文
     * @param key 密钥
     * @param ivParameterSpec 8字节向量
     * @return 明文
     * @throws Exception
     */
    public static String decryptIV(String string,byte[] key,IvParameterSpec ivParameterSpec)throws Exception{
        Cipher cipher = Cipher.getInstance(algorithm);
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, "DESede");
        cipher.init(Cipher.DECRYPT_MODE,secretKeySpec,ivParameterSpec);
        byte[] decode = Base64.decode(string);
        byte[] bytes = cipher.doFinal(decode);
        return new String(bytes);
    }

    /**
     * 生成8字节向量
     * @return
     */
    public static IvParameterSpec get8ByteIV(){
        SecureRandom secureRandom = new SecureRandom();
        byte[] seed = secureRandom.generateSeed(8);
        IvParameterSpec spec = new IvParameterSpec(seed);
        return spec;
    }

    /**
     * ECB模式 3DES加密
     * @param string 明文
     * @param key 密钥
     * @return Base64后的密文
     * @throws Exception
     */
    public static String enc(String string,byte[] key) throws Exception{
        Cipher cipher = Cipher.getInstance(algorithm);
        SecretKeySpec secretKeySpec = new SecretKeySpec(key,"DESede");
        cipher.init(Cipher.ENCRYPT_MODE,secretKeySpec);
        byte[] bytes = cipher.doFinal(string.getBytes());
        String encode = Base64.encode(bytes);
        return encode;
    }

    /**
     * ECB模式 3DES解密
     * @param string Base64后的密文
     * @param key 密钥
     * @return 明文
     * @throws Exception
     */
    public static String dec(String string,byte[] key)throws Exception{
        Cipher cipher = Cipher.getInstance(algorithm);
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, "DESede");
        cipher.init(Cipher.DECRYPT_MODE,secretKeySpec);
        byte[] decode = Base64.decode(string);
        byte[] bytes = cipher.doFinal(decode);
        return new String(bytes);
    }

}
出现的错误

源码IvParameterSpec

package javax.crypto.spec;

import java.security.spec.AlgorithmParameterSpec;

public class IvParameterSpec implements AlgorithmParameterSpec {
    private byte[] iv;

    public IvParameterSpec(byte[] var1) {
        this(var1, 0, var1.length);
    }

    public IvParameterSpec(byte[] var1, int var2, int var3) {
        if (var1 == null) {
            throw new IllegalArgumentException("IV missing");
        } else if (var1.length - var2 < var3) {
            throw new IllegalArgumentException("IV buffer too short for given offset/length combination");
        } else if (var3 < 0) {
            throw new ArrayIndexOutOfBoundsException("len is negative");
        } else {
            this.iv = new byte[var3];
            System.arraycopy(var1, var2, this.iv, 0, var3);
        }
    }

    public byte[] getIV() {
        return (byte[])this.iv.clone();
    }
}

解决:

使用伪随机数生成器生成一个8字节的向量

3. AES

密码学中的高级加密标准(Advanced Encryption Standard,AES),又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。

加密模式

对称/分组密码一般分为流加密(如OFB、CFB等)和**块加密(如ECB、CBC等)**。

对于流加密,需要将分组密码转化为流模式工作。对于块加密(或称分组加密),如果要加密超过块大小的数据,就需要涉及填充和链加密模式。

  • ECB (Electronic Code Book电子密码本)模式

ECB模式是最早采用和最简单的模式,它将加密的数据分成若干组,每组的大小跟加密密钥长度相同,然后每组都用相同的密钥进行加密。

优点:

1.简单;

2.有利于并行计算;

3.误差不会被传送;

缺点:

1.不能隐藏明文的模式;

2.可能对明文进行主动攻击;

因此,此模式适于加密小消息。

  • CBC (Cipher Block Chaining,加密块链)模式

优点:

1.不容易主动攻击,安全性好于ECB,适合传输长度长的报文,是SSL、IPSec的标准。

缺点:

1.不利于并行计算

2.误差传递;

3.需要初始化向量IV

  • CFB (Cipher FeedBack Mode,加密反馈)模式

优点:

1.隐藏了明文模式;

2.分组密码转化为流模式;

3.可以及时加密传送小于分组的数据;

缺点:

1.不利于并行计算;

2.误差传送:一个明文单元损坏影响多个单元 ;

3.唯一的IV;

  • OFB (Output FeedBack,输出反馈)模式

优点:

1.隐藏了明文模式;

2.分组密码转化为流模式;

3.可以及时加密传送小于分组的数据;

缺点:

1.不利于并行计算;

2.对明文的主动攻击是可能的;

3.误差传送:一个明文单元损坏影响多个单元 。

  • CTR计数器模式

描述:计算器模式不常见,在CTR模式中,有一个自增的算子,这个算子用密钥加密之后的输出和明文异或的结果得到密文,相当于一次一密。这种加密方式简单快速,安全可靠,而且可以并行加密,但是在计算器不能维持很长的情况下,密钥只能使用一次。

优点:并行、一次一密、不传递误差

缺点:主动攻击(改明文,后续内容不影响,只要误差不传递该缺点就存在)

AES的算法特点

优点:AES对内存的需求非常低,运算速度快,在有反馈模式、无反馈模式的软硬件中,表现出非常好的性能,

缺点:密钥传输不安全

应用场景
  • AES加密速度快,适合大量数据,处理数据后可复原。

Demo

package com.lett.symenc.improve;

import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.Security;

public class AesTest {
    /**
     * JDK AES的模式只支持128位 需要解除限制
     * AES/CBC/NoPadding (128)
     * AES/CBC/PKCS5Padding (128)
     * AES/ECB/NoPadding (128)
     * AES/ECB/PKCS5Padding (128)
     *
     * AES/OFB/PKCS5Padding
     * AES/CFB/PKCS5Padding
     * AES/CTR/PKCS5Padding
     *
     *  AES/OFB/NoPadding
     *  AES/CFB/NoPadding
     *  AES/CTR/NoPadding
     *
     */
    private static final String algorithm = "AES/CBC/NoPadding";
    private static final Integer keySize = 256;

    public static void main(String[] args) throws Exception{
		//加入BC支持
        Security.addProvider(new BouncyCastleProvider());

        //必须是8字节的倍数
        String string = "1234567812345678";

        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES","BC");
        keyGenerator.init(keySize);
        SecretKey secretKey = keyGenerator.generateKey();
        byte[] key = secretKey.getEncoded();

//        byte[] key = new SecureRandom().generateSeed(24);
//        String output = encryption(string,key);
//        System.out.println("密文:"+output+" "+output.length());
//        System.out.println("明文:"+decrypt(output,key));

//      向量为16个字节
        IvParameterSpec iv = get16ByteIV();

        String s = encryptionIV(string, key, iv);
        System.out.println(s);
        String s1 = decryptIV(s, key, iv);
        System.out.println(s1);
    }

    /**
     * AES ECB 加密
     * @param input 明文
     * @param key 密钥
     * @return 密文
     * @throws Exception
     */
    public static String encryption(String input,byte[] key) throws Exception {
        Cipher cipher = Cipher.getInstance(algorithm,"BC");

        SecretKeySpec secretKeySpec = new SecretKeySpec(key,"AES");
        cipher.init(Cipher.ENCRYPT_MODE,secretKeySpec);
        byte[] bytes = cipher.doFinal(input.getBytes());
        String encode = Base64.encode(bytes);
        return encode;
    }

    /**
     * DES ECB 解密
     * @param string 密文
     * @param key 密钥
     * @return 明文
     * @throws Exception
     */
    public static String decrypt(String string,byte[] key) throws Exception{
        Cipher cipher = Cipher.getInstance(algorithm,"BC");
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
        cipher.init(Cipher.DECRYPT_MODE,secretKeySpec);
        byte[] decode = Base64.decode(string);
        byte[] bytes = cipher.doFinal(decode);
        return new String(bytes);
    }

    /**
     * CBC模式 AES加密
     * @param string 明文
     * @param key 密钥
     * @param ivParameterSpec 8字节向量
     * @return Base64后的密文
     * @throws Exception
     */
    public static String encryptionIV(String string, byte [] key, IvParameterSpec ivParameterSpec)  throws Exception{
        Cipher cipher = Cipher.getInstance(algorithm,"BC");
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
        cipher.init(Cipher.ENCRYPT_MODE,secretKeySpec,ivParameterSpec);
        byte[] result = cipher.doFinal(string.getBytes());
        return Base64.encode(result);
    }

    /**
     * CBC模式 AES解密
     * @param string Base64后的密文
     * @param key 密钥
     * @param ivParameterSpec 8字节向量
     * @return 明文
     * @throws Exception
     */
    public static String decryptIV(String string,byte[] key,IvParameterSpec ivParameterSpec)throws Exception{
        Cipher cipher = Cipher.getInstance(algorithm,"BC");

        Provider provider = cipher.getProvider();
        System.out.println(provider.getName());

        SecretKeySpec secretKeySpec = new SecretKeySpec(key,"AES");
        cipher.init(Cipher.DECRYPT_MODE,secretKeySpec,ivParameterSpec);
        byte[] decode = Base64.decode(string);
        byte[] bytes = cipher.doFinal(decode);
        return new String(bytes);
    }

    /**
     * 生成16字节向量
     * @return
     */
    public static IvParameterSpec get16ByteIV(){
        SecureRandom secureRandom = new SecureRandom();
        byte[] seed = secureRandom.generateSeed(16);
        IvParameterSpec spec = new IvParameterSpec(seed);
        return spec;
    }

}
AES Demo在密钥长度为192,256位加密时出现的问题

解决:

JDK8的加密策略存在限制版本和无限制版本,随着越来越多的第三方工具只支持 JDK8,业务环境中,发现有些方法会报异常:

1、使用AES加解密

java.security.InvalidKeyException: Illegal key size

2、安全性机制导致的访问https会报错:

Received fatal alert: handshake_failure; nested exception is javax.net.ssl.SSLHandshakeException:
Received fatal alert: handshake_failure,accessUrl

这是因为某些国家的进口管制限制,JDK默认的加解密有一定的限制。

比如默认不允许 256 位密钥的 AES 加解密,解决方法就下载官方JCE无限制强度加密策略文件,覆盖即可。

官方网站提供了JCE无限制权限策略文件的下载:

https://links.jianshu.com/go?to=http%3A%2F%2Fwww.oracle.com%2Ftechnetwork%2Fjava%2Fjavase%2Fdownloads%2Fjce-7-download-432124.html)

JDK8的下载地址:

http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html

但从**Java 1.8.0_151和1.8.0_152 **开始,为JVM启用无限制强度管辖策略 有了一种新的更简单的方法。如果不启用此功能,则不能使用AES-256:

在 jre/lib/security 文件夹中查找文件 java.security,现在用文本编辑器打开java.security,并找到定义java安全性属性crypto.policy的行,它可以有两个值limited或unlimited - 默认值是limited。将其设置为:

crypto.policy=unlimited

现在重新启动指向JVM的Java应用程序即可。

非对称加密(公钥加密)

国际常用非对称加密算法

名称

密钥模长

成熟度

安全性(取决于密钥模长)

运算速度

资源的消耗

RSA

1024,2048,3072,4096

DSA

只能用于数字签名

ECC

160,224,256,

低(计算量小,存储空间占用小,带宽要求低)

1.RSA (目前安全的已经是在2048位以上了,不推荐1024位的了)

RSA密钥是(公钥+模值)、(私钥+模值)分组分发的,单独给对方一个公钥或私钥是没有任何用处,所以我们说的“密钥”其实是它们两者中的其中一组。但我们说的“密钥长度”一般只是指模值的位长度。目前主流可选值:1024、2048、3072、4096...

每次加密的字节数,不能超过密钥的长度值减去11,而每次加密得到的密文长度,却恰恰是密钥的长度。所以,如果要加密较长的数据,可以采用数据截取的方法,分段加密。

算法特点

优点:安全性好

缺点:运算速度慢,资源消耗高

RSA作为一种低效的加密方法,用在加密大量数据上面是不合适的,即使是签名之类的地方,能尽量少用也要少用,否则对性能影响很大。

RSA产生密钥很麻烦,受到素数产生技术的限制

用途

适用于少量数据的加密

用来加密对称算法的密钥,而密文多用对称加密算法加密传输。

数字签名

Demo

package com.lett.asyenc;

import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;

import javax.crypto.Cipher;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

/**
 * RSA demo
 * @author let
 * @createTime 2020/07/30 9:49
 */
public class RSADemo {
    /**
     * Integer 1为公钥 0为私钥
     * String BASE64得到公私钥
     */
    private static Map<Integer,String> keyMap = new HashMap<Integer, String>();

    /**
     * JDK
     * RSA/ECB/PKCS1Padding ( 1024,2048 )
     * RSA/ECB/OAEPWithSHA-1AndMGF1Padding ( 1024,2048 )
     * RSA/ECB/OAEPWithSHA-256AndMGF1Padding ( 1024,2048 )
     */
    private static final String algorithm = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";

    /**
     * JDK
     * RSA/ECB/PKCS1Padding
     * 模长  -> 明文长度
     * 1024 -> 117
     * 2048 -> 245
     */
    private static final Integer keySize = 2048;

    public static void main(String[] args) throws Exception {


        //Data must not be longer than 117 bytes
//        SecureRandom secureRandom = new SecureRandom();
//        byte[] bytes = secureRandom.generateSeed(118);
//        System.out.println(bytes.length);
//        String string = new String(bytes);
        String string = "123456781234567812234567812345678123456781234567812345678123456781234567812345678";

        getKeyPair(keySize);

        System.out.println("随机公钥:"+keyMap.get(0));
        System.out.println("随机私钥:"+keyMap.get(1));
        String temp = rsaEncrypt(string, keyMap.get(0));
        System.out.println("密文:"+ temp);
        System.out.println("明文:"+rsaDecrypt(temp, keyMap.get(1)));

    }

    /**
     * 随机生成的密钥对
     * @param keySize 密钥大小
     * @throws NoSuchAlgorithmException
     */
    public static void getKeyPair(Integer keySize) throws NoSuchAlgorithmException {
        //返回生成指定算法的公钥/私钥对的KeyPairGenerator对象
        KeyPairGenerator rsa = KeyPairGenerator.getInstance("RSA");
        //初始化密钥对生成器
        rsa.initialize(keySize);
        // 生成一个密钥对,保存在keyPair对象中
        KeyPair keyPair = rsa.generateKeyPair();
        // 得到公钥,私钥
        //PublicKey aPublic = keyPair.getPublic();
        //PrivateKey aPrivate = keyPair.getPrivate();
        RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();

        byte[] rsaPublicKeyEncoded = rsaPublicKey.getEncoded();
        byte[] rsaPrivateKeyEncoded = rsaPrivateKey.getEncoded();

        System.out.println("公钥长度:"+rsaPublicKeyEncoded.length * 8);
        System.out.println("私钥长度:"+rsaPrivateKeyEncoded.length * 8);

        // 得到Base64的密钥字符串
        String publicKeyString = new String(Base64.encode(rsaPublicKeyEncoded));
        String privateKeyString = new String(Base64.encode(rsaPrivateKeyEncoded));

        keyMap.put(0,publicKeyString);
        keyMap.put(1,privateKeyString);
    }

    /**
     * RSA 加密
     * @param string 原始数据
     * @param publicKey 公钥
     * @return 密文
     * @throws Exception
     */
    public static String rsaEncrypt(String string,String publicKey) throws Exception {
        byte[] bytes = Base64.decode(publicKey);
        //Cipher rsa = Cipher.getInstance("RSA");
        Cipher rsa = Cipher.getInstance(algorithm);
        RSAPublicKey key = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(bytes));
        rsa.init(Cipher.PUBLIC_KEY,key);
        byte[] result = rsa.doFinal(string.getBytes());
        System.out.println("密文长度:"+result.length*8);
        String encode = Base64.encode(result);
        return encode;
    }

    /**
     * RSA 解密
     * @param string 密文
     * @param privateKey 私钥
     * @return 原始数据
     * @throws Exception
     */
    public static String rsaDecrypt(String string,String privateKey) throws Exception{
        // 密文base64解码
        byte[] decode = Base64.decode(string);
        // 密钥base64解码
        byte[] keyBytes = Base64.decode(privateKey);

        // Only RSAPrivate(Crt)KeySpec and PKCS8EncodedKeySpec supported for RSA private keys
        // 注意公钥钥的材料规范不同
        RSAPrivateKey key = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(keyBytes));

        //Cipher rsa = Cipher.getInstance("RSA");
        Cipher rsa = Cipher.getInstance(algorithm);
        rsa.init(Cipher.PRIVATE_KEY,key);
        byte[] result = rsa.doFinal(decode);

        System.out.println("原文长度:"+result.length);

        return new String(result);
    }

}

使用BC,BC支持一些JDK不支持模式

package com.lett.asyenc.improve;

import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import java.security.*;

import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

public class RsaTest {
    /**
     * Integer 1为公钥 0为私钥
     * String 公私钥
     */
    private static Map<Integer,Key> keyMap = new HashMap<>();

    private static final String algorithm = "RSA/ECB/PKCS1Padding";
    private static final Integer keySize = 4096;

    public static void main(String[] args) throws Exception{

        Security.addProvider(new BouncyCastleProvider());

        String string = "12345678";

        try {
            getKeyPair();
        } catch (Exception e) {
            System.out.println("密钥生成错误!");
            e.printStackTrace();
        }

        String s = rsaEncrypt(string);
        System.out.println(s);

        String result = rsaDecrypt(s);
        System.out.println(result);

    }


    public static void getKeyPair() throws Exception{

        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC");
        keyPairGenerator.initialize(keySize);
        KeyPair keyPair = keyPairGenerator.generateKeyPair();

        byte[] pri = keyPair.getPrivate().getEncoded();
        byte[] pub = keyPair.getPublic().getEncoded();

        KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");
        PrivateKey privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(pri));
        PublicKey publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(pub));

        keyMap.put(0,privateKey);
        keyMap.put(1,publicKey);
    }

    /**
     * RSA 加密
     * @param string 原始数据
     * @return 密文
     * @throws Exception
     */
    public static String rsaEncrypt(String string) throws Exception {
        Cipher rsa = Cipher.getInstance(algorithm,"BC");
        rsa.init(Cipher.PUBLIC_KEY,keyMap.get(1));
        byte[] result = rsa.doFinal(string.getBytes());
        System.out.println("密文长度:"+result.length*8);
        String encode = Base64.encode(result);
        return encode;
    }

    /**
     * RSA 解密
     * @param string 密文
     * @return 原始数据
     * @throws Exception
     */
    public static String rsaDecrypt(String string) throws Exception{
        //密文base64解码
        byte[] decode = Base64.decode(string);
        Cipher rsa = Cipher.getInstance(algorithm,"BC");
        rsa.init(Cipher.PRIVATE_KEY,keyMap.get(0));
        byte[] result = rsa.doFinal(decode);

        System.out.println("原文长度:"+result.length);
        return new String(result);
    }
}

注意

// RSA的加密密钥规范和解密密钥规范标准是不一样的 
// RSA加密密钥规范使用x509EncodedKeySpec
// RSA解密密钥规范使用pkcs8EncodedKeySpec

根据代码的运行的速度可知,密钥模长越长则RSA 加解密的速度越慢。

数据需要分段加密

2.ECC

ECC(Elliptic Curves Cryptography,椭圆曲线密码编码学)也属于公开密钥算法。

JDK 中Chipher不支持EC算法 Chipher、Signature、KeyPairGenerator、KeyAgreement、SecretKey均不支持EC算法。

ECC是椭圆曲线算法,其加密算法叫ECIES,签名算法叫ECDSA。

算法特点

RSA的优点:JDK自己支持。不需要第三方库。同时支持RSA的开发库也很多.

EC的缺点:需要第三方库,支持的广度比不上RSA。

EC的优点:

1,在达到相同加密程度下,EC需要的秘钥长度比RSA要短得多

2,bouncycastle实现的EC加密算法,对密文长度的限制比较松。在下面的测试程序中构造了一个长字符串加密,没有报错。

RSA的加密则是有限制的,必须分片。

用途

国密算法SM基于ECC(椭圆曲线算法)实现的。

Demo

package com.lett.asyenc.improve;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import java.security.*;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.util.Base64;
/**
 * ECC demo
 * @author let
 */
public class EccTest{

    /**
     * BC 仅支持的密钥位数
     * 192, 224, 239, 256, 384, 521
     */
    private final static int KEY_SIZE = 224;//bit

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    public static void main(String[] args) {
        try {
            KeyPair keyPair = getKeyPair();
            ECPublicKey pubKey = (ECPublicKey) keyPair.getPublic();
            ECPrivateKey priKey = (ECPrivateKey) keyPair.getPrivate();
            //这里可以加长测试的长度
            String content = "12345678";
            //加密
            byte[] cipherTxt = encrypt(content.getBytes(), pubKey);
            //解密
            byte[] clearTxt = decrypt(cipherTxt, priKey);
            //打印
            System.out.println("明文:" + content);
            System.out.println("密文["+cipherTxt.length+"]:" + Base64.getEncoder().encodeToString(cipherTxt));
            System.out.println("解密后:" + new String(clearTxt));

        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("[main]-Exception:" + e.toString());
        }
    }

    //生成秘钥对
    public static KeyPair getKeyPair() throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", "BC");
        keyPairGenerator.initialize(KEY_SIZE, new SecureRandom());
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        return keyPair;
    }

    //公钥加密
    public static byte[] encrypt(byte[] content, ECPublicKey pubKey) throws Exception {
        Cipher cipher = Cipher.getInstance("ECIES", "BC");
        cipher.init(Cipher.ENCRYPT_MODE, pubKey);
        return cipher.doFinal(content);
    }

    //私钥解密
    public static byte[] decrypt(byte[] content, ECPrivateKey priKey) throws Exception {
        Cipher cipher = Cipher.getInstance("ECIES", "BC");
        cipher.init(Cipher.DECRYPT_MODE, priKey);
        return cipher.doFinal(content);
    }

}

国密算法

  • SM1对称密码(不公开)

SM1 算法是分组对称密码算法 ,分组长度为128位,密钥长度都为 128 比特 ,算法安全保密强度及相关软硬件实现性能与 AES 相当,算法不公开,仅以IP核的形式存在于芯片中。采用该算法已经研制了系列芯片、智能IC卡、智能密码钥匙、加密卡、加密机等安全产品,广泛应用于电子政务、电子商务及国民经济的各个应用领域(包括国家政务通、警务通等重要领域)。

1. SM2

SM2算法和RSA算法都是公钥密码算法,SM2算法是一种更先进安全的算法.

随着密码技术和计算机技术的发展,目前常用的1024位RSA算法面临严重的安全威胁,
我们国家密码管理部门经过研究,决定采用SM2椭圆曲线算法替换RSA算法。

SM2椭圆曲线公钥密码算法是我国自主设计的**公钥密码算法 ,包括SM2-1椭圆曲线数字签名算法,SM2-2椭圆曲线密钥交换协议,SM2-3椭圆曲线公钥加密算法,分别用于实现数字签名密钥协商和数据加密等功能。SM2算法与RSA算法不同的是,SM2算法是基于椭圆曲线上点群离散对数难题,相对于RSA算法,256位的SM2密码强度已经比2048位的RSA密码强度要高。**

类型

非对称加密

算法特点

SM2性能更优更安全:密码复杂度高、处理速度快、机器性能消耗更小

用途

加解密 Demo

package com.lett.asyenc.improve;

import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import java.security.*;

import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

public class Sm2Test {
    /**
     * Integer 1为公钥 0为私钥
     * String 公私钥
     */
    private static Map<Integer,Key> keyMap = new HashMap<>();

    private static final String algorithm = "SM2";

    private static final Integer keySize = 256;

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    public static void main(String[] args) throws Exception{
        String string = "1234123456781234567812345678123456781234567812345678123456785678123456781234567812345678123456781234567812345678";
        getKeyPair();
        String s = rsaEncrypt(string);
        System.out.println(s);
        String result = rsaDecrypt(s);
        System.out.println(result);
    }

    /**
     * 生成密钥对
     * @throws Exception
     */
    public static void getKeyPair() throws Exception{

        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", "BC");
        keyPairGenerator.initialize(keySize);
        KeyPair keyPair = keyPairGenerator.generateKeyPair();

        byte[] pri = keyPair.getPrivate().getEncoded();
        byte[] pub = keyPair.getPublic().getEncoded();

        KeyFactory keyFactory = KeyFactory.getInstance("EC", "BC");
        PrivateKey privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(pri));
        PublicKey publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(pub));

        keyMap.put(0,privateKey);
        keyMap.put(1,publicKey);
    }

    /**
     * Sm2 加密
     * @param string 原始数据
     * @return 密文
     * @throws Exception
     */
    public static String rsaEncrypt(String string) throws Exception {
        Cipher rsa = Cipher.getInstance(algorithm,"BC");
        rsa.init(Cipher.PUBLIC_KEY,keyMap.get(1));
        byte[] result = rsa.doFinal(string.getBytes());
        System.out.println("密文长度:"+result.length*8);
        String encode = Base64.encode(result);
        return encode;
    }

    /**
     * SM2 解密
     * @param string 密文
     * @return 原始数据
     * @throws Exception
     */
    public static String rsaDecrypt(String string) throws Exception{

        Cipher rsa = Cipher.getInstance(algorithm,"BC");
        //密文base64解码
        byte[] decode = Base64.decode(string);
        rsa.init(Cipher.PRIVATE_KEY,keyMap.get(0));
        byte[] result = rsa.doFinal(decode);
        System.out.println("原文长度:"+result.length);
        return new String(result);
    }


}

2. SM3

SM3杂凑算法是我国自主设计的密码**杂凑算法 ,适用于商用密码应用中的数字签名和验证消息认证码的生成与验证以及随机数的生成,可满足多种密码应用的安全需求。为了保证杂凑算法的安全性,其产生的杂凑值的长度不应太短**,例如MD5输出128比特杂凑值,输出长度太短,影响其安全性SHA-1算法的输出长度为160比特,SM3算法的输出长度为256比特 ,因此SM3算法的安全性要高于MD5算法和SHA-1算法。

类型

国产杂凑算法,摘要算法

算法特点

一个理想的密码散列函数应该有四个主要的特性:

  • 对于任何一个给定的消息,它都很容易就能运算出散列数值。

  • 难以由一个已知的散列数值,去推算出原始的消息。

  • 在不更动散列数值的前提下,修改消息内容是不可行的。

  • 对于两个不同的消息,它不能给与相同的散列数值。

用途

商用密码体系中,SM3主要用于数字签名及验证,消息认证码生成及验证、随机数生成等,其算法公开。

据国家密码管理局表示,其安全性及效率与SHA-256相当。

Demo

package com.lett.digest;

import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.security.MessageDigest;

/**
 * 国产SM3摘要算法,使用BC
 * @author lett
 * @date 2020/07/30 16:47
 */
public class SM3Demo {
    public static void main(String[] args) throws Exception{
        String msg = "hello world!dsdsddasdasdads";

        byte[] bytes = sm3(msg.getBytes());
        System.out.println("SM3摘要长度:"+bytes.length*8);
        System.out.println(Base64.encode(bytes));

        byte[] digest = sm3JDK(msg.getBytes());
        System.out.println("Sm3摘要长度:"+digest.length*8);
        System.out.println(Base64.encode(digest));

    }

    /**
     * 直接使用BC
     * @param data 原始数据
     * @return 256位的摘要数据
     */
    public static byte[] sm3(byte[] data){
        SM3Digest sm3Digest = new SM3Digest();
        sm3Digest.update(data,0,data.length);
        byte[] result = new byte[sm3Digest.getDigestSize()];
        //摘要
        sm3Digest.doFinal(result,0);
        return result;
    }

    /**
     * BC结合JDK 实现SM3
     * @param data
     * @return
     */
    public static byte[] sm3JDK(byte[] data) throws Exception{
        MessageDigest sm3 = MessageDigest.getInstance("SM3", new BouncyCastleProvider());
        byte[] digest = sm3.digest(data);
        return digest;
    }
}

3. SM4

SM4分组密码算法是我国自主设计的分组对称密码算法 ,用于实现数据的加密/解密运算,以保证数据和信息的机密性。要保证一个对称密码算法的安全性的基本条件是其具备足够的密钥长度,SM4算法与AES算法具有相同的密钥长度分组长度128比特 ,因此在安全性上高于3DES算法。

类型

对称加密算法

Demo

package com.lett.symenc;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Base64;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.security.Security;


public class SM4Demo {

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    public static final String SM4 = "SM4";

    /**
     * 加密/解密算法/工作模式/填充方式
     * ECB CBC OFB CFB CTR
     * NOPadding 模式下,明文必须是128位或者倍数 --->分组加密
     */
    public static final String CIPHER_ALGORITHM="SM4/OFB/PKCS5Padding";


    public static void main(String[] args) throws Exception {

        String msg = "hello world!";
        byte[] key = initKey();
        System.out.println("密钥长度:"+key.length*8);
        IvParameterSpec iv = getByteIv();
        byte[] encrypted = sm4Encrypt(msg.getBytes(), key,iv);
        System.out.println(new String(Base64.encode(encrypted)));
        byte[] decrypted = decrypted(encrypted, key,iv);
        System.out.println(new String(decrypted));
    }

    public static byte[] initKey() throws Exception{
        KeyGenerator keyGenerator = KeyGenerator.getInstance(SM4);
        System.out.println(keyGenerator.getProvider().getName());
        keyGenerator.init(128);
        SecretKey key = keyGenerator.generateKey();
        return key.getEncoded();
    }

    public static byte[] sm4Encrypt(byte[] data,byte[] key,IvParameterSpec ivParameterSpec) throws Exception{
        SecretKeySpec secretKeySpec = new SecretKeySpec(key,SM4);
        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE,secretKeySpec,ivParameterSpec);
        byte[] bytes = cipher.doFinal(data);
        return bytes;
    }

    public static byte[] decrypted(byte[] data,byte[] secretKey,IvParameterSpec ivParameterSpec) throws  Exception{
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey,SM4);
        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE,secretKeySpec,ivParameterSpec);
        byte[] bytes = cipher.doFinal(data);
        return bytes;
    }

    public static IvParameterSpec getByteIv(){
        byte[] bytes = new SecureRandom().generateSeed(16);
        return new IvParameterSpec(bytes);
    }

}

消息认证

杂凑算法

又称单向散列函数。消息摘要是把任意长度的输入揉和而产生长度固定伪随机输入的算法。单向散列函数的输出值也成为数字摘要或者指纹

算法原理

散列函数

算法特点

①无论输入的消息有多长,计算出来的消息摘要的长度总是固定的。

例如应用MD5算法摘要的消息有128个比特位,用SHA-1算法摘要的消息最终有160比特位的输出,SHA-1的变体可以产生192比特位和256比特位的消息摘要。一般认为,摘要的最终输出越长,该摘要算法就越安全。

②消息摘要看起来是“随机的”。这些比特看上去是胡乱的杂凑在一起的。

可以用大量的输入来检验其输出是否相同,一般,不同的输入会有不同的输出,而且输出的摘要消息可以通过随机性检验。但是,一个摘要并不是真正随机的,因为用相同的算法对相同的消息求两次摘要,其结果必然相同;而若是真正随机的,则无论如何都是无法重现的。因此消息摘要是“伪随机的”。

③一般地,只要输入的消息不同,对其进行摘要以后产生的摘要消息也必不相同;但相同的输入必会产生相同的输出。

这正是好的消息摘要算法所具有的性质:输入改变了,输出也就改变了;两条相似的消息的摘要确不相近,甚至会大相径庭。

④消息摘要函数是无陷门的单向函数,即只能进行正向的信息摘要,而无法从摘要中恢复出任何的消息,甚至根本就找不到任何与原信息相关的信息。

当然,可以采用强力攻击的方法,即尝试每一个可能的信息,计算其摘要,看看是否与已有的摘要相同,如果这样做,最终肯定会恢复出摘要的消息。但实际上,要得到的信息可能是无穷个消息之一,所以这种强力攻击几乎是无效的。

⑤好的摘要算法,没有人能从中找到“碰撞”,虽然“碰撞”是肯定存在的。即对于给定的一个摘要,不可能找到一条信息使其摘要正好是给定的。或者说,无法找到两条消息,使它们的摘要相同。

缺点

无法保证数据的真实性,即不能确定数据和散列值是来自发送方的,因为攻击者完全可以将数据和散列值一起替换。

常见算法

MD系列,SHA系列,国密SM3

应用

  • 检测数据的完整性(一致性)

  • 配合数据签名一起使用。

几乎所有的数字签名方案都要和快速高效的摘要算法(Hash函数)一起使用,当公钥算法与摘要算法结合起来使用时,便构成了一种有效地数字签名方案。

Demo

package com.lett.digest;

import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Hex;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Security;

public class MD5Demo {

    public static final String algorithm = "SHA256";

    public static void main(String[] args) {

        Security.addProvider(new BouncyCastleProvider());

        String input = "举头忘明月,低头思故乡";
        byte[] bytes = input.getBytes();
        //不同的编码汉字的字节数不同 utf-8 一般是3个字节
        System.out.println("==>明文长度"+bytes.length+"字节");
        MD5(bytes);
        MD2(bytes);
        MD4(bytes);
        SHA(bytes);
    }
    
    public static void MD5(byte[] bytes){
        try {
            //得到MD5加密对象
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            byte[] digest = md5.digest(bytes);
            print(md5,digest);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }
    
    public static void MD2(byte[] bytes){
        try {
            //得到MD5加密对象
            MessageDigest md = MessageDigest.getInstance("MD2");
            byte[] digest = md.digest(bytes);
            print(md,digest);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }
    
    public static void MD4(byte[] bytes){
//        MD4Digest md4Digest = new MD4Digest();
//        md4Digest.update(bytes,0,bytes.length);
//        byte[] output = new byte[md4Digest.getDigestSize()];
//        md4Digest.doFinal(output,0);
//        System.out.println("MD4密文:"+Hex.toHexString(output));
        try {
            MessageDigest md = MessageDigest.getInstance("MD4","BC");
            byte[] digest = md.digest(bytes);
            print(md,digest);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void SHA(byte[] bytes){
        try {
            MessageDigest md = MessageDigest.getInstance(algorithm,"BC");
            byte[] digest = md.digest(bytes);
            print(md,digest);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void print(MessageDigest messageDigest,byte[] digest){
        String name = messageDigest.getProvider().getName();
        String algorithm = messageDigest.getAlgorithm();
        System.out.println(name+"----> "+algorithm);
        System.out.println(algorithm+"密文转Base64:"+Base64.encode(digest));
        System.out.println(algorithm+"密文转16进制:"+ Hex.toHexString(digest));
        System.out.println(algorithm+"密文长度:"+digest.length*8);
        System.out.println();
    }
}

消息认证码

密码学中,消息认证码(英语:Message authentication code,缩写为MAC),又译为消息鉴别码文件消息认证码讯息鉴别码信息认证码,是经过特定算法后产生的一小段信息,检查某段消息的完整性以及作身份验证。它可以用来检查在消息传递过程中,其内容是否被更改过,不管更改的原因是来自意外或是蓄意攻击。同时可以作为消息来源的身份验证,确认消息的来源。

算法特点

消息认证码可以简单理解为一种与密钥相关的单向散列函数。

优点

可以保证数据的完整性和真实性。

缺点

接收方虽然可以确定消息的完整性和真实性,解决篡改和伪造消息的问题,但不能防止A否认发送过消息。

Demo

package com.lett.digest.improve;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Base64;

public class MacDemo {
    /**
     * HmacSHA1
     * HmacSHA256
     * HmacMD5
     *
     * Mac: AESCMAC
     * Mac: AESCCMMAC
     * Mac: AES-GMAC
     * KeyGenerator: AES-GMAC
     * Mac: ARIA-GMAC
     * KeyGenerator: ARIA-GMAC
     *Mac: HMACSHA384
     * KeyGenerator: HMACSHA384
     * Mac: OLDHMACSHA512
     * Mac: PBEWITHHMACSHA512
     * Mac: HMACSHA512
     * KeyGenerator: HMACSHA512
     * Mac: HMACSHA512/224
     * KeyGenerator: HMACSHA512/224
     * Mac: HMACSHA512/256
     * KeyGenerator: HMACSHA512/256
     * Mac: HMACSHA3-224
     * KeyGenerator: HMACSHA3-224
     * Mac: HMACSHA3-256
     * KeyGenerator: HMACSHA3-256
     * Mac: HMACSHA3-384
     * KeyGenerator: HMACSHA3-384
     * Mac: HMACSHA3-512
     * KeyGenerator: HMACSHA3-512
     * Mac: HMACSkein-256-128
     *
     * BC ----->密钥长度
     * SM4-CMAC 128
     */
    private static final String algorithm = "AES-GMAC";

    public static void main(String[] args) {
        Security.addProvider(new BouncyCastleProvider());
        String string ="123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678";
        try {
            String enc = enc(string, getSecretKeySpec());
            System.out.println(enc);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static SecretKeySpec getSecretKeySpec() throws Exception{
        //一个秘密(对称)密钥生成器
        KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm,"BC");
        //keyGenerator.init(128,new SecureRandom());
        //产生密钥
        SecretKey secretKey = keyGenerator.generateKey();
        //密钥字节数组
        byte[] key = secretKey.getEncoded();
        System.out.println("密钥长度:" + key.length * 8);
        //将密钥规范
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, algorithm);
        return secretKeySpec;
    }

    public static String enc(String string,SecretKeySpec secretKeySpec) throws Exception{
        Mac mac = Mac.getInstance(algorithm, "BC");
        System.out.println(mac.getAlgorithm());
        mac.init(secretKeySpec);
        byte[] bytes = mac.doFinal(string.getBytes());
        return Base64.getEncoder().encodeToString(bytes);
    }
}

数字签名

Demo

  • SM2 数字签名

package com.lett.sign;

import com.sun.org.apache.xml.internal.security.utils.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.security.*;

public class SignTest {
    /**
     * 密钥的获取,算法名称是EC
     *	"SHA1WithSM2"
     * 	"SHA224WithSM2"
     *  "SHA256WithSM2"
     * 	"SM3WithSM2"
     */
    private static final String algorithmKey = "EC";
    private static final String algorithm = "SHA256WITHSM2";

    private static final Integer keySize = 256;
    static {
        Security.addProvider(new BouncyCastleProvider());
    }
    public static void main(String[] args) throws Exception {
        //初始化密钥
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithmKey);
        keyPairGenerator.initialize(keySize);
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        String msg = "hello";
        //进行签名
        String s = sign(msg.getBytes(), keyPair.getPrivate());
        System.out.println(s);
        //验签
        boolean b = verifySign(msg.getBytes(), keyPair.getPublic(),Base64.decode(s) );
        System.out.println(b);
    }
    //数字签名算法
    //一般是要对原始数据进行摘要 然后非对称私钥加密
    public static String sign(byte[] data, PrivateKey privateKey) throws Exception{
        Signature sha1withRSA = Signature.getInstance(algorithm,"BC");
        sha1withRSA.initSign(privateKey);
        sha1withRSA.update(data);
        byte[] sign = sha1withRSA.sign();
        return Base64.encode(sign);
    }
    //验签
    public static boolean verifySign(byte[] data, PublicKey publicKey,byte[] singed) throws Exception{
        Signature sha1withRSA = Signature.getInstance(algorithm,"BC");
        sha1withRSA.initVerify(publicKey);
        sha1withRSA.update(data);
        boolean verify = sha1withRSA.verify(singed);
        return verify;
    }
}
package com.lett.sign;

import com.sun.org.apache.xml.internal.security.utils.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.security.*;

public class SignTest {
    /**
     * 	"SHA1WithRSA"
     * 	"SHA224WithRSA"
     *   "SHA256WithRSA"
     * 	"SHA384WithRSA"
     * 	"SHA512WithRSA"
     * 	"MD2WithRSA"
     * 	"MD4WithRSA"
     * 	"MD5WithRSA"
     */
    private static final String algorithmKey = "RSA";
    private static final String algorithm = "SHA1WithRSA";
    /**
    * Rsa 的密钥长度1024起
    */
    private static final Integer keySize = 1024;
    static {
        Security.addProvider(new BouncyCastleProvider());
    }
    public static void main(String[] args) throws Exception {
        //初始化密钥
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithmKey);
        keyPairGenerator.initialize(keySize);
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        String msg = "hello";
        //进行签名
        String s = sign(msg.getBytes(), keyPair.getPrivate());
        System.out.println(s);
        //验证
        boolean b = verifySign(msg.getBytes(), keyPair.getPublic(),Base64.decode(s) );
        System.out.println(b);
    }
    //数字签名算法
    //一般是要对原始数据进行摘要 然后使用非对称私钥加密
    public static String sign(byte[] data, PrivateKey privateKey) throws Exception{
        Signature sha1withRSA = Signature.getInstance(algorithm,"BC");
        sha1withRSA.initSign(privateKey);
        sha1withRSA.update(data);
        byte[] sign = sha1withRSA.sign();
        return Base64.encode(sign);
    }
    //验证
    public static boolean verifySign(byte[] data, PublicKey publicKey,byte[] singed) throws Exception{
        Signature sha1withRSA = Signature.getInstance(algorithm,"BC");
        sha1withRSA.initVerify(publicKey);
        sha1withRSA.update(data);
        boolean verify = sha1withRSA.verify(singed);
        return verify;
    }

}

附录 JDK自带的安全框架(可以在文件夹附带的JDK文档中找到)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值