RSA签名性能

3
当我在我的机器上运行下面的代码并使用KeyPairGenerator生成的密钥时,我得到约31毫秒的时间。
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.Signature;

public class Crypto {

    public static void main(String[] args){
        final String PROVIDER_NAME = "SunRsaSign";
        final String SIGNATURE_ALGORITHM_NAME = "SHA1withRSA";

        try {
            byte[] bytesToSign = "TEST".getBytes();

            KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
            kpg.initialize(4096);
            PrivateKey privateKey = kpg.genKeyPair().getPrivate();

            Signature rsaSign = Signature.getInstance(SIGNATURE_ALGORITHM_NAME, PROVIDER_NAME);
            rsaSign.initSign(privateKey);
            rsaSign.update(bytesToSign);
            long start = System.currentTimeMillis();
            rsaSign.sign();
            long end = System.currentTimeMillis();
            System.out.println(end-start);
        } catch (Exception e) {
            System.out.print(e.toString());
        }
    }
}

然而,当我使用 KeySpec 运行以下代码时,得到的时间是 328 毫秒。
import java.security.PrivateKey;
import java.security.Signature;
import java.security.spec.RSAPrivateKeySpec;
import java.math.BigInteger;
import java.security.KeyFactory;

public class Crypto {

    public static void main(String[] args){
        final String PROVIDER_NAME = "SunRsaSign";
        final String SIGNATURE_ALGORITHM_NAME = "SHA1withRSA";

        try {
            byte[] bytesToSign = "TEST".getBytes();
            BigInteger n = new BigInteger("597587226679466141124693638138125299950880068828254488555957644249281492201151725373904252242430008473810144110612094128024063578707460431712823842235991465354560187737393879297229743260146710677226117056578671416566287740136599124897385892941425870120428978181352342388371378999775548901123514895501669300647274487518472636693700503555192766931023284431580962701846364239256545481706926550688122371316117197948006216002474377830241838340355035516984862145128976925834940027104794937790806573064454303239801464883574025970986374457025729491416244044251160491275299917049444537591955178699287053624986215597863163779443074749369005932415039400383140953067480491452272333580572932227865814237470887152932057448674357000903536202101025652676188117995296037813643835836244002726526603485151069928993258393018157442284327764913186610742443124225235294325533610789139086190718423569760575759726606015005217606970790315033865732422275945142140911185854993011517078112760033989491003743777970147736937449399489701150359137542465776194778304313471540815791992057968970251791757741455255986669925249397189780062920148823884414124748384210776408299989145375246596521057664660283677204196251491406330933981965200587649372807527331850099013257897");
            BigInteger d = new BigInteger("4595632425140774449957208807568475077822353093511150376614777190009275250154576339906430613701950413824256719589674465424479729674360438494030291537405430497866828426990044023464665617942749014775195126363972265955863237881331857713173970276980616118233916795144751523013552268426795194259216190965909964257232194664224944823437524662279584883855466917214959567904093375903463675828620336322181571862357493748047593464230085088216456147268549642195406724768375183035854704573917278005501724412537726304726489438047535118930941799690876415821196987935099026314948062288370234324829415598279455498832614441633322162210880362668225335174174254925331410135687428608342277292478555925249301141034109001860652817038518162426120218303741256538573731958165872030034502507324328196680907973846309670995934916630480379153721692432821724120534768708296475728203534373370163067285835785480486327238089504618803125757317740030708309519453831883949072027340057586476480382666523830109818313596509315892976057010736043565833515535051861285267156916096112209738518894413968197350931670388334238393788649720192047074674730303813328065901384401315536207657662831106566630368281509079558729496437617044484467087495574832015312198202479584112626996225");

            RSAPrivateKeySpec rsaPrivateKeySpec = new RSAPrivateKeySpec(n,d);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PrivateKey privateKey = keyFactory.generatePrivate(rsaPrivateKeySpec);

            Signature rsaSign = Signature.getInstance(SIGNATURE_ALGORITHM_NAME, PROVIDER_NAME);
            rsaSign.initSign(privateKey);
            rsaSign.update(bytesToSign);
            long start = System.currentTimeMillis();
            rsaSign.sign();
            long end = System.currentTimeMillis();
            System.out.println(end-start);
        } catch (Exception e) {
            System.out.print(e.toString());
        }
    }
}

我不知道为什么第二个选项会慢这么多。有什么想法吗?
2个回答

3
“基准测试是一门独立的科学,特别是在像JVM这样复杂的环境中。然而,在您的情况下,您两个示例之间的一个重要区别是,在使用KeyPairGenerator的情况下,私钥具有其所有可选组件,允许使用中国剩余定理(CRT)加速,而在使用RSAPrivateKeySpec的第二种情况下,您只有最小的私钥,它不允许这种加速。您可以尝试使用RSAPrivateCrtKeySpec进行比较。

我可能会期望这种差异占据2-4倍,而不是10倍。”


我按照您的建议尝试使用RSAPrivateCrtKeySpec,并且得到了与使用KeyPairGenerator相同的时间。看起来肯定是CRT的问题。谢谢! - Owen Doyle
当使用仅包含 {n,d} 的密钥加载时,Java是否无法恢复扩展密钥参数,例如 {p, q, dp, dq, qinv}?如果不能,是否有一种方法告诉Java在加载时预先计算它们? - jww
2
@jww:不行。它需要知道e,而RSAPrivateSpec没有提供它。它可以猜测e=65537,但它没有这样做。 - President James K. Polk

1
如果您想进行真正的比较,仅使用简单的System.currentTimeMillis()是不够的。有一个名为JMH的杰出工具,由@Aleksey Shipilev开发,它可以实现微型基准测试以验证不同的假设,有许多不同的示例和关于如何正确执行此操作的信息在Alexey的博客上。
在您的情况下,我会重新编写测试以使用以下内容进行比较:
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 5, time = 1)
@State(Scope.Benchmark)
@Fork(1)
public class MyBenchmarkTest {

    final String PROVIDER_NAME = "SunRsaSign";

    final String SIGNATURE_ALGORITHM_NAME = "SHA1withRSA";

    @Param({"TEST"})
    private String textToSign;

    private PrivateKey keyPairGenerator;

    private PrivateKey keySpec;

    private PrivateKey crtKey;

    @Setup
    public void setup() {
        BigInteger n = new BigInteger("597587226679466141124693638138125299950880068828254488555957644249281492201151725373904252242430008473810144110612094128024063578707460431712823842235991465354560187737393879297229743260146710677226117056578671416566287740136599124897385892941425870120428978181352342388371378999775548901123514895501669300647274487518472636693700503555192766931023284431580962701846364239256545481706926550688122371316117197948006216002474377830241838340355035516984862145128976925834940027104794937790806573064454303239801464883574025970986374457025729491416244044251160491275299917049444537591955178699287053624986215597863163779443074749369005932415039400383140953067480491452272333580572932227865814237470887152932057448674357000903536202101025652676188117995296037813643835836244002726526603485151069928993258393018157442284327764913186610742443124225235294325533610789139086190718423569760575759726606015005217606970790315033865732422275945142140911185854993011517078112760033989491003743777970147736937449399489701150359137542465776194778304313471540815791992057968970251791757741455255986669925249397189780062920148823884414124748384210776408299989145375246596521057664660283677204196251491406330933981965200587649372807527331850099013257897");
        BigInteger d = new BigInteger("4595632425140774449957208807568475077822353093511150376614777190009275250154576339906430613701950413824256719589674465424479729674360438494030291537405430497866828426990044023464665617942749014775195126363972265955863237881331857713173970276980616118233916795144751523013552268426795194259216190965909964257232194664224944823437524662279584883855466917214959567904093375903463675828620336322181571862357493748047593464230085088216456147268549642195406724768375183035854704573917278005501724412537726304726489438047535118930941799690876415821196987935099026314948062288370234324829415598279455498832614441633322162210880362668225335174174254925331410135687428608342277292478555925249301141034109001860652817038518162426120218303741256538573731958165872030034502507324328196680907973846309670995934916630480379153721692432821724120534768708296475728203534373370163067285835785480486327238089504618803125757317740030708309519453831883949072027340057586476480382666523830109818313596509315892976057010736043565833515535051861285267156916096112209738518894413968197350931670388334238393788649720192047074674730303813328065901384401315536207657662831106566630368281509079558729496437617044484467087495574832015312198202479584112626996225");

        try {
            KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
            kpg.initialize(4096);
            keyPairGenerator = kpg.genKeyPair().getPrivate();

            RSAPrivateKeySpec rsaPrivateKeySpec = new RSAPrivateKeySpec(n, d);

            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            keySpec = keyFactory.generatePrivate(rsaPrivateKeySpec);

            // new BigInteger("92e08f83cc9920746989ca5034dcb384a094fb9c5a6288fcc4304424ab8f56388f72652d8fafc65a4b9020896f2cde297080f2a540e7b7ce5af0b3446e1258d1dd7f245cf54124b4c6e17da21b90a0ebd22605e6f45c9f136d7a13eaac1c0f7487de8bd6d924972408ebb58af71e76fd7b012a8d0e165f3ae2e5077a8648e619", 16)
            RSAPrivateCrtKeySpec crtKeySpec = new RSAPrivateCrtKeySpec(new BigInteger("b259d2d6e627a768c94be36164c2d9fc79d97aab9253140e5bf17751197731d6f7540d2509e7b9ffee0a70a6e26d56e92d2edd7f85aba85600b69089f35f6bdbf3c298e05842535d9f064e6b0391cb7d306e0a2d20c4dfb4e7b49a9640bdea26c10ad69c3f05007ce2513cee44cfe01998e62b6c3637d3fc0391079b26ee36d5", 16),
                    new BigInteger("11", 16),
                    d,
                    new BigInteger("f75e80839b9b9379f1cf1128f321639757dba514642c206bbbd99f9a4846208b3e93fbbe5e0527cc59b1d4b929d9555853004c7c8b30ee6a213c3d1bb7415d03", 16),
                    new BigInteger("b892d9ebdbfc37e397256dd8a5d3123534d1f03726284743ddc6be3a709edb696fc40c7d902ed804c6eee730eee3d5b20bf6bd8d87a296813c87d3b3cc9d7947", 16),
                    new BigInteger("1d1a2d3ca8e52068b3094d501c9a842fec37f54db16e9a67070a8b3f53cc03d4257ad252a1a640eadd603724d7bf3737914b544ae332eedf4f34436cac25ceb5", 16),
                    new BigInteger("6c929e4e81672fef49d9c825163fec97c4b7ba7acb26c0824638ac22605d7201c94625770984f78a56e6e25904fe7db407099cad9b14588841b94f5ab498dded", 16),
                    new BigInteger("dae7651ee69ad1d081ec5e7188ae126f6004ff39556bde90e0b870962fa7b926d070686d8244fe5a9aa709a95686a104614834b0ada4b10f53197a5cb4c97339", 16));
            crtKey = keyFactory.generatePrivate(crtKeySpec);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Test
    public void executeBecnhmarkTest() throws RunnerException {
        final Options opt = new OptionsBuilder()
                .include(this.getClass().getName() + ".*")
                .build();

        new Runner(opt).run();
    }

    @Benchmark
    public void testKeyPairGenerator(Blackhole bh) throws Exception {
        Signature rsaSign = Signature.getInstance(SIGNATURE_ALGORITHM_NAME, PROVIDER_NAME);
        rsaSign.initSign(keyPairGenerator);
        rsaSign.update(textToSign.getBytes());
        rsaSign.sign();

        bh.consume(rsaSign);
    }

    @Benchmark
    public void testKeySpec(Blackhole bh) throws Exception {
        Signature rsaSign = Signature.getInstance(SIGNATURE_ALGORITHM_NAME, PROVIDER_NAME);
        rsaSign.initSign(keySpec);
        rsaSign.update(textToSign.getBytes());
        rsaSign.sign();

        bh.consume(rsaSign);
    }

    @Benchmark
    public void testCrtKeySpec(Blackhole bh) throws Exception {
        Signature rsaSign = Signature.getInstance(SIGNATURE_ALGORITHM_NAME, PROVIDER_NAME);
        rsaSign.initSign(crtKey);
        rsaSign.update(textToSign.getBytes());
        rsaSign.sign();

        bh.consume(rsaSign);
    }

}

现在执行测试套件将为我们提供以下结果:
Benchmark                             (textToSign)  Mode  Cnt       Score       Error  Units
MyBenchmarkTest.testCrtKeySpec                TEST  avgt    5    1100.885 ±   184.517  us/op
MyBenchmarkTest.testKeyPairGenerator          TEST  avgt    5    7092.015 ±  1634.765  us/op
MyBenchmarkTest.testKeySpec                   TEST  avgt    5  168832.223 ± 20486.314  us/op

这并不足以理解差异,因此您可以简单地进行分析,以查看每个人花费大部分时间的位置。

例如,在RSAPrivateKeySpec案例中,我们将看到以下分析热点:

4647 在 java.security.Signature.sign(Signature.java:579) 处签名 4647 在 java.security.Signature$Delegate.engineSign(Signature.java:1207) 处签名 4646 "C1 CompilerThread3" #8 守护线程,优先级为9,os优先级为31,tid=0x00007fd948017800,nid=0x4e03,在条件 [0x0000000000000000] 上等待 4645 在 sun.security.rsa.RSASignature.engineSign(RSASignature.java:175) 处签名 4644 在 sun.security.rsa.RSACore.rsa(RSACore.java:124) 处进行rsa操作 4630 在 java.math.BigInteger.modPow(BigInteger.java:2502) 处进行模幂运算 4623 "main" #1 优先级为5,os优先级为31,tid=0x00007fd94a801800,nid=0x1c03,在条件 [0x000070000c113000] 上等待 4573 在 sun.security.rsa.RSACore.priCrypt(RSACore.java:150) 处进行私钥加密 4560 JNI 全局引用:346 4152 在 java.math.BigInteger.montgomerySquare(BigInteger.java:2571) 处进行蒙哥马利平方 4152 在 java.math.BigInteger.implMontgomerySquare(BigInteger.java:2613) 处进行蒙哥马利平方 4148 在 java.math.BigInteger.oddModPow(BigInteger.java:2839) 处进行奇数模幂运算

接下来我们可以进入SDK并检查priCrypt:
    /**
     * RSA non-CRT private key operations.
     */
    private static byte[] priCrypt(byte[] msg, BigInteger n, BigInteger exp)
            throws BadPaddingException {

        BigInteger c = parseMsg(msg, n);
        BlindingRandomPair brp = null;
        BigInteger m;
        if (ENABLE_BLINDING) {
            brp = getBlindingRandomPair(null, exp, n);
            c = c.multiply(brp.u).mod(n);
            m = c.modPow(exp, n);
            m = m.multiply(brp.v).mod(n);
        } else {
            m = c.modPow(exp, n);
        }

        return toByteArray(m, getByteLength(n));
}

这将符合关于使用 CRT 的假设。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接