RSA加密和數字簽名在Java中常見應用【原創】

相關術語解釋:html

一、私鑰(PrivateKey)的生成

1.一、加載 PKCS #8 標準的PEM編碼的字符串,並生成私鑰(RSAPrivateKey)

  如和生成RSA PEM 格式的私鑰文件以及如何轉換成 PKCS #8,參考: 《經過OpenSSL來生成PEM格式的私鑰、PKCS8格式的私鑰、公鑰|pfx格式的私鑰、cer格式的公鑰java

私鑰 PEM 內容樣例以下:git

-----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 代碼:apache

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.二、加載 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.三、根據證書的模(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);
  }

}

二、公鑰(PublicKey)的生成

2.一、加載 PFX(PKCS #12 標準)文件並生成(導出)公鑰(PublicKey)

Java代碼:oracle

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.二、加載 符合 X.509 國際標準 PEM base64-encoded format 的證書內容,並生成公鑰(RSAPublicKey)

須要刪除證書內容(字符串)中的 「-----BEGIN CERTIFICATE-----」 和 「-----END CERTIFICATE-----」ide

公鑰 PEM 證書內容樣例以下:ui

-----BEGIN CERTIFICATE-----this

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.三、加載 符合 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.四、根據證書的模(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);
  }

}

三、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));
  }

}

四、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);
  }

}

四、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());
  }

}

四、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. Name Description

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.

相關文章
相關標籤/搜索