0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ESP32でTOTPのシークレットを生成する

Posted at

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デバイスを登録する際に認証アプリケーションを選択するとこんな感じで表示されます。

image.png

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セントラル機能を提供します。

以上

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?