ソフトな忘却力(11) RPi4、SHA1、パディング入りのテストパターンとダイジェスト

Joseph Halfmoon

先週別件でメモリ内容のハッシュをハードウエアで計算しつづけるDMACを使ってみました。ハッシュ値を求めることにも使えますが、改竄対策?かな。とりあえずのサンプルデータで動作検証したのですが、テスト用の入力パターンの生成が手作業なのは何とも。そこでラズパイ4上でテストパターン生成プログラムを試作してみました。

別件のDMACは、MicroChip社のArm Cortex-M4搭載マイコンATSAMD51のICM(Integrity Check Monitor)というものです。デスクリプタ(リンク可能)に記載したメモリブロック群(アドレス飛び飛びの配置でよい)を自動的にスキャンしてハッシュ値(SHA1/SHA224/SHA256)を計算しつづけ、なにか変化があったら報告してくれる優れモノです。CPUとは独立動作のDMACなので一度ソフトで設定すれば後は勝手に動きつづけます(そしてDMAC自身の設定は保護されます。)かなり複雑な構造のメモリを監視対象にできると思われます。詳しくは別件記事へ。

とは言え、ブロックに対するパディングを流石にDMACはやってくれません。動作確認用のテストパターンを入力するときは、パディング付きのメモリイメージを生成してハッシュを計算させ、期待のハッシュ値と照合して動作を確認する必要があります。

期待値の生成は簡単にできます。先週はWindows上のソフトを使いましたが、Raspberry Pi OS上であれば、sha1sumコマンドなども使用できます。しかし、チョイと困ったのが、入力データ用にパディング済の入力パターンを表示してくれるようなソフトが見つからなかったことです。そんな中間データは普通必要ないのでおよびでないっと。

まあ、どこにでもありそうなSHA1だけれど、そういう中途半端な物は無いみたいということで作ってみました。

ラズパイOSで使えるSHA1のライブラリ

SHAのヘッダは以下にありました(OSはRaspberry Pi OS buster <32bit>)。

/usr/include/openssl/sha.h

使用できるアルゴリズムは、SHA1/224/256/384/512 でした。実行オブジェクトを作る際はライブラリをリンクする必要があります。リンカへの指定は以下です。

-lcrypto

これらを踏まえて、今回使用の CMakeLists.txt は以下のようになりました(VSCodeのCMake Toolsが自動生成してくれるCMakeLists.txtに、ライブラリの指定などを手動で追加したもの。)

cmake_minimum_required(VERSION 3.0.0)
project(sha1testPat
    VERSION 0.1.0
    LANGUAGES C)

set(CMAKE_C_FLAGS "-lcrypto")
set(CMAKE_C_FLAGS_DEBUG "-Wall -O0 -g")
set(CMAKE_C_FLAGS_RELEASE "-Wall -O3")

include(CTest)
enable_testing()

add_executable(sha1testPat main.c)

set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)

 

作製したCソース

SHA1の計算は、

  1. SHA1_Init()で初期化して
  2. 末尾前のブロック数に応じた回数の SHA1_Update() で計算し、
  3. 最後のブロックを SHA1_Final() で〆る

という3ステップでした。使用方法は簡単なのですが、調べてみると欲しかったパディングされたメモリブロックのイメージというのは簡単に取り出せる場所には存在しないようでした。SHA1_Final()の中で、残りのデータ、パディング、そしてビット長の末尾レコードという具合にチビチビ付け加えて計算している感じです。

しかたが無いので、自前でダミーでパディング済データを作ってダンプして入力パターンとし、それとは別にSHA1_Final()でハッシュを計算してもらう、という間抜けな方式となりました。まあ一応テストパターンと照合したので計算はあっている筈。かなりカッコ悪いです。

// Generate SHA1 test pattern. LEN < 56
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/sha.h>

#define MAX_TEST_PATTERN    (56)


void dumpHx(SHA_CTX* ptr) {
    printf("h0: 0x%08x\n", ptr->h0);
    printf("h1: 0x%08x\n", ptr->h1);
    printf("h2: 0x%08x\n", ptr->h2);
    printf("h3: 0x%08x\n", ptr->h3);
    printf("h4: 0x%08x\n", ptr->h4);
}

void dumpMEM(unsigned int* ptr) {
    for (int i=0; i < 16; i++) {
        printf("MEM[%02d]: 0x%08x\n", i, *ptr++);
    }
}

void dumpMD(unsigned int* ptr) {
    for (int i=0; i < 5; i++) {
        printf("MD[%02d]: 0x%08x\n", i, *ptr++);
    }
}

int generateTestPattern(char *dat) {
    SHA_CTX ctx;
    unsigned char buf[64];
    unsigned char padding[64] = {0x80, };
    unsigned char bits[8] = { 0, };
    unsigned char md[SHA_DIGEST_LENGTH];

    size_t slen = strlen(dat);
    printf("BIT LENGTH: 0x%03x (%d bytes)\n", slen * 8, slen);
    if (slen > MAX_TEST_PATTERN) {
        return -1; // ERROR
    }
    unsigned int bitLen = slen * 8;
    bits[7] = (bitLen & 0xFF);
    bits[6] = (bitLen & 0x300) >> 8;

    SHA1_Init(&ctx);
    dumpHx(&ctx);
    // Dummy Padding for test pattern
    memcpy(buf, dat, slen);
    memcpy(buf+slen, padding, MAX_TEST_PATTERN - slen);
    memcpy(buf+MAX_TEST_PATTERN, bits, 8);
    dumpMEM((unsigned int*)buf);
    // Calculate SHA1
    SHA1_Update(&ctx, buf, slen);
    SHA1_Final(md, &ctx); //Real padding data in this function!
    dumpMD((unsigned int*)md);
    return 0;
}


int main(int argc, char **argv) {
    char testPat[] = "abc";
    generateTestPattern(testPat);
    return 0;
}

実行結果


ラズパイ4上で実行した結果が以下に。

$ ./sha1testPat
BIT LENGTH: 0x018 (3 bytes)
h0: 0x67452301
h1: 0xefcdab89
h2: 0x98badcfe
h3: 0x10325476
h4: 0xc3d2e1f0
MEM[00]: 0x80636261
MEM[01]: 0x00000000
MEM[02]: 0x00000000
MEM[03]: 0x00000000
MEM[04]: 0x00000000
MEM[05]: 0x00000000
MEM[06]: 0x00000000
MEM[07]: 0x00000000
MEM[08]: 0x00000000
MEM[09]: 0x00000000
MEM[10]: 0x00000000
MEM[11]: 0x00000000
MEM[12]: 0x00000000
MEM[13]: 0x00000000
MEM[14]: 0x00000000
MEM[15]: 0x18000000
MD[00]: 0x363e99a9
MD[01]: 0x6a810647
MD[02]: 0x71253eba
MD[03]: 0x6cc25078
MD[04]: 0x9dd8d09c

計算はできているようです。

ATSAMD51のICMは、各種SHAの規格にのっとった初期ハッシュ値を内蔵しているだけでなく、そこをユーザー独自のものに変更できる機能もあります。その場合、SHA1_Init()でセットされる値を使わず、別にSHA_CTXを「デッチあげて」食わせれば、その場合のテストもできそうです(やってないケド。)

まあ以下同文で、アルゴリズムやブロック長などを変更した場合のパターンも作れそうなので(本当か)、本件はとりあえずここまで(何時ものパターンやな。)

ソフトな忘却力(10) RPi4、Pthreadの実験をOpenMPで書き換えてみた へ戻る 

ソフトな忘却力(12) RPi3、AES、1ブロックだけのテストパターン「素の」暗号化 へ進む