Java 实现RSA非对称加密算法

作者:没有星星的夏季 时间:2021-08-19 16:27:51 

公钥与私钥

公钥与私钥是成对的,一般的,我们认为的是公钥加密、私钥解密、私钥签名、公钥验证,有人说成私钥加密,公钥解密时不对的。

公钥与私钥的生成有多种方式,可以通过程序生成(下文具体实现),可以通过openssl工具:


   # 生成一个私钥,推荐使用1024位的秘钥,秘钥以pem格式保存到-out参数指定的文件中,采用PKCS1格式
   openssl genrsa -out rsa.pem 1024
   # 生成与私钥对应的公钥,生成的是Subject Public Key,一般配合PKCS8格式私钥使用
   openssl rsa -in rsa.pem -pubout -out rsa.pub

RSA生成公钥与私钥一般有两种格式:PKCS1和PKCS8,上面的命令生成的秘钥是PKCS1格式的,而公钥是Subject Public Key,一般配合PKCS8格式私钥使用,所以就可能会涉及到PKCS1和PKCS8之间的转换:


   # PKCS1格式私钥转换为PKCS8格式私钥,私钥直接输出到-out参数指定的文件中
   openssl pkcs8 -topk8 -inform PEM -in rsa.pem -outform pem -nocrypt -out rsa_pkcs8.pem
   # PKCS8格式私钥转换为PKCS1格式私钥,私钥直接输出到-out参数指定的文件中
   openssl rsa -in rsa_pkcs8.pem -out rsa_pkcs1.pem

# PKCS1格式公钥转换为PKCS8格式公钥,转换后的内容直接输出
   openssl rsa -pubin -in rsa.pub -RSAPublicKey_out
   # PKCS8格式公钥转换为PKCS1格式公钥,转换后的内容直接输出
   openssl rsa -RSAPublicKey_in -pubout -in rsa.pub

现实中,我们往往从pem、crt、pfx文件获取公私和私钥,crt、pfx的制作可以参考:简单的制作ssl证书,并在nginx和IIS中使用,或者使用现成的:https://pan.baidu.com/s/1MJ5YmuZiLBnf-DfNR_6D7A (提取码:c6tj),密码都是:123456

Java实现

为简化说明介绍,这里我直接封装了一个工具类,因为要从pem、crt、pfx文件获取公私和私钥,因此引用了一个第三方包:BouncyCastle,可以直接在pom.xml中添加依赖:


   <dependency>
       <groupId>org.bouncycastle</groupId>
       <artifactId>bcprov-jdk15on</artifactId>
       <version>1.68</version>
   </dependency>

或者去mvn上下载:跳转

简单封装的RsaUtil.java:


import java.io.FileInputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Signature;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Enumeration;

import javax.crypto.Cipher;

import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.DLSequence;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import org.bouncycastle.util.io.pem.PemWriter;

public class RsaUtil {

static {
       Security.addProvider(new BouncyCastleProvider());
   }

/**
    * 随机生成密钥对
    *
    * @param usePKCS8
    *            是否采用PKCS8填充模式
    */
   public static Object[] generateRsaKey(boolean usePKCS8) throws Exception {
       KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME);
       // 初始化
       keyPairGen.initialize(1024, new SecureRandom());
       // 生成一个密钥对,保存在keyPair中
       KeyPair keyPair = keyPairGen.generateKeyPair();
       RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); // 得到私钥
       RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); // 得到公钥

// 这两个公私钥是PKCS8格式的
       byte[] publicKeyBytes = publicKey.getEncoded();
       byte[] privateKeyBytes = privateKey.getEncoded();

if (!usePKCS8) {
           // 将PSCK8格式公私钥转换为PKCS1格式
           publicKeyBytes = pkcs8ToPkcs1(false, publicKeyBytes);
           privateKeyBytes = pkcs8ToPkcs1(true, privateKeyBytes);
       }

return new Object[] { publicKeyBytes, privateKeyBytes };
   }

/**
    * 从Pem文件读取密钥对
    *
    * @param reader
    *            输入流
    * @param pemFileName
    *            pem文件
    */
   public static byte[] readFromPem(String pemFileName) throws Exception {
       PemReader pemReader = new PemReader(new FileReader(pemFileName));
       PemObject pemObject = pemReader.readPemObject();
       byte[] publicKey = pemObject.getContent();
       pemReader.close();
       return publicKey;
   }

/**
    * 从Pem文件读取密钥
    *
    * @param isPrivateKey
    *            是否是私钥
    * @param buffer
    *            字节
    * @param pemFileName
    *            pem文件
    */
   public static void writeToPem(byte[] buffer, boolean isPrivateKey, String pemFileName) throws Exception {

PemObject pemObject = new PemObject(isPrivateKey ? "RSA PRIVATE KEY" : "RSA PUBLIC KEY", buffer);
       FileWriter fileWriter = new FileWriter(pemFileName);
       PemWriter pemWriter = new PemWriter(fileWriter);
       pemWriter.writeObject(pemObject);
       pemWriter.close();
   }

/**
    * 从crt文件读取公钥(pkcs8)
    *
    * @param crtFileName
    *            crt文件
    * @return 公钥
    */
   public static byte[] readPublicKeyFromCrt(String crtFileName) throws Exception {
       CertificateFactory cf = CertificateFactory.getInstance("X.509");
       X509Certificate cert = (X509Certificate) cf.generateCertificate(new FileInputStream(crtFileName));

PublicKey publicKey = cert.getPublicKey();
       return publicKey.getEncoded();
   }

/**
    * 从pfx文件读取秘钥对(pkcs8)
    *
    * @param pfxFileName
    *            pfx文件
    * @return 秘钥对
    */
   public static Object[] readFromPfx(String pfxFileName, String password) throws Exception {
       KeyStore keystore = KeyStore.getInstance("PKCS12");
       char[] passwordChars = null;
       if (password == null || password.equals("")) {
           passwordChars = null;
       } else {
           passwordChars = password.toCharArray();
       }
       keystore.load(new FileInputStream(pfxFileName), passwordChars);
       Enumeration<String> enums = keystore.aliases();

PrivateKey privateKey = null;
       Certificate certificate = null;
       while (enums.hasMoreElements()) {
           String alias = enums.nextElement();
           System.out.println(alias);
           if (keystore.isKeyEntry(alias)) {
               privateKey = (PrivateKey) keystore.getKey(alias, passwordChars);
               certificate = keystore.getCertificate(alias);
           }
           if (privateKey != null && certificate != null)
               break;
       }
       if (privateKey == null || certificate == null) {
           throw new Exception("fail to read key from pfx");
       }

PublicKey publicKey = certificate.getPublicKey();
       return new Object[] { publicKey.getEncoded(), privateKey.getEncoded() };
   }

/**
    * Pkcs8转Pkcs1
    *
    * @param isPrivateKey
    *            是否是私钥转换
    * @param buffer
    *            Pkcs1秘钥
    * @return Pkcs8秘钥
    * @throws Exception
    *             加密过程中的异常信息
    */
   public static byte[] pkcs8ToPkcs1(boolean isPrivateKey, byte[] buffer) throws Exception {
       if (isPrivateKey) {
           PrivateKeyInfo privateKeyInfo = PrivateKeyInfo.getInstance(buffer);
           return privateKeyInfo.parsePrivateKey().toASN1Primitive().getEncoded();
       } else {
           SubjectPublicKeyInfo subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(buffer);
           return subjectPublicKeyInfo.parsePublicKey().toASN1Primitive().getEncoded();
       }
   }

/**
    * Pkcs1转Pkcs8
    *
    * @param isPrivateKey
    *            是否是私钥转换
    * @param buffer
    *            Pkcs1秘钥
    * @return Pkcs8秘钥
    * @throws Exception
    *             加密过程中的异常信息
    */
   public static byte[] pkcs1ToPkcs8(boolean isPrivateKey, byte[] buffer) throws Exception {
       AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption);
       ASN1Primitive asn1Primitive = ASN1Primitive.fromByteArray(buffer);
       if (isPrivateKey) {
           PrivateKeyInfo privateKeyInfo = new PrivateKeyInfo(algorithmIdentifier, asn1Primitive);
           return privateKeyInfo.getEncoded();
       } else {
           SubjectPublicKeyInfo subjectPublicKeyInfo = new SubjectPublicKeyInfo(algorithmIdentifier, asn1Primitive);
           return subjectPublicKeyInfo.getEncoded();
       }
   }

/**
    * RSA公钥
    *
    * @param usePKCS8
    *            是否采用PKCS8填充模式
    * @param publicKey
    *            公钥
    * @return 公钥
    * @throws Exception
    *             加密过程中的异常信息
    */
   public static RSAPublicKey generatePublicKey(boolean usePKCS8, byte[] publicKey) throws Exception {
       KeySpec keySpec;
       if (usePKCS8) {
           // PKCS8填充
           keySpec = new X509EncodedKeySpec(publicKey);
       } else {
           // PKCS1填充
           DLSequence sequence = (DLSequence) ASN1Primitive.fromByteArray(publicKey);
           BigInteger v1 = ((ASN1Integer) sequence.getObjectAt(0)).getValue();
           BigInteger v2 = ((ASN1Integer) sequence.getObjectAt(1)).getValue();
           keySpec = new RSAPublicKeySpec(v1, v2);
       }

RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME).generatePublic(keySpec);
       return pubKey;
   }

/**
    * RSA私钥
    *
    * @param usePKCS8
    *            是否采用PKCS8填充模式
    * @param privateKey
    *            私钥
    * @return 私钥
    * @throws Exception
    *             解密过程中的异常信息
    */
   public static RSAPrivateKey generatePrivateKey(boolean usePKCS8, byte[] privateKey) throws Exception {
       KeySpec keySpec;
       if (usePKCS8) {
           // PKCS8填充
           keySpec = new PKCS8EncodedKeySpec(privateKey);
       } else {
           // PKCS1填充
           DLSequence sequence = (DLSequence) ASN1Primitive.fromByteArray(privateKey);
           // BigInteger v1= ((ASN1Integer)sequence.getObjectAt(0)).getValue();
           BigInteger v2 = ((ASN1Integer) sequence.getObjectAt(1)).getValue();
           BigInteger v3 = ((ASN1Integer) sequence.getObjectAt(2)).getValue();
           BigInteger v4 = ((ASN1Integer) sequence.getObjectAt(3)).getValue();
           BigInteger v5 = ((ASN1Integer) sequence.getObjectAt(4)).getValue();
           BigInteger v6 = ((ASN1Integer) sequence.getObjectAt(5)).getValue();
           BigInteger v7 = ((ASN1Integer) sequence.getObjectAt(6)).getValue();
           BigInteger v8 = ((ASN1Integer) sequence.getObjectAt(7)).getValue();
           BigInteger v9 = ((ASN1Integer) sequence.getObjectAt(8)).getValue();
           keySpec = new RSAPrivateCrtKeySpec(v2, v3, v4, v5, v6, v7, v8, v9);
       }

RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME).generatePrivate(keySpec);
       return priKey;
   }

/**
    * RSA公钥加密
    *
    * @param value
    *            加密字符串
    * @param publicKey
    *            公钥
    * @return 密文
    * @throws Exception
    *             加密过程中的异常信息
    */
   public static String rsaEncrypt(String value, RSAPublicKey publicKey) throws Exception {
       if (value == null || value.length() == 0)
           return "";

// RSA加密
       Cipher cipher = Cipher.getInstance("RSA");
       cipher.init(Cipher.ENCRYPT_MODE, publicKey);
       byte[] buffer = cipher.doFinal(value.getBytes("utf-8"));

// 使用hex格式输出公钥
       StringBuffer result = new StringBuffer();
       for (int i = 0; i < buffer.length; i++) {
           result.append(String.format("%02x", buffer[i]));
       }
       return result.toString();
   }

/**
    * RSA私钥解密
    *
    * @param value
    *            加密字符串
    * @param privateKey
    *            私钥
    * @return 明文
    * @throws Exception
    *             解密过程中的异常信息
    */
   public static String rsaDecrypt(String value, RSAPrivateKey privateKey) throws Exception {
       if (value == null || value.length() == 0)
           return "";

byte[] buffer = new byte[value.length() / 2];
       for (int i = 0; i < buffer.length; i++) {
           buffer[i] = (byte) Integer.parseInt(value.substring(i * 2, i * 2 + 2), 16);
       }

// RSA解密
       Cipher cipher = Cipher.getInstance("RSA");
       cipher.init(Cipher.DECRYPT_MODE, privateKey);
       buffer = cipher.doFinal(buffer);
       return new String(buffer, "utf-8");
   }

/**
    * RSA签名
    *
    * @param value
    *            加密字符串
    * @param privateKey
    *            私钥
    * @param halg
    *            加密算法,如MD5, SHA1, SHA256, SHA384, SHA512等
    * @return 签名
    * @throws Exception
    *             签名过程中的异常信息
    */
   public static String sign(String value, RSAPrivateKey privateKey, String halg) throws Exception {

Signature s = Signature.getInstance(halg.toUpperCase().endsWith("WithRSA") ? halg : (halg + "WithRSA"));

s.initSign(privateKey);
       s.update(value.getBytes("utf-8"));

byte[] buffer = s.sign();

// 使用hex格式输出公钥
       StringBuffer result = new StringBuffer();
       for (int i = 0; i < buffer.length; i++) {
           result.append(String.format("%02x", buffer[i]));
       }
       return result.toString();
   }

/**
    * RSA签名验证
    *
    * @param value
    *            加密字符串
    * @param publicKey
    *            公钥
    * @param halg
    *            加密算法,如MD5, SHA1, SHA256, SHA384, SHA512等
    * @return 签名合法则返回true,否则返回false
    * @throws Exception
    *             验证过程中的异常信息
    */
   public static boolean verify(String value, RSAPublicKey publicKey, String signature, String halg) throws Exception {
       Signature s = Signature.getInstance(halg.toUpperCase().endsWith("WithRSA") ? halg : (halg + "WithRSA"));
       s.initVerify(publicKey);
       s.update(value.getBytes("utf-8"));

byte[] buffer = new byte[signature.length() / 2];
       for (int i = 0; i < buffer.length; i++) {
           buffer[i] = (byte) Integer.parseInt(signature.substring(i * 2, i * 2 + 2), 16);
       }

return s.verify(buffer);
   }

}

生成公钥和私钥:


   // 生成公私钥
   Object[] rsaKey = RsaUtil.generateRsaKey(usePKCS8); //usePKCS8=true表示是否成PKCS8格式的公私秘钥,否则乘车PKCS1格式的公私秘钥
   byte[] publicKey = (byte[]) rsaKey[0];
   byte[] privateKey = (byte[]) rsaKey[1];

生成秘钥后,需要保存,一般保存到pem文件中:


   // 保存到pem文件,filePath是保存目录
   RsaUtil.writeToPem(publicKey, false, filePath + "rsa.pub");
   RsaUtil.writeToPem(privateKey, true, filePath + "rsa.pem");

可以保存到pem文件中,当然也可以从pem文件中读取了:


   // 从Pem文件读取公私钥,filePath是文件目录
   byte[] publicKey = RsaUtil.readFromPem(filePath + "rsa.pub");
   byte[] privateKey = RsaUtil.readFromPem(filePath + "rsa.pem");

还可以从crt证书中读取公钥,而crt文件不包含私钥,因此需要单独获取私钥:


   // 从crt文件读取公钥(crt文件中不包含私钥),filePath是文件目录
   byte[] publicKey = RsaUtil.readPublicKeyFromCrt(filePath + "demo.crt");
   byte[] privateKey = RsaUtil.readFromPem(filePath + "demo.key");

pfx文件中包含了公钥和私钥,可以很方便就读取到:


   // 从pfx文件读取公私钥,filePath是文件目录
   Object[] rsaKey = RsaUtil.readFromPfx(filePath + "demo.pfx", "123456");
   byte[] publicKey = (byte[]) rsaKey[0];
   byte[] privateKey = (byte[]) rsaKey[1];

有时候我们还可能需要进行秘钥的转换:


   // Pkcs8格式公钥转换为Pkcs1格式公钥
   publicKey = RsaUtil.pkcs8ToPkcs1(false, publicKey);
   // Pkcs8格式私钥转换为Pkcs1格式私钥
   privateKey = RsaUtil.pkcs8ToPkcs1(true, privateKey);
   // Pkcs1格式公钥转换为Pkcs8格式公钥
   publicKey = RsaUtil.pkcs1ToPkcs8(false, publicKey);
   // Pkcs1格式私钥转换为Pkcs8格式私钥
   privateKey = RsaUtil.pkcs1ToPkcs8(true, privateKey);

有了公钥和私钥,接下就就能实现加密、解密、签名、验证签名等操作了:


   RSAPublicKey rsaPublicKey = RsaUtil.generatePublicKey(usePKCS8, publicKey);
   RSAPrivateKey rsaPrivateKey = RsaUtil.generatePrivateKey(usePKCS8, privateKey);

String encryptText = RsaUtil.rsaEncrypt(text, rsaPublicKey);
   System.out.printf("【%s】经过【RSA】加密后:%s\n", text, encryptText);

String decryptText = RsaUtil.rsaDecrypt(encryptText, rsaPrivateKey);
   System.out.printf("【%s】经过【RSA】解密后:%s\n", encryptText, decryptText);

String signature = RsaUtil.sign(text, rsaPrivateKey, "MD5");
   System.out.printf("【%s】经过【RSA】签名后:%s\n", text, signature);

boolean result = RsaUtil.verify(text, rsaPublicKey, signature, "MD5");
   System.out.printf("【%s】的签名【%s】经过【RSA】验证后结果是:" + result, text, signature);

这里完整的demo代码:


   import java.security.interfaces.RSAPrivateKey;
   import java.security.interfaces.RSAPublicKey;

public class RsaMain {

public static void main(String[] args) {
           try {
               String text = "上山打老虎";
               boolean usePKCS8 = true; // usePKCS8=true表示是否成PKCS8格式的公私秘钥,否则乘车PKCS1格式的公私秘钥
               String filePath = RsaUtil.class.getClassLoader().getResource("").getPath();
               System.out.printf("文件路径:%s\n", filePath);// 存放pem,crt,pfx等文件的目录
               byte[] publicKey, privateKey;// 公钥和私钥

// 生成公私钥
               Object[] rsaKey = RsaUtil.generateRsaKey(usePKCS8); // usePKCS8=true表示是否成PKCS8格式的公私秘钥,否则乘车PKCS1格式的公私秘钥
               publicKey = (byte[]) rsaKey[0];
               privateKey = (byte[]) rsaKey[1];
               // 从Pem文件读取公私钥,filePath是文件目录
               // publicKey = RsaUtil.readFromPem(filePath + "rsa.pub");
               // privateKey = RsaUtil.readFromPem(filePath + "rsa.pem");
               // 从pfx文件读取公私钥,filePath是文件目录
               // Object[] rsaKey = RsaUtil.readFromPfx(filePath + "demo.pfx",
               // "123456");
               // publicKey = (byte[]) rsaKey[0];
               // privateKey = (byte[]) rsaKey[1];
               // 从crt文件读取公钥(crt文件中不包含私钥),filePath是文件目录
               // publicKey = RsaUtil.readPublicKeyFromCrt(filePath + "demo.crt");
               // privateKey = RsaUtil.readFromPem(filePath + "demo.key");

// 保存到pem文件,filePath是保存目录
               RsaUtil.writeToPem(publicKey, false, filePath + "rsa.pub");
               RsaUtil.writeToPem(privateKey, true, filePath + "rsa.pem");

// Pkcs8格式公钥转换为Pkcs1格式公钥
               publicKey = RsaUtil.pkcs8ToPkcs1(false, publicKey);
               // Pkcs8格式私钥转换为Pkcs1格式私钥
               privateKey = RsaUtil.pkcs8ToPkcs1(true, privateKey);
               // Pkcs1格式公钥转换为Pkcs8格式公钥
               publicKey = RsaUtil.pkcs1ToPkcs8(false, publicKey);
               // Pkcs1格式私钥转换为Pkcs8格式私钥
               privateKey = RsaUtil.pkcs1ToPkcs8(true, privateKey);

RSAPublicKey rsaPublicKey = RsaUtil.generatePublicKey(usePKCS8, publicKey);
               RSAPrivateKey rsaPrivateKey = RsaUtil.generatePrivateKey(usePKCS8, privateKey);

String encryptText = RsaUtil.rsaEncrypt(text, rsaPublicKey);
               System.out.printf("【%s】经过【RSA】加密后:%s\n", text, encryptText);

String decryptText = RsaUtil.rsaDecrypt(encryptText, rsaPrivateKey);
               System.out.printf("【%s】经过【RSA】解密后:%s\n", encryptText, decryptText);

String signature = RsaUtil.sign(text, rsaPrivateKey, "MD5");
               System.out.printf("【%s】经过【RSA】签名后:%s\n", text, signature);

boolean result = RsaUtil.verify(text, rsaPublicKey, signature, "MD5");
               System.out.printf("【%s】的签名【%s】经过【RSA】验证后结果是:" + result, text, signature);

} catch (Exception e) {
               // TODO Auto-generated catch block
               e.printStackTrace();
           }
       }
   }

来源:https://www.cnblogs.com/shanfeng1000/p/14840051.html

标签:java,rsa,非对称加密
0
投稿

猜你喜欢

  • java三个环境变量配置简单教程

    2023-11-28 20:45:30
  • JAVA得到数组中最大值和最小值的简单实例

    2023-03-18 01:19:39
  • SpringCloud Zuul自定义filter代码实例

    2023-12-03 02:49:50
  • java递归实现拼装多个api的结果操作方法

    2023-11-24 23:44:35
  • Java中checkbox实现跨页多选的方法

    2023-10-14 11:01:20
  • Unity实现3D循环滚动效果

    2023-04-22 14:05:47
  • 浅谈Java三大框架与应用

    2023-04-16 18:25:01
  • java 创建线程的四种方式

    2023-11-02 21:38:07
  • 解决SpringBoot中使用@Async注解失效的问题

    2023-08-24 07:38:46
  • Java数组传递及可变参数操作实例详解

    2023-08-26 21:21:59
  • Java设计模式之命令模式(Command模式)介绍

    2021-12-02 01:01:02
  • 基于ChatGPT+SpringBoot实现智能聊天AI机器人接口并上线至服务器的方法

    2023-07-01 06:19:34
  • Eclipse+Java+Swing+Mysql实现工资管理系统

    2023-07-26 08:51:05
  • java开发之闹钟的实现代码

    2021-07-08 12:46:49
  • 浅谈三分钟学习Java泛型中T、E、K、V、?的含义

    2022-09-01 20:12:38
  • 动态配置Spring Boot日志级别的全步骤

    2023-01-29 01:57:19
  • Java 关键字 速查表介绍

    2022-07-03 22:04:48
  • Java String源码分析并介绍Sting 为什么不可变

    2021-09-23 06:10:42
  • java POI解析Excel 之数据转换公用方法(推荐)

    2023-06-10 11:04:53
  • IDEA基于支付宝小程序搭建springboot项目的详细步骤

    2021-10-30 22:44:46
  • asp之家 软件编程 m.aspxhome.com