2

Is it possible to use SHA3-512(a subset of keccak available in Java9) to generate keys in Java?

I have searched through a lot of noise and documentation to try to figure this out. Currently it seems SHA3-512 is available as a hash for MessageDigest but not for generating keys. My code below tries to generate keys predictably(for wallet purposes like BIP32 but beyond currency to blockchain uses)

https://github.com/devssh/BlockchainFullNode/blob/d2978e598b4cdecdf4b3337713b2c3e839a6b181/src/main/java/app/model/Keyz.java#L111-L128

    public static String GenerateSeed() throws Exception {
        SecureRandom random = new SecureRandom();
        byte[] seed = random.generateSeed(512);
        return Base64.getEncoder().encodeToString(seed);
    }

    public static Keyz GenerateKey(String seedString) {
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        KeyPairGenerator keyGen1 = KeyPairGenerator.getInstance("ECDSA");
        ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256k1");
        SecureRandom random1 = SecureRandom.getInstance("SHA1PRNG");
        random1.setSeed(Base64.getDecoder().decode(seedString));
        keyGen1.initialize(ecSpec, random1);
        KeyPair keyPair1 = keyGen1.generateKeyPair();
        PublicKey pub1 = keyPair1.getPublic();
        PrivateKey priv1 = keyPair1.getPrivate();
        //Keyz is a simple model that stores the 3 fields below and overrides equals and hashcode on those fields
        return new Keyz("random", pub1, priv1);
    }

As you can see, it uses SHA1PRNG to predictably generate keypair deterministically(I am fine with the security concerns on this) so that the keys can be recreated deterministically.

Here is a JUnit test to make sure the keys are deterministic(works for SHA1PRNG, needs to work in SHA3PRNG). Ideally what is needed is a SHA3-512 TRNG in the GenerateSeed and a SHA3PRNG in the GenerateKey. Since the keygenerator needs a SecureRandom I would be surprised if java.Security.SecureRandom is still on something as insecure as SHA1PRNG.

https://github.com/devssh/BlockchainFullNode/blob/d2978e598b4cdecdf4b3337713b2c3e839a6b181/test/main/java/app/model/KeyzTest.java#L16-L22

    @Test
    public void shouldReturnDeterministicKeys() throws Exception {
        String seedString = GenerateSeed();
        Keyz random1 = GenerateKey(seedString);
        Keyz random2 = GenerateKey(seedString);
        //This assertion works as we override equals and hashcode
        assertEquals(random1, random2);
    }

Can someone please let me know if they figured a way to get this to work

devssh
  • 1,184
  • 12
  • 28
  • 2
    What do you mean by "deterministically"? SHA1PRNG, as its suffix stands for, is used for PseudoRandom Number Generation. So your code produces different keys at (almost) each run. I think you are missing out something. – ram Apr 26 '18 at 21:44
  • SHA3 is a hash; it doesn't generate keys, it generates bytes. – President James K. Polk Apr 27 '18 at 02:10
  • 1
    no @ram, I need it to be deterministic for a given seed. Let us assume I generate a seed with sufficient randomness. Now I want to store the seed and at any future points of time recreate the key and get the same public-private key pair. SHA1PRNG lets me set the seed of SecureRandom and it respects my seed value instead of ignoring it. Now assume I have a million users who have multiple keys, I just need to store the root seed instead of all their keys. Please read up BIP32 for more context. That part is solved in my code. I want to move it from hashing with SHA1PRNG to SHA3-512. – devssh Apr 27 '18 at 07:33
  • @James Yes, I have added more code to the question to show how that is being handled – devssh Apr 27 '18 at 08:05
  • There are huge problems with what you are proposing. The SHA1PRNG is not well defined and may change between implementations. Other Java versions may not use `setSeed` to actually set a seed. There are multiple ways of generating a number from a random number generator; the generator may be used differently in other variations of deriving the private key for ECDSA (OK, this is more a theoretical point, but still, imagine a native one on big and little endian platforms). Reseeding is also a possibility that you don't want to encounter. – Maarten Bodewes Apr 28 '18 at 18:47
  • Better use a KDF, stream cipher or a XOF to generate the value S of the private key, and then use point addition with G to calculate the public point. You may need Bouncy Castle for that. "I am fine with the security concerns on this": I would call that naive at best and negligent at worst. – Maarten Bodewes Apr 28 '18 at 18:47
  • @Maarten Yes, I made sure to fix all my versions in Gradle since I foresaw that problem. I have also Dockerized my Java versions. – devssh Apr 29 '18 at 05:38
  • 1
    @Maarten The security concerns are invalid as we are never going to use these keys in the public. We will use these keys as generators to generate more keys which will never be repeated. So use-case does matter, also it's not a centralised system. I just provide an implementation, if someone wants to make their own keys, keep them private and use them for signing, they should in my blockchain. As for the breaking changes, that's why I'm writing tests to make sure it doesn't happen. – devssh Apr 29 '18 at 05:49

1 Answers1

2

It seems what you are looking for is not available out of the box:

Note that SHA1 and SHA1PRNG are not equivalent. While the former is a hash algorithm, the latter is a pseudo random generation algorithm (that uses SHA1 to update its internal state, of course.) One trivial result of this difference is, SHA1 outputs a fixed size of bits, where SHA1PRNG outputs as many bits as you like.

Because of this difference, SHA3-512 cannot be used as PRNG directly, although it is available in Java. What you need to do is, implement a PRNG algorithm using SHA3-512 (this part is really tricky, since generating a pseudo random stream is quite difficult.) and register it through your custom Security Provider (like Bouncy Castle does) with some name MySHA3PRNG. After that, you can get an instance of it with name MySHA3PRNG as you do for SHA1PRNG. The rest remains as-is.

A major problem with this tricky part might be as follows: Quoting from here,

The paper "Sponge-based pseudo-random number generators" talks about just that and it also describes a clean and efficient way to construct a re-seedable PRNG with a (Keccak) sponge function. What you'll get is a PRNG based on a cryptographic hash function… with the usual security implications.

For example: the paper explicitly states that you should reseed regularly with sufficient entropy to prevent an attacker from going backwards on the period of the PRNG (which is probably what you've been hearing about).

However, what you need is a PRNG algorithm that does not need to be re-seeded. I hope you have sufficient theoretical background to prove that your custom PRNG algorithm is secure.

Good luck!

Community
  • 1
  • 1
ram
  • 1,099
  • 1
  • 7
  • 22
  • 1
    You don't need to create a custom security provider. You only need to (carefully) subclass SecureRandom and pass instances of that around. You can't use `SecureRandom.getInstance()` though, you have to do `new MySecureRandom()`. – President James K. Polk Apr 27 '18 at 23:30
  • 1
    @JamesKPolk that might be an option, though the provider solution seems more elegant to me. Anyway, the crucial part is to implement PRNG from hash. – ram Apr 28 '18 at 00:11
  • 1
    @Ram thanks, I have scrapped java.security and am implementing my own algorithm for key generation and signatures on ECDSA. Sometimes you just need to write the code yourself to get what you need. Btw, just to show that I'm not crazy for asking this feature here is a page in Ripple which implements this in Java. https://github.com/ripple/ripple-lib-java/blob/master/ripple-core/src/main/java/com/ripple/crypto/ecdsa/ECDSASignature.java – devssh Apr 29 '18 at 05:41
  • @James SecureRandom needs to be serialized and stored if it is initialized as new SecureRandom(). See alexbt's reponse below https://stackoverflow.com/questions/7608089/should-i-seed-a-securerandom – devssh Apr 29 '18 at 06:07
  • The problem with SHA1PRNG is it uses 256 bits which can be brute forced. The pseudo-random part is taken care of by giving it a seed with sufficient randomness. That's why we need SHA3 which is inherently deterministic for a given seed while being sufficiently random as long as the seed is random. – devssh Apr 29 '18 at 06:18
  • @Ram I intend to reseed it sufficiently across users. I just don't intend to re-seed it for a single user unless they ask for a re-seed or provide separate seed, or bypass my client and make their own client to sign stuff. – devssh Apr 29 '18 at 06:21
  • @devssh: Nothing about serialization is mentioned anywhere in the question, answers, or comments. And I see no reason to serialize anything, although of course you do need to save the seed if you want to regenerate the same sequence of randoms. Oh, and you can't brute force 256 bits. – President James K. Polk Apr 29 '18 at 14:13
  • 1
    To implement a provider in Oracle Java 9 check out [this link](https://docs.oracle.com/javase/9/security/howtoimplaprovider.htm#JSSEC-GUID-C485394F-08C9-4D35-A245-1B82CDDBC031) – President James K. Polk Apr 29 '18 at 14:42
  • @JamesKPolk here is the post for the serialization https://stackoverflow.com/questions/36791447/generate-keypair-with-randomsecure/36797232#36797232 Here is an article on how Brute force collision was achieved on SHA1 https://thehackernews.com/2017/02/sha1-collision-attack.html – devssh Apr 29 '18 at 14:44
  • Well, there is no reason to serialize regardless of that post, please serialization-deserialization is not guaranteed to work. And you are quite misunderstanding the security implications of the collision weaknesses of SHA1 and how they pertain to SHA1PRNG. – President James K. Polk Apr 29 '18 at 15:09