RSA 除了用于数据加密之外,还可以用于数据签名。数据签名为了防止信息被篡改。

RSA 实现数据签名主要是使用 SHA256withRSA。注意,不是 RSA256 then RSA!这是两回事。

关于 SHA256withRSA 和 SHA256 then RSA 的区别在 Stackoverflow 上也有相应的回答:

Stackoverflow:Difference between SHA256withRSA and SHA256 then RSA

下面就来看下 Java 如何使用 RSA 实现数据签名和验签。

在前面也介绍了如何生成 RSA 密匙对,但是使用时有些区别。

一种是使用 RSA 公钥和私钥字符串,另一种是读取 RSA 密匙对。这两种方式是有些区别的,下面就来分别说下:

RSA 密匙对字符串实现签名和验签

这里直接拿前面介绍的 openssl 生成 RSA 密匙对 进行说明,将生成的密匙对内容进行调整,最后得到的私钥和公钥如下:

PrivateKey:

  1. MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDQmTJAhcdYp+SjYaCyZBdnbubX2kvpTNIKTVmnFgHqN12LvOXSCHeNtDO9+R+XZyaBVQ+5UQTrw6XqVXypKY3Z0JMh1qkQUmQ0WAlNju8KUCfJXAy2whNK99ABQ2PrFl6vDsrMdOtrrcMpz3SoNKz82RYdNHbKKdNqkw+mZMwaAzXDCoFeUJMl1qHKTwMpX2MXEZYbL1k4P1WDYVsWHEd4GFfupd5h+pNlB9z9/sRIKnP8viw8xbS4oBVvNZ2gA3NfZjwstCGRneuns8nLwvfrqIUIgduvATG5PGFfmZx2m9JOvOKwzTzBzBpRvVVd7TyWpkGOBKbK6n+B+zSOIswLAgMBAAECggEALHcbgSmDLuDLGhCrgptXadL4HjcIS3j2pP+MBtPoIJcgt7LSgo0PfwNUrEA4fbudN6B8M5KTq/YOnf8PDbgv5qhRVLs/Wp1VgtrpUMERL2+aq4+VnjeMiUR98Gb/G5OvJz3N+PtLW660lWsFgP2JinR0BFG0qK91w5gJk/yUjU6TV6xeck9wGVvkHnBIoab4A2l42QaSxSM1Eyp6yYDPBKrDRRvS0dSiTQetVZ1gTwVikrVJWHMrk/Ls5+ukjKIPhIRdhsNrzjpCToEc7YzFp4GEa2VEb/hmqXZEf0oJCGZ0gavd3lNhj24kp6CqQmiNz95FgnHE8bi1wP5dOoxRoQKBgQDopzIFYNzfIyK5Mo/8Agf8XD1naPil3570q3+rOpcLDnzOHf9A4lm/ndecNWSaIhUha6czNmWlLlUIoJzFv9VpRVeopSkc6opgjo2kclJ8uoyCnV9dyP0Pd1HTQRtmjnHUl3v1YwYjt0tFkXvD+AwgxJg68MRnXeW3K/1XlBNW0wKBgQDliAqn68CM9rfpuQ27gmMiqfFQenFq5P8Kj/uGqcs6VUVWujN2XNuAXnF7us8NTEc15590y/IcPLuMvI4iA3ZrneOVx/TQ8ivCWjactMbOdJjgkufa21XsyaaR+bksqcIuiAFGP/ATFr/w1D4pXZ5BTkKOf8wwQxjzHKHeIgRi6QKBgQCYQBI0AteIDu5CVBx1xr6DH7nvWnqd0mGrrC+4VndR/QEfwfGw/G/PPfRDfY2AcJ1zaYfZs9eA6XksVC9EGe4HHiHnc24cRkCYP7Hh1A63IT2inGo0bbtty5/4p7rOupkzjo7IXy09Yk4YEMT10fXd28njiHx/SKtz243HKlgdkQKBgQCuWjZt86Ch808klNMfmh2f2SNbFIdOwYASD+jqE9QyDU/MX0h0InkB+7uMVwysd0KoabcwSzMvy9pTP29f2u17NcYIookOpsYirdBKHO/fJ6ZxAGZqUq3kXhDPVbgZeyHropgFOtAsT92hHDfTyC9MQBxCjkUWbAFpulgimghm8QKBgBG0wINsOVGuOSQlY4Djh8FkH4LvbG/Uubc3ozGEY1sR+iyRl1MRVYA0EHRmOpmpjsb4RHlqs1iGQnpC+zD/G7Kz++QEjZqe5XbOvqYuWjfuIiwwWxEg1NzMFLE8dlm/TTvNAf+QoApHNfF1249GtAmjjs5AF3khQXT3Zo4k4lFe

PublicKey:

  1. MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0JkyQIXHWKfko2GgsmQXZ27m19pL6UzSCk1ZpxYB6jddi7zl0gh3jbQzvfkfl2cmgVUPuVEE68Ol6lV8qSmN2dCTIdapEFJkNFgJTY7vClAnyVwMtsITSvfQAUNj6xZerw7KzHTra63DKc90qDSs/NkWHTR2yinTapMPpmTMGgM1wwqBXlCTJdahyk8DKV9jFxGWGy9ZOD9Vg2FbFhxHeBhX7qXeYfqTZQfc/f7ESCpz/L4sPMW0uKAVbzWdoANzX2Y8LLQhkZ3rp7PJy8L366iFCIHbrwExuTxhX5mcdpvSTrzisM08wcwaUb1VXe08lqZBjgSmyup/gfs0jiLMCwIDAQAB

[

](https://www.yuque.com/daoyou/java/vswczg#yuUru)
现在就用这对密匙对进行测试:

私钥签名方法

  1. /**
  2. * 私钥签名
  3. */
  4. public static String sha256withRSASignature(String privateKeyStr, String dataStr) {
  5. try {
  6. byte[] key = Base64.getDecoder().decode(privateKeyStr);
  7. byte[] data = dataStr.getBytes();
  8. PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(key);
  9. KeyFactory keyFactory = KeyFactory.getInstance("RSA");
  10. PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
  11. // 这个根据需求填充 SHA1WithRSA 或 SHA256WithRSA
  12. Signature signature = Signature.getInstance("SHA256withRSA");
  13. signature.initSign(privateKey);
  14. signature.update(data);
  15. return new String(Base64.getEncoder().encode(signature.sign()));
  16. } catch (Exception e) {
  17. // print log: xxx
  18. }
  19. }

1)privateKeyStr:就是私钥文本。
2)dataStr:要加密的内容。

公钥验签方法

  1. /**
  2. * RSA公钥验签
  3. */
  4. public static boolean rsaVerify(String dataStr, String publicKeyStr, String signStr) throws Exception {
  5. KeyFactory keyFactory = KeyFactory.getInstance("RSA");
  6. X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKeyStr));
  7. PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
  8. // 这个根据需求填充 SHA1WithRSA 或 SHA256WithRSA
  9. Signature signature = Signature.getInstance("SHA256withRSA");
  10. signature.initVerify(publicKey);
  11. signature.update(dataStr.getBytes());
  12. return signature.verify(Base64.getDecoder().decode(signStr));
  13. }

1)dataStr:要验证的文本,与生成签名时的文本是同一个。
2)publicKeyStr:公钥文本。
3)signStr:有 私钥签名 方法生成的签名

签名测试

  1. public static void main(String[] args) throws Exception {
  2. // 上节生成的密匙对
  3. String privateKey = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDQmTJAhcdYp+SjYaCyZBdnbubX2kvpTNIKTVmnFgHqN12LvOXSCHeNtDO9+R+XZyaBVQ+5UQTrw6XqVXypKY3Z0JMh1qkQUmQ0WAlNju8KUCfJXAy2whNK99ABQ2PrFl6vDsrMdOtrrcMpz3SoNKz82RYdNHbKKdNqkw+mZMwaAzXDCoFeUJMl1qHKTwMpX2MXEZYbL1k4P1WDYVsWHEd4GFfupd5h+pNlB9z9/sRIKnP8viw8xbS4oBVvNZ2gA3NfZjwstCGRneuns8nLwvfrqIUIgduvATG5PGFfmZx2m9JOvOKwzTzBzBpRvVVd7TyWpkGOBKbK6n+B+zSOIswLAgMBAAECggEALHcbgSmDLuDLGhCrgptXadL4HjcIS3j2pP+MBtPoIJcgt7LSgo0PfwNUrEA4fbudN6B8M5KTq/YOnf8PDbgv5qhRVLs/Wp1VgtrpUMERL2+aq4+VnjeMiUR98Gb/G5OvJz3N+PtLW660lWsFgP2JinR0BFG0qK91w5gJk/yUjU6TV6xeck9wGVvkHnBIoab4A2l42QaSxSM1Eyp6yYDPBKrDRRvS0dSiTQetVZ1gTwVikrVJWHMrk/Ls5+ukjKIPhIRdhsNrzjpCToEc7YzFp4GEa2VEb/hmqXZEf0oJCGZ0gavd3lNhj24kp6CqQmiNz95FgnHE8bi1wP5dOoxRoQKBgQDopzIFYNzfIyK5Mo/8Agf8XD1naPil3570q3+rOpcLDnzOHf9A4lm/ndecNWSaIhUha6czNmWlLlUIoJzFv9VpRVeopSkc6opgjo2kclJ8uoyCnV9dyP0Pd1HTQRtmjnHUl3v1YwYjt0tFkXvD+AwgxJg68MRnXeW3K/1XlBNW0wKBgQDliAqn68CM9rfpuQ27gmMiqfFQenFq5P8Kj/uGqcs6VUVWujN2XNuAXnF7us8NTEc15590y/IcPLuMvI4iA3ZrneOVx/TQ8ivCWjactMbOdJjgkufa21XsyaaR+bksqcIuiAFGP/ATFr/w1D4pXZ5BTkKOf8wwQxjzHKHeIgRi6QKBgQCYQBI0AteIDu5CVBx1xr6DH7nvWnqd0mGrrC+4VndR/QEfwfGw/G/PPfRDfY2AcJ1zaYfZs9eA6XksVC9EGe4HHiHnc24cRkCYP7Hh1A63IT2inGo0bbtty5/4p7rOupkzjo7IXy09Yk4YEMT10fXd28njiHx/SKtz243HKlgdkQKBgQCuWjZt86Ch808klNMfmh2f2SNbFIdOwYASD+jqE9QyDU/MX0h0InkB+7uMVwysd0KoabcwSzMvy9pTP29f2u17NcYIookOpsYirdBKHO/fJ6ZxAGZqUq3kXhDPVbgZeyHropgFOtAsT92hHDfTyC9MQBxCjkUWbAFpulgimghm8QKBgBG0wINsOVGuOSQlY4Djh8FkH4LvbG/Uubc3ozGEY1sR+iyRl1MRVYA0EHRmOpmpjsb4RHlqs1iGQnpC+zD/G7Kz++QEjZqe5XbOvqYuWjfuIiwwWxEg1NzMFLE8dlm/TTvNAf+QoApHNfF1249GtAmjjs5AF3khQXT3Zo4k4lFe";
  4. String publicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0JkyQIXHWKfko2GgsmQXZ27m19pL6UzSCk1ZpxYB6jddi7zl0gh3jbQzvfkfl2cmgVUPuVEE68Ol6lV8qSmN2dCTIdapEFJkNFgJTY7vClAnyVwMtsITSvfQAUNj6xZerw7KzHTra63DKc90qDSs/NkWHTR2yinTapMPpmTMGgM1wwqBXlCTJdahyk8DKV9jFxGWGy9ZOD9Vg2FbFhxHeBhX7qXeYfqTZQfc/f7ESCpz/L4sPMW0uKAVbzWdoANzX2Y8LLQhkZ3rp7PJy8L366iFCIHbrwExuTxhX5mcdpvSTrzisM08wcwaUb1VXe08lqZBjgSmyup/gfs0jiLMCwIDAQAB";
  5. String content = "I love Y";
  6. // 签名
  7. String signature = sha256withRSASignature(privateKey, content);
  8. // 如果验签通过输出 true
  9. System.out.println(rsaVerify(content, publicKey, signature));
  10. }

最后输出如下:

  1. V9MvnKaLvRJmjZLfjOWXkgcP1io5efIaVrYSDiyAs+sSJIrd4ry3auH2laZ5nZaA8ur6yx7ZDDVCYrd34WHHrQcx/gQuUlSM4NbkDs1kgDY0O8r2mzi2mY43uXLodSqJBBLh9dxtJjCHOxDUDkxewd0elBiHpgnQ53fgOInQjV8oXOzRgzAaL2wUfIKuV7MHmT+aaOoRuMeEJ4847rX/3a4fljnPdzuLITGSY7Kd3LiKxoQU+YwaWptZH4KffCjheTkjoQMjULXu2cD8B/vWmbdh5C49kZ9NfAxNreEwFdOnHDz52eFIdsiXBwxMmv9Wpu5SpxGatVcjm7Jp33lD1Q==
  2. true

说明签名和验签一切正常~

读取RSA密匙对文件实现签名和验签

前面是直接使用的 RSA 密匙对文本,但是在实际开发中肯定不会是文本。而是一个具体的密匙对文件!

还拿我们之前生成的私钥文件文件为例:

  1. $ ls
  2. id_rsa.pem

注意:是私钥文件,不是私钥 PKCS8 文件!

我们需要将私钥 PEM 文件转换为 DER 文件,命令如下:

  1. openssl pkcs8 -topk8 -inform PEM -outform DER -in id_rsa.pem -out id_rsa.der -nocrypt

注意上面的命令,id_rsa.pem 是私钥文件,id_rsa.der 是要生成的 der 文件。

命令运行后就会生成一个 id_rsa.der 文件,接着生成响应的公钥 der 文件:

  1. openssl rsa -in id_rsa.pem -pubout -outform DER -out id_rsa_pub.der


之后将这两个文件(id_rsa.der 和 id_rsa_pub.der)放到项目工程的 resources 目录下。并修改之前的私钥签名方法和公钥验签方法:

私钥签名方法

仅仅是修改私钥文件的读取方法,修改后的代码如下:

  1. /**
  2. * 私钥签名
  3. */
  4. public static String sha256withRSASignature(String dataStr) {
  5. try {
  6. byte[] data = dataStr.getBytes();
  7. // 私钥文件名 id_rsa.der
  8. PrivateKey privateKey = readPrivateKey("id_rsa.der");
  9. // 这个根据需求填充 SHA1WithRSA 或 SHA256WithRSA
  10. Signature signature = Signature.getInstance("SHA256withRSA");
  11. signature.initSign(privateKey);
  12. signature.update(data);
  13. return new String(Base64.getEncoder().encode(signature.sign()));
  14. } catch (Exception e) {
  15. e.printStackTrace();
  16. }
  17. return null;
  18. }
  19. /**
  20. * 读取 classpath 下的 RSA 私钥文件
  21. *
  22. * @param priDer 私钥 der 文件
  23. */
  24. public static PrivateKey readPrivateKey(String priDer) throws Exception {
  25. // 加载 Resources 目录下的资源(即classpath下)
  26. URL resource = Thread.currentThread().getContextClassLoader().getResource(priDer);
  27. if (resource == null) {
  28. // 私钥文件不存在
  29. }
  30. byte[] keyBytes = Files.readAllBytes(Paths.get(resource.toURI()));
  31. PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
  32. KeyFactory kf = KeyFactory.getInstance("RSA");
  33. return kf.generatePrivate(spec);
  34. }

公钥签名方法

  1. /**
  2. * RSA公钥验签
  3. */
  4. public static boolean rsaVerify(String dataStr, String signStr) throws Exception {
  5. // 公钥文件名 id_rsa_pub.der
  6. PublicKey publicKey = readPublicKey("id_rsa_pub.der");
  7. // 这个根据需求填充 SHA1WithRSA 或 SHA256WithRSA
  8. Signature signature = Signature.getInstance("SHA256withRSA");
  9. signature.initVerify(publicKey);
  10. signature.update(dataStr.getBytes());
  11. return signature.verify(Base64.getDecoder().decode(signStr));
  12. }
  13. /**
  14. * 读取 classpath 下的 RSA 公钥文件
  15. *
  16. * @param pubDer 公钥 der 文件
  17. */
  18. public static PublicKey readPublicKey(String pubDer) throws Exception {
  19. URL resource = Thread.currentThread().getContextClassLoader().getResource(pubDer);
  20. if (resource == null) {
  21. // 公钥文件不存在
  22. }
  23. byte[] keyBytes = Files.readAllBytes(Paths.get(resource.toURI()));
  24. X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
  25. KeyFactory kf = KeyFactory.getInstance("RSA");
  26. return kf.generatePublic(spec);
  27. }

最后做下签名测试:

  1. public static void main(String[] args) throws Exception {
  2. String content = "I love Y";
  3. String signature = sha256withRSASignature(content);
  4. System.out.println(signature);
  5. System.out.println(rsaVerify(content, signature));
  6. }

输出结果如下:

  1. V9MvnKaLvRJmjZLfjOWXkgcP1io5efIaVrYSDiyAs+sSJIrd4ry3auH2laZ5nZaA8ur6yx7ZDDVCYrd34WHHrQcx/gQuUlSM4NbkDs1kgDY0O8r2mzi2mY43uXLodSqJBBLh9dxtJjCHOxDUDkxewd0elBiHpgnQ53fgOInQjV8oXOzRgzAaL2wUfIKuV7MHmT+aaOoRuMeEJ4847rX/3a4fljnPdzuLITGSY7Kd3LiKxoQU+YwaWptZH4KffCjheTkjoQMjULXu2cD8B/vWmbdh5C49kZ9NfAxNreEwFdOnHDz52eFIdsiXBwxMmv9Wpu5SpxGatVcjm7Jp33lD1Q==
  2. true

SHA256withRSA证书签名,私钥签名/公钥验签:https://www.shuzhiduo.com/A/ZOJPrPaEdv/

完结,撒花~