zoukankan      html  css  js  c++  java
  • RSA私钥加密研究

    朋友碰到调用第三方API的加密问题,JAVA代码中用pfx私钥文件来加密字符串,流程如下: 

    • 输入私钥文件地址pfxPath、私钥密码pfxKey、被加密串dataContent
    • dataContent转成base64串,使用sun.misc.BASE64Decoder包
    • 用pfx私钥及PKCS12方式生成privateKey
    • privateKey和RSA/ECB/PKCS1Padding加密方式生成加密字节数组,再转成十六进制字符串

    需求是在.net程序中得到同样的加密字符串,常见方法如下:

    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才用私钥来加密吧。

    C#提供的RSA算法类有RSACryptoServiceProvider,它的实现按常规的做法,公钥加密,私钥解密,默认情况下,没有提供用私钥加密的现成方法,#1方法失效;

    网上有用私钥加密的实现,类似的参考有:

    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.html

    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-INFMAINFEST.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调用,读取输出流,解析即可。

  • 相关阅读:
    【java编程】使用System.getProperty方法,如何配置JVM系统属性
    【java多线程】CountDownLatch
    【java多线程】ConcurrentLinkedHashMap
    【java编程】ServiceLoader使用看这一篇就够了
    【JVM】java对象
    【git】Git常用命令
    centos7安装配置mysql5.7
    第十三章 redis-cluster原理
    《mysql技术内幕 InnoDB存储引擎(第二版)》阅读笔记
    第二章 BIO与NIO
  • 原文地址:https://www.cnblogs.com/tanglang/p/4766769.html
Copyright © 2011-2022 走看看