יצירת מספרים אקראיים עם ‎/dev/urandom וכמה קל ליפול בפח

ב־16.9.2010, מאת ארתיום; פורסם תחת: תכנה חופשית, לינוקס, פיתוח, תכנה ומחשבים; ‏7 תגובות

רובינו מכירים את ‎/dev/urandom הנפלא שמאפשר לייצר מספרים אקראיים בטוחים מבחינה קריפטוגרפית במהירות רבה.

למשל, אנחנו רוצים ליצור session-id חדש, מה קל יותר? פונים ל־‎/dev/urandom ומקבלים בשפע.

אבל כשבפועל השתמשתי בו ליצירת session-ids בגודל של 16 בתים, גיליתי... הוא מאוד אטי... הצלחתי לייצר משהו כמו 1,100 כאלה בשנייה, לא הרבה. התחלתי להתחכם, יצרתי מספר אחד להתחלה, הוספתי לו כמה שדות כמו זמן, מספר, לקחתי לו איזה md5 ואז זה עבד יפה וגם מהר (למרות שהמספרים האלה לא באים באותה איכות כמו שיכולתי לקבל אותם מ־‎/dev/urandom.

יום אחד בהיר הסתכלתי בקוד של Boost.UUID וראיתי שבקוד שלו, במקום להשתמש בפונקציות סטנדרטיות ופשוטות כמו std::ifstream או fopen פותחים את הקובץ ישירות עם open של מערכת ההפעלה וקוראים אותו עם read. מוזר, עשיתי בדיקה קצרה ו... זה קיבלתי 110,000 מפתחות של 16 בתים בשנייה במקום 1,100 - הבדל של שני סדרי גודל!

מה קורה פה?

אחרי מחקר קצר גיליתי ש:

FILE *f=fopen("/dev/urandom","r");
setbuf(f,0);
char buf[16];
fread(buf,1,16,f);
fclose(f);

עובד מהר מאוד, אבל אם מוחקים את השורה השנייה setbuf(f,0)‎ מגלים שהוא הופך לאטי להחריד.

הסבר: ביטל של buffer פנימי בעזרת setbuf()‎, גורם לכך, שכל פניה לקריאה מהקובץ מתבצעת בדיוק במספר הבתים שביקשנו. אבל, כאשר buffer מוגדל, הספרייה הסטנדרטית מבצעת תחכום ומנסה לקרוא כמה שיותר (למעשה 4K) ובפועל במקום לקרוא מ־‎/dev/urandom ‏ 16 בתים, היא קוראת 4096...

מוסר השכל: אם אתה פותח ‎/dev/urandom תמיד וודא שאתה לא משתמש ב־buffering בספריה שלך!

תגובות

meijin, ב־16.9.2010, 23:45

האם אפשר להשתמש בזה לקבלת מספרים אקראיים משורת הפקודה?

אסף.

קפלן, ב־17.9.2010, 0:52

אם תעשה cat לקובץ תגלה שהוא בור ללא תחתית. כלומר הפקודה לא תסתיים אלא ע"י CTRL+C. כדי להגביל את גודל הקריאה התחכמתי קצת:

dd if=/dev/urandom bs=2 count=1 2>/dev/null

ההפניה ל-null נועדה להעלים את ההודעות של dd עצמו. כמו כן לא ציינתי of כדי שידפיס את המידע לפלט הסטנדרטי. התוצאה נראית בד"כ כמו גיבריש כי מדובר במספרים רנדומליים ולא במשהו מסודר לפי ASCII.

עמרי, ב־17.9.2010, 2:32

לחילופון אתה יכול פשוט להמשיך להשתמש באותו קובץ פתוח במקום לפתוח אותו כל פעם מחדש.

ארתיום, ב־17.9.2010, 8:19

לחילופון אתה יכול פשוט להמשיך להשתמש באותו קובץ פתוח במקום לפתוח אותו כל פעם מחדש.

נכון, אבל זה לא תמיד אפשרי כי אז צריך לשמור איפשהו במקום גלובלי קובץ כזה ועכשיו אם יש לך הרבה threads אז אתה פתאום צריך לפתוח קובץ כזה מקומי ל־thread.

במילים אחרות... צריך להיזהר

דוד חי גוטויליג, ב־17.9.2010, 9:25

רובינו מכירים את ‎/dev/urandom הנפלא שמאפשר לייצר מספרים אקראיים בטוחים מבחינה קריפטוגרפית במהירות רבה.

אמרת בטוחים מבחינה קריפטוגרפית.

אני רק רוצה להזכיר שלפרנואידים מביננו כדאי להשתמש רק ב ‎/dev/random. שחוסם כשמספר הביטים המוערך של רעש במאגר האנטרופיה בקרנל נגמר.

גמר חתימה טובה

עמוס, ב־19.9.2010, 7:46

א. אני קצת מופתע שאתה חושב ש-hash וכו' עם דברים עם טווח ערכים יחסית מוגבל כמו PID וזמן הם בטוחים. אישית הייתי שוקל אולי להישתמש במספר שבא מ-urandom כ-seed של פסאודו-רנדום לכמה שניות.

ב. מכיוון שהקריאה של 16 בתים יכולה להיות אטומית ע"י פעולת read בודדת, אפשר להישתמש באותו FD גלובלי (או בטח מקומי של class) ללא בעיית תיאום בין threads.

ארתיום, ב־19.9.2010, 10:39

א. אני קצת מופתע שאתה חושב ש-hash וכו' עם דברים עם טווח ערכים יחסית מוגבל כמו PID וזמן הם בטוחים. אישית הייתי שוקל אולי להישתמש במספר שבא מ-urandom כ-seed של פסאודו-רנדום לכמה שניות

לא, הכוונה שלי הייתה, אני מייצר seed בעזרת urandom, אליו אני מוסיף counter ו־timestamp שהוא ברזולוציה מאוד גבוהה, ואז אני לוקח hash קריפוטוגרפי,

init():
  seed_a = read_urandom(16)
  seed_b = read_urandom(16)
  counter = 0

get_next_session_id()
  counter ++
  source= seed_a + counter + high-res-timestamp + seed_b
  return md5(source)

קרי זה בהחלט מספיק בטוח מכיוון ש־:

  1. אי אפשר לעשות ל־sid קיים תהליך הפוך
  2. השינוי הקטן בערכים של מה שאנחנו מפעילים עליו hash גורם לשינויים ומשמעותיים ולא צפויים. קרי ה־sid הוא מספיק מורכב כדי שלא נוכל לנחש את הבא שנוצר.

בכל מקרה, בקוד החדש, אחרי שתיקנתי את מהירות הקריאה מ־urandom אין בזה יותר צורך.

לגבי ב' גם נכון.

הוסף תגובה:

 
 כתובת דוא"ל לא תוצג
 

ניתן לכתוב תגובות עם שימוש בתחביר Markdown.

חובה לאפשר JavaScript כדי להגיב.

דפים

נושאים