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

相关术语解释:

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.

posted @ 2019-09-12 15:11  FrankYou  阅读(1809)  评论(0编辑  收藏