RSA加密和数字签名在Java中常见应用【原创】

摘要:
相关术语解释:RSA,参考:https://en.wikipedia.org/wiki/RSA_(Cryptosystem)非对称加密算法,指:https://baike.baidu.com/item/%E9%9D%9E%E5%AF%B9%E7%A7%B0%E5%8A%A0%E5%AF%86%E7%AE%97%E6%B3%95/1208652?fr=aladdinPEM,参考:https://e

相关术语解释:

1、私钥(PrivateKey)的生成

1.1、加载 PKCS #8 标准的PEM编码的字符串,并生成私钥(RSAPrivateKey)

关于PKCS #8: In cryptography, PKCS #8 is a standard syntax for storing private key information. PKCS #8 is one of the family of standards called Public-Key Cryptography Standards (PKCS) created by RSA Laboratories。PKCS #8 private keys are typically exchanged in the PEM base64-encoded format

  如和生成RSA PEM 格式的私钥文件以及如何转换成 PKCS #8,参考: 《通过OpenSSL来生成PEM格式的私钥、PKCS8格式的私钥、公钥|pfx格式的私钥、cer格式的公钥

私钥 PEM 内容样例如下:

-----BEGIN PRIVATE KEY-----
MIIBVgIBADANBgkqhkiG9w0BAQEFAASCAUAwggE8AgEAAkEAq7BFUpkGp3+LQmlQ
Yx2eqzDV+xeG8kx/sQFV18S5JhzGeIJNA72wSeukEPojtqUyX2J0CciPBh7eqclQ
2zpAswIDAQABAkAgisq4+zRdrzkwH1ITV1vpytnkO/NiHcnePQiOW0VUybPyHoGM
/jf75C5xET7ZQpBe5kx5VHsPZj0CBb3b+wSRAiEA2mPWCBytosIU/ODRfq6EiV04
lt6waE7I2uSPqIC20LcCIQDJQYIHQII+3YaPqyhGgqMexuuuGx+lDKD6/Fu/JwPb
5QIhAKthiYcYKlL9h8bjDsQhZDUACPasjzdsDEdq8inDyLOFAiEAmCr/tZwA3qeA
ZoBzI10DGPIuoKXBd3nk/eBxPkaxlEECIQCNymjsoI7GldtujVnr1qT+3yedLfHK
srDVjIT3LsvTqw==
-----END PRIVATE KEY-----

使用下面的方法来生成私钥(RSAPrivateKey)需要删除上面的“-----BEGIN PRIVATE KEY-----” 和“-----END PRIVATE KEY-----”

Java 代码:

package rsa;

import java.security.KeyFactory;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import org.apache.commons.codec.binary.Base64;

@UtilityClass
public class PrivateKeyGen {

  /**
   * 加载 PKCS8 私钥证书(PEM base64-encoded format)
   * <br/> PKCS #8 is a standard syntax for storing private key information.
   * <br/> PKCS #8 is one of the family of standards called Public-Key Cryptography Standards (PKCS) created by RSA Laboratories.
   *
   * @param privateKeyPem 私钥文件内容(PEM Base64编码)
   */
  @SneakyThrows
  public static RSAPrivateKey getPrivateKey(String privateKeyPem){
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyPem));
    return (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);
  }

}

1.2、加载 PFX(PKCS #12 标准)文件并生成私钥(PrivateKey)

java代码:

package rsa;

import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import lombok.Cleanup;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;

@UtilityClass
public class PrivateKeyGen {

  /**
   * 读取 PFX 格式的证书文件并生成 {@link PrivateKey} 类型实例
   *
   * @param keyStorePath PFX 格式的证书文件路径
   * @param keyStorePasswd KeyStroe 的 password
   */
  @SneakyThrows
  public static PrivateKey getPrivateKey(String keyStorePath, String keyStorePasswd) {
    @Cleanup FileInputStream fis = new FileInputStream(keyStorePath);
    KeyStore store = KeyStore.getInstance("PKCS12");
    store.load(fis, keyStorePasswd.toCharArray());
    String alia = store.aliases().nextElement();
    return (PrivateKey) store.getKey(alia, keyStorePasswd.toCharArray());
  }

}

1.3、根据证书的模(Modulus)和指数(Exponent)来生成私钥(PrivateKey)

import java.math.BigInteger;
import java.security.Key;
import java.security.KeyFactory;
import java.security.spec.RSAPrivateKeySpec;
import lombok.SneakyThrows;

/**
 * @author xfyou
 */
public class RsaPrivateKey {

  @SneakyThrows
  private Key generatePrivateKey(byte[] keyModulus, byte[] keyExponent) {
    return KeyFactory.getInstance("RSA").generatePrivate(new RSAPrivateKeySpec(newBigInteger(keyModulus), newBigInteger(keyExponent)));
  }

  private static BigInteger newBigInteger(byte[] keyInfo) {
    return new BigInteger(1, keyInfo);
  }

}

2、公钥(PublicKey)的生成

2.1、加载 PFX(PKCS #12 标准)文件并生成(导出)公钥(PublicKey)

Java代码:

package rsa;

import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.PublicKey;
import lombok.Cleanup;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;

/**
 * KeyGen
 */
@UtilityClass
public class KeyGen {

  /**
   * 读取 PFX 格式的证书文件并生成 {@link PublicKey} 类型实例
   *
   * @param keyStorePath PFX 格式的证书文件路径
   * @param keyStorePasswd KeyStroe 的 password
   */
  @SneakyThrows
  public static PublicKey getPublicKey(String keyStorePath, String keyStorePasswd) {
    @Cleanup FileInputStream fis = new FileInputStream(keyStorePath);
    KeyStore store = KeyStore.getInstance("PKCS12");
    store.load(fis, keyStorePasswd.toCharArray());
    String alia = store.aliases().nextElement();
    return store.getCertificate(alia).getPublicKey();
  }

}

2.2、加载 符合 X.509 国际标准 PEM base64-encoded format 的证书内容,并生成公钥(RSAPublicKey)

需要删除证书内容(字符串)中的 “-----BEGIN CERTIFICATE-----” 和 “-----END CERTIFICATE-----”

公钥 PEM 证书内容样例如下:

-----BEGIN CERTIFICATE-----

MIIC6DCCAlGgAwIBAgIUI2ZSO2i7FA4iBKUOvjsZRzCQj8YwDQYJKoZIhvcNAQEL
BQAwgYUxCzAJBgNVBAYTAkNOMREwDwYDVQQIDAhTaGFuZ0hhaTERMA8GA1UEBwwI
mu1GI8mCpMYVGyUnJVNHqb3PG5uECbcKk8SfVg==
-----END CERTIFICATE-----

Java代码:

package rsa;

import java.security.KeyFactory;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.X509EncodedKeySpec;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import org.apache.commons.codec.binary.Base64;

@UtilityClass
public class KeyGen {

  /**
   * 加载 PEM base64-encoded format 的公钥证书内容,并生成 {@link RSAPublicKey} 类型实例
   *
   * @param pemContent PEM base64-encoded format 的公钥证书内容,此公钥证书符合 X.509 国际标准
   * @return {@link RSAPublicKey} 类型实例
   */
  @SneakyThrows
  private RSAPublicKey getPublicKey(String pemContent) {
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decodeBase64(pemContent));
    return (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);
  }

}

2.3、加载 符合 X.509 国际标准的CER(*.cer)格式的证书文件,并生成公钥(PublicKey)

Java代码:

package rsa;

import java.io.FileInputStream;
import java.security.PublicKey;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import lombok.Cleanup;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;

@UtilityClass
public class KeyGen {

  /**
   * 读取符合 X.509 国际标准的 CER 格式的公钥证书文件,并生成 {@link PublicKey} 类型的实例
   *
   * @param cerPath 公钥证书文件(*.cer)的路径
   */
  @SneakyThrows
  public static PublicKey getPublicKey(String cerPath) {
    @Cleanup FileInputStream bais = new FileInputStream(cerPath);
    CertificateFactory cf = CertificateFactory.getInstance("X.509");
    X509Certificate cert = (X509Certificate) cf.generateCertificate(bais);
    return cert.getPublicKey();
  }

}

如果通过读取完整的 PEM 证书内容(字符串)来生成公钥证书(PublicKey)则通过以下方式。

Java代码

package rsa;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.PublicKey;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import lombok.Cleanup;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;

@UtilityClass
public class KeyGen {

  /**
   * 读取符合 X.509 国际标准的公钥证书的 PEM base64-encoded 内容字符串 ,并生成 {@link PublicKey} 类型的实例
   *
   * @param pubKeyCertPem 公钥证书 PEM base64-encoded 内容字符串
   */
  @SneakyThrows
  public static PublicKey getPublicKey(String pubKeyCertPem) {
    @Cleanup InputStream is = new ByteArrayInputStream(pubKeyCertPem.getBytes(StandardCharsets.UTF_8));
    CertificateFactory cf = CertificateFactory.getInstance("X.509");
    X509Certificate cert = (X509Certificate) cf.generateCertificate(is);
    return cert.getPublicKey();
  }

}

2.4、根据证书的模(Modulus)和指数(Exponent)来生成公钥(PublicKey)

import java.math.BigInteger;
import java.security.Key;
import java.security.KeyFactory;
import java.security.spec.RSAPublicKeySpec;
import lombok.SneakyThrows;

public class RsaPublicKey {
  
  @SneakyThrows
  private Key generatePublicKey(byte[] keyModulus, byte[] keyExponent) {
    return KeyFactory.getInstance("RSA").generatePublic(new RSAPublicKeySpec(newBigInteger(keyModulus), newBigInteger(keyExponent)));
  }
  
  private static BigInteger newBigInteger(byte[] keyInfo) {
    return new BigInteger(1, keyInfo);
  }

}

3、RSA非对称-加密

公钥加密,私钥解密 或 私钥加密,公钥解密

Java代码:

package rsa;

import javax.crypto.Cipher;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import org.apache.commons.codec.binary.Base64;

@UtilityClass
public class CryptTool {

  /**
   * RSA 公钥加密
   *
   * @param data 待加密的数据
   * @return 加密后的字节数组
   */
  @SneakyThrows
  public byte[] encrypt(byte[] data) {
    Cipher cipher = Cipher.getInstance("RSA");
    // The key is the {@link java.security.PublicKey} instance
    cipher.init(Cipher.ENCRYPT_MODE, publicKey);
    return cipher.doFinal(data);
  }

  /**
   * RSA 公钥加密
   *
   * @param data 待加密的数据
   * @return 加密后并Base64的字符串
   */
  @SneakyThrows
  public String encrypt(byte[] data) {
    Cipher cipher = Cipher.getInstance("RSA");
    // The key is the {@link java.security.PublicKey} instance
    cipher.init(Cipher.ENCRYPT_MODE, publicKey);
    return Base64.encodeBase64String(cipher.doFinal(data));
  }

}

4、RSA非对称-解密

Java代码:

package rsa;

import java.nio.charset.StandardCharsets;
import javax.crypto.Cipher;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;

@UtilityClass
public class CryptTool {

  /**
   * RSA 私钥解密
   *
   * @param data 待解密的数据
   * @return 解密后的字节数组
   */
  @SneakyThrows
  public byte[] decrypt(byte[] data) {
    Cipher cipher = Cipher.getInstance("RSA");
    // The key is the {@link java.security.PrivateKey} instance
    cipher.init(Cipher.DECRYPT_MODE, privateKey);
    return cipher.doFinal(data);
  }

  /**
   * RSA 私钥解密
   *
   * @param data 待解密的数据
   * @return 解密后的字符串
   */
  @SneakyThrows
  public String decrypt(String data) {
    Cipher cipher = Cipher.getInstance("RSA");
    // The key is the {@link java.security.PrivateKey} instance
    cipher.init(Cipher.DECRYPT_MODE, privateKey);
    return new String(cipher.doFinal(data.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
  }

}

4、RSA非对称-签名

Java代码:

package rsa;

import java.security.Signature;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import org.apache.commons.codec.binary.Base64;

@UtilityClass
public class CryptTool {

  /**
   * RSA 使用私钥进行签名,可能的 Signature Algrithom: </br>
   * <ol>
   *   <li>SHA1withRSA</li>
   *   <li>SHA256withRSA</li>
   *   <li>SHA384withRSA</li>
   *   <li>SHA512withRSA</li></li>
   * </ol>
   * @param data 待签名的数据
   * @return 签名后的数据
   */
  @SneakyThrows
  public byte[] sign(byte[] data) {
    Signature signature = Signature.getInstance("SHA256withRSA");
    signature.initSign(privateKey);
    signature.update(data);
    return signature.sign();
  }

  /**
   * RSA 使用私钥进行签名,可能的 Signature Algrithom: </br>
   * <ol>
   *   <li>SHA1withRSA</li>
   *   <li>SHA256withRSA</li>
   *   <li>SHA384withRSA</li>
   *   <li>SHA512withRSA</li></li>
   * </ol>
   * @param data 待签名的数据
   * @return 签名后的数据
   */
  @SneakyThrows
  public String sign(byte[] data) {
    Signature signature = Signature.getInstance("SHA256withRSA");
    signature.initSign(privateKey);
    signature.update(data);
    return Base64.encodeBase64String(signature.sign());
  }

}

4、RSA非对称-验签

Java代码:

package rsa;

import java.security.Signature;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import org.apache.commons.codec.binary.Base64;

@UtilityClass
public class CryptTool {

  /**
   * RSA 公钥验签
   *
   * @param data 待验签的数据
   * @param sign 对方已签名的数据
   * @return 验证结果
   */
  @SneakyThrows
  public boolean verify(byte[] data, String sign) {
    Signature signature = Signature.getInstance("SHA256withRSA");
    signature.initVerify(publicKey);
    signature.update(data);
    return signature.verify(Base64.decodeBase64(sign));
  }

}

附:Signature Algorithms

The algorithm names in this section can be specified when generating an instance of Signature.

Alg. NameDescription

NONEwithRSA

The RSA signature algorithm which does not use a digesting algorithm (e.g. MD5/SHA1) before performing the RSA operation. For more information about the RSA Signature algorithms, please see PKCS1.

MD2withRSA

MD5withRSA

The MD2/MD5 with RSA Encryption signature algorithm which uses the MD2/MD5 digest algorithm and RSA to create and verify RSA digital signatures as defined in PKCS1.

SHA1withRSA

SHA256withRSA
SHA384withRSA
SHA512withRSA

The signature algorithm with SHA-* and the RSA encryption algorithm as defined in the OSI Interoperability Workshop, using the padding conventions described in PKCS1.

NONEwithDSA

The Digital Signature Algorithm as defined in FIPS PUB 186-2. The data must be exactly 20 bytes in length. This algorithms is also known under the alias name of rawDSA.

SHA1withDSA

The DSA with SHA-1 signature algorithm which uses the SHA-1 digest algorithm and DSA to create and verify DSA digital signatures as defined in FIPS PUB 186.

NONEwithECDSA

SHA1withECDSA

SHA256withECDSA

SHA384withECDSA

SHA512withECDSA

(ECDSA)

The ECDSA signature algorithms as defined in ANSI X9.62.

Note:"ECDSA" is an ambiguous name for the "SHA1withECDSA" algorithm and should not be used. The formal name "SHA1withECDSA" should be used instead.

<digest>with<encryption>

Use this to form a name for a signature algorithm with a particular message digest (such as MD2 or MD5) and algorithm (such as RSA or DSA), just as was done for the explicitly-defined standard names in this section (MD2withRSA, etc.).

For the new signature schemes defined in PKCS1 v 2.0, for which the <digest>with<encryption> form is insufficient, <digest>with<encryption>and<mgf> can be used to form a name. Here, <mgf> should be replaced by a mask generation function such as MGF1. Example: MD5withRSAandMGF1.

免责声明:文章转载自《RSA加密和数字签名在Java中常见应用【原创】》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇ssh secure Shell连接阿里云服务器,报错的问题,openssh无法登录:server responded "algorithm negotiation failed”何为优秀的前端?下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

MySql与Java的时间类型

MySql与Java的时间类型 MySql的时间类型有Java中与之对应的时间类型datejava.sql.Date Datetimejava.sql.Timestamp Timestampjava.sql.Timestamp Timejava.sql.Time Yearjava.sql.Date 对其进行分析参考MySql 的reference ma...

Android 学习笔记之 JSON的序列化基本用法

最近在找怎样可以将一个数据对象通过WCF传给服务器,在网上看到了一个对象序列化成JSON的数据格式的帖子,这里整理下。 用到了JSON中的JSONStringer这个类,此类的主要功能就是可以快速的将一个数据类型序列化,成为一个标准的JSON数据。 其中需要注意的是: .object()和.endObject()必须同时使用,是为了按照Object标准给数值...

关于Delphi中的字符串的浅析(瓢虫大作,里面有内存错误的举例)

关于Delphi中的字符串的浅析 只是浅浅的解析下,让大家可以快速的理解字符串。 其中的所有代码均在Delphi7下测试通过。 Delphi 4,5,6,7中有字符串类型包括了: 短字符串(Short String) 长字符串(Long String) 宽字符串(Wide String) 零结尾字符串(Null-Terminated String)、P...

整理分布式锁:业务场景&amp;amp;分布式锁家族&amp;amp;实现原理

1、引入业务场景 业务场景一出现: 因为小T刚接手项目,正在吭哧吭哧对熟悉着代码、部署架构。在看代码过程中发现,下单这块代码可能会出现问题,这可是分布式部署的,如果多个用户同时购买同一个商品,就可能导致商品出现库存超卖 (数据不一致)现象,对于这种情况代码中并没有做任何控制。 原来一问才知道,以前他们都是售卖的虚拟商品,没啥库存一说,所以当时没有考虑那么多...

每个Web开发者都应该知道的关于URL编码的知识

  本文首先阐述了人们关于统一资源定位符(URL)编码的普遍的误读,其后通过阐明HTTP场景下的URL encoding 来引出我们经常遇到的问题及其解决方案。本文并不特定于某类编程语言,我们在Java环境下阐释问题,最后从Web应用的多个层次描述如何解决URL编码的问题来结尾。    简介   当我们每天上网冲浪时,有一些技术我们无时无刻不在面对。有数据...

在Entity Framework中使用存储过程(一):实现存储过程的自动映射

之前给自己放了一个比较长的假期,在这期间基本上没怎么来园子逛。很多朋友的留言也没有一一回复,在这里先向大家道个歉。最近一段时间的工作任务是如何将ADO.NET Entity Framework 4.0(以下简称EF)引入到我们的开发框架,进行相应的封装、扩展,使之成为一个符合在特定场景下进行企业级快速开发的ORM。在此过程中遇到了一些挑战,也有一些心得。为...