专业的JAVA编程教程与资源

网站首页 > java教程 正文

java 数字签名以及证书生成(电子签章java 数字证书)

temp10 2024-10-04 12:36:01 java教程 9 ℃ 0 评论

大纲

  1. 数字签名
  2. 数字签名的过程
  3. 单向散列函数,哈希函数
  4. 国产商用密码算法(国密)
  5. 证书颁发机构CA
  6. 使用 openssl自行生成用户证书
  7. 通过 java 代码生成证书,密钥对


数字签名

数据的完整性 : 传输数据的双方都总希望确认消息未在传输的过程中被修改

java 数字签名以及证书生成(电子签章java 数字证书)


数字签名技术: 将摘要信息用发送者的私钥加密,与原文一起传送给接收者。接收者用发送者的公钥解密被加密的摘要信息, 然后用HASH函数对收到的原文产生一个摘要信息 ,与解密的摘要信息对比。如果相同,则说明收到的信息是完整的,在传输过程中没有被修改, 否则说明信息被修改过,因此数字签名能够验证信息的完整性


数字签名涉及到哈希函数、接收者的公钥、发送方的私钥.

数字签名是非对称密钥加密技术与数字摘要技术的应用.


数字签名有两种功效:

  • 能确定消息确实是由发送方签名并发出来的,因为别人假冒不了发送方的签名。
  • 数字签名能确定消息的完整性。因为数字签名的特点是它代表了文件的特征,文件如果发生改变,数字摘要的值也将发生变化。不同的文件将得到不同的数字摘要。

个人开发过程中数字签名常用场景:

  • 微信支付宝 api 接口验签
  • 支付类接口,银行接口对接 验签


数字签名的过程

每个人都有一对“钥匙”(密钥对),其中私钥由本人保管,公钥对外公开。消息发送者用私钥签名,消息接受者使用公钥验证签名,确认消息真实完整。

1 发送报文时,

  • 发送方用一个哈希函数从报文文本中"生成报文摘要",
  • 然后用发送方的私钥"对报文摘要进行加密"(这个摘要非对称加密后的密文,也就是数字签名), - 这个"加密后的摘要"将作为报文的数字签名和报文一起发送给接收方,

2 接收方

  • 首先用与发送方一样的哈希函数"从接收到的原始报文中计算出报文摘要"
  • 接着再"用发送方的公钥" 来对"报文的数字签名进行解密" (解密后的摘要),
  • 把上一步得到的两份摘要进行比较、如果两份摘要相等,说明消息是真实完整的


单向散列函数,哈希函数

参考文档 https://blog.csdn.net/Q221022/article/details/123298302

单向散列函数有一个输入和一个输出,其中输入的称为消息,输出的称为散列值,

单项散列函数可以根据消息的内容计算出散列值,而散列值就可以被用来检查消息的完整性

单向散列函数特点

  • 单向性 消息不同散列值也不同
  • 根据任意长度的消息计算出固定长度的散列值
  • 能够快速计算出散列值


专有名词

  • 碰撞:两个不同的消息产生同一个散列值的情况称为碰撞
  • 单向散列函数也称为消息摘要函数、哈希函数或者杂凑函数,输入单向散列函数的消息也称为"原像",输出的散列值也称为"消息摘要或者指纹"

单项散列函数的具体例子: MD4 MD5 SHA1 SHA-224 SHA-256 SHA-384 SHA-512

- MD4和MD5(MD是消息摘要的缩写) MD4是由Rivest于1990年设计的单向散列函数,能够产生128比特的散列值,不过由于Dobbertin提出寻找MD4散列碰撞的方法,因此现在它已经不安全了 MD5是由Rivest于1991年设计的单向散列函数,能够产生128比特的散列值,比MD4更复杂,更安全


- SHA-1、SHA-256、SHA-384、SHA-512 SHA-1是由NIST设计的一种能够产生160比特的散列值的单项散列函数,SHA-1的消息长度存在上限,但这个值接近2^64比特


- SHA-256、SHA-384、SHA-512 SHA-256、SHA-384、SHA-512都是由NIST设计的单向散列函数,他们的散列值长度分别为256比特、384比特、512比特。 这些单向散列函数合起来统称SHA-2,他们的消息长度也存在上限,分别为2^64、2^128、2^128比特

单项散列函数的实际应用

  • 检测软件是否被篡改
  • 基于口令的加密
  • 消息认证码
  • 数字签名
  • 为随机数生成器
  • 一次性口令


国产商用密码算法(国密)

> 参考文档 https://zhuanlan.zhihu.com/p/132352160


> 国产商密算法是我国自主研发、具有自主知识产权的一系列密码算法,具有较高安全性,由国家密码局公开并大力推广

> 我国公开的国产商用密码算法包括SM1、SM2、SM3、SM4、SM7、SM9及祖冲之算法,> 其中SM2、SM3、SM4最为常用,用于对应替代RSA、DES、3DES、SHA等国际通用密码算法体系

证书颁发机构CA

> 证书颁发机构,即认证中心CA (Certification Authority),来将公钥与其对应的实体(人或机器)进行绑定(binding);即给公司或个人颁发证书

> 认证中心一般由政府出资建立。每个实体都有CA 发来的证书(certificate),里面有公钥及其拥有者的标识信息。此证书被 CA 进行了数字签名。任何用户都可从可信的地方获得认证中心 CA 的公钥,此公钥用来验证某个公钥是否为某个实体所拥有。有的大公司也提供认证中心服务

使用 openssl自行生成用户证书

生成证书需要使用openssl工具。

在生成证书的具体步骤之前,我们需要知道几个与证书相关的文件格式,

所有这些格式都属于PKCS(The Public-Key Cryptography Standards)标准:


.key文件:私钥文件,通常使用rsa算法,私钥需要自己保存,无需提交给CA机构

.csr文件:证书签名请求(证书请求文件),含有公钥信息,certificate signing request的缩写。生成该文件时需要用到自己的私钥。

.crt文件:CA认证后的证书文件,certificate的缩写。

.crl文件:证书吊销列表,Certificate Revocation List的缩写

.pem文件:用于导出,导入证书时候的证书的格式。该文件实际上是.crt文件和.key文件的合体,与windows下使用.pfx类似,不同的是.pem使用base64字符存储,而.pfx使用二进制存储。

通常情况,我们部署在内网的服务会采用自签名的证书

  • 生成私钥(.key)-->生成证书请求(.csr)-->用CA根证书签名得到证书(.crt)
  • 参考文档 https://zhuanlan.zhihu.com/p/423506052
  • 使用脚本生成自定义证书 https://www.jianshu.com/p/3361fdba88aa
1 生成私钥-使用des3算法 (会让输入私钥密码,然后生成private-rsa.key私钥文件)
openssl genrsa -des3 -out private-rsa.key 1024 

2 使用私钥文件生成证书申请文件 (过程中需要私钥密码,并且填入一些信息)
2.1 生成.csr 文件
openssl req -new -key private-rsa.key -out ca.csr
2.2 用户证书
openssl x509 -req -days 365 -in ca.csr -signkey private-rsa.key -out public-rsa.crt
也可以将 3.1 和 3.2 步骤合并生成证书 (.cer 和.crt 应该只是后缀不一致)
openssl req -new -x509 -key private-rsa.key -days 3650 -out public-rsa.cer  

## 生成证书文件后,在 winddows 系统下是可以直接查看公钥信息的
  
有时需要用到pem格式的证书,可以用以下方式合并证书文件(crt)和私钥文件(key)来生成
cat server.crt server.key > server.pem
生成pfx即PKCS格式证书
openssl pkcs12 -export -name test-alias -in public-rsa.cer -inkey private-rsa.key -out user-rsa.pfx


通过 java 代码生成证书,密钥对

参考文档 https://blog.csdn.net/weixin_43915808/article/details/114210542

private static X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
private static ECDomainParameters ecDomainParameters = new ECDomainParameters(x9ECParameters.getCurve(),
        x9ECParameters.getG(), x9ECParameters.getN());
private static ECParameterSpec ecParameterSpec = new ECParameterSpec(x9ECParameters.getCurve(),
        x9ECParameters.getG(), x9ECParameters.getN());

static {
    if (Security.getProvider("BC") == null) {
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
    }
}

public static void main(String[] args) {
    //生成密钥对
    KeyPair kp = generateKeyPair();
    
    //获取公钥
    PublicKey publicKey = kp.getPublic();
    
    //获取私钥
    PrivateKey privateKey = kp.getPrivate();
    
    //明文
    String text = "SM2 Demo";
    byte[] encrypt = encrypt(text.getBytes(), publicKey, "C1C3C2");
    System.out.println(Base64.encodeBase64String(encrypt));
    
    encrypt = decrypt(encrypt, privateKey, "C1C3C2");
    System.out.println(new String(encrypt));
}


private static KeyPair generateKeyPair() {
    try {
        //根据算法名称 创建KeyPairGenerator对象  ;该对象为指定算法生成公钥/私钥对
        // getInstance(String algorithm, String provider) /
        KeyPairGenerator kpGen = KeyPairGenerator.getInstance("EC", "BC");
        kpGen.initialize(ecParameterSpec, new SecureRandom());
        return kpGen.generateKeyPair();
    } catch (Exception e) {
        throw new RuntimeException("算法不存在",e);
    } 
}


private static byte[] encrypt(byte[] data, PublicKey key, String standard) {
    if ("C1C2C3".equals(standard)) {
        return encryptNew(encryptOld(data, key));
    }
    return encryptOld(data, key);
}

private static byte[] decrypt(byte[] data, PrivateKey key, String standard) {
    if ("C1C2C3".equals(standard)) {
        return decryptOld(decryptNew(data), key);
    }
    return decryptOld(data, key);
}

private static byte[] encryptOld(byte[] data, PublicKey key) {
    BCECPublicKey bcecPublicKey = (BCECPublicKey) key;
    ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(bcecPublicKey.getQ(), ecDomainParameters);
    SM2Engine sm2Engine = new SM2Engine();
    sm2Engine.init(true, new ParametersWithRandom(ecPublicKeyParameters, new SecureRandom()));
    try {
        return sm2Engine.processBlock(data, 0, data.length);
    } catch (InvalidCipherTextException e) {
        throw new RuntimeException(e);
    }
}

private static byte[] encryptNew(byte[] c1c2c3) {
    final int c1Len = (x9ECParameters.getCurve().getFieldSize() + 7) / 8 * 2 + 1;
    final int c3Len = 32;
    byte[] result = new byte[c1c2c3.length];
    System.arraycopy(c1c2c3, 0, result, 0, c1Len);
    System.arraycopy(c1c2c3, c1c2c3.length - c3Len, result, c1Len, c3Len);
    System.arraycopy(c1c2c3, c1Len, result, c1Len + c3Len, c1c2c3.length - c1Len - c3Len);
    return result;
}

private static byte[] decryptOld(byte[] data, PrivateKey key) {
    BCECPrivateKey bcecPrivateKey = (BCECPrivateKey) key;
    ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(bcecPrivateKey.getD(), ecDomainParameters);
    SM2Engine sm2Engine = new SM2Engine();
    sm2Engine.init(false, ecPrivateKeyParameters);
    try {
        return sm2Engine.processBlock(data, 0, data.length);
    } catch (InvalidCipherTextException e) {
        throw new RuntimeException(e);
    }
}

private static byte[] decryptNew(byte[] c1c3c2) {
    final int c1Len = (x9ECParameters.getCurve().getFieldSize() + 7) / 8 * 2 + 1;
    final int c3Len = 32;
    byte[] result = new byte[c1c3c2.length];
    System.arraycopy(c1c3c2, 0, result, 0, c1Len);
    System.arraycopy(c1c3c2, c1Len + c3Len, result, c1Len, c1c3c2.length - c1Len - c3Len);
    System.arraycopy(c1c3c2, c1Len, result, c1c3c2.length - c3Len, c3Len);
    return result;
}

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表