Arduino'da gerçekten rasgele bir sayı elde etmek


13

Arduino'da gerçekten (sahte yerine) rasgele bir sayı elde etmek için en iyi yöntem veya en azından mümkün olan en iyi yaklaşım nedir? Anladığım kadarıyla, randomSeed (analogRead (x)) işlevi yeterince rastgele değil.

Mümkünse yöntem tek başına temel Arduino kurulumundan yararlanmalıdır (ek sensör yok). Harici sensörlerdeki çözümler, temel kurulum üzerindeki rastgele özellikleri önemli ölçüde geliştirirse hoş karşılanır.


Uygulama nedir? Kriptografik olarak güvenli olmalı mı? O zaman rastgelelik ile ne yapıyorsun? Daha sonra fiziksel bir entropi kaynağından bir TRNG uygulayan harici bir yonga olmadan şansınız kalmaz. HMAC DRBG gibi deterministik bir RNG uygulayabilir ve statik bir şey artı düşük kaliteli bir entropi kaynağından tohumlayabilirsiniz, ancak bu yine de kriptografik olarak güvenli olmayacaktır.
Maximilian Gerhardt

Evet, kriptografik olarak güvenli uygulamalar için rasgele sayılara ihtiyacım var.
Rexcirus

Yanıtlar:


10

Entropi kütüphane kullanımları:

gerçek rastgele sayıların güvenilir bir akışını üretmek için bekçi zamanlayıcısının doğal titreşimi

Bu çözümü seviyorum çünkü mikrodenetleyicinizin herhangi bir pimini kullanmıyor ve harici bir devre gerektirmiyor. Bu aynı zamanda harici arızalara daha az maruz kalmasını sağlar.

Bir kütüphaneye ek olarak, mikrodenetleyicinin PRNG'si için kütüphane olmadan rastgele bir tohum oluşturmak için kullanılan aynı tekniğin kullanımını gösteren bir taslak sağlarlar: https://sites.google.com/site/astudyofentropy/project-definition / zamanlayıcı-titreşim-entropi kaynakları / entropi kütüphane / arduino rasgele tohum


8

randomSeed(analogRead(x))yalnızca 255 dizi sayı üretecek, bu da tüm kombinasyonları denemeyi ve çıktı akışınıza bağlanabilecek bir kârı üretmeyi önemsiz hale getirerek tüm çıktıyı% 100 tahmin ediyor. Ancak doğru yoldasınız, bu sadece bir sayı oyunu ve bunlardan çok daha fazlasına ihtiyacınız var. Örneğin, 4 ADC'den 100 analog okuma almak, hepsini toplamak ve bunu beslemek randomSeedçok daha iyi olurdu. Maksimum güvenlik için hem öngörülemeyen girdilere hem de deterministik olmayan karıştırmaya ihtiyacınız vardır.

Ben bir kriptograf değilim, ancak donanım ve yazılım rastgele jeneratörlerini araştırmak ve inşa etmek için binlerce saat harcadım, bu yüzden öğrendiklerimin bazılarını paylaşmama izin verin:

Öngörülemeyen Girdi:

  • analogRead () (kayan pinlerde)
  • GetTemp ()

Potansiyel Olarak Tahmin Edilemeyen Girdi:

  • micros () (deterministik olmayan örnekleme periyodu hariç)
  • saat titreşimi (düşük bant genişliği, ancak kullanılabilir)
  • readVCC () (pille çalışmazsa)

Harici Tahmin Edilemeyen Girdi:

  • sıcaklık, nem ve basınç sensörleri
  • mikrofonlar
  • LDR voltaj bölücüler
  • ters yönlü transistör gürültüsü
  • pusula / hızlanma titreşimi
  • esp8266 wifi hotspot taraması (ssid, db, vb.)
  • esp8266 zamanlaması (arka plan wifi görevleri planlanan mikroları yapar () belirsizdir)
  • esp8266 HWRNG - RANDOM_REG32son derece hızlı ve tahmin edilemez, 1 kademeli

toplama Yapmak istediğiniz son şey, geldiği gibi entropi tükürmek. Bir bozuk parayı tahmin etmek bir kova madeni paradan daha kolaydır. Toplamak iyidir. unsigned long bank;o zaman sonra bank+= thisSample;iyidir; devrilir. bank[32]daha da iyi, okumaya devam edin. Her bir çıktı grubu için en az 8 girdi örneği toplamak istiyorsunuz, ideal olarak çok daha fazlası.

Zehirlenmeye karşı koruma Tahtayı ısıtmak belirli bir maksimum saat titreşimine neden oluyorsa, bu bir saldırı vektörüdür. AnalogRead () girişlerine doğru patlayan RFI ile aynı. Başka bir yaygın saldırı sadece ünitenin fişini çekerek biriken tüm entropiyi boşaltır. Hız pahasına bile olsa güvenli olduğunu bilinceye kadar sayılar çıkarmamalısınız.

Eğer içine vb EEPROM, SD, Bak kullanarak, etrafında uzun vadeli bazı entropi tutmak istiyoruz nedeni budur Fortuna PRNG 32 banka kullanır, yarım güncellenen her biri genellikle daha önce biri. Bu, 32 bankaya makul bir sürede saldırmayı zorlaştırır.

İşlem "Entropi" yi topladıktan sonra, onu temizlemeniz ve ters-ters bir şekilde girişten boşaltmanız gerekir. SHA / 1/256 bunun için iyidir. Düz metin güvenlik açığına sahip olmadığınız için hız için SHA1'i (hatta gerçekten MD5'i) kullanabilirsiniz. Hasat etmek için asla tam entopy bankasını kullanmayın ve DAİMA her zaman entropi banka değişikliği yapılmadan aynı çıktıları önlemek için çıktıya her zaman farklı bir "tuz" ekleyin: output = sha1( String(micros()) + String(bank[0]) + [...] );Sha işlevi hem girdileri gizler hem de çıktıyı beyazlatır, zayıf tohumlara karşı korur, düşük birikmiş ent ve diğer yaygın sorunlar.

Zamanlayıcı girişlerini kullanmak için, onları belirsiz yapmanız gerekir. Bu basit delayMicroseconds(lastSample % 255); bu da öngörülemeyen bir süreyi duraklatır ve "ardışık" saat okumasını farktan eşit olmayan bir şekilde yapar. if(analogRead(A1)>200){...}A1'in gürültülü olması veya dinamik bir girişe bağlanması koşuluyla bunu yarı düzenli olarak yapın . Akışınızın her bir çatalının belirlenmesini oldukça zorlaştırmak, ayrıştırılmış / yırtılmış çıktıda kriptoanalizi önleyecektir.

Gerçek güvenlik, saldırganın tüm sisteminizi tanıdığı ve üstesinden gelmek için hala çaresiz olduğu zamandır.

Son olarak, çalışmanızı kontrol edin. Çıktınızı ENT.EXE (nix / mac için de kullanılabilir) aracılığıyla çalıştırın ve bunun iyi olup olmadığını görün. En önemlisi, genellikle% 33 ile% 66 arasında olması gereken ki kare dağılımıdır. % 1.43 veya% 99.999 veya bunun gibi sinirli bir şey alırsanız, arka arkaya birden fazla test yaparsanız, rastgele boktur. Ayrıca entropi KBB raporlarını bayt başına mümkün olduğunca 8 bite yakın,> 7,9 kesin istersiniz.

TLDR: En basit aptalca yol ESP8266'nın HWRNG'sidir. Hızlı, tek tip ve öngörülemez. Ardunio çekirdeğini çalıştıran bir ESP8266'da böyle bir şey çalıştırın ve AVR ile konuşmak için seriyi kullanın:

// 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"
}

** Düzenle

Burada sadece bir koleksiyoncu olarak değil, seri porttan tüküren bir CSPRNG olan bir süre önce yazdığım çıplak tahta HWRNG çizimi. Bir pro-mini için üretilmiştir, ancak diğer kartlara kolayca uyarlanabilir olmalıdır. Sadece kayan analog pimleri kullanabilirsiniz, ancak bunlara, tercihen farklı şeyler eklemek daha iyidir. Mikrofonlar, LDR'ler, termistörler (oda sıcaklığına maksimum yayılmak üzere kesilmiş) ve hatta uzun teller gibi. Eğer orta derecede gürültüye sahipseniz, KBB'de oldukça iyi sonuç verir.

Taslak, cevap ve takip yorumlarımda bahsettiğim birkaç kavramı birleştiriyor: entropinin biriktirilmesi, ideal olmayan entropiden daha az örnekleme ile germe (von neumann serin olduğunu söyledi) ve tekdüzelik için karma. Entropi kalite tahminini "muhtemelen dinamik olan herhangi bir şeyi ver" ve kriptografik bir ilkel kullanarak karıştırma lehine terk eder.

// 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()

(Buradaki karakterler konusunda özür dilerim, üzgünüm.) İyi bir genel bakış! Tuz için bir sayaç kullanmanızı öneririm; micros () bir bit israfıdır, çünkü aramalar arasında birkaç adım atlayabilir. Analog girişlerdeki yüksek bitlerden kaçının, en düşük bir veya iki bitle sınırlandırın. Hedefe yönelik bir saldırı olsa bile, bunları sabitlemek zordur (Giriş'e bir tel koyamazsanız). "Deterministik olmayan karıştırma" yazılımda yapabileceğiniz bir şey değildir. SHA-1 karıştırma standartlaştırılmıştır: crypto.stackexchange.com/a/6232 . Indet. önerdiğiniz zamanlayıcı sadece sahip olduğunuz kaynak kadar rastgele. Burada fazla kazanç yok.
Jonas Schäfer

sha basitleştirir ve korur, böylece bir analog girişten kaç bitin alınacağından endişelenmenize gerek kalmaz. bir analoga (veya bir serpantin pcb izine) bağlı birkaç inç tel birkaç bitten daha fazla sallanır. birleştirilmiş değerlerin bir alt örneği ile karmaya beslenen kaydedilmemiş ve bilinmeyen tuz nedeniyle karıştırma deterministik değildir. deterministik olmayan aralıklarla ateşlendiğinde, mikros () 'un bir sayaçtan yeniden oynatılması daha zordur.
dandavis

1
Bir sorum var. 100 önlem almanın daha iyi olduğunu söyledin. Ancak bu "rastgele" verileri almanın etkinliğini sınırlayan bir çeşit "ortalama" önlem almıyor musunuz? Yani, genellikle daha az gürültülü (çok daha az "rastgele") ölçümleri almak için ortalama ...
frarugi87

iyi sürekli örnekleme tavsiye, ben sadece 100 daha fazla kombinasyon sunuyor çünkü 1'den daha iyi olduğunu söylüyordu. Yarrow / Fortuna gibi bir birikim modeli hala çok daha iyi. Bu 100 analog örneği birleştirmeden önce birleştirmeyi (toplamayı değil) düşünün; daha güçlü çünkü örnek siparişini önemli kılıyor ve bir karakter dışı olmak tamamen farklı bir hash veriyor. Yani, bir kişi daha az gürültü elde etmek için örneklerin ortalamasını alabilse de, bir saldırganın tüm değerleri sözel olarak belirtmesi veya eşleşmesi gerekmemesi gerekir ... Asıl amacım, belirli bir gürültü kaynağını savunmaktan daha fazla "biriktirmek, karıştırmak ve doğrulamak" tır.
dandavis

4

Deneyimlerime göre, analogRead()yüzen bir pim üzerinde çok düşük entropi var. Belki çağrı başına bir veya iki rasgele bit. Kesinlikle daha iyi bir şey istiyorsun. Bekçi köpeği zamanlayıcısı titresi, per1234'ün cevabında önerildiği gibi, iyi bir alternatiftir. Bununla birlikte, oldukça yavaş bir oranda entropi üretir, bu da program başladığında doğru ihtiyacınız varsa bir sorun olabilir. dandavis'in birkaç iyi önerisi var, ancak genellikle bir ESP8266 veya harici donanım gerektiriyorlar.

Henüz belirtilmemiş ilginç bir entropi kaynağı var: başlatılmamış RAM'in içeriği. MCU'ya güç verildiğinde, bazı RAM bitleri (en simetrik transistörlere sahip olanlar) rastgele bir durumda başlar. Bu hackaday makalesinde tartışıldığı gibi , bu bir entropi kaynağı olarak kullanılabilir. Sadece soğuk bir önyüklemede kullanılabilir, bu nedenle daha sonra potansiyel olarak yavaş bir kaynaktan düzenli olarak dolduracağınız bir başlangıç ​​entropi havuzunu doldurmak için kullanabilirsiniz. Bu şekilde programınız havuzun yavaşça dolmasını beklemek zorunda kalmadan çalışmasına başlayabilir.

İşte bunun AVR tabanlı bir Arduino'da nasıl hasat edilebileceğine bir örnek. XOR altındaki kod snippet'i, daha sonra besleyeceği bir tohum oluşturmak için tüm RAM'i oluşturur srandom(). İşin zor kısmı, C çalışma zamanı .data ve .bss bellek bölümlerini başlatmadan önce hasat işleminin yapılması ve daha sonra tohumun C çalışma zamanının üzerine yazılmayacağı bir yere kaydedilmesi gerektiğidir. Bu, belirli bellek bölümleri kullanılarak yapılır .

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);
}

Bir üzerinde, unutmayın sıcak hala bütün grup içerikleri havuzu entropi vardır böylece sıfırlama, SRAM, korunur. Bu aynı kod daha sonra bir sıfırlama boyunca toplanan entropinin korunması için kullanılabilir.

Düzenleme : Yerel sürümü kullanmak yerine seed_from_ram(), global olarak çalıştığım ilk sürümümdeki bir sorun düzeltildi . Bu, tohumun kendisiyle XORed edilmesine ve şimdiye kadar hasat edilen tüm entropinin yok edilmesine yol açabilir.random_seedseed


İyi iş! çalabilir miyim re: pinler: doğru kullanılırsa bilinmeyen bir veya iki bit yeterlidir; Bu sadece mükemmel gizlilik (yuck) çıkış hızını sınırlar, ama ihtiyacımız olan hesaplama gizliliğini değil ...
dandavis

1
@dandavis: Evet, tekrar kullanabilirsiniz, tabi. Ne analogRead()yaptığınızı biliyorsanız, kullanılabilir olma konusunda haklısınız . Havuzunuzun entropisinin bir tahminini güncellerken rasgeleliğini abartmamaya dikkat etmelisiniz. Hakkında Anlatmak analogRead()çoğunlukla fakir henüz bir eleştiri olarak kastedilmektedir sık tekrarlanan “reçete” : randomSeed(analogRead(0)) sadece bir kez de setup()'s yeterince üstlenecek ve.
Edgar Bonet

analogRead(0)Çağrı başına 1 bit entropi varsa , tekrar tekrar çağırmak Entropy kütüphanesinin 150 katı kadar 10000/8 = 1.25 KByte / sn entropi verecektir.
Dmitry Grigoryev

0

Gerçekten entropiye ihtiyacınız yoksa ve her başlangıçta farklı bir sahte rasgele sayı dizisi almak istiyorsanız, ardışık tohumlar arasında yineleme yapmak için EEPROM'u kullanabilirsiniz. Teknik olarak süreç tamamen deterministik olacaktır, ancak pratik olarak randomSeed(analogRead(0)), genellikle 0 veya 1023'lük aynı tohumla başlamanıza neden olacak, bağlı olmayan bir pimden çok daha iyidir . her seferinde tohum.

#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);
}

Gerçek entropiye ihtiyacınız varsa, saat kaymasından veya harici gürültüyü artırarak toplayabilirsiniz. Ve çok fazla entropiye ihtiyacınız varsa, tek uygun seçenek dış gürültüdür. Zener diyot, özellikle 5-6V üzerinde bir voltaj kaynağınız varsa (uygun bir Zener diyotuyla 5V ile de çalışacak, ancak daha az entropi üretecek) popüler bir seçimdir:

resim açıklamasını buraya girin

( kaynak ).

Amplifikatör çıkışı, her biri analogRead()onlarca MHz'e kadar (Arduino'nun örnekleyebileceğinden daha hızlı) birkaç entropi üretecek bir analog pime bağlanmalıdır .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.