如何根据密码在Java中创建密钥对?

时间:2012-07-22 13:03:16

标签: java encryption

我想允许Alice创建一个公钥/私钥对,以便Bob可以发送她的机密消息。但是,我希望Alice能够从任何地方检查她的消息,并且她必须携带包含她的私钥的记忆棒是一件痛苦的事。 Alice是否有某种方法可以根据她记得的密码创建公钥/私钥对?通过这种方式,她可以随时生成私钥(和公钥)。

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

另外,Stack Overflow上的here's the same question,但是对于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();

6 个答案:

答案 0 :(得分:5)

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

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

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

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

答案 1 :(得分:3)

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

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

答案 2 :(得分:1)

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

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

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

答案 3 :(得分:0)

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

答案 4 :(得分: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())));

注意 Hex是:import org.apache.commons.codec.binary.Hex; 在maven:

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

答案 5 :(得分:0)

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

代码

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