9

I want to allow Alice to create a public/private key pair so that Bob can send her confidential messages. However, I want Alice to be able to check her messages from anywhere, and it would be a pain for her to have to carry around a memory stick containing her private key. Is there some way that Alice can create a public/private key pair based on a password which she remembers? In this way she could simply generate the private key (and public key) whenever she wanted to.

The short version of this question is: Where can I find the Java equivalent of cryptico.js.

Also, here's the same question on Stack Overflow, but for javascript.

Edit: Here's my first attempt at a solution:

    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();
Community
  • 1
  • 1
Mark
  • 4,749
  • 7
  • 44
  • 53

6 Answers6

5

When talking RSA: You could use the result of PBKDF2 to seed a pseudo random number generator, which can in turn be used to generate a key pair. Note that using SecureRandom won't work as it will add the seed to the pool instead of full reinitializing the rng. RSA needs a PRNG to find a random prime.

You are better off if you can use Elliptic Curve Cryptography. You could choose a standard NIST or Brainpool curve over F(p). Then you could use 32 bytes output of the PBKDF2 as the private key and calculte the public key. ECC only requires a random private key and as the output of PBKDF2 should be indistinguishable from random, the output would be fine. Not only don't you need an additional PRNG, you are saving yourself the time to calculate an RSA key pair as well - and this can be substantial.

Note that nothing will prevent a brute force attack against something encrypted with said calculated key, so you should better ask for a passphrase of 16 characters or more, containing non-dictionary words, numbers and signs. Anything less will likely fail, especially if the users are unaware of possible attacks. Note that if you don't have storage, you cannot use a random salt. If you cannot have a random salt you cannot defend against rainbow tables (for your specific application, you can use a application specific salt of course). Furthermore, persons with the same passphrase will generate the same private key.

Of course the default way - for instance in PGP - is to store the private key encrypted using password based encryption. This however requires a storage. The advantage of that approach is that you can have a fully random key, which means that without access to the key storage, brute force attacks against cipher texts are impossible. It adds an important extra layer.

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
  • Very interesting, owlstead. Thanks - I'll check out Elliptic Curve Cryptography. But is a 32-byte private key large enough to resist a brute-force attack? – Mark Jul 22 '12 at 14:45
  • See http://www.keylength.com/en/3/, that would be a 256 bit Elliptic Curve... The passphase itself definitely has less strength than that. – Maarten Bodewes Jul 22 '12 at 14:52
  • WARNING: Creating even the ECC scheme goes against that all important rule: don't try to implement a cryptographic algorithm or protocol yourself. You've been warned. End of standard disclaimer. – Maarten Bodewes Jul 22 '12 at 14:57
  • Yes I'd much prefer to use an off-the-shelf implementation (similar to cryptico.js) but there doesn't seem to be one around. To answer your earlier question; the salt would be stored with Alice's public key so she (and anyone else) would always have access to it. – Mark Jul 22 '12 at 15:32
  • Hmmm...maybe Alice should just encrypt her private key using a symmetric key based on her password, then publish the result alongside her public key? – Mark Jul 22 '12 at 15:46
  • That would be an option. The drawback is that in that case *anyone* with access to the public key can perform a brute force attack on the symmetric key. Of course, you could counter that with some sort of access control. There's a lot of possibilities, and you will have to balance security and usability (and probably complexity). I like the idea of sharing the salt with the public key, interesting option. – Maarten Bodewes Jul 22 '12 at 22:43
  • Was this maybe anough to answer your question, Mark? Or do you need more information? – Maarten Bodewes Jul 30 '12 at 00:04
  • @owlstead I like your answer. Can you take a look at my question? I'm trying to implement it, but having issues. http://stackoverflow.com/questions/19819709/ – David Silva Smith Nov 19 '13 at 13:05
3

You don't provide many details but if you want to generate your keypair using the java.security.KeyPairGenerator you will have to define your own class that extends SecureRandom, but uses only the supplied password as the entropy source.

You don't need to implement a SecureRandomSpi class, you can just call the protected constructor of the superclass with (null, null) arguments.

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
President James K. Polk
  • 40,516
  • 21
  • 95
  • 125
1

RSA key length is typically 1024 or 2048 bits. That makes 128 or 256 bytes.

Passwords are typically 8 bytes long (and use only a around 64 different bytes).

You would lose much of the strength of the algorithm if the RSA key was derived from the password. Attackers would only have to guess or brute-force an 8-bytes password rather than a 128 or 256-bytes long key.

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • 3
    That's true, but what if I used an intentionally slow algorithm like bcrypt or PBKDF2 to convert the 8 byte password into 256 bytes which are then used to initialize the key generation procedure. Wouldn't that thwart a brute-force attack on the password? – Mark Jul 22 '12 at 13:42
  • 1
    It would help for brute-force attacks, but not for social-engineering attacks and dictionary attacks. – JB Nizet Jul 22 '12 at 14:11
0

Why don't you allow access to your web app via password (since that's effectively what you are doing) and just use https?

Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • 1
    How does that help Bob in sending an encryped message to Alice? He needs to get a public key from Alice, and Alice needs to be able to generate the key on demand from her passphrase (to avoid having to store the private key somewhere). – Thilo Dec 11 '15 at 07:51
0

use a shared key.

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

To encrypt use:

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()));

To decrypt use:

   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())));

note Hex is: import org.apache.commons.codec.binary.Hex; in maven:

<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.6</version>
</dependency>
Davide Consonni
  • 2,094
  • 26
  • 27
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
ursa
  • 4,404
  • 1
  • 24
  • 38