RSA签名与验签

之前看过RSA加密算法的一些介绍,对RSA加密的原理有一些了解。其实刚刚挺熟非对称加密时是觉得很神奇的。通常对称加密很好理解,比如原理是ANA,我们将每个字母后移一位,那么就是BOB了,这就是很简单的加密过程(上帝ana就变成了凡人bob了,只有dota玩家才知道的梗)。解密就将每个字母前移一位。如果把移动的位数看成密钥,那么密钥就是1。加密和解密密钥是一样的。

刚好最近项目中使了RSA签名,在此记录下。

项目使用

目前项目中是使用RSA的签名与验签功能。第三方发送文件给我们,同时会发送该文件的摘要(SHA256)信息,而摘要信息是使用私钥签名过的。我们将收到的文件做摘要(SHA256),然后使用公钥进行验签。如果签名验证通过才能认为文件是可信的。

问题

项目组的同事在开发好后想测试一下,但是发现第三方只提供了一个样例文件,而且文件内容还是不符合业务要求的。没办法验证整个流程。

其实解决方式很简单:替换公钥

生成密钥对

第三方提供的公钥是一个没有后缀名的文件,使用文本编辑器打开是乱码的,可以猜测是二进制文件。看了第三方提供的代码,是直接从文件中读取byte数据,然后构建一个PublicKey。

然后我尝试生成自己的密钥对,一开始我使用的是Xshell。因为SSH是支持RSA加密的,一般的SSH工具都支持生成RSA密钥。

Xshell

  1. 打开Xshell
  2. 点击“工具”菜单
  3. 点击“新建用户密钥生成向导”
  4. 密钥类型选“RSA”,密钥长度选1024(第三方给的密钥是1024位)
  5. 然后一路下一步,其中,会让输入“用户密钥加密的密码”,我因为是代码中使用,直接没输入。如果是作为SSH登陆的话还是需要输入一下,不然私钥被别人盗用,没有密码,别人就可以为所欲为了。
  6. 然后密钥就生成好了,提供和三种格式的公钥:“SSH1”、“SSH2-IETF SECSH”、“SSH2-OpenSSH”,除了SSH1,SSH2的都是一样的形式只是备注不一样

生成后发现没法使用,因为Xshell生成的密钥是字符串(Base64编码),类似下面的。

AAAAB3NzaC1yc2EAAAABIwAAAIEAnfJxPq3OLqfZVt+YNH6tkO7d5qx8etFU8g7adwFzTwuXOaDtm0qoQXMkiRLTt3p5S6ExWM9+NLQePhbSur0d8d6Y4sf3SNB/oIpAYIezkihFczuxHBi1RVNUdVwLdWyFW69eWBGkyaRyt7q0kzIPXMpRz/Gj+JTP45MaNOapW5U=

我通过代码将这个字符串Base64解码成byte数组。然后构建一个公钥,抛出异常。

Exception in thread "main" java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: invalid key format
	at sun.security.rsa.RSAKeyFactory.engineGeneratePublic(RSAKeyFactory.java:205)
	at java.security.KeyFactory.generatePublic(KeyFactory.java:334)
	at io.github.loanon.rsa.RSAStudy.main(RSAStudy.java:17)
Caused by: java.security.InvalidKeyException: invalid key format
	at sun.security.x509.X509Key.decode(X509Key.java:387)
	at sun.security.x509.X509Key.decode(X509Key.java:403)
	at sun.security.rsa.RSAPublicKeyImpl.<init>(RSAPublicKeyImpl.java:84)
	at sun.security.rsa.RSAKeyFactory.generatePublic(RSAKeyFactory.java:298)
	at sun.security.rsa.RSAKeyFactory.engineGeneratePublic(RSAKeyFactory.java:201)
	... 2 more

关键词“invalid key format”让我觉得是密钥格式不对。

我从网上找了其他RSA的教程,看到别人的密钥(Base64)字符串都是这样的:

MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCqPvovSfXcwBbW8cKMCgwqNpsYuzF8RPAPFb7LGsnVo44JhM/xxzDyzoYtdfNmtbIuKVi9PzIsyp6rg+09gbuI6UGwBZ5DWBDBMqv5MPdOF5dCQkB2Bbr5yPfURPENypUz+pBFBg41d+BC+rwRiXELwKy7Y9caD/MtJyHydj8OUwIDAQAB

首先从长度上就和我用Xshell生成的不一样。为了验证我的猜想。我将第三方提供的密钥二进制文件读出来,通过Base64编码后,打印出来,和网上别人教程里面的格式、长度一样(这里就不贴出来了)。那么就说明不能用Xshell来生成密钥对。个人认为是密钥格式不通,至于如何转换,还没弄明白。有知道的大神,还请指教一二。

Java

既然Xshell工具生成的密钥对不用用,那就自己用Java代码生成,网上也有教程。总结一下:

KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
keyPairGen.initialize(1024);
KeyPair keyPair = keyPairGen.generateKeyPair();
byte[] publicKey = keyPair.getPublic().getEncoded();
byte[] privateKey = keyPair.getPrivate().getEncoded();

这样就可以拿到密钥对的二进制数据了。

将公钥数据保存成二进制文件,放到项目中。现在就可以自己签名进行验证了。

密钥格式

找到一篇文章好像解释了为什么Xshell生成的密钥对无法使用。

因为Xshell生成的是OpenSSH格式的密钥,但是Java代码中使用的是OpenSSL格式的密钥。其实通过代码或者工具也是可以将两种格式的密钥进行转换的。