기본적으로 주석에는 이미 언급 한 마크 스페이스와 같은 공유 비밀이 필요합니다.
하나는 단순히 의사 난수 생성기를 사용하고 seed는 SecureRandom
인스턴스 (예 : SecureRandom
)를 명시 적으로 사용합니다. seed
공유 비밀을 나타내는 바이트 배열 (예를 들어, 16 바이트, AES 키의 크기)이며
new SecureRandom(seed);
. 결과 인스턴스에 대한 호출을 동기화하는 한 값은 동일해야합니다.
구현 및 알고리즘은 플랫폼간에 다를 수 있습니다
물론 구체적인 getInstance
방법을 사용하여 범위를 좁힐 수는 있지만 문제가 완전히 해결되지는 않습니다. 나는 주로 그것을 사용하고 싶습니다 - 그리고 저는 현재 테스트 목적으로 그것을 실제로 사용하고 있습니다.
또 다른 방법은 키 스트림을 생성하는 스트림 암호 입력으로서 공유 비밀을 사용하는 것이다. 이 키 스트림은 일반적으로 평문과 XOR되어 암호문을 형성합니다. 가장 쉬운 스트림 암호 중 하나는 카운터 모드 (CTR 또는 SIC 모드)의 AES입니다. 이 필요한 모든 기능을 포함하는 구현을 보려면 아래를 참조하십시오.
이러한 접근 방식의 문제점은 결과 키 스트림이 비트 단위이기 때문에 SecureRandom
클래스의 모든 이점이나 그것이 제공하는 호환성을 얻지 못한다는 것입니다. 불행하게도 Java는 Duck 타이핑을 제공하지 않습니다 (같은 메소드를 가진 클래스가 다른 타입과 같은 것으로 간주되는 곳).
이 문제를 해결하는 방법은 두 가지입니다. SecureRandomSpi
을 구현하면 의 경우 대부분을 제외한 모든 문제를 처리합니다. 서명 된 공급자을 만들지 않고 서비스 공급자 구현 을 사용할 수 없으므로 Oracle에서 서명 키가 필요합니다. 다른 방법은 SecureRandom
을 직접 구현하고 모두 임의의 시퀀스를 반환하는 메서드를 재정의하는 것입니다. 이후 버전의 Java에서 추가 메서드는 여전히 지원되지 않습니다.어느 쪽도 매우 매력적이지 않다.
주 :
- 불행하게도 내가 어떤 SecureRandom의 PRNG의 자바 잘 결정하고 잘 정의되어 있는지 알지 못한다.
- 두 기기 모두에서 공유 된 비밀번호를 안전하게 생성 할 수있는 방법이 있습니다 (예 : Diffie-Hellman을 사용합니다.
package nl.owlstead.stackoverflow;
import java.nio.ByteBuffer;
import java.util.Random;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
* A well-defined pseudo-random generator that is based on a stream cipher.
* <p>
* This class mimics the {@link Random} class method signatures; it however does currently not provide:
* <ul>
* <li>operations returning floats or doubles including returning a Gaussian value in the range [0, 1.0) </li>
* <li>streams of integers or longs</li>
* </ul>
* due to laziness of the developer.
* It does not allow for re-seeding as re-seeding is not defined for a stream cipher;
* the same goes from retrieving a seed from the underlying entropy source as it hasn't got one.
* <p>
* It is assumed that most significant (leftmost) bytes are taken from the stream cipher first.
* All the algorithms used to return the random values are well defined, so that compatible implementations can be generated.
* <p>
* Instances of this class are stateful and not thread safe.
*
* @author Maarten Bodewes
*/
public class StreamCipherPseudoRandom {
private static final long TWO_POW_48 = 1L << 48;
private final Cipher streamCipher;
// must be a buffer of at least 6 bytes
// a buffer that is x times 16 is probably most efficient for AES/CTR mode encryption within getBytes(byte[])
private final ByteBuffer zeros = ByteBuffer.allocate(64);
/**
* Creates a SecureRandom from a stream cipher.
*
* @param streamCipher an initialized stream cipher
* @throws NullPointerException if the cipher is <code>null</code>
* @throws IllegalStateException if the cipher is not initialized
* @throws IllegalArgumentException if the cipher is not a stream cipher
*/
public StreamCipherPseudoRandom(final Cipher streamCipher) {
if (streamCipher.getOutputSize(1) != 1) {
throw new IllegalArgumentException("Not a stream cipher");
}
this.streamCipher = streamCipher;
}
/**
* Generates a pseudo-random number of bytes by taking exactly the required number of bytes from the stream cipher.
*
* @param data the buffer to be randomized
*/
public void nextBytes(final byte[] data) {
generateRandomInBuffer(ByteBuffer.wrap(data));
}
/**
* Generates a pseudo-random boolean value by taking exactly 1 byte from the stream cipher,
* returning true if and only if the returned value is odd (i.e. if the least significant bit is set to 1), false otherwise.
*
* @return the random boolean
*/
public boolean nextBoolean() {
return (generateRandomInBuffer(ByteBuffer.allocate(Byte.BYTES)).get() & 1) == 1;
}
/**
* Generates a pseudo-random <code>int</code> value by taking exactly 4 bytes from the stream cipher.
*
* @return the random <code>int</code> value
*/
public int nextInt() {
return generateRandomInBuffer(ByteBuffer.allocate(Integer.BYTES)).getInt();
}
/**
* Generates a pseudo-random <code>long</code> value by taking exactly 8 bytes from the stream cipher.
*
* @return the random <code>long</code> value
*/
public long nextLong() {
return generateRandomInBuffer(ByteBuffer.allocate(Long.BYTES)).getLong();
}
/**
* Generates a pseudo-random <code>int</code> value with <code>bits</code> random bits in the lower part of the returned integer.
* This method takes the minimum number of bytes required to hold the required number of bits from the stream cipher (e.g. 13 bits requires 2 bytes to hold them).
*
* @param bits the number of bits in the integer, between 0 and 32
* @return the random <code>int</code> value in the range [0, 2^n) where n is the number of bits
*/
public int next(final int bits) {
final int bytes = (bits + Byte.SIZE - 1)/Byte.SIZE;
final ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
buf.position(Integer.BYTES - bytes);
generateRandomInBuffer(buf);
final long l = buf.getInt(0);
final long m = (1L << bits) - 1;
return (int) (l & m);
}
/**
* Generates a pseudo-random <code>int</code> value in a range [0, n) by:
*
* <ol>
* <li>taking 6 bytes from the stream cipher and converting it into a number y</li>
* <li>restart the procedure if y is larger than x * n where x is the largest value such that x * n <= 2^48
* <li>return y % n
* </ol>
*
* An exception to this rule is for n is 1 in which case this method direct returns 0, without taking any bytes from the stream cipher.
* @param n the maximum value (exclusive) - n must be a non-zero positive number
* @return the random <code>int</code> value in the range [0, n)
* @throws IllegalArgumentException if n is zero or negative
*/
public int nextInt(final int n) {
if (n <= 0) {
throw new IllegalArgumentException("max cannot be negative");
} else if (n == 1) {
// only one choice
return 0;
}
final ByteBuffer buf = ByteBuffer.allocate(48/Byte.SIZE);
long maxC = TWO_POW_48 - TWO_POW_48 % n;
long l;
do {
buf.clear();
generateRandomInBuffer(buf);
// put 16 bits into position 32 to 47
l = (buf.getShort() & 0xFFFFL) << Integer.SIZE;
// put 32 bits into position 0 to 31
l |= buf.getInt() & 0xFFFFFFFFL;
} while (l > maxC);
return (int) (l % n);
}
/**
* Retrieves random bytes from the underlying stream cipher.
* All methods that affect the stream cipher should use this method.
* The bytes between the position and the limit will contain the random bytes; position and limit are left unchanged.
* <p>
* The buffer may not be read only and must support setting a mark; previous marks are discarded.
*
* @param buf the buffer to receive the bytes between the position and limit
* @return the same buffer, to allow for
*/
protected ByteBuffer generateRandomInBuffer(final ByteBuffer buf) {
while (buf.hasRemaining()) {
// clear the zeros buffer
zeros.clear();
// set the number of zeros to process
zeros.limit(Math.min(buf.remaining(), zeros.capacity()));
try {
// process the zero's into buf (note that the input size is leading)
buf.mark();
streamCipher.update(zeros, buf);
} catch (ShortBufferException e) {
// not enough output size, which cannot be true for a stream cipher
throw new IllegalStateException(
String.format("Cipher %s not behaving as a stream cipher", streamCipher.getAlgorithm()));
}
}
buf.reset();
return buf;
}
public static void main(String[] args) throws Exception {
Cipher streamCipher = Cipher.getInstance("AES/CTR/NoPadding");
// zero key and iv for demo purposes only
SecretKey aesKey = new SecretKeySpec(new byte[24], "AES");
IvParameterSpec iv = new IvParameterSpec(new byte[16]);
streamCipher.init(Cipher.ENCRYPT_MODE, aesKey, iv);
StreamCipherPseudoRandom rng = new StreamCipherPseudoRandom(streamCipher);
// chosen by fair dice roll, guaranteed to be random
System.out.println(rng.nextInt(6) + 1);
}
}
내 i7의 노트북 (균형 잡힌 전원 설정에 약 5 초를 사용
OK, 그래서 여기에 최적화 된 (만 제한적으로 테스트) 구현은 제공이에게 자신을해야 할 수도 있습니다 (1 GiB 입력 배열) 및 카운터 모드에서는 AES-128이고 RC4에서는 4 바이트입니다. 위의 AES-192를 사용하면 XKCD 농담이 효과가 없습니다.
두 장치 모두에서 난수 생성기를 똑같이 시드하려는 것처럼 들립니다. – khelwood
사용자/코더가 저장된 시드와 알고리즘을 볼 수 있으면 100 % 정확도로 시퀀스를 예측할 수 있습니다. –
TOTP와 같은 것이 당신이 찾고있는 것일 수도 있습니다. –