2017-04-14 2 views
2

사용자/코더가 다음 번호 또는 정수 시리즈를 예측할 수 없으면 Java의 두 장치에서 동일한 난수를 생성하는 안전한 방법이 있습니까? 첫 번째 사용자와 같은 동기화 된 시작이 프로그램 실행시 동일한 번호를 입력한다고 생각했습니다.이 번호는 암호화 기술 (?)을 사용하여 처리 될 수 있습니다. 그리고 두 장치 모두에서 같은 번호 계열을 생성합니다. 그러나 나는 그것을 어떻게하는지 그리고 얼마나 안전한지 정말로 모른다.서로 다른 두 장치에서 동기화 된 난수를 생성 할 수 있습니까?

참고 : 검색했지만이 특정 상황에 대한 충분한 지식이 없습니다.

+2

두 장치 모두에서 난수 생성기를 똑같이 시드하려는 것처럼 들립니다. – khelwood

+1

사용자/코더가 저장된 시드와 알고리즘을 볼 수 있으면 100 % 정확도로 시퀀스를 예측할 수 있습니다. –

+0

TOTP와 같은 것이 당신이 찾고있는 것일 수도 있습니다. –

답변

3

기본적으로 주석에는 이미 언급 한 마크 스페이스와 같은 공유 비밀이 필요합니다.

하나는 단순히 의사 난수 생성기를 사용하고 seed는 SecureRandom 인스턴스 (예 : SecureRandom)를 명시 적으로 사용합니다. seed 공유 비밀을 나타내는 바이트 배열 (예를 들어, 16 바이트, AES 키의 크기)이며

new SecureRandom(seed); 

. 결과 인스턴스에 대한 호출을 동기화하는 한 값은 동일해야합니다.

구현 및 알고리즘은 플랫폼간에 다를 수 있습니다
  • , 예를 들면 :

    그러나이 방법 몇 가지 문제가 있습니다 IBM과 Oracle JDK 또는 Android.

  • Java의 버전에 따라 구현 및 알고리즘이 다를 수 있습니다.
  • 구현 및 알고리즘은 Java에서 다른 런타임에서 다를 수 있습니다.

물론 구체적인 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 농담이 효과가 없습니다.

1

안전한 RNG를 원합니다. 안타깝게도 보안 RNG는 특정 시스템에 고유 한 엔트로피 피드를 통합하므로 서로 다른 시스템에서 동일한 출력을 생성하지 않도록 설계되었습니다.

ECB 모드에서 AES를 사용하고 두 기계에서 동일한 키를 사용하여 숫자 0, 1, 2, 3, ...을 암호화하는 것이 좋습니다. 128 비트 출력 (AES 블록이 128 비트)을 원하지 않으면 블록의 첫 번째 n 비트 또는 마지막 n 비트를 사용하는 데 동의하십시오. 두 시스템을 모두 단계적으로 유지하면 각 시스템은 동일한 순서로 동일한 의사 임의 출력을 생성합니다.