返回列表

转载 java 和 ruby 中使用 rsa 签名和验证数据

默认分类 2013-01-06 15:50:18

原文地址 https://wido.me/sunteya/java-and-ruby-use-sign-or-verify-data-by-rsa/

最近在做项目的支付接口, 需要对传输数据做签名以确保数据安全, 本以为只要有私钥和公钥签名应该挺简单的, 调2个函数就能搞定的事情. 但做了才发现这还是挺复杂的。

本以为收到的会是像 ssh 的 pem 证书一样的 公钥和私钥

-----BEGIN RSA PUBLIC KEY-----
MIGJAoGBAJ7vDNqDsv9RyD7CN0vXwLZBKvYWVHG9oirrmSldRUmCOWik88Me8anr
v4tXI2C1NFbnk6C1o6cM1kkeqEQSXQ3DSdHOOPmoTvHNwGR+C2FJHuuLR8YPraIr
oi8DpQzJl3qVdq0m0XmflDboGd+Cijb6z+oVrWtZ9KKssiI2glhLAgMBAAE=
-----END RSA PUBLIC KEY-----

但实际收到的却是

308189028181009EEF0CDA83B2FF51C83EC2374BD7C0B6412AF6165471BDA22AEB99295D4549823968A4F3C31EF
1A9EBBF8B572360B53456E793A0B5A3A70CD6491EA844125D0DC349D1CE38F9A84EF1CDC0647E0B61491EEB8B47
C60FADA22BA22F03A50CC9977A9576AD26D1799F9436E819DF828A36FACFEA15AD6B59F4A2ACB2223682584B020
3010001

ruby 完全无法读取这个公钥, 完全不知道这个串是咋生成的, 要来对方源代码才发现这个串其实是 java 中生成的公钥的16进制的显示内容. 而 java 默认生成的公钥和私钥都是 der 的二进制格式的. 所以首先是需要把收到的东西还原成标准的 der 证书. ruby 可以很简单的通 过一行代码转换成标准的 der 证书文件.

File.open("rsa_pub.der", "w") {|f| f << [ File.read("rsa_pub.key") ].pack("H*") }

这样我们得到 java 标准的 der 证书, 这时通过 ruby 自带的 openssl 库已可以读取公钥文件, 但是却始终无法读取私钥的der文件. 所以还是需要把证书转到到常用的 pem 格式供 ruby 读取. 方便的是 openssl 库已经提供了转换命令了.

转换 der 到 pem

我们可以通过 openssl 命令

openssl pkcs8 -nocrypt -inform der -in rsa_pri.der -outform pem -out rsa_pri.pem

将私钥转换成 pem 格式, 还可以通过

openssl rsa -pubin -inform der -in rsa_pub.der -outform pem -out rsa_pub.pem

将公钥也转换成 pem 格式.

测试代码

至此我们就得到了 ruby 常用的 pem 格式的密钥了. 接下来写段程序测试下这 2个 pem 是不是和 java 读取的一样.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import java.io.*;
import java.security.*;
import java.security.spec.*;

public class KeyTest {
    public static void main(String[] args) throws Exception {

     // use private key sign "abc" string
        byte[] priBytes = readBytes(new File("rsa_pri.der"));     
        KeyFactory factory = KeyFactory.getInstance("RSA");
     PrivateKey privateKey = factory.generatePrivate(new PKCS8EncodedKeySpec(priBytes));
        Signature signer = Signature.getInstance("SHA1WithRSA");
        signer.initSign(privateKey);
     signer.update("abc".getBytes("utf-8"));
     byte[] sign = signer.sign();
  
        System.out.print("sign is : ");
     for (byte b : sign) { System.out.print(String.format("%02x", b & 0xFF));  }
        System.out.println();
   
        // use public verify "abc" string
       byte[] pubBytes = readBytes(new File("rsa_pub.der"));
     PublicKey publicKey = factory.generatePublic(new X509EncodedKeySpec(pubBytes));
        Signature verifier = Signature.getInstance("SHA1WithRSA");
      verifier.initVerify(publicKey);
      verifier.update("abc".getBytes("utf-8"));
       boolean result = verifier.verify(sign);
        if (result) {
            System.out.println("verify successful!");
     } else {
           throw new RuntimeException("verify failed!");
        }
    }

    private static byte[] readBytes(File f) throws Exception {
     FileInputStream fis = new FileInputStream(f);
       byte[] bytes = new byte[(int) f.length()];
       fis.read(bytes);
     fis.close();
       return bytes;
  }
}

运行后我们可以看到输出了

sign is : 0f323518c49577bee82cbf70dd984d2601666d3939fe9efb89142227d8baf0b07751a47cceab6cd14e6feea92af4f8b20fe8bde27ddf0dc02248fb067f82b322af13f5b7e13a0e0680b3c57d989dfa0446350b52a027d8361179556d80c41e69d2f69224c99d0c6f90b158bbde37de3e1f8da91c2e8eef184f0175a58624da12
verify successful!

我们再用 ruby 实现同样逻辑的代码

1
2
3
4
5
6
7
8
9
require "openssl"

pri = OpenSSL::PKey::RSA.new(File.read("rsa_pri.pem"))
sign = pri.sign("sha1", "abc".force_encoding("utf-8"))
puts "sign is : #{sign.unpack('H*').first}"

pub = OpenSSL::PKey::RSA.new(File.read("rsa_pub.pem"))
result = pub.verify("sha1", sign, "abc".force_encoding("utf-8"))
puts "verify #{result ? 'successful!' : 'failed!'}"

运行后可以看到同样打印出了

sign is : 0f323518c49577bee82cbf70dd984d2601666d3939fe9efb89142227d8baf0b07751a47cceab6cd14e6feea92af4f8b20fe8bde27ddf0dc02248fb067f82b322af13f5b7e13a0e0680b3c57d989dfa0446350b52a027d8361179556d80c41e69d2f69224c99d0c6f90b158bbde37de3e1f8da91c2e8eef184f0175a58624da12
verify successful!

转换 pem 到 der

当然有时候我们还需要将 ruby 中生成的 pem 转换成 java 可以读取的 der 格式, 这个同样可以使用 openssl 命令

openssl pkcs8 -topk8 -nocrypt -inform pem -in rsa_pri.pem -outform der -out rsa_pri.der

这样就可以生成 java 可以读取的私钥 der 文件, 对于公钥我们既可以通过公钥的 pem 生成

openssl rsa -pubin -inform pem -in rsa_pub.pem -outform der -out rsa_pub.der

也可以直接通过私钥生成对应的公钥 der 文件

openssl rsa -inform pem -in rsa_pri.pem -pubout -outform der -out rsa_pub.der

写在最后

之前一直认为不会有多少困难的签名和验证, 也花了大半天才搞明白 java 和 ruby 之间的不同. 另外也严重鄙视下那家支付公司的愚蠢文档和一问三不知的技术人员.

代码里测试的证书可以到 这里下载

顺便说一句, 这例子也充分体现出, 不同语言之间代码行数的巨大差别. 从语法上来说 java 却是该被淘汰了.

参考文章