なかなか完全な在宅勤務にならず、今週は今日まで出勤でした。明日からは在宅予定。
先日、動画をアップしたLINEダッシュボタン ですが、WiFiのアクセスポイントとパスワード、LINE notifyのトークン、ボタンを押したときに送信するメッセージをスケッチ内に書くのではなく、microSDに格納するようにしました。プログラムを直さなくてもmicroSDの内容を書き換えれば、これらを変更できます。
平文でパスワードやトークンを書くのはリスキーなので簡単な暗号化を行いました。EXOR暗号とシーザー暗号のようなものを組み合わせています。以下のような感じで暗号化されます。
key :Open Sesame plain_ssid :WiFi_AccessPoint_SSID plain_passwd :SSID_password plain_token :LINE_TOKEN_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 crypted_ssid :)$GF$Z_5$D#'*D;B*SS#8#SY+%CL crypted_passwd:*%/O-J_F$##5)CK`(# crypted_token :#VGN-Z_*-FCG+VK1/E;M<47E1UCJ.C/`-U(S#F?C06CV)%C_0$$E8W$89(0[5(U
EXOR暗号は2回EXOR操作すると元に戻る性質を使って、暗号化と復号化を行うものです。まず、暗号化keyと平文テキストの文字のEXORを取ります。そのままだとascii文字で表現できないので、8ビット毎の文字列(をEXORした結果)から6ビット毎に取り出してascii文字に変換します。シーザー暗号は文字をずらして暗号化しますが、これは6ビット毎に取り出して文字をずらしています。復号はこの操作の逆を行います。
暗号化/復号化のスケッチは以下です。無保証です。暗号化はC言語などで書いてPCで実行すればよいですが、今回はarduino IDEで開発し、ESP32で動かしています。
//--------------------------------------------------------------
// 簡単なXOR暗号 '22.01.31 naka
// 実行例)
// key :Open Sesame
//
// plain_ssid :WiFi_AccessPoint_SSID
// plain_passwd :SSID_password
// plain_token :LINE_TOKEN_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
//
// crypted_ssid :)$GF$Z_5$D#'*D;B*SS#8#SY+%CL
// crypted_passwd:*%/O-J_F$##5)CK`(#
// crypted_token :#VGN-Z_*-FCG+VK1/E;M<47E1UCJ.C/`-U(S#F?C06CV)%C_0$$E8W$89(0[5(U
//
// decrypt_ssid :WiFi_AccessPoint_SSID
// decrypt_passwd:SSID_password
// decrypt_token :LINE_TOKEN_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
//--------------------------------------------------------------
void setup() {
char crypted_ssid[128],crypted_passwd[128],crypted_token[128];
char dec_ssid[128],dec_passwd[128],dec_token[128];
Serial.begin(115200);
char* key = "Open Sesame";
char* plain_ssid = "WiFi_AccessPoint_SSID";
char* plain_passwd = "SSID_password";
char* plain_token = "LINE_TOKEN_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
encrypt(crypted_ssid,plain_ssid,key);
encrypt(crypted_passwd,plain_passwd,key);
encrypt(crypted_token,plain_token ,key);
decrypt(dec_ssid,crypted_ssid,key);
decrypt(dec_passwd,crypted_passwd,key);
decrypt(dec_token,crypted_token,key);
Serial.printf("key :%s\n\n",key);
Serial.printf("plain_ssid :%s\n",plain_ssid);
Serial.printf("plain_passwd :%s\n",plain_passwd);
Serial.printf("plain_token :%s\n",plain_token);
Serial.printf("\n");
Serial.printf("crypted_ssid :%s\n",crypted_ssid);
Serial.printf("crypted_passwd:%s\n",crypted_passwd);
Serial.printf("crypted_token :%s\n",crypted_token);
Serial.printf("\n");
Serial.printf("decrypt_ssid :%s\n",dec_ssid);
Serial.printf("decrypt_passwd:%s\n",dec_passwd);
Serial.printf("decrypt_token :%s\n",dec_token);
Serial.printf("\n");
sleep;
}
void loop() {
}
void encrypt( char*a, char*b, char* key) {
char x[128];
int i,j,k,m;
k = 0;
int n = strlen(key);
for (i=0; b[i]!=0; i++) {
x[i] = b[i]^key[k++];
k %= n;
}
m = i;
// 8bit -> 6bit
// 8bit 0 1 2
// 000000|00, 0000|0000, 00|000000
// 6bit 0 1 2 3
j = 0;
for (i=0; i<m; j++) {
switch (j%4) {
case 0:
a[j] = (x[i]&0xFC)>>2;
break;
case 1:
a[j] = (x[i]&0x03)<<4 | (x[i+1]&0xF0)>>4;
i++;
break;
case 2:
a[j] = (x[i]&0x0F)<<2 | (x[i+1]&0xC0)>>6;
i++;
break;
case 3:
a[j] = x[i]&0x3F;
i++;
break;
}
}
// ascii文字に変換
for (i=0; i<j; i++) {
a[i] += (int)'#';
}
a[i] = 0;
}
void decrypt( char*a, char*b, char* key) {
char x[128];
int i,j,k,m;
// ascii文字をbinaryに戻す
for(i=0; b[i]!=0; i++) {
x[i] = b[i] - (int)'#';
}
m = i - 1;
// 6bit -> 8bit
j = 0;
for(i=0; j<m ; i++) {
switch (i%3) {
case 0:
a[i] = (x[j]&0x3F)<<2 | (x[j+1]&0x30)>>4;
j++;
break;
case 1:
a[i] = (x[j]&0x0F)<<4 | (x[j+1]&0x3C)>>2;
j++;
break;
case 2:
a[i] = (x[j]&0x03)<<6 | x[j+1]&0x3F;
j += 2;
break;
}
}
m = i;
k = 0;
int n = strlen(key);
for (i=0; i<m; i++) {
a[i] ^= key[k++];
k %= n;
}
a[i]= 0;
}
microSDはESP32の起動時に読み込みます。エラーで読み込めない場合には赤色LEDが点滅したのち、点灯して止まります。正常に読み込めれば、ボタンを押すとWiFiに接続し(その間、緑LEDが点滅)、LINE notifyにメッセージを送り(赤色LED点灯)、WiFi接続を切ります(緑と赤LED消灯)。
回路図とスケッチです。無保証です。
//---------------------------------------------------------------
// 2022.02.02 naka
// LINE Dash Button
//
// ・WiFi ssid,password,LINE notify tokenは、暗号化してmicroSDに格納
// ファイル名:setup.txt
// 暗号化したWiFi ssid, password, LINE notify tokenをそれぞれ1行ずつ
// 順に記載し、4行目に送るメッセージを記載。
// (例)
// &D/)%X/C04S+#T'?+USM
// *%/O-J_F$##5)CK`(%
// #VGN-Z_*-FCG+VK1/E;M<47E1UCJ.C/`-U(S#F?C06CV)%C_0$$E8W$89(0[5(U
// ボタンが押されました。
//---------------------------------------------------------------
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <SD.h>
#define SD_CS 16 // SD card chip select
#define SW 25
#define LED_G 33
#define LED_R 32
#define ON 1
#define OFF 0
#define FILENAME "/setup.txt"
// ssid,pw,tokenを暗号化したときのキー
char* crypt_key = "Open Sesame";
// LINE Notify
const char* host = "notify-api.line.me";
char ssid[64],password[64],token[64],message[128];
int sw_newest;
void setup()
{
Serial.begin(115200);
pinMode(SW,INPUT_PULLUP);
pinMode(LED_G,OUTPUT);
pinMode(LED_R,OUTPUT);
led_green(OFF);
led_red(OFF);
sw_newest = digitalRead(SW);
if (read_setup_file(FILENAME)) {
blink_red(10);
led_red(ON);
sleep;
}
}
void loop(){
if (sw_FallingEdge()) {
wifi_connect(); // WiFi接続
send_msg2line(); // LINE notifyにメッセージを送る
wifi_disconnect(); // WiFi接続解除
while(!digitalRead(SW));
delay(100); // チャタリング対策
}
}
int sw_FallingEdge() {
int prev = sw_newest;
sw_newest = digitalRead(SW);
return prev & ~sw_newest; // change 1 -> 0
}
void led_green(int onoff) {
if (onoff==ON) digitalWrite(LED_G,LOW);
else digitalWrite(LED_G,HIGH);
}
void led_red(int onoff) {
if (onoff==ON) digitalWrite(LED_R,LOW);
else digitalWrite(LED_R,HIGH);
}
void wifi_connect() {
WiFi.begin(ssid, password);
int onoff = 0;
while (WiFi.status() != WL_CONNECTED) {
delay(500);
if (onoff==0) led_green(ON);
else led_green(OFF);
onoff ^= 1;
}
// 以下5行は、普通は不要なハズ
IPAddress gateway(192,168,0,1);
IPAddress subnet(255,255,255,0);
IPAddress dns1(8, 8, 8, 8); // Google DNS primery server
IPAddress dns2(8, 8, 4, 4); // Google DNS secondary server
WiFi.config(WiFi.localIP(),gateway,subnet,dns1,dns2);
led_green(ON);
}
void wifi_disconnect() {
WiFi.disconnect(true);
led_green(OFF);
}
void send_msg2line() {
WiFiClientSecure client;
led_red(ON);
client.setInsecure();
if (!client.connect(host, 443)) {
blink_red(10);
led_red(OFF);
return;
}
String query = String("message=") + String(message);
String request = String("") +
"POST /api/notify HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Authorization: Bearer " + token + "\r\n" +
"Content-Length: " + String(query.length()) + "\r\n" +
"Content-Type: application/x-www-form-urlencoded\r\n\r\n" +
query + "\r\n";
client.print(request);
while (client.connected()) {
String line = client.readStringUntil('\n');
if (line == "\r") {
break;
}
}
String line = client.readStringUntil('\n');
led_red(OFF);
}
void blink_red(int no) {
int onoff = 1;
int i;
for (i=0; i<no*2 ;i++) {
delay(100);
if (onoff==0) led_red(ON);
else led_red(OFF);
onoff ^= 1;
}
}
int read_setup_file(char* fileName) {
char buff[128];
if (!SD.begin(SD_CS, SPI, 24000000)) return 1;
File setupFile = SD.open(fileName,FILE_READ);
if (setupFile) {
readLine(setupFile,buff);
decrypt(ssid,buff,crypt_key);
readLine(setupFile,buff);
decrypt(password,buff,crypt_key);
readLine(setupFile,buff);
decrypt(token,buff,crypt_key);
readLine(setupFile,message);
setupFile.close();
}
return 0;
}
// microSDから1行読み取る
int readLine(File fp, char *buff){
int i = 0;
if(!fp.available()) {
return -1;
}
while (fp.available()) {
char c = fp.read();
if (c=='\r');
else if (c=='\n') break;
else buff[i++] = c;
}
buff[i] = 0;
return i;
}
void decrypt( char*a, char*b, char* key) {
char x[128];
int i,j,k,m;
// ascii文字をbinaryに戻す
for(i=0; b[i]!=0; i++) {
x[i] = b[i] - (int)'#';
}
m = i - 1;
// 6bit -> 8bit
j = 0;
for(i=0; j<m ; i++) {
switch (i%3) {
case 0:
a[i] = (x[j]&0x3F)<<2 | (x[j+1]&0x30)>>4;
j++;
break;
case 1:
a[i] = (x[j]&0x0F)<<4 | (x[j+1]&0x3C)>>2;
j++;
break;
case 2:
a[i] = (x[j]&0x03)<<6 | x[j+1]&0x3F;
j += 2;
break;
}
}
m = i;
k = 0;
int n = strlen(key);
for (i=0; i<m; i++) {
a[i] ^= key[k++];
k %= n;
}
a[i]= 0;
}
//---------------------------------------------------------------
// EOF
//---------------------------------------------------------------
0 件のコメント:
コメントを投稿