HADOOP-14246. Authentication Tokens should use SecureRandom instead of Random and 256 bit secrets
(Conttributed by Robert Konter via Daniel Templeton)
(cherry picked from commit 4dd6206547
)
This commit is contained in:
parent
1bfb382296
commit
f20aa38a1d
|
@ -15,8 +15,9 @@ package org.apache.hadoop.security.authentication.util;
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
|
||||||
import java.nio.charset.Charset;
|
import java.security.SecureRandom;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import org.apache.hadoop.classification.InterfaceAudience;
|
import org.apache.hadoop.classification.InterfaceAudience;
|
||||||
import org.apache.hadoop.classification.InterfaceStability;
|
import org.apache.hadoop.classification.InterfaceStability;
|
||||||
|
|
||||||
|
@ -32,7 +33,7 @@ public class RandomSignerSecretProvider extends RolloverSignerSecretProvider {
|
||||||
|
|
||||||
public RandomSignerSecretProvider() {
|
public RandomSignerSecretProvider() {
|
||||||
super();
|
super();
|
||||||
rand = new Random();
|
rand = new SecureRandom();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -48,6 +49,8 @@ public class RandomSignerSecretProvider extends RolloverSignerSecretProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected byte[] generateNewSecret() {
|
protected byte[] generateNewSecret() {
|
||||||
return Long.toString(rand.nextLong()).getBytes(Charset.forName("UTF-8"));
|
byte[] secret = new byte[32]; // 32 bytes = 256 bits
|
||||||
|
rand.nextBytes(secret);
|
||||||
|
return secret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ package org.apache.hadoop.security.authentication.util;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
import java.security.SecureRandom;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -149,7 +150,7 @@ public class ZKSignerSecretProvider extends RolloverSignerSecretProvider {
|
||||||
|
|
||||||
public ZKSignerSecretProvider() {
|
public ZKSignerSecretProvider() {
|
||||||
super();
|
super();
|
||||||
rand = new Random();
|
rand = new SecureRandom();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -342,8 +343,11 @@ public class ZKSignerSecretProvider extends RolloverSignerSecretProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] generateRandomSecret() {
|
@VisibleForTesting
|
||||||
return Long.toString(rand.nextLong()).getBytes(Charset.forName("UTF-8"));
|
protected byte[] generateRandomSecret() {
|
||||||
|
byte[] secret = new byte[32]; // 32 bytes = 256 bits
|
||||||
|
rand.nextBytes(secret);
|
||||||
|
return secret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -14,22 +14,37 @@
|
||||||
package org.apache.hadoop.security.authentication.util;
|
package org.apache.hadoop.security.authentication.util;
|
||||||
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
|
import org.apache.log4j.Level;
|
||||||
|
import org.apache.log4j.LogManager;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.spy;
|
||||||
|
import static org.mockito.Mockito.timeout;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
public class TestRandomSignerSecretProvider {
|
public class TestRandomSignerSecretProvider {
|
||||||
|
|
||||||
|
// rollover every 50 msec
|
||||||
|
private final int timeout = 100;
|
||||||
|
private final long rolloverFrequency = timeout / 2;
|
||||||
|
|
||||||
|
{
|
||||||
|
LogManager.getLogger(
|
||||||
|
RolloverSignerSecretProvider.LOG.getName()).setLevel(Level.DEBUG);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetAndRollSecrets() throws Exception {
|
public void testGetAndRollSecrets() throws Exception {
|
||||||
long rolloverFrequency = 15 * 1000; // rollover every 15 sec
|
// Use the same seed and a "plain" Random so we can predict the RNG
|
||||||
// use the same seed so we can predict the RNG
|
|
||||||
long seed = System.currentTimeMillis();
|
long seed = System.currentTimeMillis();
|
||||||
Random rand = new Random(seed);
|
Random rand = new Random(seed);
|
||||||
byte[] secret1 = Long.toString(rand.nextLong()).getBytes();
|
byte[] secret1 = generateNewSecret(rand);
|
||||||
byte[] secret2 = Long.toString(rand.nextLong()).getBytes();
|
byte[] secret2 = generateNewSecret(rand);
|
||||||
byte[] secret3 = Long.toString(rand.nextLong()).getBytes();
|
byte[] secret3 = generateNewSecret(rand);
|
||||||
RandomSignerSecretProvider secretProvider =
|
MockRandomSignerSecretProvider secretProvider =
|
||||||
new RandomSignerSecretProvider(seed);
|
spy(new MockRandomSignerSecretProvider(seed));
|
||||||
try {
|
try {
|
||||||
secretProvider.init(null, null, rolloverFrequency);
|
secretProvider.init(null, null, rolloverFrequency);
|
||||||
|
|
||||||
|
@ -39,7 +54,8 @@ public class TestRandomSignerSecretProvider {
|
||||||
Assert.assertEquals(2, allSecrets.length);
|
Assert.assertEquals(2, allSecrets.length);
|
||||||
Assert.assertArrayEquals(secret1, allSecrets[0]);
|
Assert.assertArrayEquals(secret1, allSecrets[0]);
|
||||||
Assert.assertNull(allSecrets[1]);
|
Assert.assertNull(allSecrets[1]);
|
||||||
Thread.sleep(rolloverFrequency + 2000);
|
verify(secretProvider, timeout(timeout).atLeastOnce()).rollSecret();
|
||||||
|
secretProvider.realRollSecret();
|
||||||
|
|
||||||
currentSecret = secretProvider.getCurrentSecret();
|
currentSecret = secretProvider.getCurrentSecret();
|
||||||
allSecrets = secretProvider.getAllSecrets();
|
allSecrets = secretProvider.getAllSecrets();
|
||||||
|
@ -47,7 +63,8 @@ public class TestRandomSignerSecretProvider {
|
||||||
Assert.assertEquals(2, allSecrets.length);
|
Assert.assertEquals(2, allSecrets.length);
|
||||||
Assert.assertArrayEquals(secret2, allSecrets[0]);
|
Assert.assertArrayEquals(secret2, allSecrets[0]);
|
||||||
Assert.assertArrayEquals(secret1, allSecrets[1]);
|
Assert.assertArrayEquals(secret1, allSecrets[1]);
|
||||||
Thread.sleep(rolloverFrequency + 2000);
|
verify(secretProvider, timeout(timeout).atLeast(2)).rollSecret();
|
||||||
|
secretProvider.realRollSecret();
|
||||||
|
|
||||||
currentSecret = secretProvider.getCurrentSecret();
|
currentSecret = secretProvider.getCurrentSecret();
|
||||||
allSecrets = secretProvider.getAllSecrets();
|
allSecrets = secretProvider.getAllSecrets();
|
||||||
|
@ -55,9 +72,40 @@ public class TestRandomSignerSecretProvider {
|
||||||
Assert.assertEquals(2, allSecrets.length);
|
Assert.assertEquals(2, allSecrets.length);
|
||||||
Assert.assertArrayEquals(secret3, allSecrets[0]);
|
Assert.assertArrayEquals(secret3, allSecrets[0]);
|
||||||
Assert.assertArrayEquals(secret2, allSecrets[1]);
|
Assert.assertArrayEquals(secret2, allSecrets[1]);
|
||||||
Thread.sleep(rolloverFrequency + 2000);
|
verify(secretProvider, timeout(timeout).atLeast(3)).rollSecret();
|
||||||
|
secretProvider.realRollSecret();
|
||||||
} finally {
|
} finally {
|
||||||
secretProvider.destroy();
|
secretProvider.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A hack to test RandomSignerSecretProvider.
|
||||||
|
* We want to test that RandomSignerSecretProvider.rollSecret() is
|
||||||
|
* periodically called at the expected frequency, but we want to exclude the
|
||||||
|
* race-condition and not take a long time to run the test.
|
||||||
|
*/
|
||||||
|
private class MockRandomSignerSecretProvider
|
||||||
|
extends RandomSignerSecretProvider {
|
||||||
|
MockRandomSignerSecretProvider(long seed) {
|
||||||
|
super(seed);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
protected synchronized void rollSecret() {
|
||||||
|
// this is a no-op: simply used for Mockito to verify that rollSecret()
|
||||||
|
// is periodically called at the expected frequency
|
||||||
|
}
|
||||||
|
|
||||||
|
public void realRollSecret() {
|
||||||
|
// the test code manually calls RandomSignerSecretProvider.rollSecret()
|
||||||
|
// to update the state
|
||||||
|
super.rollSecret();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] generateNewSecret(Random rand) {
|
||||||
|
byte[] secret = new byte[32];
|
||||||
|
rand.nextBytes(secret);
|
||||||
|
return secret;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,13 +13,11 @@
|
||||||
*/
|
*/
|
||||||
package org.apache.hadoop.security.authentication.util;
|
package org.apache.hadoop.security.authentication.util;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.nio.charset.Charset;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import javax.servlet.ServletContext;
|
import javax.servlet.ServletContext;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
|
||||||
import org.apache.commons.logging.LogFactory;
|
|
||||||
import org.apache.curator.test.TestingServer;
|
import org.apache.curator.test.TestingServer;
|
||||||
import org.apache.log4j.Level;
|
import org.apache.log4j.Level;
|
||||||
import org.apache.log4j.LogManager;
|
import org.apache.log4j.LogManager;
|
||||||
|
@ -37,13 +35,13 @@ public class TestZKSignerSecretProvider {
|
||||||
|
|
||||||
private TestingServer zkServer;
|
private TestingServer zkServer;
|
||||||
|
|
||||||
// rollover every 2 sec
|
// rollover every 50 msec
|
||||||
private final int timeout = 100;
|
private final int timeout = 100;
|
||||||
private final long rolloverFrequency = timeout / 2;
|
private final long rolloverFrequency = timeout / 2;
|
||||||
|
|
||||||
static final Log LOG = LogFactory.getLog(TestZKSignerSecretProvider.class);
|
|
||||||
{
|
{
|
||||||
LogManager.getLogger( RolloverSignerSecretProvider.LOG.getName() ).setLevel(Level.DEBUG);
|
LogManager.getLogger(
|
||||||
|
RolloverSignerSecretProvider.LOG.getName()).setLevel(Level.DEBUG);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
|
@ -63,12 +61,12 @@ public class TestZKSignerSecretProvider {
|
||||||
// Test just one ZKSignerSecretProvider to verify that it works in the
|
// Test just one ZKSignerSecretProvider to verify that it works in the
|
||||||
// simplest case
|
// simplest case
|
||||||
public void testOne() throws Exception {
|
public void testOne() throws Exception {
|
||||||
// use the same seed so we can predict the RNG
|
// Use the same seed and a "plain" Random so we can predict the RNG
|
||||||
long seed = System.currentTimeMillis();
|
long seed = System.currentTimeMillis();
|
||||||
Random rand = new Random(seed);
|
Random rand = new Random(seed);
|
||||||
byte[] secret2 = Long.toString(rand.nextLong()).getBytes();
|
byte[] secret2 = generateNewSecret(rand);
|
||||||
byte[] secret1 = Long.toString(rand.nextLong()).getBytes();
|
byte[] secret1 = generateNewSecret(rand);
|
||||||
byte[] secret3 = Long.toString(rand.nextLong()).getBytes();
|
byte[] secret3 = generateNewSecret(rand);
|
||||||
MockZKSignerSecretProvider secretProvider =
|
MockZKSignerSecretProvider secretProvider =
|
||||||
spy(new MockZKSignerSecretProvider(seed));
|
spy(new MockZKSignerSecretProvider(seed));
|
||||||
Properties config = new Properties();
|
Properties config = new Properties();
|
||||||
|
@ -115,7 +113,7 @@ public class TestZKSignerSecretProvider {
|
||||||
* A hack to test ZKSignerSecretProvider.
|
* A hack to test ZKSignerSecretProvider.
|
||||||
* We want to test that ZKSignerSecretProvider.rollSecret() is periodically
|
* We want to test that ZKSignerSecretProvider.rollSecret() is periodically
|
||||||
* called at the expected frequency, but we want to exclude the
|
* called at the expected frequency, but we want to exclude the
|
||||||
* race-condition.
|
* race-condition and not take a long time to run the test.
|
||||||
*/
|
*/
|
||||||
private class MockZKSignerSecretProvider extends ZKSignerSecretProvider {
|
private class MockZKSignerSecretProvider extends ZKSignerSecretProvider {
|
||||||
MockZKSignerSecretProvider(long seed) {
|
MockZKSignerSecretProvider(long seed) {
|
||||||
|
@ -134,6 +132,116 @@ public class TestZKSignerSecretProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
// HADOOP-14246 increased the length of the secret from 160 bits to 256 bits.
|
||||||
|
// This test verifies that the upgrade goes smoothly.
|
||||||
|
public void testUpgradeChangeSecretLength() throws Exception {
|
||||||
|
// Use the same seed and a "plain" Random so we can predict the RNG
|
||||||
|
long seed = System.currentTimeMillis();
|
||||||
|
Random rand = new Random(seed);
|
||||||
|
byte[] secret2 = Long.toString(rand.nextLong())
|
||||||
|
.getBytes(Charset.forName("UTF-8"));
|
||||||
|
byte[] secret1 = Long.toString(rand.nextLong())
|
||||||
|
.getBytes(Charset.forName("UTF-8"));
|
||||||
|
byte[] secret3 = Long.toString(rand.nextLong())
|
||||||
|
.getBytes(Charset.forName("UTF-8"));
|
||||||
|
rand = new Random(seed);
|
||||||
|
// Secrets 4 and 5 get thrown away by ZK when the new secret provider tries
|
||||||
|
// to init
|
||||||
|
byte[] secret4 = generateNewSecret(rand);
|
||||||
|
byte[] secret5 = generateNewSecret(rand);
|
||||||
|
byte[] secret6 = generateNewSecret(rand);
|
||||||
|
byte[] secret7 = generateNewSecret(rand);
|
||||||
|
// Initialize the znode data with the old secret length
|
||||||
|
MockZKSignerSecretProvider oldSecretProvider =
|
||||||
|
spy(new OldMockZKSignerSecretProvider(seed));
|
||||||
|
Properties config = new Properties();
|
||||||
|
config.setProperty(
|
||||||
|
ZKSignerSecretProvider.ZOOKEEPER_CONNECTION_STRING,
|
||||||
|
zkServer.getConnectString());
|
||||||
|
config.setProperty(ZKSignerSecretProvider.ZOOKEEPER_PATH,
|
||||||
|
"/secret");
|
||||||
|
try {
|
||||||
|
oldSecretProvider.init(config, getDummyServletContext(),
|
||||||
|
rolloverFrequency);
|
||||||
|
|
||||||
|
byte[] currentSecret = oldSecretProvider.getCurrentSecret();
|
||||||
|
byte[][] allSecrets = oldSecretProvider.getAllSecrets();
|
||||||
|
Assert.assertArrayEquals(secret1, currentSecret);
|
||||||
|
Assert.assertEquals(2, allSecrets.length);
|
||||||
|
Assert.assertArrayEquals(secret1, allSecrets[0]);
|
||||||
|
Assert.assertNull(allSecrets[1]);
|
||||||
|
oldSecretProvider.realRollSecret();
|
||||||
|
|
||||||
|
currentSecret = oldSecretProvider.getCurrentSecret();
|
||||||
|
allSecrets = oldSecretProvider.getAllSecrets();
|
||||||
|
Assert.assertArrayEquals(secret2, currentSecret);
|
||||||
|
Assert.assertEquals(2, allSecrets.length);
|
||||||
|
Assert.assertArrayEquals(secret2, allSecrets[0]);
|
||||||
|
Assert.assertArrayEquals(secret1, allSecrets[1]);
|
||||||
|
} finally {
|
||||||
|
oldSecretProvider.destroy();
|
||||||
|
}
|
||||||
|
// Now use a ZKSignerSecretProvider with the newer length
|
||||||
|
MockZKSignerSecretProvider newSecretProvider =
|
||||||
|
spy(new MockZKSignerSecretProvider(seed));
|
||||||
|
try {
|
||||||
|
newSecretProvider.init(config, getDummyServletContext(),
|
||||||
|
rolloverFrequency);
|
||||||
|
|
||||||
|
byte[] currentSecret = newSecretProvider.getCurrentSecret();
|
||||||
|
byte[][] allSecrets = newSecretProvider.getAllSecrets();
|
||||||
|
Assert.assertArrayEquals(secret2, currentSecret);
|
||||||
|
Assert.assertEquals(2, allSecrets.length);
|
||||||
|
Assert.assertArrayEquals(secret2, allSecrets[0]);
|
||||||
|
Assert.assertArrayEquals(secret1, allSecrets[1]);
|
||||||
|
newSecretProvider.realRollSecret();
|
||||||
|
|
||||||
|
currentSecret = newSecretProvider.getCurrentSecret();
|
||||||
|
allSecrets = newSecretProvider.getAllSecrets();
|
||||||
|
Assert.assertArrayEquals(secret3, currentSecret);
|
||||||
|
Assert.assertEquals(2, allSecrets.length);
|
||||||
|
Assert.assertArrayEquals(secret3, allSecrets[0]);
|
||||||
|
Assert.assertArrayEquals(secret2, allSecrets[1]);
|
||||||
|
newSecretProvider.realRollSecret();
|
||||||
|
|
||||||
|
currentSecret = newSecretProvider.getCurrentSecret();
|
||||||
|
allSecrets = newSecretProvider.getAllSecrets();
|
||||||
|
Assert.assertArrayEquals(secret6, currentSecret);
|
||||||
|
Assert.assertEquals(2, allSecrets.length);
|
||||||
|
Assert.assertArrayEquals(secret6, allSecrets[0]);
|
||||||
|
Assert.assertArrayEquals(secret3, allSecrets[1]);
|
||||||
|
newSecretProvider.realRollSecret();
|
||||||
|
|
||||||
|
currentSecret = newSecretProvider.getCurrentSecret();
|
||||||
|
allSecrets = newSecretProvider.getAllSecrets();
|
||||||
|
Assert.assertArrayEquals(secret7, currentSecret);
|
||||||
|
Assert.assertEquals(2, allSecrets.length);
|
||||||
|
Assert.assertArrayEquals(secret7, allSecrets[0]);
|
||||||
|
Assert.assertArrayEquals(secret6, allSecrets[1]);
|
||||||
|
} finally {
|
||||||
|
newSecretProvider.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A version of {@link MockZKSignerSecretProvider} that uses the old way of
|
||||||
|
* generating secrets (160 bit long).
|
||||||
|
*/
|
||||||
|
private class OldMockZKSignerSecretProvider
|
||||||
|
extends MockZKSignerSecretProvider {
|
||||||
|
private Random rand;
|
||||||
|
OldMockZKSignerSecretProvider(long seed) {
|
||||||
|
super(seed);
|
||||||
|
rand = new Random(seed);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected byte[] generateRandomSecret() {
|
||||||
|
return Long.toString(rand.nextLong()).getBytes(Charset.forName("UTF-8"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMultiple1() throws Exception {
|
public void testMultiple1() throws Exception {
|
||||||
testMultiple(1);
|
testMultiple(1);
|
||||||
|
@ -151,19 +259,19 @@ public class TestZKSignerSecretProvider {
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public void testMultiple(int order) throws Exception {
|
public void testMultiple(int order) throws Exception {
|
||||||
|
// Use the same seed and a "plain" Random so we can predict the RNG
|
||||||
long seedA = System.currentTimeMillis();
|
long seedA = System.currentTimeMillis();
|
||||||
Random rand = new Random(seedA);
|
Random rand = new Random(seedA);
|
||||||
byte[] secretA2 = Long.toString(rand.nextLong()).getBytes();
|
byte[] secretA2 = generateNewSecret(rand);
|
||||||
byte[] secretA1 = Long.toString(rand.nextLong()).getBytes();
|
byte[] secretA1 = generateNewSecret(rand);
|
||||||
byte[] secretA3 = Long.toString(rand.nextLong()).getBytes();
|
byte[] secretA3 = generateNewSecret(rand);
|
||||||
byte[] secretA4 = Long.toString(rand.nextLong()).getBytes();
|
byte[] secretA4 = generateNewSecret(rand);
|
||||||
// use the same seed so we can predict the RNG
|
|
||||||
long seedB = System.currentTimeMillis() + rand.nextLong();
|
long seedB = System.currentTimeMillis() + rand.nextLong();
|
||||||
rand = new Random(seedB);
|
rand = new Random(seedB);
|
||||||
byte[] secretB2 = Long.toString(rand.nextLong()).getBytes();
|
byte[] secretB2 = generateNewSecret(rand);
|
||||||
byte[] secretB1 = Long.toString(rand.nextLong()).getBytes();
|
byte[] secretB1 = generateNewSecret(rand);
|
||||||
byte[] secretB3 = Long.toString(rand.nextLong()).getBytes();
|
byte[] secretB3 = generateNewSecret(rand);
|
||||||
byte[] secretB4 = Long.toString(rand.nextLong()).getBytes();
|
byte[] secretB4 = generateNewSecret(rand);
|
||||||
MockZKSignerSecretProvider secretProviderA =
|
MockZKSignerSecretProvider secretProviderA =
|
||||||
spy(new MockZKSignerSecretProvider(seedA));
|
spy(new MockZKSignerSecretProvider(seedA));
|
||||||
MockZKSignerSecretProvider secretProviderB =
|
MockZKSignerSecretProvider secretProviderB =
|
||||||
|
@ -258,4 +366,10 @@ public class TestZKSignerSecretProvider {
|
||||||
.thenReturn(null);
|
.thenReturn(null);
|
||||||
return servletContext;
|
return servletContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private byte[] generateNewSecret(Random rand) {
|
||||||
|
byte[] secret = new byte[32];
|
||||||
|
rand.nextBytes(secret);
|
||||||
|
return secret;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue