欢迎光临散文网 会员登陆 & 注册

JAVA密码学功能实现大集合(2022.7.16更新)

2022-02-27 21:56 作者:寂风也过路  | 我要投稿

一、序章

近一个半月的抽空,约莫有四十个小时,查阅无数他人文档,翻阅各种官方说明和标准文件,反复修改整理......我终于!终于写好了这个使用示例类!

不得不说,这个方向上的示例代码真的少,甚至一些库里的类都没有解释方法含义,全靠硬啃源码+参考前人使用示例+用OpenssL进行比对,这才能稍稍猜出答案。

当然,网上材料这么少,估计也是因为深度使用纯软代码来实现PKI业务的概率较低,更多的时候其实还是轻度使用+用硬件安全设备(HSM),毕竟会更好一些。

不过保不齐就有用到的时候,所以我算是"未雨绸缪"(杞人忧天)

反正就是提前将这些代码研究了一通,整理了出来,以后要用就很方便了,而且也算是在填补空白(恬不知耻)。

下图是我整理出的方法列表:

2022.2.27首稿具备的方法

二、已具备的方法介绍

注:本文内容仅代表截止至2022.2.27的情况,后续有更新的话,未必会同步在本文进行更新

注:2022.7.8发现,"OpenssL 1.1.1h 22 Sep 2020"版本生成的RSA密钥对,在本示例的代码中进行私钥加载老是有问题。其他版本OpenssL(1.0.2、1.1.1i、3.0等实测过)未出现该问题。经过分析后发现,该版本产生的私钥文件内容经过PEMParser加载后,是一个PEMKeyPair对象,而非常见的PrivateKeyInfo对象,于是加载成私钥对象时,需要进行特殊处理,然后用于加解密就没发现问题了。

注:个人能力有限,有些地方应该有更好的写法或者其他的写法,无法全部想到,如果有大佬看到,还请不吝赐教

注:有些地方因为一开始构思过于简单,所以经历了反复的修改,所以测试可能会有遗漏,如有发现,希望能够指出,十分感谢!

注:因为代码太长(1300+行),就不贴这里了,要看可以去github仓库阅览,还有单元测试的使用示范(300+行)。仓库地址:https://github.com/17lhf/happyTest


1、获取BC库支持的密钥算法

特别推荐阅览一遍,这样你在用字符串指定的时候,就知道库里面有没有支持了

2、生成摘要

支持MD5、SHA-1(或SHA1)、SHA-256(或SHA256)

3、生成密钥对

可选算法:DiffieHellman(等同于:DH)、DSA、RSA

RSA时推荐长度为:2048/1024/3096,DH时推荐为:1024,DSA时推荐长度为:1024

其实我还有提取密钥中含有的P、X、Q、G之类密码学理论知识中的密钥关键值,但是网上有好多大佬都有详尽的讲解了,我这里就不献丑了......

4、生成ECC密钥对

注意:密钥长度推荐256

5、读取RSA私钥文件转换成私钥对象

注意:这个方法只支持PKCS1标准格式且无口令保护的私钥

注意:方法使用了sun.*包,可能会导致打包失败

6、从文件加载受口令保护的私钥

注意:这个方法只支持PKCS8标准格式

注意:私钥类型支持ECC、RSA

7、从文件加载无口令保护私钥

注意:文件要求是PKCS8标准格式

注意:私钥类型支持ECC、RSA

注意:2022.7.20新增对加载后是PEMKeyPair的特殊私钥文件的支持

8、从文件中获取PKCS8的无口令保护的私钥,然后转成以指定口令保护的PKCS8私钥,并将结果存入文件

9、从文件加载公钥

注意:密钥类型支持ECC、RSA

10、从文件加载公钥方式二

注意:密钥类型支持ECC、RSA

11、输出pem格式的密钥内容到控制台

可选算法:DiffieHellman(等同于:DH)、DSA、RSA

RSA时推荐长度为:2048/1024/3096,DH时推荐为:1024,DSA时推荐长度为:1024

12、输入pem格式的密钥到指定文件中

可选算法:DiffieHellman(等同于:DH)、DSA、RSA

RSA时推荐长度为:2048/1024/3096 DH时推荐为:1024 DSA时推荐长度为:1024

13、生成证书请求

注意:密钥类型支持ECC、RSA

14、生成附带一些扩展字段的证书请求

注意:扩展字段方面主要是举例说明,实际使用要做修改

注意:算法类型支持ECC、RSA

涵盖扩展字段:

keyUsage(密钥用途)

extendedKeyUsage(扩展密钥用途)(可自定义扩展密钥字段)

15、分步进行csr生成

环节1:利用公钥和subject生成csr中待签名的部分

环节2:将获取到的被私钥签名后的签名值与csr中待签名的数据进行拼装,生成csr

16、将csr对象存入pem格式的文件中

17、读取证书请求文件,并加载成一个证书请求对象

注意:证书请求文件的编码方式支持“PEM”,“DER”

18、从证书请求中获取公钥

注意:算法类型支持ECC、RSA

19、获取证书请求中包含的一些信息

涵盖:

公钥算法标识

证书请求签名值

扩展字段OID(ExtensionOIDs)

关键扩展字段OID(CriticalExtensionOIDs)

非关键扩展字段OID(NonCriticalExtensionOIDs)

CSR使用者Subject及各个属性(Country、Province、State、local、Organization、Organization Unit、EmailAddress、 Common name)

20、颁发V1自签名证书

21、颁发V3无扩展属性证书

注意:仅支持RSA证书颁发

注意:Java颁发的子证书无法通过SSL或者OpenssL的证书链检验,有可能是颁发者的subject属性顺序错了,具体修复这个bug可以看我的代码里的描述,这是Java颁发子证书的一个坑。

22、颁发V3证书,同时设置一些扩展字段

注意:扩展字段方面主要是举例说明,实际使用要做修改

注意:仅支持RSA证书颁发

注意:Java颁发的子证书无法通过SSL或者OpenssL的证书链检验,有可能是颁发者的subject属性顺序错了,具体修复这个bug可以看我的代码里的描述,这是Java颁发子证书的一个坑。

包含:

复用CSR中的扩展字段(如:keyUsage、extendedKeyUsage)

涵盖扩展字段:

basicConstraints(基本约束)

keyUsage(密钥用途)

extendedKeyUsage(扩展密钥用途)

subjectKeyIdentifier(使用者密钥标识)

authorityKeyIdentifier(颁发者密钥标识)

23、将一个证书对象以PEM格式存入对应路径的文件中

24、从证书中读取出公钥(含加载pem格式证书)

25、读取证书文件并加载成一个证书对象(复杂式写法)

注意:方法使用了sun.*包,可能会导致打包失败

注意:证书编码类型支持“PEM"和”DER“

26、验证子证书是否由父证书颁发

注意:这里其实只做了密钥签名验证,未用sn之类的数据验证

subject、sn(好像用得不频繁)之类可以通过解析子证书和父证书信息来比较

27、从der格式转换成pem格式

注意:为了方便传递,一般der格式内容会转为十六进制串在网络中传递,所以这里入参用十六进制字符串

涵盖文件类型;

CSR(证书请求)

CERT(数字证书)

PRV_KEY(私钥)

PUB_KEY(公钥)

28、将传进来的pem格式证书字符串转为der格式(Hex-String)

注意:方法使用了sun.*包,可能会导致打包失败

29、依据传入的证书对象,获取证书的所有信息

涵盖:

证书版本

证书的序列号

证书的签名算法

证书签名值

颁发者Subject及各个属性值(Country、Province、State、local、Organization、Organization Unit、EmailAddress、 Common name)

使用者Subject及各个属性值(Country、Province、State、local、Organization、Organization Unit、EmailAddress、 Common name)

证书有效期

证书是否处于有效期

扩展字段涵盖:

BasicConstraints(基本约束字段)

KeyUsage(密钥用途)

ExtendedKeyUsage(扩展密钥用途)

注意:

 RFC5280(4.1.2.8节)说明:issuerUniquelID和subjectUniqueID仅当版本为 2 或 3 时,这些字段必须显示。 如果版本为 1,则不得显示这些字段。 证书中存在使用者和颁发者唯一标识符,以处理随着时间的推移重用使用者和/或颁发者名称的可能性。 此配置文件建议不要对不同的实体重复使用名称,并且 Internet 证书不要使用唯一标识符。 符合此配置文件的 CA 不得生成具有唯一标识符的证书。 符合此配置文件的应用程序应该能够分析包含唯一标识符的证书,但没有与唯一标识符关联的处理要求

30、密钥加密

注意:如果是ECC,则只能用公钥加密,不支持私钥加密数据

支持的算法/模式/填充模式:

RSA(这么写的话,模式和填充模式依赖于算法提供者怎么设置默认值,BC库的话等同于“RSA/ECB/NoPadding”)

RSA/ECB/PKCS1Padding

RSA/ECB/OAEPWithSHA-1AndMGF1Padding

RSA/ECB/OAEPWithSHA-256AndMGF1Padding

ECIES (ECIES表示ECC)

31、解密

注意:如果是ECC,则只能用私钥解密,不支持公钥解密数据

注意:填写的算法/模式/填充模式要与加密时使用的一致,否则将导致解密数据错误

支持的算法/模式/填充模式:

RSA(这么写的话,模式和填充模式依赖于算法提供者怎么设置默认值,BC库的话等同于“RSA/ECB/NoPadding”)

RSA/ECB/PKCS1Padding

RSA/ECB/OAEPWithSHA-1AndMGF1Padding

RSA/ECB/OAEPWithSHA-256AndMGF1Padding

ECIES (ECIES表示ECC)

32、利用私钥对数据进行签名

注意:密钥的算法支持:RSA,DSA,ECDSA、ECNR

注意:签名过程中要用到的摘要算法,支持:SHA1,SHA256 (较常用的是SHA256)

33、分理论步骤进行利用私钥对数据进行签名

注意:中间使用经验得来的标准化方式,慎用!!!

注意:密钥的算法支持:RSA,DSA

注意:签名过程中要用到的摘要算法支持:SHA-1(或SHA1),SHA-256(或SHA256) (较常用的是SHA-256)

34、利用密钥对数据进行验签

注意:密钥的算法支持:RSA,DSA,ECDSA、ECNR

注意:签名过程中用到的摘要算法,支持:SHA1,SHA256 (较常用的是SHA256)

注意:验签的时候,用到的算法都要与签名时用到的算法一致,否则最后肯定时验签失败

35、生成p12文件到指定文件夹中

注意:可选择是否要放入证书链

注意:这种方式生成的p12文件等同于在openssl1.1.1版本中生成的p12文件

注意:在openssl3.0处进行解析验证时,会出现无法正常显示证书的问题。因为使用的加密方式是比较旧的RC2-40-CBC,该加密方式已被认为是不安全的,于是openssl在3.0中进行了剔除,3.0之前版本的openssl可以照常解析。

36、从文件中加载p12,并试着解析并获取其中的数据

包含提取:私钥、证书、证书链

37、DER格式数据转对象(2022.5.10新增)

支持:证书、证书请求、公钥、私钥

38、PEM格式数据转对象(2022.5.10新增)

支持:证书、证书请求、公钥、私钥

39、利用RSA私钥加载公钥(2022.7.15新增)

仅支持RSA2048/1024长度

实测发现:java生成密钥对,则1024和2048长度的公钥的指数都是用的65537(十进制)

40、RSA密钥的分段加密(2022.7.16新增)

Java 默认的 RSA加密实现不允许明文长度超过密钥长度减去 11(单位是字节,也就是 byte)。
也就是说,如果我们定义的密钥(如:java.security.KeyPairGenerator.initialize(int keySize) 来定义密钥长度)长度为 1024(单位是位,也就是 bit),则生成的密钥长度就是 1024位 /(8位/字节) = 128字节,那么我们需要加密的明文长度不能超过 128字节 -11 字节 = 117字节
也就是说,我们最大能将 117 字节长度的明文进行加密,否则会出问题(抛诸如 javax.crypto.IllegalBlockSizeException: Data must not be longer than 11 bytes 的异常)

实测“RSA/ECB/PKCS1Padding”时,2048RSA还是最大245


BC库的话,是密钥长度减去1, 如127(128-1),255(256-1)。超过的话,报错:org.bouncycastle.crypto.DataLengthException: input too large for RSA cipher.

但是,上面BC库时所说的,都是NoPadding的情况,如果有Padding,则blockSize大小就不一样了


另外,每次加密生成密文的长度等于密钥长度,如1024bit的密钥,加密一次出来的密文长度是256字节(byte)
所以分段加密的最终的密文长度,必然是密钥长度的正整数倍(如:256字节、512字节)

41、RSA密钥的分段解密(2022.7.16新增)

解密分块的大小,如果密钥大小是2048则块大小是256,如果是1024则最大的块是128
此处的256和128其实对应的就是加密时每段被加密后的密文的大小
如果是用BC库,若输入的size不是解密分块大小(但恰好能被密文整除),则不会报错,但是解密结果与原文对不上
如果是用JAVA库,若输入的size不是解密分块大小(但恰好能被密文整除),则doFinal会报错javax.crypto.BadPaddingException: Decryption error

三、补充说明

1、对比OpenssL食用更佳

OpenssL命令行实例

2、关于验证密钥匹配

验证一个公钥和一个私钥是否匹配,其实就是加密一个数据,然后解密,看看解密后的结果与原始数据是否一致(目前没有找到更便捷的流程)
验证一个私钥与一本证书是否匹配,其实就是先从证书中提取公钥,然后就是和上一个一样的流程(目前没有找到更便捷的流程)

3、遇到的一些报错

3.1 java.security.InvalidKeyException: IOException : Detect premature EOF 

发生场景:提供der格式数据来加载key对象,加载时报错
原因:密钥的数据不足,存在缺漏,导致解析失败

四、最后

如果我的代码对你有帮助,还请多多评论 点赞 收藏 转发

谢谢每个阅读到这里的人!


JAVA密码学功能实现大集合(2022.7.16更新)的评论 (共 条)

分享到微博请遵守国家法律