9

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

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

此外,这是关于 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();
4

6 回答 6

5

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

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

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

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

于 2012-07-22T14:17:32.037 回答
3

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

您不需要实现SecureRandomSpi类,只需使用参数调用超类的受保护构造(null, null)函数即可。

于 2012-07-22T14:04:10.403 回答
1

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

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

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

于 2012-07-22T13:33:28.993 回答
0

使用共享密钥。

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>
于 2012-07-22T14:41:01.810 回答
0

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

于 2012-07-22T14:09:46.250 回答
0

Disclaimer: this naive approach is insecure, so it is NOT recommended for production systems. However for testing - suits very well.

Code

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

@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));
}

Test output

private key 1: 5A2009E6DC8B25321C6304F62BE45398
private key 1: 5A2009E6DC8B25321C6304F62BE45398
private key 2: 2ACB65656AF9AF7036F40ACF0CFE7CA3
private key 2: 2ACB65656AF9AF7036F40ACF0CFE7CA3
于 2020-11-19T10:40:28.930 回答