如何在Java中基于密码创建密钥对



我想允许Alice创建一个公钥/私钥对,以便Bob可以向她发送机密消息。然而,我希望爱丽丝能够在任何地方查看她的信息,如果她不得不随身携带一个装有私钥的记忆棒,那将是一种痛苦。有没有什么方法可以让Alice根据她记忆中的密码创建一个公钥/私钥对?通过这种方式,她可以随时生成私钥(和公钥)

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

此外,这里还有关于堆栈溢出的相同问题,但针对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();

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

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

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

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

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

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

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

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

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

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

使用共享密钥。

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.mons.codec.binary.Hex;在maven:

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

免责声明:这种天真的方法是不安全的,因此不建议用于生产系统。然而,对于测试来说,它非常适合。

代码

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

最新更新