RSA私鑰加密研究

朋友碰到調用第三方API的加密問題,JAVA代碼中用pfx私鑰文件來加密字符串,流程以下: html

  • 輸入私鑰文件地址pfxPath、私鑰密碼pfxKey、被加密串dataContent
  • dataContent轉成base64串,使用sun.misc.BASE64Decoder包
  • 用pfx私鑰及PKCS12方式生成privateKey
  • privateKey和RSA/ECB/PKCS1Padding加密方式生成加密字節數組,再轉成十六進制字符串

需求是在.net程序中獲得一樣的加密字符串,常見方法以下:java

  1. 使用.net framework中相應的加密類實現一樣的算法
  2. #1失敗,根據原理,實現一樣的算法
  3. 使用工具把java/jar包轉成.net程序能調用的dll,如IKVM.NET,下載:http://www.ikvm.net/download.html 
  4. 將調用java生成加密串的代碼打成jar包,包含在命令行中,在.net程序中調用,取得結果 

照理說,第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調用,讀取輸出流,解析便可。

相關文章
相關標籤/搜索