【问题标题】:How can I create a key pair in Java, based on a password?如何根据密码在 Java 中创建密钥对?
【发布时间】:2020-11-19 18:40:28
【问题描述】:

我想让 Alice 创建一个公钥/私钥对,以便 Bob 可以发送她的机密消息。但是,我希望 Alice 能够从任何地方查看她的消息,而她不得不随身携带一个包含她的私钥的记忆棒会很痛苦。爱丽丝有什么方法可以根据她记得的密码创建公钥/私钥对吗?通过这种方式,她可以随时简单地生成私钥(和公钥)。

这个问题的简短版本是:我在哪里可以找到 cryptico.js 的 Java 等效项。

另外,here's the same question 在 Stack Overflow 上,但适用于 javascript。

编辑:这是我第一次尝试解决方案:

    SecureRandom saltRand = new SecureRandom(new byte[] { 1, 2, 3, 4 });
    byte[] salt = new byte[16];
    saltRand.nextBytes(salt);

    int keyLength = 3248;
    SecretKeyFactory factory = SecretKeyFactory
            .getInstance("PBKDF2WithHmacSHA1");
    KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 8192, keyLength);
    SecretKey key = factory.generateSecret(spec);

    SecureRandom keyGenRand = SecureRandom.getInstance("SHA1PRNG");
    keyGenRand.setSeed(key.getEncoded());

    KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA");
    gen.initialize(keyLength, keyGenRand);
    java.security.KeyPair p = gen.generateKeyPair();

【问题讨论】:

标签: java encryption


【解决方案1】:

在谈论 RSA 时:您可以使用 PBKDF2 的结果来播种伪随机数生成器,该生成器又可以用于生成密钥对。请注意,使用 SecureRandom 将不起作用,因为它会将种子添加到池中,而不是完全重新初始化 rng。 RSA 需要 PRNG 才能找到随机素数。

如果你能使用椭圆曲线密码学,你会更好。您可以在 F(p) 上选择标准 NIST 或 Brainpool 曲线。然后您可以使用 PBKDF2 的 32 字节输出作为私钥并计算公钥。 ECC 只需要一个随机私钥,并且由于 PBKDF2 的输出应该与随机无法区分,因此输出会很好。您不仅不需要额外的 PRNG,而且还为自己节省了计算 RSA 密钥对的时间 - 这可能非常重要。

请注意,没有什么可以阻止对使用所述计算密钥加密的内容进行暴力攻击,因此您最好要求提供 16 个字符或更多字符的密码,其中包含非字典单词、数字和符号。任何更少的东西都可能会失败,特别是如果用户没有意识到可能的攻击。请注意,如果您没有存储空间,则不能使用随机盐。如果您不能使用随机盐,则无法防御彩虹表(对于您的特定应用程序,您当然可以使用特定于应用程序的盐)。此外,具有相同密码的人将生成相同的私钥

当然,默认方式 - 例如在 PGP 中 - 是使用基于密码的加密来存储加密的私钥。然而,这需要存储。这种方法的优点是您可以拥有一个完全随机的密钥,这意味着如果不访问密钥存储,就不可能对密文进行暴力攻击。它增加了一个重要的额外层。

【讨论】:

  • 非常有趣,owlstead。谢谢 - 我会检查椭圆曲线密码学。但是一个 32 字节的私钥是否足以抵抗暴力攻击?
  • keylength.com/en/3,那将是一个 256 位的椭圆曲线...... passphase 本身的强度肯定比这要小。
  • 警告:即使创建 ECC 方案也违反了所有重要的规则:不要尝试自己实现加密算法或协议。你已经被警告过了。标准免责声明结束。
  • 是的,我更喜欢使用现成的实现(类似于 cryptico.js),但似乎没有。回答您之前的问题;盐将与 Alice 的公钥一起存储,因此她(和其他任何人)始终可以访问它。
  • 嗯...也许 Alice 应该根据她的密码使用对称密钥加密她的私钥,然后将结果与她的公钥一起发布?
【解决方案2】:

您没有提供太多详细信息,但如果您想使用 java.security.KeyPairGenerator 生成密钥对,则必须定义自己的扩展 SecureRandom 的类,但仅使用提供的密码作为熵源。

您不需要实现SecureRandomSpi 类,只需使用(null, null) 参数调用超类的protected constructor

【讨论】:

    【解决方案3】:

    RSA 密钥长度通常为 1024 或 2048 位。这使得 128 或 256 个字节。

    密码通常有 8 个字节长(并且只使用大约 64 个不同的字节)。

    如果 RSA 密钥是从密码中派生的,您将失去算法的大部分强度。攻击者只需猜测或暴力破解 8 字节密码,而不是 128 或 256 字节长的密钥。

    【讨论】:

    • 是的,但是如果我使用像 bcrypt 或 PBKDF2 这样的故意慢速算法将 8 字节密码转换为 256 字节,然后用于初始化密钥生成过程。这不会阻止对密码的暴力攻击吗?
    • 它有助于暴力攻击,但不适用于社会工程攻击和字典攻击。
    【解决方案4】:

    您为什么不允许通过密码访问您的网络应用程序(因为这实际上就是您正在做的事情)而只使用 https?

    【讨论】:

    • 这对 Bob 向 Alice 发送加密消息有何帮助?他需要从 Alice 那里得到一个公钥,而 Alice 需要能够根据她的密码短语按需生成密钥(以避免将私钥存储在某个地方)。
    【解决方案5】:

    使用共享密钥。

    String encryptionKey = "53616d706c6550617373776f726453616d706c6550617373776f726453616d70"; // string to hex of "SamplePasswordSamplePasswordSamp"
    String sampleText = "sampletext";
    String encrypted = null;
    String decrypted = null;
    

    加密使用:

    Cipher cipher = Cipher.getInstance("AES");
    cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(Hex.decodeHex(encryptionKey.toCharArray()), "AES"));
    encrypted = Hex.encodeHexString(cipher.doFinal((sampleText.toString()).getBytes()));
    

    要解密使用:

       Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(Hex.decodeHex(encryptionKey.toCharArray()), "AES"));
        decrypted = new String(cipher.doFinal(Hex.decodeHex(enc.toCharArray())));
    

    注意 十六进制为:import org.apache.commons.codec.binary.Hex; 在 Maven 中:

    <dependency>
        <groupId>commons-codec</groupId>
        <artifactId>commons-codec</artifactId>
        <version>1.6</version>
    </dependency>
    

    【讨论】:

      【解决方案6】:

      免责声明:这种幼稚的方法是不安全的,因此不建议将其用于生产系统。但是对于测试 - 非常适合。

      代码

      private KeyPair getKeyPair(String password) throws GeneralSecurityException {
          /*// https://stackoverflow.com/a/992413/2078908
          byte[] salt = new byte[]{1, 2, 3};
          SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
          KeySpec seedSpec = new PBEKeySpec(password.toCharArray(), salt, 65536, 256);
          byte[] seed = factory.generateSecret(seedSpec).getEncoded();*/
          byte[] seed = password.getBytes(UTF_8);
      
          SecureRandom rnd = SecureRandom.getInstance("SHA1PRNG");
          rnd.setSeed(seed);
      
          RSAKeyGenParameterSpec spec = new RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4);
          KeyPairGenerator pairGenerator = KeyPairGenerator.getInstance("RSA");
          pairGenerator.initialize(spec, rnd);
      
          return pairGenerator.generateKeyPair();
      }
      

      测试

      @Test
      public void testPrivateKeys() throws Exception {
          System.out.println("private key 1: " + md5(getKeyPair("pwd-1").getPrivate().getEncoded()));
          System.out.println("private key 1: " + md5(getKeyPair("pwd-1").getPrivate().getEncoded()));
          System.out.println("private key 2: " + md5(getKeyPair("pwd-2").getPrivate().getEncoded()));
          System.out.println("private key 2: " + md5(getKeyPair("pwd-2").getPrivate().getEncoded()));
      }
      
      
      private String md5(byte[] data) throws GeneralSecurityException {
          return javax.xml.bind.DatatypeConverter.printHexBinary(
              MessageDigest.getInstance("md5").digest(data));
      }
      

      测试输出

      private key 1: 5A2009E6DC8B25321C6304F62BE45398
      private key 1: 5A2009E6DC8B25321C6304F62BE45398
      private key 2: 2ACB65656AF9AF7036F40ACF0CFE7CA3
      private key 2: 2ACB65656AF9AF7036F40ACF0CFE7CA3
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-02-18
        • 2012-07-16
        • 2015-05-02
        • 2023-03-08
        • 1970-01-01
        • 1970-01-01
        • 2020-04-15
        相关资源
        最近更新 更多