朋友碰到調用第三方API的加密問題,JAVA代碼中用pfx私鑰文件來加密字符串,流程以下: html
需求是在.net程序中獲得一樣的加密字符串,常見方法以下:java
照理說,第4種方法是最簡單快速的,不過屬於暴力破解的法子,按常規的思路,我仍是第1種方法入手。算法
首先我很奇怪爲何有API是用私鑰來加密,雖說公鑰私鑰交換使用是能夠的,但什麼場景會這樣使用?知乎有些推論:http://www.zhihu.com/question/25912483 數組
可是若是你想發佈一個公告,須要一個手段來證實這確實是你本人發的,而不是其餘人冒名頂替的。那你能夠在你的公告開頭或者結尾附上一段用你的私鑰加密的內容(例如說就是你公告正文的一段話),那全部其餘人均可以用你的公鑰來解密,看看解出來的內容是否是相符的。若是是的話,那就說明這公告確實是你發的---由於只有你的公鑰才能解開你的私鑰加密的內容,而其餘人是拿不到你的私鑰的。
從現象來講,公鑰加密,每次獲得的加密信息都不固定,私鑰加密獲得的加密信息是固定的。可能基於這些緣由,此API才用私鑰來加密吧。dom
C#提供的RSA算法類有RSACryptoServiceProvider,它的實現按常規的作法,公鑰加密,私鑰解密,默認狀況下,沒有提供用私鑰加密的現成方法,#1方法失效;ide
網上有用私鑰加密的實現,相似的參考有:工具
C#使用RSA進行私鑰加密公鑰解密(蝸牛大俠), http://blog.csdn.net/a351945755/article/details/21965533測試
基於私鑰加密公鑰解密的RSA算法C#實現(zhilunchen),http://blog.csdn.net/zhilunchen/article/details/2943158,加密
C#使用RSA私鑰加密公鑰解密的改進,解決特定狀況下解密後出現亂碼的問題,http://www.byywee.com/page/M0/S545/545934.htmlurl
BigInteger類下載:http://www.codeproject.com/Articles/2728/C-BigInteger-Class
但朋友和我驗證後都失敗了,得出來的加密串與java得出的不一致,關鍵的算法以下:
//paramsters是C#加載私鑰文件後輸出的RSAParameters對象 BigInteger d = new BigInteger(paramsters.D); BigInteger n = new BigInteger(paramsters.Modulus); BigInteger biText = new BigInteger(context); //context是被加密串轉成base64後取字節數組 BigInteger biEnText = biText.modPow(d, n);
看上去多是算法不同所致,有必要去查看一下java是如何實現的。
Java加密串部分:
Cipher cipher = Cipher.getInstance(RsaConst.RSA_CHIPER);// RSA_CHIPER = "RSA/ECB/PKCS1Padding"; cipher.init(mode, privateKey); //mode = Cipher.ENCRYPT_MODE = 1 byte[] doFinal = cipher.doFinal(subarray(srcData, i, i + blockSize));
Cipher是個基類,從「RSA/ECB/PKCS1Padding」找到com.sun.crypto.provider.RSACipher(源碼地址)
再找到它執行的方法:doFinal()(源碼地址)
這兩句就是真正的執行代碼:
data = padding.pad(buffer, 0, bufOfs); return RSACore.rsa(data, privateKey);
接下來找到sun.security.rsa.RSACore (源碼地址)
public static byte[] rsa(byte[] msg, RSAPrivateKey key) throws BadPaddingException { if (key instanceof RSAPrivateCrtKey) { return crtCrypt(msg, (RSAPrivateCrtKey)key); } else { return priCrypt(msg, key.getModulus(), key.getPrivateExponent()); } }
這裏有兩個方法,crtCrypt和priCrypt,那麼到底執行哪一種方法呢?取決於加載的私鑰文件是哪一種類型,這點很容易驗證,調試java代碼就能夠獲知,朋友提供的私鑰文件是實現了sun.security.rsa.RSAPrivateCrtKeyImpl的RSAPrivateCrtKey類,因此它會執行crtCrypt方法。
private static byte[] crtCrypt(byte[] msg, RSAPrivateCrtKey key) throws BadPaddingException { BigInteger n = key.getModulus(); BigInteger c = parseMsg(msg, n); BigInteger p = key.getPrimeP(); BigInteger q = key.getPrimeQ(); BigInteger dP = key.getPrimeExponentP(); BigInteger dQ = key.getPrimeExponentQ(); BigInteger qInv = key.getCrtCoefficient(); BigInteger e = key.getPublicExponent(); BigInteger d = key.getPrivateExponent(); BlindingRandomPair brp; if (ENABLE_BLINDING) { brp = getBlindingRandomPair(e, d, n); c = c.multiply(brp.u).mod(n); } // m1 = c ^ dP mod p BigInteger m1 = c.modPow(dP, p);
// m2 = c ^ dQ mod q BigInteger m2 = c.modPow(dQ, q);
// h = (m1 - m2) * qInv mod p BigInteger mtmp = m1.subtract(m2);
if (mtmp.signum() < 0) { mtmp = mtmp.add(p); }
BigInteger h = mtmp.multiply(qInv).mod(p); // m = m2 + q * h BigInteger m = h.multiply(q).add(m2); if (ENABLE_BLINDING) { m = m.multiply(brp.v).mod(n); } return toByteArray(m, getByteLength(n)); }
用C#來實現一樣的算法費時頗多,先用#3的方法來試驗一下,用工具IKVM把相關的jar包生成dll,在C#調用調試。
Java中privateKey的屬性與C#中RSAParameters中的屬性對比:(原理在這)
d=Q; e=Exponent;n=Modulus;p=P;pe=DP;q=Q;qe=DQ;encodedKey在C#中的byte[]數組與java的byte[]轉成的C#的sbyte[]數組相等;
在C#中把一個個方法拆下來運行,結果與java生成的都不一致,與這些算法相關的類較多,如BlindingRandomPair,RSAPadding等,一個個實現很費時,研究下相關的源碼也是一樂趣。
同時發現網上所寫的C#代碼用私鑰加密的算法,與java中用公鑰加密的算法同樣,可是不能替代java中的私鑰加密算法。請看對比:
public static byte[] rsa(byte[] msg, RSAPublicKey key) throws BadPaddingException { return crypt(msg, key.getModulus(), key.getPublicExponent()); } private static byte[] crypt(byte[] msg, BigInteger n, BigInteger exp) throws BadPaddingException { BigInteger m = parseMsg(msg, n); BigInteger c = m.modPow(exp, n); return toByteArray(c, getByteLength(n)); }
在與上面所寫的C#自寫的私鑰加密關鍵部分:
//paramsters是C#加載私鑰文件後輸出的RSAParameters對象 BigInteger d = new BigInteger(paramsters.D); BigInteger n = new BigInteger(paramsters.Modulus); BigInteger biText = new BigInteger(context); //context是被加密串轉成base64後取字節數組 BigInteger biEnText = biText.modPow(d, n);
除了取publicExponent和D算子不一樣外(由於key類型不同)。
綜上所述,第3種方法和第4種方法都是能夠解決的,但實質仍是在java環境下運行。
第3種方法的概略是:
- 封裝好調用,Export成jar包,修改jar包中的META-INF\MAINFEST.MF文件,設置Main-Class和Class-Path(可能會包含其它的jar包,若是沒有則不設置)
- 能夠用ikvm –jar xxx.jar驗證一下,看是否能正常運行Main中的測試代碼(若是包含其它jar包,最好放在同一路徑下)
- 用ikvmc –target:library xxx.jar (lib1.jar lib2.jar)命令生成相應的dll文件
- 在C#項目中引用,測試(必須引入IKVM.OpenJDK.Core)
第4種方法則很簡單,用java命令調用,或者用bat封裝命令,在代碼中用Process調用,讀取輸出流,解析便可。