Arduino에서 진정한 난수 구하기


13

Arduino에서 (의사와 달리) 임의의 숫자를 얻는 가장 좋은 방법은 무엇입니까? 내 이해에 따르면 randomSeed (analogRead (x)) 함수는 충분하지 않습니다.

가능하면이 방법은 기본 Arduino 설정 만 사용해야합니다 (추가 센서 없음). 외부 센서가있는 솔루션은 기본 설정에 비해 무작위성이 크게 향상되는 경우 환영합니다.


응용 프로그램은 무엇입니까? 암호로 안전해야합니까? 무작위로 무엇을하고 있습니까? 그런 다음 물리적 엔트로피 소스에서 TRNG를 구현하는 외부 칩이 없으면 운이 없습니다. HMAC DRBG와 같은 결정 론적 RNG를 구현하고 정적 엔트로피 소스와 낮은 품질의 엔트로피 소스에서 시드 할 수 있지만 여전히 암호화 적으로 안전하지는 않습니다.
막시밀리안 게르 하트

예, 암호화 적으로 안전한 응용 프로그램에는 난수가 필요합니다.
Rexcirus

답변:


10

엔트로피 라이브러리 사용 :

진정한 난수의 안정적인 스트림을 생성하는 워치 독 타이머의 자연스러운 지터

마이크로 컨트롤러의 핀을 사용하지 않고 외부 회로가 필요하지 않기 때문에이 솔루션이 마음에 듭니다. 또한 외부 고장의 영향을 덜받습니다.

라이브러리 외에도 라이브러리없이 마이크로 컨트롤러의 PRNG에 대해 랜덤 시드를 생성하는 데 사용 된 것과 동일한 기술을 사용하는 방법을 보여주는 스케치를 제공합니다. https://sites.google.com/site/astudyofentropy/project-definition / timer-jitter-entropy-sources / entropy-library / arduino- 랜덤 시드


8

randomSeed(analogRead(x))255 개의 시퀀스 만 생성하므로 모든 콤보를 시도하고 출력 스트림에 커플 링 할 수있는 오라클을 생성하여 모든 출력을 100 % 예측할 수 있습니다. 당신은 올바른 길을 가고 있지만, 그것은 숫자 게임 일뿐이며, 더 많은 것들이 필요합니다. 예를 들어, 4 개의 ADC에서 100 개의 아날로그 판독 값을 가져 와서 모두 합산하면 randomSeed훨씬 더 좋습니다. 최대 보안을 위해서는 예측할 수없는 입력과 비 결정적 혼합이 모두 필요합니다.

나는 암호 전문가가 아니지만 하드웨어 및 소프트웨어 무작위 생성기를 연구하고 구축하는 데 수천 시간을 보냈으므로 내가 배운 것들을 공유하겠습니다.

예측할 수없는 입력 :

  • analogRead () (플로팅 핀)
  • GetTemp ()

잠재적으로 예측할 수없는 입력 :

  • micros () (비 결정적 샘플 기간 포함)
  • 클록 지터 (저 대역폭이지만 사용 가능)
  • readVCC () (배터리로 작동하지 않는 경우)

외부 예측 불가능한 입력 :

  • 온도, 습도 및 압력 센서
  • 마이크
  • LDR 전압 분배기
  • 역 바이어스 트랜지스터 잡음
  • 나침반 / 가속 지터
  • esp8266 wifi 핫스팟 스캔 (ssid, db 등)
  • esp8266 타이밍
  • esp8266 HWRNG- RANDOM_REG32매우 빠르고 예측 불가능한 원 스톱

수집 당신이하고 싶은 마지막 일은 엔트로피를 뱉어내는 것입니다. 동 전통보다 동전 뒤집기를 추측하는 것이 더 쉽습니다. 합산이 좋다. unsigned long bank;나중에 bank+= thisSample;좋다 롤오버됩니다. bank[32]더 좋습니다, 계속 읽으십시오. 각 출력 청크에 대해 적어도 8 개의 입력 샘플을 수집하려고합니다.

중독으로부터 보호 보드를 가열하면 특정 최대 클록 지터가 발생하면 이는 공격 벡터입니다. analogRead () 입력을 향한 블라스팅 RFI와 동일합니다. 또 다른 일반적인 공격은 단순히 장치의 플러그를 뽑아서 축적 된 엔트로피를 모두 덤프합니다. 속도가 빠르더라도 안전하다는 것을 알기 전까지는 숫자를 출력하지 마십시오.

이것이 EEPROM, SD 등을 사용하여 장기적으로 엔트로피를 유지하려는 이유입니다. 32 개의 뱅크를 사용 하는 Fortuna PRNG를 살펴보십시오. 각 뱅크는 이전 뱅크보다 절반 씩 자주 업데이트됩니다. 따라서 합리적인 시간 내에 32 개 은행을 모두 공격하기가 어렵습니다.

처리 "엔트로피"를 수집 한 후에는이를 정리하고 입력과 반대 방향으로 이혼해야합니다. SHA / 1 / 256이 좋습니다. 일반 텍스트 취약점이 없으므로 SHA1 (또는 실제로 MD5)을 속도로 사용할 수 있습니다. 수확, 항상 더 엔트로피 은행 변경 주어지지 동일한 출력을 방지하기 위해 매번 다르다 출력에 "소금"을 추가 항상 전체 entopy 뱅크를 사용하지, 결코 : output = sha1( String(micros()) + String(bank[0]) + [...] );약한 씨에 대한 보호, 샤 기능을 모두 은폐 입력 및 백화 출력, 낮은 누적 엔트 및 기타 일반적인 문제.

타이머 입력을 사용하려면 비 결정적이어야합니다. 이것은 다음과 같이 간단합니다 delayMicroseconds(lastSample % 255). 예측할 수없는 시간을 일시 정지하여 "성공적인"클럭 읽기의 차이를 불균일하게 만듭니다. if(analogRead(A1)>200){...}A1이 잡음이 있거나 동적 입력에 연결되어 있다면 ,처럼 반 정기적으로 수행하십시오 . 플로우의 각 포크를 판별하기 어렵게하면 디 컴파일 / 리핑 된 출력에서 ​​암호화 분석을 방지 할 수 있습니다.

실제 보안은 공격자가 전체 시스템을 알고 여전히이를 극복 할 수없는 경우입니다.

마지막으로 작업을 확인하십시오. ENT.EXE (nix / mac에서도 사용 가능)를 통해 출력을 실행하고 좋은지 확인하십시오. 가장 중요한 것은 카이 제곱 분포이며 보통 33 %에서 66 % 사이 여야합니다. 1.43 % 또는 99.999 % 또는 이와 같이 초초 한 테스트를 두 번 이상 받으면 랜덤이 불량입니다. 또한 엔트로피 ENT 보고서는 가능한 한 바이트 당 8 비트에 가깝게> 7.9를 원합니다.

TLDR : 가장 간단한 방법은 ESP8266의 HWRNG입니다. 빠르고 균일하며 예측할 수 없습니다. Ardunio 코어를 실행하는 ESP8266에서 다음과 같이 실행하고 직렬을 사용하여 AVR과 대화하십시오.

// ESP8266 Arduino core code:
void setup(){
 Serial.begin(9600); // or whatever
}

void loop() {
  // Serial.write((char)(RANDOM_REG32 % 256)); // "bin"
  Serial.print( String(RANDOM_REG32, HEX).substring(1)); // "hex"
}

** 편집하다

여기에 콜렉터가 아닌 직렬 포트에서 전체 CSPRNG가 작동하면서 잠시 동안 쓴 베어 보드 HWRNG 스케치가 있습니다. 프로 미니 용으로 제작되었지만 다른 보드에 쉽게 적용 할 수 있어야합니다. 플로팅 아날로그 핀만 사용할 수는 있지만 선호하는 다른 항목을 추가하는 것이 좋습니다. 마이크, LDR, 서미스터 (실내 온도가 최대로 퍼짐) 및 긴 전선처럼. 적당한 소음이 있으면 ENT에서 꽤 잘 수행됩니다.

이 스케치에는 대답과 후속 의견에서 언급 한 몇 가지 개념이 통합되어 있습니다. 그것은 "역동적 인 모든 것을 짐작하고"암호 프리미티브를 사용하여 혼합하는 것을 선호하여 엔트로피 품질 추정을 잊어 버렸습니다.

// AVR (ardunio) HWRNG by dandavis. released to public domain by author.
#include <Hash.h> 

unsigned long read[8] = {0, 0, 0, 0, 0, 0, 0, 0};
const int pincount = 9; // adjust down for non pro-mini boards
int pins[9] = {A0, A1, A2, A3, A4, A5, A6, A7, A0}; // adjust for board, name analog inputs to be sampled
unsigned int ticks = 0;
String buff = ""; // holds one round of derivation tokens to be hashed.
String cache; // the last read hash



void harvest() { // String() slows down the processing, making micros() calls harder to recreate
  unsigned long tot = 0; // the total of all analog reads
  buff = String(random(2147483647)) + String(millis() % 999);
  int seed =  random(256) + (micros() % 32);
  int offset =  random(2147483647) % 256;

  for (int i = 0; i < 8; i++) {
    buff += String( seed + read[i] + i + (ticks % 65), HEX );
    buff += String(random(2147483647), HEX);
    tot += read[i];
  }//next i

  buff += String( (micros() + ticks + offset) % 99999, HEX);
  if (random(10) < 3) randomSeed(tot + random(2147483647) + micros()); 
  buff = sha1( String(random(2147483647)) + buff + (micros()%64) + cache); // used hash to uniform output and waste time
  Serial.print( buff ); // output the hash
  cache = buff;
  spin();
}//end harvest()


void spin() { // add entropy and mix
  ticks++;
  int sample = 128;
  for (int i = 0; i < 8; i++) { // update ~6/8 banks 8 times
    read[ read[i] % 8] += (micros() % 128);
    sample = analogRead(  pins[i] ); // a read from each analog pin
    read[ micros() % 8] += ( read[i] % 64 ); // mix timing and 6LSBs from read
    read[i] += sample; // mix whole raw sample
    read[(i + 1) % 8] += random(2147483647) % 1024; // mix prng
    read[ticks % 8] += sample % 16; // mix the best nibble of the read
    read[sample % 8] += read[ticks % 8] % 2147483647; // intra-mix banks
  }

}//end spin()



void setup() {
  Serial.begin(9600);
  delay(222);
  int mx = 2028 + ((analogRead(A0)  + analogRead(A1) + analogRead(A2)  + analogRead(A3)) % 256);  
  while (ticks < mx) {
    spin();
    delay(1);
    randomSeed(read[2] + read[1] + read[0] + micros() + random(4096) + ticks);
  }// wend
}// end setup()



void loop() {
  spin();
  delayMicroseconds((read[ micros() % 8] %  2048) + 333  );
  delay(random(10));
  //if (millis() < 500) return;
  if ((ticks % 16) == (millis() % 16) ) harvest();
}// end loop()

(여기 문자가 짧습니다. 죄송합니다.) 개요가 훌륭합니다! 소금에 카운터를 사용하는 것이 좋습니다. micros ()는 호출 사이에 여러 단계로 이동할 수 있기 때문에 비트 낭비입니다. 아날로그 입력에서 높은 비트를 피하고 가장 낮은 1 또는 2 비트로 제한하십시오. 표적 공격을하더라도 입력에 전선을 꽂을 수 없다면 핀을 고정하기가 어렵습니다. "비 결정적 믹싱"은 소프트웨어에서 할 수있는 것이 아닙니다. SHA-1 믹싱이 표준화되었습니다 : crypto.stackexchange.com/a/6232 . 과연. 당신이 제안하는 타이머는 이미 가지고있는 소스만큼 무작위입니다. 여기서 많이 얻지 못했습니다.
Jonas Schäfer

sha는 단순화하고 보호하므로 예를 들어 아날로그 입력에서 몇 비트를 가져올 지 걱정할 필요가 없습니다. 아날로그 (또는 구불 구불 한 PCB 트레이스)에 연결된 몇 인치의 와이어는 몇 비트 이상 스윙합니다. 축적 된 값의 서브 샘플과 함께 해시에 공급 된 저장되지 않고 알려지지 않은 염으로 인해 혼합은 비 결정적이다. micros ()는 비 결정적 간격으로 실행될 때 카운터보다 재생하기가 더 어렵습니다.
dandavis

1
질문이 있습니다. 당신은 100 가지 조치를 취하는 것이 더 좋다고 말했습니다. 그러나 이러한 "무작위"데이터를 수집하는 효과를 제한하는 일종의 "평균"조치를 많이 취하지 않습니까? 나는 ... (너무 적은 "랜덤")는 일반적으로 적은 소음을 얻기 위해 평균, 측정을 의미
frarugi87

글쎄, 나는 일정한 샘플링을 권장합니다 .100은 1보다 낫습니다. 더 많은 조합을 제공하기 때문입니다. Yarrow / Fortuna와 같은 누적 모델이 여전히 훨씬 좋습니다. 해싱하기 전에 100 개의 아날로그 샘플을 연결 (합산하지 않음)하는 것을 고려하십시오. 샘플 순서를 중요하게 만들고 하나의 문자를 제거하면 완전히 다른 해시가 생성되므로 더욱 강력합니다. 따라서 노이즈를 줄이기 위해 샘플의 평균을 구할 수는 있지만 공격자는 모든 값을 암송하거나 일치하지 않아야합니다. 제 주요 요점은 특정 노이즈 소스를 옹호하는 것보다 "누적, 혼합 및 확인"입니다.
dandavis

4

내 경험 analogRead()으로는 플로팅 핀에서 엔트로피가 매우 낮습니다. 통화 당 하나 또는 두 비트의 임의성 일 수 있습니다. 당신은 분명히 더 나은 것을 원합니다. per1234의 답변에서 제안한 바와 같이 워치 독 타이머의 지터는 좋은 대안입니다. 그러나 엔트로피는 매우 느린 속도로 생성되므로 프로그램이 시작될 때 필요할 때 문제가 될 수 있습니다. dandavis에는 몇 가지 좋은 제안이 있지만 일반적으로 ESP8266 또는 외부 하드웨어가 필요합니다.

아직 언급되지 않은 흥미로운 엔트로피 소스가 있습니다 : 초기화되지 않은 RAM의 내용. MCU의 전원을 켜면 일부 RAM 비트 (가장 대칭적인 트랜지스터가있는 비트)가 임의의 상태로 시작됩니다. 이 해커 데이 기사 에서 논의했듯이 , 이것은 엔트로피 소스로 사용될 수 있습니다. 콜드 부팅에서만 사용할 수 있으므로 초기 엔트로피 풀을 채우는 데 사용할 수 있으며 잠재적으로 느린 다른 소스에서 주기적으로 보충합니다. 이렇게하면 풀이 천천히 채워질 때까지 기다리지 않고도 프로그램이 작업을 시작할 수 있습니다.

다음은 AVR 기반 Arduino에서 수확하는 방법의 예입니다. 아래의 코드 스 니펫은 나중에 RAM에 시드를 구축하기 위해 전체 RAM을 XOR합니다 srandom(). 까다로운 부분은 C 런타임이 .data 및 .bss 메모리 섹션을 초기화 하기 전에 수집을 수행 한 다음 시드를 C 런타임이 덮어 쓰지 않는 곳에 저장해야한다는 것입니다. 특정 메모리 섹션 을 사용하여 수행됩니다 .

uint32_t __attribute__((section(".noinit"))) random_seed;

void __attribute__((naked, section(".init3"))) seed_from_ram()
{
    const uint32_t * const ramstart = (uint32_t *) RAMSTART;
    const uint32_t * const ramend   = (uint32_t *) RAMEND;
    uint32_t seed = 0;
    for (const uint32_t *p = ramstart; p <= ramend; p++)
        seed ^= *p;
    random_seed = seed;
}

void setup()
{
    srandom(random_seed);
}

A의 그 참고 따뜻한의 그것은 여전히 당신의 전체 내용이 풀을 엔트로피가되도록 리셋의 SRAM이 보존됩니다. 이 동일한 코드를 사용하여 재설정 동안 수집 된 엔트로피를 보존 할 수 있습니다.

편집 : 내 초기 버전 에서 로컬을 사용하는 대신 seed_from_ram()전역에서 작동 하는 문제가 수정되었습니다 . 이것은 씨앗이 스스로 XOR되어서 지금까지 수확 된 모든 엔트로피를 파괴 할 수 있습니다.random_seedseed


잘 하셨어요! 훔칠 수 있나요? re : 핀 : 올바르게 활용하면 알 수없는 한두 비트만으로 충분합니다. 그것은 완전한 비밀 (yuck)의 출력 속도만을 제한 할 것이지만 우리가 필요로하는 계산 비밀은 아닙니다.
dandavis

1
@dandavis : 예, 재사용 할 수 있습니다. analogRead()자신이하고있는 일을 안다면 사용할 수있는 것이 옳습니다 . 수영장의 엔트로피 추정치를 업데이트 할 때 무작위성을 과대 평가하지 않도록주의해야합니다. 에 대한 내 지점 analogRead()대부분 가난한 아직 비판으로 의미 자주 반복 "레시피" : randomSeed(analogRead(0)) 단 한 번setup()그것의 정도를 가정합니다.
Edgar Bonet

analogRead(0)호출 당 1 비트의 엔트로피가있는 경우 반복해서 호출하면 10000/8 = 1.25KBytes / sec의 엔트로피가 Entropy 라이브러리의 150 배가됩니다.
Dmitry Grigoryev

0

엔트로피가 실제로 필요하지 않고 모든 시작마다 다른 일련의 의사 난수를 얻으려면 EEPROM을 사용하여 연속 시드를 반복 할 수 있습니다. 기술적으로 프로세스는 완전히 결정 론적이지만 실제적인 관점에서는 randomSeed(analogRead(0))연결되지 않은 핀 보다 훨씬 낫습니다 . 종종 0 또는 1023의 동일한 시드로 시작하게됩니다. 다음 시드를 EEPROM에 저장하면 다른 시드로 시작할 수 있습니다. 매번 씨앗.

#include <EEPROM.h>

const int seed_addr = 0;
unsigned long seed;

void setup() {
    seed = EEPROM.read(seed_addr);
    EEPROM.write(seed_addr, seed+1);
    randomSeed(seed);
}

실제 엔트로피가 필요한 경우 클럭 드리프트 또는 외부 노이즈 증폭을 통해 엔트로피를 수집 할 수 있습니다. 엔트로피가 많이 필요한 경우 외부 노이즈가 유일하게 실행 가능한 옵션입니다. 제너 다이오드는 특히 5-6V 이상의 전압 소스가있는 경우 널리 사용됩니다 (적절한 제너 다이오드에서는 5V에서도 작동하지만 엔트로피는 줄어 듭니다).

여기에 이미지 설명을 입력하십시오

( 소스 ).

앰프 출력은 아날로그 핀에 연결해야합니다. 아날로그 핀은 각각 analogRead()최대 수십 MHz에서 몇 비트의 엔트로피를 생성합니다 (Arduino가 샘플링 할 수있는 것보다 빠름).

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.