# RSA-签名与校验-前端与后端(Java)

# 1. 介绍

前端使用 私钥 对摘要进行签名

后端(Java)使用 公钥 验证签名是否有效

# 2. Java

public final class RsaEncryptUtil {
  // 生成 公钥、私钥 字符串
  public static HashMap<String, String> genKeyPair() {
      try {
          KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
          keyPairGen.initialize(1024, new SecureRandom());
          
          KeyPair keyPair = keyPairGen.generateKeyPair();
          
          RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
          RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();

          String publicKeyString = Base64.encode(publicKey.getEncoded());
          String privateKeyString = Base64.encode(privateKey.getEncoded());
          
          HashMap<String, String> map = Maps.newHashMap();
          map.put("pub", publicKeyString);
          map.put("pri", privateKeyString);

          return map;
      } catch (NoSuchAlgorithmException e) {
          e.printStackTrace();
          return null;
      }
  }

  // 公钥字符串 转 RSAPublicKey 对象
  public static RSAPublicKey strToRsaPublicKey(String publicKeyStr) throws Exception {
      byte[] buffer = Base64.decode(publicKeyStr);
      
      KeyFactory keyFactory = KeyFactory.getInstance("RSA");
      
      X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer);

      return (RSAPublicKey) keyFactory.generatePublic(keySpec);
  }
  // 私钥字符串 转 RSAPrivateKey 对象
  public static RSAPrivateKey strToRsaPrivateKey(String privateKeyStr) throws Exception {
      byte[] buffer = Base64.decode(privateKeyStr);
      PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
      KeyFactory keyFactory = KeyFactory.getInstance("RSA");
      return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
  }

  /**
   * 验证签名
   * @param algorithm  "SHA256withRSA" | "MD5withRSA"
   */
  public static Boolean validateSignature(RSAPublicKey publicKey, String digest, String signedDigest, String algorithm) throws Exception {
      byte[] signedDigestBytes = Base64.decode(signedDigest);

      KeyFactory keyFactory = KeyFactory.getInstance("RSA");
      PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(publicKey.getEncoded()));

      java.security.Signature signature = java.security.Signature.getInstance(algorithm);
      signature.initVerify(pubKey);
      signature.update(digest.getBytes(StandardCharsets.UTF_8));

      return signature.verify(signedDigestBytes);
  }

  public static Boolean validateSignature(String publicKey, String digest, String signedDigest, String algorithm) throws Exception {
      return validateSignature(strToRsaPublicKey(publicKey),digest, signedDigest, algorithm);
  }
}

# 3. 前端

/*
  "crypto-js": "^4.2.0",
  "jsencrypt": "^3.5.4",
 */

import CryptoJS from 'crypto-js';
import { JSEncrypt } from 'jsencrypt';

export enum HashType {
  SHA256 = 'sha256',
  MD5 = 'md5'
}

class RsaUtil {
  toDigest(content: string, hashType = HashType.MD5): string {
    if (hashType === HashType.SHA256) {
      return CryptoJS.SHA256(content).toString(CryptoJS.enc.Hex);
    }

    return CryptoJS.MD5(content).toString(CryptoJS.enc.Hex);
  }

  signContentWithPrivateKey(privateKey: string, content: string, hashType = HashType.MD5) {
    const digest = this.toDigest(content, hashType);

    this.signDigestWithPrivateKey(privateKey, digest, hashType);
  }

  signDigestWithPrivateKey(privateKey: string, digest: string, hashType = HashType.MD5) {
    const encrypt = new JSEncrypt();
    encrypt.setPrivateKey(privateKey);

    return encrypt.sign(digest, (data) => this.toDigest(data, hashType), hashType);
  }
}

export const rasUtil = new RsaUtil();

# 4. 测试

# 4.1. 生成 公钥、私钥

HashMap<String, String> map = RsaEncryptUtil.genKeyPair();
map.forEach((key, val) -> {
    System.out.println(key + " = " + val);
});
/*
私钥 = MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAK8cceYQYB657UizmY1kq3uScZKtIFNltl0n5Br6DwEPm5mgPBCLDhvfX5rcLdrrdKRwkvuT7ElT/CPpGccYCbluVg/kXUU4kfFp5htrt38stA3pzIXpkmD6J4xVFET7OtDyJYr3gY0XmHDuTFXFfWYK2f3hGTJgZD2gpnToIPzxAgMBAAECgYAmY0b413gq5DmHaY/s9je9lEH/lKJ0heSvkVIpM85cPi6vQ/hG7CUAqTxNN51504ozjlY6fpgbDyVEPhKfmb5voPdJq1KlIvgm8PE6CQ6a3Fhhl+1k2dfS2kN+8kUx35TLjOU0q+uPb6UKsCb+UJrd1kuwC5mgpqBF0yQ3H7JaDQJBAPsqXbkWkwYvZEXvtCGVa11xsPY0vNhKDlfA5yW2i29GRy4GolFsM9Ugb1JH/RN4+rV4ms7xvWZhm/UDLBJYGHMCQQCye1DneII8aWUwgPquGhiajCarqM5fjDMFyXaqL/I7UGiX5je5u7JnCrgD3pkrlyXNakEsvs3DIVxQI3eFfFALAkEAr+xs2UfGk35+bb4IHb3bBgisOseVvqmggjbLsM67u6UuFj7vUrjHVXDyiF+EFC+Y66MeS/VnBF86LdOa+v5ZoQJARRxtEoReYcgp76Mx7nKablWwr62449Sk+SuusG3KbV9QcOkrUNc2WKIU6SKryu5HLAhl3k6v3Ewxs7JYGK8vSwJBAPBwyLILDySqeEVnr0lIhWuOLDn5lejMGQ0VqOnnhCwB1e3TU3SiYa0jit/tlLI1yHVBb3vm1QIE911oiY17d9s=

公钥 = MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvHHHmEGAeue1Is5mNZKt7knGSrSBTZbZdJ+Qa+g8BD5uZoDwQiw4b31+a3C3a63SkcJL7k+xJU/wj6RnHGAm5blYP5F1FOJHxaeYba7d/LLQN6cyF6ZJg+ieMVRRE+zrQ8iWK94GNF5hw7kxVxX1mCtn94RkyYGQ9oKZ06CD88QIDAQAB
*/

# 4.2. 前端使用私钥进行签名

import { HashType, rasUtil } from '@/commons/utils/RsaUtil';

const privateKey = 'MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAK8cceYQYB657UizmY1kq3uScZKtIFNltl0n5Br6DwEPm5mgPBCLDhvfX5rcLdrrdKRwkvuT7ElT/CPpGccYCbluVg/kXUU4kfFp5htrt38stA3pzIXpkmD6J4xVFET7OtDyJYr3gY0XmHDuTFXFfWYK2f3hGTJgZD2gpnToIPzxAgMBAAECgYAmY0b413gq5DmHaY/s9je9lEH/lKJ0heSvkVIpM85cPi6vQ/hG7CUAqTxNN51504ozjlY6fpgbDyVEPhKfmb5voPdJq1KlIvgm8PE6CQ6a3Fhhl+1k2dfS2kN+8kUx35TLjOU0q+uPb6UKsCb+UJrd1kuwC5mgpqBF0yQ3H7JaDQJBAPsqXbkWkwYvZEXvtCGVa11xsPY0vNhKDlfA5yW2i29GRy4GolFsM9Ugb1JH/RN4+rV4ms7xvWZhm/UDLBJYGHMCQQCye1DneII8aWUwgPquGhiajCarqM5fjDMFyXaqL/I7UGiX5je5u7JnCrgD3pkrlyXNakEsvs3DIVxQI3eFfFALAkEAr+xs2UfGk35+bb4IHb3bBgisOseVvqmggjbLsM67u6UuFj7vUrjHVXDyiF+EFC+Y66MeS/VnBF86LdOa+v5ZoQJARRxtEoReYcgp76Mx7nKablWwr62449Sk+SuusG3KbV9QcOkrUNc2WKIU6SKryu5HLAhl3k6v3Ewxs7JYGK8vSwJBAPBwyLILDySqeEVnr0lIhWuOLDn5lejMGQ0VqOnnhCwB1e3TU3SiYa0jit/tlLI1yHVBb3vm1QIE911oiY17d9s=';

const data = '123';

const digest = rasUtil.toDigest(data);

const signature = rasUtil.signDigestWithPrivateKey(privateKey, digest, HashType.MD5);

console.log('data: ', data);
console.log('digest: ', digest);
console.log('signature: ', signature);

/*
data:  123
digest:  202cb962ac59075b964b07152d234b70
signature:  PmpYH2NAYUwszIJBuoR9D6Tw6fBz0Rt+9qYpJ/jm0QgG4mAuHwRgWlWT9IIajFWdWze2Ui4O0planno9vz9pwHdnwgAfToFvVPjQ/amB7xUOHiLnLYBiYyWlyRsO2WZUpVdM/HNH5uZE9xuuEqS1Ee7QkWobFfc8TYzULieof/g=
*/

# 4.3. 后端(Java)使用公钥验证签名

String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvHHHmEGAeue1Is5mNZKt7knGSrSBTZbZdJ+Qa+g8BD5uZoDwQiw4b31+a3C3a63SkcJL7k+xJU/wj6RnHGAm5blYP5F1FOJHxaeYba7d/LLQN6cyF6ZJg+ieMVRRE+zrQ8iWK94GNF5hw7kxVxX1mCtn94RkyYGQ9oKZ06CD88QIDAQAB";
String digest = "202cb962ac59075b964b07152d234b70";
String signedDigest = "PmpYH2NAYUwszIJBuoR9D6Tw6fBz0Rt+9qYpJ/jm0QgG4mAuHwRgWlWT9IIajFWdWze2Ui4O0planno9vz9pwHdnwgAfToFvVPjQ/amB7xUOHiLnLYBiYyWlyRsO2WZUpVdM/HNH5uZE9xuuEqS1Ee7QkWobFfc8TYzULieof/g=";
Boolean isValid = RsaEncryptUtil.validateSignature(publicKey, digest, signedDigest, "MD5withRSA");

System.out.println("isValid = " + isValid);

# 5. 参考

本章目录