Mật mã IOT thực tế trên Espressif ESP8266
Chipset Espressif ESP8266 khiến bảng phát triển ‘Internet của mọi thứ’ có ba đô la một thực tế kinh tế. Theo trang web xây dựng chương trình cơ sở tự động phổ biến Nodemcu-Builds, trong 60 ngày qua, có 13.341 phần mềm tùy chỉnh được xây dựng cho nền tảng đó. Trong số đó, chỉ có 19% có hỗ trợ SSL và 10% bao gồm mô-đun mật mã.
Chúng tôi thường chỉ trích sự thiếu bảo mật trong lĩnh vực IOT và thường xuyên bao gồm các botnet và các cuộc tấn công khác, nhưng chúng tôi sẽ giữ các dự án của chúng tôi theo các tiêu chuẩn tương tự mà chúng tôi yêu cầu? Chúng ta sẽ dừng lại trong việc xác định vấn đề, hoặc chúng ta có thể là một phần của giải pháp?
Bài viết này sẽ tập trung vào việc áp dụng các chức năng ủy quyền mã hóa và băm AES cho giao thức MQTT bằng cách sử dụng chip ESP8266 phổ biến chạy firmware NodeMCU. Mục đích của chúng tôi không phải là để cung cấp một bản sao / Paste Panacea, mà để trải qua quá trình từng bước, xác định những thách thức và giải pháp trên đường đi. Kết quả là một hệ thống được mã hóa kết thúc và được xác thực, ngăn chặn nghe lén trên đường đi và giả mạo dữ liệu hợp lệ, mà không dựa vào SSL.
Chúng tôi biết rằng cũng có nhiều nền tảng mạnh mẽ hơn có thể dễ dàng hỗ trợ SSL (ví dụ Raspberry Pi, Orange Pi, Thân thiện), nhưng hãy bắt đầu với phần cứng rẻ nhất Hầu hết chúng ta đều nằm xung quanh và một giao thức phù hợp với nhiều dự án của chúng tôi . AES là thứ bạn có thể thực hiện trên AVR nếu bạn cần.
Học thuyết
MQTT là một giao thức nhắn tin nhẹ chạy trên đỉnh TCP / IP và thường được sử dụng cho các dự án IOT. Các thiết bị khách đăng ký hoặc xuất bản sang chủ đề (ví dụ: cảm biến / nhiệt độ / bếp) và những thông điệp này được chuyển tiếp bởi một nhà môi giới MQTT. Thông tin thêm về MQTT có sẵn trên trang web của họ hoặc trong loạt phát bắt đầu của chúng ta.
Giao thức MQTT không có bất kỳ tính năng bảo mật tích hợp nào ngoài xác thực tên người dùng / mật khẩu, do đó, phổ biến để mã hóa và xác thực trên mạng với SSL. Tuy nhiên, SSL có thể khá yêu cầu đối với ESP8266 và khi được bật, bạn còn lại ít bộ nhớ hơn nhiều cho ứng dụng của mình. Là một sự thay thế nhẹ, bạn chỉ có thể mã hóa tải trọng dữ liệu được gửi và sử dụng ID phiên và hàm băm để xác thực.
Một cách đơn giản để làm điều này là sử dụng LUA và mô-đun Crypto NodeMcu, bao gồm hỗ trợ cho thuật toán AES ở chế độ CBC cũng như hàm HMAC HASH. Sử dụng mã hóa AES, yêu cầu chính xác ba thứ để tạo ra bản mã: một thông báo, phím và vectơ khởi tạo (iv). Tin nhắn và khóa là các khái niệm đơn giản, nhưng vectơ khởi tạo đáng để thảo luận.
Khi bạn mã hóa một thông báo trong AES với khóa tĩnh, nó sẽ luôn tạo ra cùng một đầu ra. Ví dụ: thông báo “USERNAMAPASSWORD” được mã hóa với khóa “1234567890abcdef” có thể tạo ra kết quả như “E40D86C04D723AFF”. Nếu bạn chạy lại mã hóa với cùng một khóa và tin nhắn, bạn sẽ nhận được kết quả tương tự. Điều này sẽ mở ra cho bạn một số loại tấn công phổ biến, đặc biệt là phân tích mẫu và các cuộc tấn công phát lại.
Trong một cuộc tấn công phân tích mẫu, bạn sử dụng kiến thức rằng một đoạn dữ liệu nhất định sẽ luôn tạo ra bản mã tương tự để đoán mục đích hoặc nội dung của các thông báo khác nhau mà không thực sự biết khóa bí mật. Ví dụ: nếu thông báo “E40D86C04D723Aff” được gửi trước tất cả các thông tin liên lạc khác, người ta có thể nhanh chóng đoán là đăng nhập. Nói tóm lại, nếu hệ thống đăng nhập đơn giản, hãy gửi gói đó (một cuộc tấn công phát lại) có thể đủ để xác định bản thân như một người dùng được ủy quyền và Chaos xảy ra.
IVS làm cho phân tích mô hình khó khăn hơn. IV là một phần dữ liệu được gửi cùng với khóa sửa đổi kết quả bản mã cuối. Như tên cho thấy, nó sẽ khởi tạo trạng thái của thuật toán mã hóa trước khi dữ liệu vào. IV cần phải khác nhau đối với mỗi tin nhắn được gửi để dữ liệu lặp lại mã hóa thành các bản mã khác nhau và một số mật mã (như AES-CBC) yêu cầu nó không thể đoán trước – một cách thực tế để thực hiện điều này chỉ là ngẫu nhiên mỗi lần. IVS không cần phải giữ bí mật, nhưng đó là điển hình để làm phiền chúng theo một cách nào đó.
Trong khi điều này bảo vệ chống lại phân tích mẫu, nó không giúp phát lại các cuộc tấn công. Ví dụ: truyền lại một tập hợp dữ liệu được mã hóa nhất định sẽ sao chép kết quả. Để ngăn chặn điều đó, chúng ta cần xác thực người gửi. Chúng tôi sẽ sử dụng ID phiên Public, được tạo giả cho mỗi tin nhắn. ID phiên này có thể được tạo bởi thiết bị nhận bằng cách đăng lên một chủ đề MQTT.
Ngăn chặn các loại tấn công này rất quan trọng trong một vài trường hợp sử dụng chung. Các bếp đóng kiểm soát Internet tồn tại và tiện ích đáng ngờ sang một bên, sẽ rất tuyệt nếu chúng không sử dụng các lệnh không an toàn. Thứ hai, nếu tôi đang xếp dữ liệu từ một trăm cảm biến, tôi không muốn bất kỳ ai điền vào cơ sở dữ liệu của mình bằng rác.
Mã hóa thực tế
Thực hiện các trên trên NodeMCU đòi hỏi một số nỗ lực. Bạn sẽ cần phần sụn được biên dịch để bao gồm mô-đun ‘Crypto’ ngoài bất kỳ người khác nào bạn yêu cầuire cho ứng dụng của bạn. Hỗ trợ SSL là không cần thiết.
Đầu tiên, giả sử bạn đã kết nối với một nhà môi giới MQTT với một cái gì đó như sau. Bạn có thể thực hiện điều này như một hàm riêng biệt từ mật mã để giữ cho mọi thứ sạch sẽ. Khách hàng đăng ký vào một kênh sessionID, xuất bản một cách phù hợp, ID phiên giả lập. Bạn có thể mã hóa chúng, nhưng không cần thiết.
1.
2.
3.
4
5.
6.
7.
số 8
9.
10.
11.
12.
13.
14.
15.
m = mqtt.client (& quot; clientId & quot ;, 120)
m: kết nối (& quot; myserver.com & quot ;, 1883, 0,
Chức năng (khách hàng)
In (& quot; kết nối & quot;)
Khách hàng: Đăng ký (& quot; MyTopic / sessionId & quot ;, 0,
chức năng (khách hàng) in (& quot; đăng ký thành công & quot;) kết thúc
)
chấm dứt,
Chức năng (Khách hàng, Lý do)
In (& quot; Lý do thất bại: & quot; .. Lý do)
chấm dứt
)
m: on (& quot; tin nhắn & quot ;, Chức năng (khách hàng, chủ đề, sessionId) Kết thúc)
Di chuyển trên, ID nút là một cách thuận tiện để giúp xác định các nguồn dữ liệu. Bạn có thể sử dụng bất kỳ chuỗi nào bạn muốn mặc dù: NodeID = node.chipid ().
Sau đó, chúng tôi thiết lập một vector khởi tạo tĩnh và một phím. Điều này chỉ được sử dụng để obfuscate vectơ khởi tạo ngẫu nhiên được gửi với mỗi tin nhắn, không được sử dụng cho bất kỳ dữ liệu nào. Chúng tôi cũng chọn một khóa riêng cho dữ liệu. Những phím này là hex 16 bit, chỉ cần thay thế chúng bằng của bạn.
Cuối cùng chúng ta sẽ cần một cụm mật khẩu cho hàm băm, chúng tôi sẽ sử dụng sau. Một chuỗi có độ dài hợp lý là tốt.
1.
2.
3.
4
staticiv = & quot; abcdef2345678901 & quot;
Ivkey = & quot; 2345678901abcdef & quot;
Datakey = & quot; 0123456789abcdef & quot;
Mật khẩu = & quot; mypassphrase & quot;
Chúng tôi cũng sẽ cho rằng bạn có một số nguồn dữ liệu. Trong ví dụ này, nó sẽ là một giá trị được đọc từ ADC. DATA = ADC.READ (0)
Bây giờ, chúng tôi tạo ra một vector khởi tạo giả. Một số hex gồm 16 chữ số quá lớn cho chức năng số giả, vì vậy chúng tôi tạo ra nó trong hai nửa (16 ^ 8 trừ 1) và nối chúng.
1.
2.
3.
4
5.
HALF1 = NODE.RANDOM (4294967295)
Half2 = node.random (4294967295)
I = string.format (& quot;% 8x & quot ;, nửa1)
V = string.format (& quot;% 8x & quot ;, Half2)
iv = i .. v
Bây giờ chúng ta có thể chạy mã hóa thực tế. Ở đây chúng tôi đang mã hóa vectơ khởi tạo hiện tại, ID nút và một đoạn dữ liệu cảm biến.
1.
2.
3.
Encrypted_iv = Crypto.encrypt (& quot; AES-CBC & quot ;, Ivkey, IV, STATICIV)
Encrypted_nodeID = Crypto.encrypt (& quot; AES-CBC & quot ;, Datakey, NodeID, IV)
Encrypted_data = crypto.encrypt (& quot; AES-CBC & quot ;, Datakey, Data, IV)
Bây giờ chúng tôi áp dụng hàm băm để xác thực. Đầu tiên, chúng tôi kết hợp ID NODEID, IV, DỮ LIỆU và ID phiên vào một tin nhắn duy nhất, sau đó tính toán hàm băm HMAC SHA1 bằng cách sử dụng cụm mật khẩu mà chúng tôi đã xác định trước đó. Chúng tôi chuyển đổi nó thành hex để làm cho nó có thể đọc được nhiều hơn một chút cho bất kỳ gỡ lỗi nào.
1.
2.
FullMessage = NODEID .. IV .. Dữ liệu .. SessionId
hmac = crypto.tohex (crypto.hmac (& quot; sha1 & quot ;, fullmessage, cụm mật khẩu))
Bây giờ cả hai lần kiểm tra mã hóa và xác thực đều có thể đặt tất cả thông tin này trong một số cấu trúc và gửi nó. Ở đây, chúng tôi sẽ sử dụng các giá trị được phân tách bằng dấu phẩy vì nó thuận tiện:
1.
2.
PayLoad = Table.concat ({encrypted_iv, eid, data1, hmac}, & quot;, & quot;)
m: xuất bản (& quot; yourmqtttopic & quot ;, tải trọng, 2, 1, chức năng (khách hàng) p = & quot; đã gửi & quot; in (p) kết thúc)
Khi chúng ta chạy đoạn mã trên trên một NodeMCU thực tế, chúng ta sẽ nhận được một thứ gì đó như thế này:
1D54DD1AF0F75A91A00D4DCD8F4AD28D,
d1a0b14d187c5adfc948dfd77c2b2ee5,
564633A4A053153BCBD6ED25370346D5,
C66697DF7E7D467112757C841BFB6BCE051D6289.
Tất cả cùng nhau, chương trình mã hóa như sau (các phần MQTT bị loại trừ cho rõ ràng):
1.
2.
3.
4
5.
6.
7.
số 8
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
nodeid = node.chipid ()
staticiv = & quot; abcdef2345678901 & quot;
Ivkey = & quot; 2345678901abcdef & quot;
Datakey = & quot; 0123456789abcdef & quot;
Mật khẩu = & quot; mypassphrase & quot;
DATA = ADC.READ (0)
HALF1 = NODE.RANDOM (4294967295)
Half2 = node.random (4294967295)
I = string.format (& quot;% 8x & quot ;, nửa1)
V = string.format (& quot;% 8x & quot ;, Half2)
iv = i .. v
Encrypted_iv = Crypto.encrypt (& quot; AES-CBC & quot ;, Ivkey, IV, STATICIV)
Encrypted_nodeID = Crypto.encrypt (& quot; AES-CBC & quot ;, Datakey, NodeID, IV)
Encrypted_data = crypto.encrypt (& quot; AES-CBC & quot ;, Datakey, Data, IV)
FullMessage = NODEID .. IV .. Dữ liệu .. SessionId
hmac = crypto.tohex (crypto.hmac (& quot; sha1 & quot;, fullmessage, passphrase))
PayLoad = Table.concat ({Encrypted_iv, Encrypted_nodeID, Encrypted_data, hmac}, & quot;, & quot;)
Giải mã.
Bây giờ, nhà môi giới MQTT của bạn không biết hoặc quan tâm rằng dữ liệu được mã hóa, nó chỉ chuyển nó vào. Vì vậy, các máy khách MQTT khác của bạn đã đăng ký vào chủ đề này sẽ cần biết cách giải mã dữ liệu. Trên NodeMCU điều này khá dễ dàng. Chỉ cần chia dữ liệu đã nhận thành chuỗi thông qua dấu phẩy và làm một cái gì đó giống như dưới đây. Lưu ý Kết thúc này sẽ tạo ra ID phiên vì vậy đã biết nó.
1.
2.
3.
4
5.
6.
7.
số 8
9.
10.
staticiv = & quot; abcdef2345678901 & quot;
Ivkey = & quot; 2345678901abcdef & quot;
Datakey = & quot; 0123456789abcdef & quot;
Mật khẩu = & quot; mypassphrase & quot;
iv = crypto.decrypt (& quot; AES-CBC & quot ;, Ivkey, mã hóa_iv, staticiv)
NODEID = Crypto.Decrypt (& quot; AES-CBC & Quot ;, Datakey, Encrypted_nodeID, IV)
Dữ liệu = Crypto.decrypt (& quot; AES-CBC & quot;, Datakey, Encrypted_data, iv)
FullMessage = NODEID .. IV .. Dữ liệu .. SessionId
hmac = crypto.tohex (crypto.hmac (& quot; sha1 & quot;, fullmessage, passphrase))
Sau đó so sánh HMAC đã nhận và được tính toán, và bất kể kết quả, vô hiệu hóa ID phiên đó bằng cách tạo một ID mới.
Một lần nữa, trong Python
Đối với một chút đa dạng, hãy xem xét cách chúng tôi xử lý giải mã trong Python, nếu chúng tôi có một máy khách MQTT trên cùng một máy ảo với nhà môi giới đã phân tích dữ liệu hoặc lưu trữ nó trong cơ sở dữ liệu. Hãy giả sử bạn đã nhận được dữ liệu dưới dạng chuỗi “tải trọng”, từ một thứ giống như khách hàng Paho MQTT xuất sắc cho Python.
Trong trường hợp này, thuận tiện để hex mã hóa dữ liệu được mã hóa trên NodeMCU trước khi truyền. Vì vậy, trên NodeMCU, chúng tôi chuyển đổi tất cả dữ liệu được mã hóa sang Hex, ví dụ: mã hóa_iv = crypto.tohex (Crypto.encrypt (“AES-CBC”, Ivkey, IV, Staticiv))
Xuất bản một phiên ngẫu nhiên không được thảo luận dưới đây, nhưng dễ dàng sử dụng OS.URANANOM () và máy khách Paho MQTT. Giải mã được xử lý như sau:
1.
2.
3.
4
5.
6.
7.
số 8
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
Vả lại 24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
từ crypto.csodes nhập AES
Nhập khẩu Binascii.
từ crypto.hash nhập sha, hmac
# Xác định tất cả các phím
ivkey = ‘2345678901abcdef’
Datakey = ‘0123456789ABCDEF’
staticiv = ‘abcdef2345678901’
passphrase = ‘mypassphrase’
# Chuyển đổi chuỗi đã nhận thành một danh sách
Dữ liệu = Tải xuống.Split (& quot;, & quot;)
# Trích xuất danh sách các mục
Encrypted_iv = binascii.unhexlify (dữ liệu [0])
Encrypted_nodeID = Binascii.UnhExlify (dữ liệu [1])
Encrypted_data = binascii.unhexlify (dữ liệu [2])
Đã nhận_hash = binascii.unhexlify (dữ liệu [3])
# giải mã vector khởi tạo
iv_decrypt_suite = AES.NEW (Ivkey, AES.MODE_CBC, STATICIV)
iv = iv_decryption_suite.decrypt (mã hóa_iv)
# Giải mã dữ liệu bằng vectơ khởi tạo
id_decrypt_suite = aes.new (Datakey, AES.MODE_CBC, IV)
nodeid = id_decrypt_suite.decrypt (mã hóa_nodeid)
data_decrypt_suite = AES.New (Datakey, AES.MODE_CBC, IV)
Sensordata = data_decryption_suite.decrypt (Encrypted_data)
# tính toán hàm băm để so sánh với nhận_hash
fullmessage = s.join ([NODEID, IV, Sensordata, sessionId])
hmac = hmac.new (cụm mật khẩu, fullmessage, sha)
tính toán_hash = hmac.hexdigest ()
# xem tài liệu.python.org/2/l Library/hmac.html để so sánh băm an toàn
Kết thúc, sự khởi đầu
Bây giờ chúng tôi có một hệ thống gửi các tin nhắn được mã hóa, được xác thực thông qua máy chủ MQTT sang máy khách ESP8266 khác hoặc hệ thống lớn hơn chạy Python. Vẫn còn những kết thúc lỏng lẻo quan trọng để bạn trói buộc nếu bạn tự thực hiện này. Tất cả các phím đều được lưu trữ trong bộ nhớ flash của ESP8266S, vì vậy bạn sẽ muốn kiểm soát quyền truy cập vào các thiết bị này để tránh Kỹ thuật đảo ngược. Các phím cũng được lưu trữ trong mã trên máy tính nhận dữ liệu, ở đây chạy Python. Hơn nữa, bạn có thể muốn mỗi khách hàng có một khóa và cụm mật khẩu khác nhau. Đó là rất nhiều tài liệu bí mật để giữ an toàn và có khả năng cập nhật khi cần thiết. Giải quyết vấn đề phân phối chính được để lại như một bài tập cho người đọc có động lực.
Và trong một ghi chú kết thúc, một trong những điều đáng sợ về việc viết một bài viết liên quan đến mật mã là khả năng bị sai trên Internet. Đây là một ứng dụng khá đơn giản của chế độ AES-CBC được thử nghiệm và thực sự với HMAC, vì vậy nó sẽ khá vững chắc. Tuy nhiên, nếu bạn tìm thấy bất kỳ thiếu sót thú vị nào ở trên, xin vui lòng cho chúng tôi biết trong các ý kiến.