הסבר הקוד הנ"ל
השיטה לחלץ את המפתח בנויה על שתי ההערות הבאות:
פעולת XOR:
אפשר להסתכל על פעולת XOR בצורה כזו: בכל מקום שיש ביט 1 במפתח אנחנו מחליפים בקלט 1 ל-0 ו-0 ל-1.
יוצא ששום מידע לא הולך לאיבוד בפלט, רק ששמור בסוד בתוך המפתח באיזה ביט משמעותו של 1 הוא 1 ובאיזה מהם משמעותו 0. וכן לגבי 0 איזה מהם משמעותו 0 ואיזה מהם משמעותו 1.
מספרים בינאריים עוקבים:
אפשר ליצור רצף של מספרים עוקבים בינאריים אם נעקוב אחרי שני כללים פשוטים:
- בכל איטרציה נחליף את הערך של הביט הכי ימני
- כל ביט שערכו הופך מ-1 ל-0, גורר שינוי בביט שלשמאלו
למשל, אם נתחיל במספר 0
- איטרציה 1: נחליף את הערך של הביט הכי ימני => קיבלנו
1
- איטרציה 2: נחליף את הערך של הביט הכי ימני, יצא 0, עכשיו לפי הכלל השני זה מחייב אותנו לשנות את הביט שלשמאלו מ-0 ל-1 => קיבלנו
10
- איטרציה 3: כלל 1 =>
11
- איטרציה 4: כלל 1 נותן לנו
10 כלל 2 מחייב שינוי בביט השני , קיבלנו 00 שוב כלל 2 מחייב שינוי בביט השלישי => קיבלנו 100
וכן הלאה, אם רק נעקוב אחרי שני כללים אלו ניצור תמיד את המספר הבא ברצף
יוצא מזה שאפשר לזהות מספרים שאינם עוקבים אם הם לא מתאימים לתבנית זו. כלומר, או שהשינויים לא מתחילים מימין, או ששינוי של ביט שמאלי יותר לא נגרר משינוי של הביט לימינו מ-1 ל-0
והנה במספרים שמוצפנים בשיטת XOR, כבר אי אפשר לדעת אם ה-1 הוא 1 או 0 אבל עדיין אפשר לדעת שמספרים אינם עוקבים לפי הכלל השני, אם נראה שינוי בביט שמאלי כאשר אין שינוי בביט שלימינו נדע שהמספרים לא עוקבים
ולפי זה נבין גם איך לזהות את הביטים של מפתח ההצפנה, אם נראה שינוי בביט כלשהו שנגרר משינוי של הביט לימינו מ-0 ל-1 במקום מ-1 ל-0 נדע שהביט לימינו משמעותו הפוך שה-1 הוא 0 וה-0 הוא 1 וכך אנו עולים עם ביט 1 במפתח ההצפנה
ומכאן להסבר הקוד:
הקוד מתחיל עם כמה פוקציות עזר, היחיד מביניהם שאולי דורש הסבר זה makeBitmask
התפקיד של פונקציה זו היא ליצור "מסיכה" של ביטים באורך מסויים. (נראה בהמשך למה זה שימושי). דרך אחת לעשות את זה הוא על ידי פעולת right shift על מספר שמורכב רק מביטים של 1. אז אנו לוקחים את המספר -1 שבשיטת 2's complement (שיטת ייצוג מספרים שלמים במעבדים) הוא מורכב מרצף של 32 (או 64) ביטים של 1., ואז אנו עושים פעולתunsigned right shift עליו כדי לדחוף את המספר הנכון של ביטים של 0 משמאל.
הקוד לקחתי מפה: https://stackoverflow.com/a/43894311
function makeRand (max) {
return Math.floor(Math.random() * max)
}
function makeRange (start, length) {
const ret = []
for (i = 0; i < length; i++) {
ret.push(start + i)
}
return ret;
}
function makeBitmask(width) {
return width && -1 >>> 32 - width;
}
פה אנו יוצרים את רשימת המספרים ומפתח אקראי בין 1 - 1000
const KEY = makeRand(1000)
const nums = makeRange(makeRand(1000), 60)
// console.log('Numbers:', nums)
כאן אנו מגיעים לבשר:
שלב ראשון נרצה למצוא את המספר שבו הכי הרבה ביטים משתנים בו לעומת המספר הקודם, כי זה יאפשר לנו לגלות הכי הרבה ביטים של המפתח (יצויין, לא תמיד אפשר לגלות את כל הביטים, הקוד שלי מגלה את המקסימום האפשרי)
זה התפקיד של הלולאה.
תוך כדי כך אנו בודקים גם כן שמדובר במספרים עוקבים
הבדיקה היא על ידי XOR עם המספר הקודם.
זה נותן לנו ביט של 1 בכל מיקום שהקודם היה 0 והנוכחי 1 או הפוך, הוי אומר - כל ביט שהשתנה לעומת המספר הקודם
קיבלנו מפה של ביטים
כדי לוודא שמדובר במספר עוקב אנו צריכים לבדוק שהשינויים זורמים מימין לשמאל, זה נעשה על ידי בדיקה שהמספר מורכב מביטים של 1 בלבד (מספר כזה יש לו רק ביטים של 1 לצד ימין).
יש דרכים לבקוד את זה, אני השתמשתי בדרך קצת טריקית (חכמה
) שמובאת פה: https://www.geeksforgeeks.org/check-bits-number-set ה-method 2
function extractXORKeySeq (numList) {
let bestDiff, bestIndex
bestDiff = bestIndex = -1
for (let i = 1; i < numList.length; i++) {
const diff = numList[i] ^ numList[i-1]
if (diff & (diff + 1)) {
throw new Error('Numbers are not sequential!')
}
if (diff > bestDiff) {
bestDiff = diff
bestIndex = i
}
}
מקווה שעד כאן מובן,
ואם כן, הרי קיבלנו את המספר שהכי השתנה בביטים מהמספר הקודם
עכשיו אנו לוקחים את מפת הביטים ו-
א. האורך של מפת הביטים זה האורך המקסימלי של המפתח שנוכל לחלץ (אנו מקבלים את האורך על ידי הפיכה למחרוזת ומדידת האורך, בטח יש דרכים יותר חסכוניות, חכמות, טריקיות לזה...)
ב. נרצה לדעת מתוך זה איזה ביטים השתנו מ-0 ל-1 כי אלה הם הביטים שלא היו אמורים לגרור שינוי בביט שלימינם, ואם הם גררו שינוי - סימן שהם הפוכים (כלומר, ערכם 1 במפתח ה-XOR)
ג. הביט הכי שמאלי, נרצה לבדוק שהוא שינוי מ-0 ל-1 אחרת הוא היה אמור לגרור עוד שינוי בביט שלשמאלו
לשם ב וג הנ"ל אנו עושים XOR של המפה עם המספר הקודם, רק שלפני כן אנו הופכים את הביט שמקביל לביט הכי שמאלי של המפה, דוק ותשכח שזה נותן לנו את המידע
אח"כ אני "מנקים" את שאר המספר (הופכים ל-0) על ידי פעולת AND עם מסיכה שאנו יוצרים עם פונקציית העזר הנ"ל
והנה קיבלנו את התוצאה!
const previous = numList[bestIndex - 1]
const extractLen = bestDiff.toString(2).length
const keyBits = bestDiff ^ (previous ^ (1 << (extractLen - 1))) & makeBitmask(extractLen)
return [extractLen, keyBits]
}
אני מקווה שנהניתם מהאתגר והפתרון, אני מאוד מאוד נהניתי 
תודה ל @DOVID!