Lấy số thực sự ngẫu nhiên trong Arduino


13

Phương pháp tốt nhất để có được một số ngẫu nhiên thực sự (trái ngược với giả) trong Arduino, hoặc ít nhất là xấp xỉ tốt nhất có thể là gì? Theo hiểu biết của tôi, hàm RandomSeed (analogRead (x)) không đủ ngẫu nhiên.

Nếu có thể, phương pháp nên tận dụng thiết lập Arduino cơ bản (không có cảm biến bổ sung). Các giải pháp với cảm biến bên ngoài được hoan nghênh nếu chúng cải thiện đáng kể tính ngẫu nhiên so với thiết lập cơ bản.


Ứng dụng này là gì? Nó phải được bảo mật bằng mật mã? Bạn đang làm gì với sự ngẫu nhiên sau đó? Sau đó, không có chip bên ngoài thực hiện TRNG từ nguồn entropy vật lý, bạn sẽ không gặp may. Bạn cũng có thể triển khai RNG xác định như HMAC DRBG và lấy nó từ một thứ gì đó tĩnh cộng với nguồn entropy chất lượng thấp, nhưng điều đó vẫn không được bảo mật bằng mật mã.
Maximilian Gerhardt

Có, tôi cần số ngẫu nhiên cho các ứng dụng bảo mật bằng mật mã.
Rexcirus

Câu trả lời:


10

Các Entropy thư viện sử dụng:

jitter tự nhiên của bộ giám sát để tạo ra một luồng đáng tin cậy của các số ngẫu nhiên thực sự

Tôi thích giải pháp này vì nó không sử dụng bất kỳ chân nào của vi điều khiển của bạn và không yêu cầu bất kỳ mạch ngoài nào. Điều này cũng làm cho nó ít chịu các thất bại bên ngoài.

Ngoài thư viện, họ cũng cung cấp một bản phác thảo cho thấy việc sử dụng cùng một kỹ thuật được sử dụng để tạo một hạt giống ngẫu nhiên cho PRNG của vi điều khiển mà không cần thư viện: https://sites.google.com/site/astudyofentropy/project-def định / timer-jitter-entropy-nguồn / entropy-library / arduino-Random-seed


8

randomSeed(analogRead(x))sẽ chỉ tạo ra 255 chuỗi số, điều này khiến cho việc thử tất cả các combo trở nên tầm thường và tạo ra một lời tiên tri có thể kết hợp với luồng đầu ra của bạn, dự đoán tất cả 100% đầu ra. Tuy nhiên, bạn đang đi đúng hướng, đây chỉ là một trò chơi số và bạn cần RẤT NHIỀU trò chơi này. Ví dụ, lấy 100 lần đọc tương tự từ 4 ADC, tổng hợp tất cả chúng và cho ăn điều đó randomSeedsẽ tốt hơn nhiều. Để bảo mật tối đa, bạn cần cả đầu vào không thể đoán trước và trộn không xác định.

Tôi không phải là một nhà mật mã học, nhưng tôi đã dành hàng ngàn giờ để nghiên cứu và xây dựng các trình tạo ngẫu nhiên phần cứng và phần mềm, vì vậy hãy để tôi chia sẻ một số điều tôi đã học được:

Đầu vào không thể đoán trước:

  • analogRead () (trên chân nổi)
  • GetTemp ()

Đầu vào tiềm năng không thể đoán trước:

  • micros () (w / một khoảng thời gian mẫu không xác định)
  • jitter đồng hồ (băng thông thấp, nhưng có thể sử dụng)
  • readVCC () (nếu không chạy bằng pin)

Đầu vào bên ngoài không thể đoán trước:

  • cảm biến nhiệt độ, độ ẩm và áp suất
  • micro
  • Bộ chia điện áp LDR
  • đảo ngược bóng bán dẫn tiếng ồn
  • la bàn / gia tốc jitter
  • Quét điểm truy cập wifi Esp8266 (ssid, db, v.v.)
  • thời gian đặc biệt (các tác vụ wifi nền làm cho micros () được lập lịch tìm nạp không xác định)
  • Esp8266 HWRNG - RANDOM_REG32cực kỳ nhanh và không thể đoán trước, 1 điểm dừng

thu thập Điều cuối cùng bạn muốn làm là nhổ entropy như đi cùng. Thật dễ dàng để đoán một đồng xu lật hơn một xô tiền xu. Tổng kết là tốt. unsigned long bank;sau đó bank+= thisSample;là tốt; nó sẽ lăn qua bank[32]thậm chí còn tốt hơn, đọc tiếp Bạn muốn thu thập ít nhất 8 mẫu đầu vào cho mỗi khối đầu ra, lý tưởng hơn nhiều.

Bảo vệ chống nhiễm độc Nếu làm nóng bảng gây ra hiện tượng giật đồng hồ tối đa nhất định, đó là một vectơ tấn công. Tương tự với nổ RFI đối với các đầu vào analogRead (). Một cuộc tấn công phổ biến khác chỉ đơn giản là rút phích cắm của thiết bị do đó vứt bỏ toàn bộ entropy tích lũy. Bạn không nên xuất số cho đến khi bạn biết an toàn để làm như vậy, ngay cả với chi phí tốc độ.

Đây là lý do tại sao bạn muốn giữ một số entropy trong thời gian dài, sử dụng EEPROM, SD, v.v. Hãy nhìn vào Fortuna PRNG , sử dụng 32 ngân hàng, mỗi ngân hàng được cập nhật một nửa như trước đây. Điều đó gây khó khăn cho việc tấn công tất cả 32 ngân hàng trong một khoảng thời gian hợp lý.

Xử lý Một khi bạn thu thập "entropy", bạn phải dọn sạch nó và tách nó khỏi đầu vào theo cách khó đảo ngược. SHA / 1/256 là tốt cho việc này. Bạn có thể sử dụng SHA1 (hoặc thậm chí MD5 thực sự) cho tốc độ vì bạn không có lỗ hổng văn bản gốc. Để thu hoạch, không bao giờ sử dụng ngân hàng entopy đầy đủ và LUÔN LUÔN LUÔN thêm "muối" vào đầu ra khác nhau mỗi lần để ngăn chặn đầu ra giống hệt nhau khi không có thay đổi ngân hàng entropy: output = sha1( String(micros()) + String(bank[0]) + [...] );Chức năng sha vừa che giấu đầu vào vừa làm trắng đầu ra, bảo vệ chống lại hạt giống yếu, ent tích lũy thấp, và các vấn đề phổ biến khác.

Để sử dụng đầu vào hẹn giờ, bạn cần làm cho chúng không xác định. Đây là một đơn giản như delayMicroseconds(lastSample % 255); trong đó tạm dừng một lượng thời gian không thể đoán trước, làm cho đồng hồ "kế tiếp" đọc không đồng nhất khác nhau. Làm điều đó một cách thường xuyên, như if(analogRead(A1)>200){...}, với điều kiện A1 là ồn ào hoặc được nối với đầu vào động. Làm cho mỗi ngã ba của luồng của bạn khá khó xác định sẽ ngăn phân tích tiền điện tử trên đầu ra được dịch ngược / tách.

An ninh thực sự là khi kẻ tấn công biết toàn bộ hệ thống của bạn và vẫn bất lực để vượt qua nó.

Cuối cùng, kiểm tra công việc của bạn. Chạy đầu ra của bạn thông qua ENT.EXE (cũng có sẵn cho nix / mac) và xem nó có tốt không. Quan trọng nhất là phân phối chi vuông, thường nên nằm trong khoảng từ 33% đến 66%. Nếu bạn nhận được 1,43% hoặc 99,999% hoặc một cái gì đó sắc nét như thế, nhiều hơn một bài kiểm tra liên tiếp, ngẫu nhiên của bạn là tào lao. Bạn cũng muốn các báo cáo ENT entropy càng gần 8 bit trên mỗi byte càng tốt,> 7.9 chắc chắn.

TLDR: Cách chống lừa đơn giản nhất là sử dụng HWRNG của ESP8266. Nó nhanh, đồng đều và không thể đoán trước. Chạy một cái gì đó như thế này trên một chiếc ESP8266 chạy lõi Ardunio và sử dụng nối tiếp để nói chuyện với 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"
}

** biên tập

Đây là một bản phác thảo HWRNG trần trụi mà tôi đã viết cách đây một thời gian, hoạt động như một người sưu tầm, mà không chỉ là một CSPRNG phun ra khỏi cổng nối tiếp. Nó được chế tạo cho một chiếc pro-mini nhưng có thể dễ dàng thích ứng với các bo mạch khác. Bạn chỉ có thể sử dụng các chân tương tự nổi, nhưng tốt hơn là thêm công cụ vào chúng, tốt nhất là những thứ khác nhau. Giống như micro, LDR, thermistors (được cắt xén tối đa xung quanh nhiệt độ phòng) và thậm chí cả dây dài. Nó hoạt động khá tốt trong ENT nếu bạn có độ ồn vừa phải.

Bản phác thảo tích hợp một số khái niệm mà tôi đã đề cập trong câu trả lời và nhận xét tiếp theo của mình: tích lũy entropy, kéo dài bằng cách lấy mẫu quá mức entropy kém lý tưởng (von neumann nói rằng nó rất tuyệt) và băm cho đồng đều. Nó bỏ qua ước tính chất lượng entropy để ủng hộ "gimme bất cứ điều gì có thể năng động" và trộn bằng cách sử dụng một nguyên thủy mã hóa.

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

(Tôi xin lỗi về các nhân vật ở đây, xin lỗi.) Tổng quan tốt! Tôi sẽ đề nghị sử dụng một quầy cho muối; micros () là một sự lãng phí bit vì nó có thể nhảy qua một vài bước giữa các cuộc gọi. Tránh các bit cao trong đầu vào tương tự, hạn chế ở mức thấp nhất một hoặc hai bit. Ngay cả với một cuộc tấn công được nhắm mục tiêu, những người khó có thể xác định được (trừ khi bạn có thể đặt dây vào Đầu vào). "Trộn không xác định" không phải là điều bạn có thể làm trong phần mềm. Trộn SHA-1 được chuẩn hóa: crypto.stackexchange.com/a/6232 . Các indet. bộ đếm thời gian bạn đề xuất chỉ là ngẫu nhiên như nguồn bạn đã có. Không có nhiều lợi ích ở đây.
Jonas Schäfer

sha đơn giản hóa và bảo vệ, do đó bạn không phải lo lắng về việc lấy bao nhiêu bit từ đầu vào tương tự. một vài inch dây được kết nối với một dấu vết tương tự (hoặc một dấu vết pcb serpentine) sẽ cuốn nó nhiều hơn một vài bit. sự pha trộn là không xác định bởi tính chất của muối chưa được lưu và chưa biết được đưa vào hàm băm với một mẫu các giá trị tích lũy. micros () khó phát lại hơn bộ đếm, đặc biệt khi được bắn ở các khoảng không xác định.
dandavis

1
Tôi có một câu hỏi. Bạn nói rằng thực hiện 100 biện pháp là tốt hơn. Nhưng không phải thực hiện nhiều biện pháp là một loại "trung bình" làm hạn chế tính hiệu quả của việc lấy các dữ liệu "ngẫu nhiên" này sao? Ý tôi là, thông thường bạn trung bình để có được số đo ít ồn hơn (vì vậy ít "ngẫu nhiên") ...
frarugi87

Tôi khuyên bạn nên lấy mẫu liên tục, tôi chỉ nói 100 là tốt hơn 1 vì nó cung cấp nhiều kết hợp hơn. Một mô hình tích lũy như Yarrow / Fortuna vẫn tốt hơn rất nhiều. Cân nhắc ghép (không tổng hợp) 100 mẫu tương tự trước khi băm; mạnh hơn bởi vì nó làm cho thứ tự mẫu trở nên quan trọng và việc trở thành một char mang lại một hàm băm hoàn toàn khác. Vì vậy, mặc dù người ta có thể lấy trung bình các mẫu để có ít tiếng ồn hơn, kẻ tấn công sẽ phải đọc nguyên văn tất cả các giá trị hoặc không khớp ... Điểm chính của tôi là "tích lũy, trộn và xác minh" hơn là ủng hộ một nguồn nhiễu cụ thể.
dandavis

4

Từ kinh nghiệm của tôi, analogRead()trên một pin nổi có entropy rất thấp. Có thể một hoặc hai bit ngẫu nhiên cho mỗi cuộc gọi. Bạn chắc chắn muốn một cái gì đó tốt hơn. Jitter đồng hồ bấm giờ, như đề xuất trong câu trả lời của per1234, là một lựa chọn tốt. Tuy nhiên, nó tạo ra entropy với tốc độ khá chậm, đây có thể là một vấn đề nếu bạn cần nó ngay khi chương trình bắt đầu. dandavis có khá nhiều gợi ý hay, nhưng chúng thường yêu cầu một phần cứng ESP8266 hoặc phần cứng bên ngoài.

Có một nguồn entropy thú vị chưa được đề cập: nội dung của RAM chưa được khởi tạo. Khi MCU được cấp nguồn, một số bit RAM của nó (những bit xảy ra có bóng bán dẫn đối xứng nhất) khởi động ở trạng thái ngẫu nhiên. Như đã thảo luận trong bài viết hackaday này , nó có thể được sử dụng như một nguồn entropy. Nó chỉ có sẵn trên một khởi động lạnh, vì vậy bạn có thể sử dụng nó để lấp đầy một nhóm entropy ban đầu, sau đó bạn sẽ bổ sung định kỳ từ một nguồn khác, có khả năng chậm. Bằng cách này, chương trình của bạn có thể bắt đầu công việc mà không cần phải đợi hồ bơi từ từ lấp đầy.

Dưới đây là một ví dụ về cách điều này có thể được thu hoạch trên Arduino dựa trên AVR. Đoạn mã bên dưới XOR toàn bộ RAM để xây dựng một hạt giống mà sau này nó cung cấp srandom(). Phần khó khăn là việc thu hoạch phải được thực hiện trước khi thời gian chạy C khởi tạo các phần bộ nhớ .data và .bss, và sau đó hạt giống phải được lưu ở một nơi mà thời gian chạy C sẽ không ghi đè. Điều này được thực hiện bằng cách sử dụng các phần bộ nhớ cụ thể .

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

Lưu ý rằng, trên thiết lập lại ấm , SRAM được bảo toàn, do đó, nó vẫn có toàn bộ nội dung của nhóm entropy của bạn. Mã tương tự này sau đó có thể được sử dụng để bảo toàn entropy được thu thập trong quá trình thiết lập lại.

Chỉnh sửa : đã khắc phục sự cố trong phiên bản ban đầu của seed_from_ram()tôi đã hoạt động trên toàn cầu random_seedthay vì sử dụng cục bộ seed. Điều này có thể dẫn đến hạt giống bị XOR với chính nó, phá hủy tất cả các entropy được thu hoạch cho đến nay.


Công việc tốt đẹp! tôi có thể ăn cắp? re: chân: một hoặc hai bit không xác định là đủ nếu được sử dụng đúng; điều đó sẽ chỉ giới hạn tốc độ đầu ra của bí mật hoàn hảo (yuck), nhưng không phải là bí mật tính toán mà chúng ta cần ...
dandavis

1
@dandavis: Vâng, bạn có thể tái sử dụng, chắc chắn. Bạn đúng về analogRead()việc có thể sử dụng được nếu bạn biết bạn đang làm gì. Bạn chỉ cần cẩn thận để không đánh giá quá cao tính ngẫu nhiên của nó khi cập nhật ước tính về entropy của hồ bơi của bạn. Quan điểm của tôi analogRead()chủ yếu là một lời chỉ trích về một công thức nghèo nàn nhưng thường được lặp đi lặp lại : randomSeed(analogRead(0)) chỉ một lần vào setup()và cho rằng nó là đủ.
Edgar Bonet

Nếu analogRead(0)có 1 bit entropy cho mỗi cuộc gọi, sau đó gọi liên tục sẽ mang lại 10000/8 = 1,25 KB / giây entropy, gấp 150 lần so với thư viện Entropy.
Dmitry Grigoryev

0

Nếu bạn không thực sự cần entropy và chỉ muốn nhận một chuỗi các số giả ngẫu nhiên khác nhau trên mỗi lần khởi động, bạn có thể sử dụng EEPROM để lặp qua các hạt liên tiếp. Về mặt kỹ thuật, quy trình sẽ hoàn toàn mang tính quyết định, nhưng về mặt thực tế, nó tốt hơn nhiều so với randomSeed(analogRead(0))mã pin không được kết nối, thường sẽ khiến bạn bắt đầu với cùng một hạt giống là 0 hoặc 1023. Lưu hạt giống tiếp theo trong EEPROM sẽ đảm bảo rằng bạn bắt đầu bằng một loại khác hạt giống mỗi lần.

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

Nếu bạn cần entropy thực sự, bạn có thể thu thập nó từ trôi đồng hồ hoặc bằng cách khuếch đại tiếng ồn bên ngoài. Và nếu bạn cần nhiều entropy, tiếng ồn bên ngoài là lựa chọn khả thi duy nhất. Diode Zener là một lựa chọn phổ biến, đặc biệt nếu bạn có nguồn điện áp trên 5-6V (nó cũng hoạt động với 5V với một diode Zener thích hợp, nhưng sẽ tạo ra ít entropy hơn):

nhập mô tả hình ảnh ở đây

( nguồn ).

Đầu ra bộ khuếch đại phải được kết nối với một chân tương tự, nó sẽ tạo ra một số bit entropy với mỗi bit analogRead()lên đến hàng chục MHz (nhanh hơn so với Arduino có thể lấy mẫu).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.