前言
與第三方對接最麻煩的是語言不同,因語言不同內建實現相關標準加密演算法還是略微有所差異,對接單點登入場景再尋常不過,由於時間緊迫且對接方使用Java,所以留給我對接開發和聯調的時間本就不多,於是乎,在熬夜發版後,繼而開始提前研究對接方所提供的加密方式大致處理
方案一(C#實現)
資料對接加密演算法採用RSA SHA1 1024位、同時呢,在Java中對於1024或其他位數,對密文有長度限制,所以利用了分段加密,密文長度為117,解密長度為128,如此通用處理方式,網上肯定是可以搜尋到的,擷取加密部分片段,如下:
public static byte[] encryptByPublicKey(byte[] data, String publicKey) throws Exception { byte[] keyBytes = Base64.getDecoder().decode(publicKey); X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); Key publicK = keyFactory.generatePublic(x509KeySpec); Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); cipher.init(Cipher.ENCRYPT_MODE, publicK); int inputLen = data.length; ByteArrayOutputStream out = new ByteArrayOutputStream(); int offSet = 0; byte[] cache; int i = 0; while (inputLen - offSet > 0) { if (inputLen - offSet > 117) { cache = cipher.doFinal(data, offSet, 117); } else { cache = cipher.doFinal(data, offSet, inputLen - offSet); } out.write(cache, 0, cache.length); i++; offSet = i * 117; } byte[] encryptedData = out.toByteArray(); out.close(); return encryptedData; }
當然對於金鑰在從證照中匯出來肯定是二進位制的,那麼各個對接方最後轉換為字串方式有多種,比如base64、再比如轉換為16進位制等等,大同小異,這裡就不展開了
瞭解完Java完整程式碼實現,我們需要對相關業務引數利用對接方所提供的公鑰進行加密從而形成簽名,以此請求時,將我們本地生成的公鑰傳遞過去,在響應後進行驗籤,然後通過私鑰解密
好了,講到這裡,我們假設已經本地生成證照,然後我們匯出RSA公鑰和私鑰,虛擬碼如下:
var certificate = new X509Certificate2("pfx_path", "password", X509KeyStorageFlags.Exportable); var rsa = certificate.GetRSAPrivateKey(); var privateKey = rsa.ExportRSAPrivateKey(); var publicKey = rsa.ExportRSAPublicKey();
那麼我們如何利用對接方公鑰進行加密呢?接下來在.NET Core中實現就需要解決上述加密演算法出現的兩個問題
1. Java中可以通過RSA金鑰(公鑰或私鑰)字串轉換為RSA金鑰類,.NET Core提供了?
2. 獲取到RSA金鑰類,呼叫對應例項doFinal方法進行分段加密,那麼是否可以通過.NET Core中的RSA加密方法,也分段加密,結果是否一致?對接方通過公鑰字串載入RSA 公鑰,程式碼如下:
public static RSAPublicKey loadPublicKeyByStr(String publicKeyStr) { byte[] buffer = Util.hexToByte(publicKeyStr); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer); return (RSAPublicKey) keyFactory.generatePublic(keySpec); }
然後稍微檢視了下載入類的解釋
所以我依圖索驥,於是乎,大致有了如下模樣
var servicePublicKey = "123456"; var rsa = RSA.Create(); rsa.ImportSubjectPublicKeyInfo(Encoding.UTF8.GetBytes(servicePublicKey), out _);
好了,目前通過匯入公鑰主題資訊貌似拿到了RSA,接下來則是分段加密,上述方法中cipher.doFinal,應該是指定演算法例項的加密處理,那我們首先擷取對應資料的分段,然後呼叫rsa的Encrypt方法,那加密填充模式是否一致,不得而知,預設如下提供為PKCS1?
var servicePublicKey = "123456"; var plainTextData = Encoding.UTF8.GetBytes("jeffcky"); var rsa = RSA.Create(); rsa.ImportSubjectPublicKeyInfo(Encoding.UTF8.GetBytes(servicePublicKey), out _); var outputStream = new MemoryStream(); var inputLen = plainTextData.Length; int offSet = 0; int i = 0; while (inputLen - offSet > 0) { Span<byte> bytes = plainTextData; byte[] encryptData; if (inputLen - offSet > 117) { Span<byte> slicedBytes = bytes.Slice(start: offSet, length: 117); encryptData = rsa.Encrypt(slicedBytes.ToArray(), RSAEncryptionPadding.Pkcs1); } else { Span<byte> slicedBytes = bytes.Slice(start: offSet, length: inputLen - offSet); encryptData = rsa.Encrypt(slicedBytes.ToArray(), RSAEncryptionPadding.Pkcs1); } outputStream.Write(encryptData, 0, encryptData.Length); i++; offSet = i * 117; }
一頓不知其結果的操作後,經呼叫對接方介面,響應驗籤失敗,反覆改了幾版後,依然失敗
方案二(IKVM)
不再過多深究其細節實現差異,採用穩妥方式借用IKVM直接在C#中複製對接方Java程式碼應該可以搞定(https://github.com/ikvm-revived/ikvm)
根據經驗來看,我只用到加密,應該只需要用到IKVM.OpenJDK.Core和IKVM.OpenJDK.Security兩個庫,於是我們在nuget上下載.NET Framework實現版本,我使用的是.NET Framework 4.7.2,想想.NET Framework 4.6+可以適配.NET Standard,那是否也可以經過編譯後,通過.NET Core新增程式集引用呢?
在.NET Framework 4.7.2引入上述核心庫後,在控制檯測試驗證加密、驗籤一點問題都麼有,好了,接下來則是將其編譯,在.NET Core 3.1中新增程式集進行呼叫,載入程式集方法時直接丟擲大致如下異常
FileNotFoundException: Could not load file or assembly 'System.Configuration.ConfigurationManager, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'. The system cannot find the file specified.
繼續新增對應版本程式集,版本過低,在nuget上根本找不到,涉及到.NET Core基礎設施,看是否在github runtimes倉庫能否找到對應issue
解決了上述程式集問題,接下來再次執行,又來其他異常,.NET Core版本IO載入檔案該方法已被剔除
坑一波接著一波,看來使用.NET Core引用.NET Framework程式集此路行不通,而且這僅僅只是在windows下測試,釋出到linux上出現的問題無法預知,我再次翻了下,居然發現了.NET Core分支,驚喜不驚喜,檢視分支就可以知道已有老外在處理遷移到.NET Core版本,只是還處於未釋出狀態
fork原.NET Framework版本遷移至適配.NET Core連結(https://github.com/ams-ts-ikvm/ikvm-bin/tree/net_core_compat/bin),如果只是用到相關加密,只需引用上述連結bin目錄下,如下庫即可
最後,還需要新增上述在.NET Framework中測試中出現異常的包以及版本(System.Configuration.ConfigurationManager),如下:
經過上述一番折騰過程,終於對接上,耗時一天多,真尼瑪是不容易,搞完一個字,累的一p
總結
? 使用還未釋出適配.NET Core的IKVM,在僅有時間內,快速對接上了Java加密,如若後續再遇到類似比較耗時耗力的加密方式,還不如使用IKVM,將更多時間花在處理業務邏輯上才是正道