מעשית IOT Cryptography על אספרסיפף ESP8266
ESPASSIF ESP8266 שבבים עושה שלושה דולר “אינטרנט של דברים פיתוח לוחות מציאות כלכלית. על פי אתר הקושחה האוטומטית הפופולארית אתר בונה NODEMCU, ב -60 הימים האחרונים היו 13,341 קושחה מותאמת אישית בונה עבור פלטפורמה זו. של אלה, רק 19% יש תמיכה SSL, ו 10% כוללים את מודול קריפטוגרפיה.
לעתים קרובות אנו מבקרים על חוסר הביטחון במגזר IOT, ולעתים קרובות לכסות בוטנים ובהתקפות אחרות, אבל האם נוכל להחזיק בפרויקטים שלנו לאותם סטנדרטים שאנו דורשים? האם נפסיק לזהות את הבעיה, או שאנחנו יכולים להיות חלק מהפתרון?
מאמר זה יתמקד ביישום AES הצפנה ואת ההרשאה חשיש פונקציות פרוטוקול MQTT באמצעות שבב ESP8266 פופולרי הפעלת קושחה NODEMCU. המטרה שלנו היא לא לספק עותק / הדבק para, אבל כדי לעבור את התהליך צעד אחר צעד, זיהוי אתגרים ופתרונות לאורך הדרך. התוצאה היא מערכת שמוצפנת ומאומתת, המונעת האזנות לאורך הדרך, וזייפת נתונים חוקיים, ללא הסתמכות ב- SSL.
אנו מודעים לכך שיש גם פלטפורמות חזקות יותר שיכולות בקלות לתמוך ב- SSL (למשל פטל PI, PI כתום, בני ידידות), אבל נתחיל עם החומרה הזולה ביותר שבהנו יש שוכב סביב, ופרוטוקול מתאים לפרויקטים שלנו רבים שלנו . AES הוא משהו שאתה יכול ליישם על AVR אם אתה צריך.
תֵאוֹרִיָה
MQTT הוא פרוטוקול הודעות קל אשר פועל על גבי TCP / IP והוא משמש לעתים קרובות עבור פרויקטים IOT. התקני לקוח הצטרף כמנוי או לפרסם לנושאים (E.G. חיישנים / טמפרטורה / מטבח), והודעות אלה מועברות על ידי מתווך MQTT. מידע נוסף על MQTT זמין בדף האינטרנט שלהם או בסדרה שלנו להתחיל.
לפרוטוקול MQTT אין תכונות אבטחה מובנות מעבר לאימות שם משתמש / סיסמה, ולכן זה נפוץ להצפין ולאמת ברשת עם SSL. עם זאת, SSL יכול להיות די דורש עבור ESP8266 ומתי אפשרות, אתה נשאר עם הרבה פחות זיכרון עבור הבקשה שלך. כחלופה קל, אתה יכול להצפין רק את נתוני המטען להישלח, ולהשתמש מזהה הפעלה ופונקציה חשיש לאימות.
דרך פשוטה לעשות זאת היא באמצעות Lua ואת מודול Crypto החדש, הכולל תמיכה באלגוריתם AES במצב CBC, כמו גם את הפונקציה HMAC Hash. באמצעות הצפנה AES נכון דורש שלושה דברים לייצר ciphertext: הודעה, מפתח, וקטור אתחול (IV). הודעות ומפתחות הם מושגים פשוטים, אבל וקטור האתחול שווה קצת דיון.
כאשר אתה מקודד הודעה ב AES עם מפתח סטטי, זה תמיד יהיה לייצר את אותו פלט. לדוגמה, ההודעה “USERNamePassword” מוצפנת עם מפתח “1234567890ACKCEF” עשוי לייצר תוצאה כמו “E40D86C04D723Aff”. אם אתה מפעיל שוב את ההצפנה עם אותו מפתח והודעה, תקבל את אותה תוצאה. זה פותח אותך כמה סוגים נפוצים של התקפה, במיוחד ניתוח תבנית התקפות החוזר.
ב התקף ניתוח תבנית, אתה משתמש בידע כי חתיכת נתון נתון תמיד לייצר את אותו ciphertext כדי לנחש מה המטרה או תוכן של הודעות שונות הם מבלי לדעת למעשה את המפתח הסודי. לדוגמה, אם ההודעה “E40D86C04D723Aff” נשלחת לפני כל התקשורת האחרת, אפשר לנחש במהירות שזה כניסה. בקיצור, אם מערכת ההתחברות היא פשטנית, שליחת מנות (התקפה חוזרת) עשויה להיות מספיק כדי לזהות את עצמך כמשתמש מורשה, וכאוס ensues.
IVs לעשות ניתוח תבנית קשה יותר. IV הוא פיסת נתונים שנשלחו יחד עם המפתח שמשנה את התוצאה הצ’יפטטקסט הסופי. כפי שהשם מציע, הוא מאתחל את מצב האלגוריתם הצפנה לפני שהנתונים נכנסים. ה- IV צריך להיות שונה עבור כל הודעה שנשלחו כך נתונים חוזרים מצפינים לתוך ciphertext שונים, וכמה ciphers (כמו AES-CBC) דורשים את זה להיות בלתי צפוי – דרך מעשית להשיג זאת היא רק כדי לאקוף אותו בכל פעם. IVs לא צריך להישמר בסוד, אבל זה אופייני לערפל אותם בדרך כלשהי.
While this protects against pattern analysis, it doesn’t help with replay attacks. For example, retransmitting a given set of encrypted data will still duplicate the result. To prevent that, we need to authenticate the sender. We will use a public, pseudorandomly generated session ID for each message. This session ID can be generated by the receiving device by posting to an MQTT topic.
Preventing these types of attacks is important in a couple of common use cases. Internet controlled stoves exist, and questionable utility aside, it would be nice if they didn’t use insecure commands. Secondly, if I’m datalogging from a hundred sensors, I don’t want anyone filling my database with garbage.
Practical Encryption
Implementing the above on the NodeMCU requires some effort. You will need firmware compiled to include the ‘crypto’ module in addition to any others you require for your application. SSL support is not required.
First, let’s assume you’re connected to an MQTT broker with something like the following. You can implement this as a separate function from the cryptography to keep things clean. The client subscribes to a sessionID channel, which publishes suitably long, pseudorandom session IDs. You could encrypt them, but it’s not necessary.
1.
2.
3.
4.
5.
6.
7
8
9
10
11
12
13
14
15
m = mqtt.Client("clientid", 120)
m:connect("myserver.com", 1883, 0,
function(client)
print("connected")
client:subscribe("mytopic/sessionID", 0,
function(client) print("subscribe success") end
)
סוֹף,
function(client, reason)
print("failed reason: " .. reason)
סוֹף
)
m:on("message", function(client, topic, sessionID) end)
Moving on, the node ID is a convenient way to help identify data sources. You can use any string you wish though: nodeid = node.chipid().
Then, we set up a static initialization vector and a key. This is only used to obfuscate the randomized initialization vector sent with each message, NOT used for any data. We also choose a separate key for the data. These keys are 16-bit hex, just replace them with yours.
Finally we’ll need a passphrase for a hash function we’ll be using later. A string of reasonable length is fine.
1.
2.
3.
4.
staticiv = "abcdef2345678901"
ivkey = "2345678901abcdef"
datakey = "0123456789abcdef"
passphrase = "mypassphrase"
We’ll also assume you have some source of data. For this example it will be a value read from the ADC. data = adc.read(0)
Now, we generate a pseudorandom initialization vector. A 16-digit hex number is too large for the pseudorandom number function, so we generate it in two halves (16^8 minus 1) and concatenate them.
1.
2.
3.
4.
5.
half1 = node.random(4294967295)
half2 = node.random(4294967295)
I = string.format("%8x", half1)
V = string.format("%8x", half2)
iv = I .. V
We can now run the actual encryption. here we are encrypting the current initialization vector, the node ID, and one piece of sensor data.
1.
2.
3.
encrypted_iv = crypto.encrypt("AES-CBC", ivkey, iv, staticiv)
encrypted_nodeid = crypto.encrypt("AES-CBC", datakey, nodeid,iv)
encrypted_data = crypto.encrypt("AES-CBC", datakey, data,iv)
Now we apply the hash function for authentication. first we combine the nodeid, iv, data, and session ID into a single message, then compute a HMAC SHA1 hash using the passphrase we defined earlier. We convert it to hex to make it a bit more human-readable for any debugging.
1.
2.
fullmessage = nodeid .. iv .. data .. sessionID
hmac = crypto.toHex(crypto.hmac("sha1", fullmessage, passphrase))
Now that both encryption and authentication checks are in place, we can place all this information in some structure and send it. Here, we’ll use comma separated values as it’s convenient:
1.
2.
payload = table.concat({encrypted_iv, eid, data1, hmac}, ",")
m:publish("yourMQTTtopic", payload, 2, 1, function(client) p = "Sent" print(p) end)
When we run the above code on an actual NodeMCU, we would get output something like this:
1d54dd1af0f75a91a00d4dcd8f4ad28d,
d1a0b14d187c5adfc948dfd77c2b2ee5,
564633a4a053153bcbd6ed25370346d5,
c66697df7e7d467112757c841bfb6bce051d6289
All together, the encryption program is as follows (MQTT sections excluded for clarity):
1.
2.
3.
4.
5.
6.
7
8
9
10
11
12
13
14
15
16
17
18
19
nodeid = node.chipid()
staticiv = "abcdef2345678901"
ivkey = "2345678901abcdef"
datakey = "0123456789abcdef"
passphrase = "mypassphrase"
data = adc.read(0)
half1 = node.random(4294967295)
half2 = node.random(4294967295)
I = string.format("%8x", half1)
V = string.format("%8x", half2)
iv = I .. V
encrypted_iv = crypto.encrypt("AES-CBC", ivkey, iv, staticiv)
encrypted_nodeid = crypto.encrypt("AES-CBC", datakey, nodeid,iv)
encrypted_data = crypto.encrypt("AES-CBC", datakey, data,iv)
fullmessage = nodeid .. iv .. data .. sessionID
hmac = crypto.toHex(crypto.hmac("sha1",fullmessage,passphrase))
payload = table.concat({encrypted_iv, encrypted_nodeid, encrypted_data, hmac}, ",")
פענוח
Now, your MQTT broker doesn’t know or care that the data is encrypted, it just passes it on. So, your other MQTT clients subscribed to the topic will need to know how to decrypt the data. On NodeMCU this is rather easy. just split the received data into strings via the commas, and do something like the below. note this end will have generated the session ID so already knows it.
1.
2.
3.
4.
5.
6.
7
8
9
10
staticiv = "abcdef2345678901"
ivkey = "2345678901abcdef"
datakey = "0123456789abcdef"
passphrase = "mypassphrase"
iv = crypto.decrypt("AES-CBC", ivkey, encrypted_iv, staticiv)
nodeid = crypto.decrypt("AES-CBC&quOT;, Datakey, Encrypted_nodeid, IV)
נתונים = Crypto.decrypt (& quot, AES-CBC & quot;, Datakey, Encrypted_data, IV)
fullmessage = nodidid .. iv .. נתונים ..
HMAC = Crypto.tohex (Crypto.hmac (& quot; sha1 & quot;, fullmessage, ביטוי סיסמה))
לאחר מכן, השווה את ה- HMAC שהתקבל וחישב, וללא קשר לתוצאה, לבטל את מזהה הפגישה על ידי יצירת אחד חדש.
שוב, בפיתון
עבור מגוון קטן, לשקול איך היינו להתמודד עם פענוח Python, אם היה לנו לקוח MQTT על אותו מחשב וירטואלי כמו הברוקר כי ניתח את הנתונים או לאחסן אותו במסד נתונים. מאפשר להניח שקיבלת את הנתונים כמחרוזת “מטען”, ממשהו כמו לקוח Paho MQTT מעולה עבור Python.
במקרה זה זה נוח hex לקודד את הנתונים מוצפנים על NODEMCU לפני שידור. אז על NODEMCU אנו להמיר את כל הנתונים מוצפנים hex, לדוגמה: Encrypted_iv = Crypto.tohex (Crypto.encrypt (“AES-CBC”, IVKEY, IV, סטטייב))
פרסום מושב אקראי לא נדון להלן, אבל הוא קל מספיק באמצעות OS.Urandom () ואת הלקוח Paho MQTT. פענוח מטופל כדלקמן:
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
מ Crypto.cipher ייבוא AES
ייבוא Binascii.
מ Crypto.hash ייבוא Sha, HMAC
# הגדר את כל המפתחות
IVKEY = ‘2345678901ABCEF’
datakey = ‘0123456789ABCEF’
Staticiv = ‘ABCDEF2345678901’
Passphrase = ‘mypassphrase’
# המרת מחרוזת שהתקבלה לרשימה
נתונים = payload.split (& quot; & quot;)
# רשימת פריטים
Encrypted_iv = binascii.unhexlify (נתונים [0])
Encrypted_nodeid = binascii.unhexlify (נתונים [1])
Encrypted_data = binascii.unhexlify (נתונים [2])
REPADE_HASH = BINASCII.UNHEXLIFD (נתונים [3])
# פענוח את האתחול וקטור
IV_DECRYPTION_SUITE = AES.NEW (IVKEY, AES.MODE_CBC, Staticiv)
IV = IV_DECRYPTION_SUITE.DECRYPT (ENCRYPTED_IV)
# פענוח הנתונים באמצעות וקטור האתחול
ID_DECRYPTION_SUITE = AES.New (Datakey, AES.MODE_CBC, IV)
NodeID = ID_DECRYPTION_SUITE.DECRYPT (ENCRYPTED_NODEID)
DATA_DECRYPTION_SUITE = AES.New (Datakey, AES.MODE_CBC, IV)
SENSORDATA = DATA_DECRYPTION_SUITE.DECRYPT (ENCRYPTED_DATA)
# מחשוב Hash פונקציה כדי להשוות את ההתקנה
fullmessage = s.join ([nodidid, iv, sensordata, מושב])
HMAC = HMAC.New (סיסמה, fullmessage, sha)
Computed_hash = HMAC.HEXDigest ()
# ראה docs.python.org/2/library/hmac.html כיצד להשוות hashes בבטחה
הסוף, ההתחלה
עכשיו יש לנו מערכת ששולחת מסרים מוצפנים, מאומתים באמצעות שרת MQTT או לקוח אחר ESP8266 או מערכת גדולה יותר פועל Python. יש עדיין קצות רופפים חשובים לך לקשור אם אתה מיישם את זה בעצמך. המקשים מאוחסנים כולם בזיכרון הבזק של ESP8266S, כך תרצה לשלוט בגישה למכשירים אלה כדי למנוע הנדסה לאחור. המקשים מאוחסנים גם בקוד במחשב המקבל את הנתונים, כאן פועל Python. יתר על כן, אתה בטח רוצה כל לקוח יש מפתח אחר ו passphrase. זה הרבה חומר סודי כדי לשמור על עדכון בטוח ופעולה בעת הצורך. פתרון הבעיה הפצה של המפתח נשאר כרגיל עבור הקורא מוטיבציה.
ועל פתק סגירה, אחד הדברים הנוראים על כתיבת מאמר של קריפטוגרפיה היא האפשרות לטעות באינטרנט. זהו יישום פשוט למדי של מצב AES-CBC נבדק עם HMAC, אז זה צריך להיות מוצק למדי. עם זאת, אם אתה מוצא חסרונות מעניינים באמור לעיל, אנא יידע אותנו בהערות.