RSA簽名與驗簽
之前看過RSA加密算法的一些介紹,對RSA加密的原理有一些了解。其實剛剛挺熟非對稱加密時是覺得很神奇的。通常對稱加密很好理解,比如原理是ANA,我們將每個字母后移一位,那么就是BOB了,這就是很簡單的加密過程(上帝ana就變成了凡人bob了,只有dota玩家才知道的梗)。解密就將每個字母前移一位。如果把移動的位數看成密鑰,那么密鑰就是1。加密和解密密鑰是一樣的。
剛好最近項目中使了RSA簽名,在此記錄下。
項目使用
目前項目中是使用RSA的簽名與驗簽功能。第三方發送文件給我們,同時會發送該文件的摘要(SHA256)信息,而摘要信息是使用私鑰簽名過的。我們將收到的文件做摘要(SHA256),然后使用公鑰進行驗簽。如果簽名驗證通過才能認為文件是可信的。
問題
項目組的同事在開發好后想測試一下,但是發現第三方只提供了一個樣例文件,而且文件內容還是不符合業務要求的。沒辦法驗證整個流程。
其實解決方式很簡單:替換公鑰。
生成密鑰對
第三方提供的公鑰是一個沒有后綴名的文件,使用文本編輯器打開是亂碼的,可以猜測是二進制文件。看了第三方提供的代碼,是直接從文件中讀取byte數據,然后構建一個PublicKey。
然后我嘗試生成自己的密鑰對,一開始我使用的是Xshell。因為SSH是支持RSA加密的,一般的SSH工具都支持生成RSA密鑰。
Xshell
-
打開Xshell
-
點擊“工具”菜單
-
點擊“新建用戶密鑰生成向導”
-
密鑰類型選“RSA”,密鑰長度選1024(第三方給的密鑰是1024位)
-
然后一路下一步,其中,會讓輸入“用戶密鑰加密的密碼”,我因為是代碼中使用,直接沒輸入。如果是作為SSH登陸的話還是需要輸入一下,不然私鑰被別人盜用,沒有密碼,別人就可以為所欲為了。
-
然后密鑰就生成好了,提供和三種格式的公鑰:“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格式的密鑰。其實通過代碼或者工具也是可以將兩種格式的密鑰進行轉換的。