הבלוג של ארתיום
בלוג על לינוקס, תוכנה חופשית, מוזיקה, סלסה, ומה לא!
יצירת מספרים אקראיים עם /dev/urandom וכמה קל ליפול בפח
רובינו מכירים את /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 בספריה שלך!
תגובות
האם אפשר להשתמש בזה לקבלת מספרים אקראיים משורת הפקודה?
אסף.
אם תעשה cat לקובץ תגלה שהוא בור ללא תחתית. כלומר הפקודה לא תסתיים אלא ע"י CTRL+C. כדי להגביל את גודל הקריאה התחכמתי קצת:
dd if=/dev/urandom bs=2 count=1 2>/dev/null
ההפניה ל-null נועדה להעלים את ההודעות של dd עצמו. כמו כן לא ציינתי of כדי שידפיס את המידע לפלט הסטנדרטי. התוצאה נראית בד"כ כמו גיבריש כי מדובר במספרים רנדומליים ולא במשהו מסודר לפי ASCII.
לחילופון אתה יכול פשוט להמשיך להשתמש באותו קובץ פתוח במקום לפתוח אותו כל פעם מחדש.
נכון, אבל זה לא תמיד אפשרי כי אז צריך לשמור איפשהו במקום גלובלי קובץ כזה ועכשיו אם יש לך הרבה threads אז אתה פתאום צריך לפתוח קובץ כזה מקומי ל־thread.
במילים אחרות... צריך להיזהר
אמרת בטוחים מבחינה קריפטוגרפית.
אני רק רוצה להזכיר שלפרנואידים מביננו כדאי להשתמש רק ב /dev/random. שחוסם כשמספר הביטים המוערך של רעש במאגר האנטרופיה בקרנל נגמר.
גמר חתימה טובה
א. אני קצת מופתע שאתה חושב ש-hash וכו' עם דברים עם טווח ערכים יחסית מוגבל כמו PID וזמן הם בטוחים. אישית הייתי שוקל אולי להישתמש במספר שבא מ-urandom כ-seed של פסאודו-רנדום לכמה שניות.
ב. מכיוון שהקריאה של 16 בתים יכולה להיות אטומית ע"י פעולת read בודדת, אפשר להישתמש באותו FD גלובלי (או בטח מקומי של class) ללא בעיית תיאום בין threads.
לא, הכוונה שלי הייתה, אני מייצר seed בעזרת urandom, אליו אני מוסיף counter ו־timestamp שהוא ברזולוציה מאוד גבוהה, ואז אני לוקח hash קריפוטוגרפי,
קרי זה בהחלט מספיק בטוח מכיוון ש־:
בכל מקרה, בקוד החדש, אחרי שתיקנתי את מהירות הקריאה מ־urandom אין בזה יותר צורך.
לגבי ב' גם נכון.
הוסף תגובה:
חובה לאפשר JavaScript כדי להגיב.