ESP32でTOTPのシークレットを生成します。
以下に分けられます。
・Base32デコード
・HMAC(SHA-1)
・TOTPライブラリ
Base32デコード
通常シークレット鍵はよくQRコードで表示されますが、中身はBase32でエンコードされたバイト配列です。
なので、まずは、Base32デコードするための関数を作成します。
lib_base32.cpp
#include <Arduino.h>
static const char* base32Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
static int base32CharToValue(char c) {
const char* p = strchr(base32Alphabet, c);
return p ? (p - base32Alphabet) : -1;
}
unsigned long base32_decode(const char* input, unsigned char* output, unsigned long outputSize) {
unsigned long buffer = 0;
int bitsLeft = 0;
unsigned long count = 0;
for (unsigned long i = 0; input[i] != '\0'; ++i) {
if (input[i] == '=' || input[i] == ' ') continue;
int val = base32CharToValue(toupper(input[i]));
if (val < 0) continue;
buffer <<= 5;
buffer |= val;
bitsLeft += 5;
if (bitsLeft >= 8) {
bitsLeft -= 8;
if (count < outputSize) {
output[count++] = (buffer >> bitsLeft) & 0xFF;
}
}
}
return count;
}
String base32_encode(const unsigned char* data, unsigned long length) {
String result;
int buffer = 0;
int bitsLeft = 0;
for (unsigned long i = 0; i < length; ++i) {
buffer <<= 8;
buffer |= data[i];
bitsLeft += 8;
while (bitsLeft >= 5) {
int index = (buffer >> (bitsLeft - 5)) & 0x1F;
result += base32Alphabet[index];
bitsLeft -= 5;
}
}
if (bitsLeft > 0) {
int index = (buffer << (5 - bitsLeft)) & 0x1F;
result += base32Alphabet[index];
}
// Padding to make length a multiple of 8
while (result.length() % 8 != 0) {
result += '=';
}
return result;
}
unsigned long base32_encodedLength(unsigned long inputLength) {
unsigned long bits = inputLength * 8;
unsigned long base32Chars = (bits + 4) / 5; // ceil(bits / 5)
unsigned long paddedLength = ((base32Chars + 7) / 8) * 8; // round up to multiple of 8
return paddedLength;
}
unsigned long base32_decodedLength(const char* base32_str) {
unsigned long len = strlen(base32_str);
unsigned long pad = 0;
// パディング '=' を除外
for (int i = len - 1; i >= 0 && base32_str[i] == '='; --i) {
pad++;
}
unsigned long clean_len = len - pad;
return (clean_len * 5) / 8;
}
HMAC(SHA-1)の算出
6桁のシークレットコードを生成するには、HMAC(SHA-1)が必要です。
mbedtlsがあるので、それを活用します。
lib_hmac.cpp
#include "lib_hmac.h"
#include <mbedtls/md.h>
long hmac_calculate(unsigned char type, const unsigned char *p_key, int key_len,const unsigned char *p_input, int input_len, unsigned char *p_hmac)
{
int output_len;
mbedtls_md_type_t md_type;
if( type == HMAC_TYPE_SHA1 ){
output_len = 20;
md_type = MBEDTLS_MD_SHA1;
}else if( type == HMAC_TYPE_MD5 ){
output_len = 16;
md_type = MBEDTLS_MD_MD5;
}else if( type == HMAC_TYPE_SHA256 ){
output_len = 32;
md_type = MBEDTLS_MD_SHA256;
}else{
return -1;
}
mbedtls_md_context_t context;
mbedtls_md_init(&context);
mbedtls_md_setup(&context, mbedtls_md_info_from_type(md_type), 1);
mbedtls_md_hmac_starts(&context, (const unsigned char*)p_key, key_len);
mbedtls_md_hmac_update(&context, (const unsigned char*)p_input, input_len);
mbedtls_md_hmac_finish(&context, p_hmac); // output_len bytes
mbedtls_md_free(&context);
return 0;
}
TOTPのシークレットコードを生成
こちらのライブラリを使わせていただきました。
この中で、HMAC(SHA-1)の生成は、先ほど作ったものを使うように改造しました。
TOTP.cpp
・・・
char* TOTP::getCodeFromSteps(long steps) {
// STEP 0, map the number of steps in a 8-bytes array (counter value)
_byteArray[0] = 0x00;
_byteArray[1] = 0x00;
_byteArray[2] = 0x00;
_byteArray[3] = 0x00;
_byteArray[4] = (int)((steps >> 24) & 0xFF);
_byteArray[5] = (int)((steps >> 16) & 0xFF);
_byteArray[6] = (int)((steps >> 8) & 0XFF);
_byteArray[7] = (int)((steps & 0XFF));
// // STEP 1, get the HMAC-SHA1 hash from counter and key
// Sha1.initHmac(_hmacKey, _keyLength);
// Sha1.write(_byteArray, 8);
// _hash = Sha1.resultHmac();
unsigned char hmac[20];
hmac_calculate(HMAC_TYPE_SHA1, _hmacKey, _keyLength, _byteArray, 8, hmac);
// STEP 2, apply dynamic truncation to obtain a 4-bytes string
_offset = hmac[20 - 1] & 0xF;
_truncatedHash = 0;
for (int j = 0; j < 4; ++j) {
_truncatedHash <<= 8;
_truncatedHash |= hmac[_offset + j];
}
// STEP 3, compute the OTP value
_truncatedHash &= 0x7FFFFFFF;
_truncatedHash %= 1000000;
// convert the value in string, with leading zeroes
sprintf(_code, "%06ld", _truncatedHash);
return _code;
}
・・・
ESP32内のJavascsriptで実行
ESP32で動作するJavascriptから生成できるようにしました。
たとえば、AWSでMFAデバイスを登録する際に認証アプリケーションを選択するとこんな感じで表示されます。
import * as crypto from "Crypto";
import * as utils from "Utils";
var enckey = "KQA5PGYV5UUJF7AMRBWTJ3A652W3362ZEUV2K25TXGM4S7UWZK5AGOUQ7BOPEMMQ";
var key = utils.base32Decode(enckey);
var code = crypto.totpGenerate(new Uint8Array(key));
console.log(JSON.stringify(code));
(参考) ESP32で動作するJavascript実行環境
ESP32で動作するJavascript実行環境を公開しています。
「電子書籍:M5StackとJavascriptではじめるIoTデバイス制御」
サポートサイト
(参考)ESP32で動作するJavascriptライブラリ
| ライブラリ名 | 概要 |
|---|---|
| Esp32 | ESP32 に関連する基本的な機能を提供します。 |
| Console | UART にデバッグ文を出力します。 |
| Audio | I2S に接続されたスピーカから MP3 音声を再生します。 |
| Camera | ESP32 に接続されたカメラの画像撮影機能を提供します。 |
| Crypto | 暗号機能を提供します。 |
| Env | I2C に接続された温湿度センサ(SHT30、DH12)を操作します。 |
| EspNow | EspNow の機能を提供します。 |
| Gpio | ESP32 の GPIO を制御します。 |
| Http | HttpGateway を介して、HTTPS 通信します。 |
| Imu | ESP32 に接続された 6 軸姿勢センサを制御します。 |
| Ir | ESP32 に接続した赤外線送受信機を制御します。 |
| Input | ボタンの押下を検出します。 |
| Lcd | ESP32 に接続した LCD の表示を制御します。 |
| Ledc | ESP32 の GPIO ピンに対して PWM 出力します。 |
| Pixels | ESP32 に接続した RGB LED を制御します。 |
| Prefs | ESP32 の不揮発メモリの読み書きをします。 |
| Rtc | ESP32 に接続した RTC から時刻を設定・取得します。 |
| Sd | ESP32 に接続された microSD カードのストレージに対するファイルの読み書きをします。 |
| Udp | UDP パケットを送受信します。 |
| Uart | UART 通信のための機能を提供します。 |
| Utils | base64、url エンコード、HTTP 通信などのユーティリティです。 |
| Websocket | Websocket によるサーバ通信機能を提供します。 |
| WebsocketClient | Websocket によるクライアント通信機能を提供します。 |
| Wire | 周辺デバイスとの I2C 通信のための機能を提供します。 |
| UnitColor | カラーセンサユニットを制御します。 |
| UnitGas | ガスセンサーユニットを制御します。 |
| UnitGesture | ジェスチャーユニットを制御します。 |
| UnitPbhub | I/O ハブユニットを制御します。 |
| UnitSonicIo | 超音波測距ユニット I/O を制御します。 |
| UnitAngle8 | 8 ポテンショメータユニットを制御します。 |
| UnitEnvPro | 環境センサ Pro ユニットを制御します。 |
| UnitImuPro | IMU Pro ユニットを制御します。 |
| UnitStep16 | Step16ロータリエンコーダユニットを制御します。 |
| BlePeripheral | BLEペリフェラル機能を提供します。 |
| BleCentral | BLEセントラル機能を提供します。 |
以上
