• בלוג
  • הדרך הקלה להצפין הודעות בפייתון

הדרך הקלה להצפין הודעות בפייתון

06/06/2016

פוסט זה כולל טיפ קצר לעבודה יעילה עם Python. אם אתם רוצים ללמוד פייתון יותר לעומק אני ממליץ על קורס Python כאן באתר.
הקורס כולל עשרות שיעורי וידאו והמון תרגול מעשי וילמד אתכם Python בצורה מקצועית מההתחלה ועד הנושאים המתקדמים.

הצפנה הפכה לחלק אינטגרלי מהחיים שלנו כמתכנתים. האלגוריתמים ידועים ורובם כבר ממומשים כחלק מהשפה או בספריות עזר. עדיין, התיעוד של חלק מספריות אלו עמוס במילות קוד שלא כולם מבינים וכשמגיע רגע האמת לכתוב קוד להצפנה עושים טעויות. אני מקוה שדוגמת הקוד וההסברים בפוסט הזה יעזרו לכם בפעם הבאה שתצטרכו להעביר הודעה מוצפנת בפייתון.

1. כלי עבודה

הספריה הכי קלה שאני מכיר להצפנת הודעות בפייתון נקראת cryptography. יש בה מימוש למנגנון בשם fernet המאפשר הצפנה סימטרית של הודעה באמצעות מצפין AES עם מפתח 128 ביט ובמצב עבודה CBC, וחתימת HMAC על ההודעה כדי למנוע שינוי המידע המוצפן לפני פענוחו. בקיצור זה אחלה. ואגב את fernet תוכלו למצוא גם בשפות נוספות כגון רובי או גו בגיטהאב.

התקינו את הספריה עם:

pip install cryptography

ואם אתם מסתבכים אפשר לחפש פתרונות לבעיות נפוצות בעמוד התיעוד שלהם: https://cryptography.io/en/latest/installation/

2. הצפנת הודעות

מנגנון ההצפנה הוא סימטרי ולכן יש לתאם מפתח סודי בין שולח ההודעה למקבל ההודעה (או אם מדובר בהצפנת קבצים קטנים בין מי שמצפין למי שמפענח). אפשר גם להשתמש בסיסמא סודית המתואמת בין שני הצדדים, ולגזור ממנה את המפתח באמצעות פונקציה שנקראת Key Derivation Function.

הפונקציה הבאה מקבלת שם קובץ להצפנה, שם קובץ אליו יישמר המידע המוצפן וסיסמא ומצפינה את הקובץ באמצעות הסיסמא:

def encrypt(plaintext_name, cipher_name, password):
  salt = os.urandom(16)
  kdf = PBKDF2HMAC(
      algorithm=hashes.SHA256(),
      length=32,
      salt=salt,
      iterations=100000,
      backend=default_backend()
      )
  key = base64.urlsafe_b64encode(kdf.derive(password))
  f = Fernet(key)
  with open(plaintext_name, "r") as fin: data = fin.read()
  cipher = f.encrypt(data)

  with open(cipher_name, "w") as fout: 
    fout.write(salt)
    fout.write(cipher)

גזירת מפתח מסיסמא דורשת ערך אקראי שנקרא Salt. ערך זה נועד למנוע מתוקף פוטנציאלי לבצע חישוב מקדים של כל הסיסמאות האפשריות. אין סיבה להחביאו ולכן אני שומר אותו כפשוטו בתוך הקובץ המוצפן לפני ההודעה עצמה. מידע זה ישמש את המפענח כשיבוא לגזור מפתח מאותה הסיסמא.

3. פענוח הודעות

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

def decrypt(cipher_name, plaintext_name, password):
  with open(cipher_name, "r") as fin:
    salt = fin.read(16)
    data = fin.read()

  kdf = PBKDF2HMAC(
      algorithm=hashes.SHA256(),
      length=32,
      salt=salt,
      iterations=100000,
      backend=default_backend()
      )
  key = base64.urlsafe_b64encode(kdf.derive(password))
  f = Fernet(key)
  plaintext = f.decrypt(data)

  with open(plaintext_name, "w") as fout:
    fout.write(plaintext)

4. ממה להיזהר

עקרון חשוב בהצפנה קובע שחוזק ההצפנה נקבע לפי חוזק המפתח. במקרה שלנו המפתח הוא תוצאת הפעלת הפונקציה PBKDF2 על סיסמא שבחרנו. אם הסיסמא קצרה, קבועה או מילה ממילון אנחנו בצרות.

נניח לדוגמא שברחנו בתור סיסמא את המילה monkey. תוקף שמקבל הודעה יכול לנסות לפענח אותה באותו המנגנון שהלקוח ההגון יפענח. התוקף לא יודע את הסיסמא ולכן ינסה בכל פעם סיסמא אחרת עד שהפענוח יצליח ותתקבל הודעה הגיונית. כמה זמן זה ייקח? על מכונת לינוקס טיפוסית תוכלו למצוא את הקובץ /usr/share/dict/words שכולל די הרבה מילים באנגלית. ברשימה שלי יש 99,171 מילים. בהנחה הסופר מקלה שהתוקף מנסה סיסמאות בקצב של סיסמא בשניה (בפועל הקצב הרבה יותר מהיר), ייקח קצת יותר מיממה להגיע לסיסמא הנכונה.

לצורך השוואה אם אותו התוקף יצטרך לנסות את כל המחרוזות באורך עד 9 ספרות המורכבות מאותיות קטנות, אותיות גדולות וספרות (ולא רק מילים ממילון) באותו הקצב החישוב ייקח מאות מיליוני שנים.

בנוסף מאחר וההצפנה טוענת את כל תוכן הקובץ לזכרון, המנגנון לא מתאים להצפנת קבצים גדולים. שם תצטרכו להשתמש ברכיבי הבסיס של הספריה cryptography, בפרט במצפין AES ישירות, כדי להצפין את הקובץ בלוק אחר בלוק.