PG日誌

各記事はブラウザの横幅を1410px以上にすると2カラムの見出しが表示されます。なるべく横に広げてみてください。

Cocos2d-x 3.17.1でハッシュ計算を行う(MD5・SHA-256)

ゲームでデータが改ざんされていないかどうかを確かめるためにデータのハッシュ値を取って照合する手法があり、ハッシュ計算が必要になった時に使用するライブラリですが、最近のCocos2d-xにはすでにハッシュ計算用のライブラリが入っています。従って以前のように、外部からライブラリを取得して自分で追加する必要がありません。

なのでハッシュの計算方法と(Win32プロジェクトへの)導入方法とを紹介したいと思います。

  • 例によってWin32だとひと手間必要です
  • Androidはcmakeのビルドが設定済みなので何もしないでOK
  • iOS & MacのXCodeは持ってないので説明除外

MD5

最近はイケてない過去の遺物扱いを受けていますがゆるーく内容をバリデーションする程度ならまだ使えます。(と思いたい。

MD5単体を使う場合

Cocos2d-xのライブラリのexternalフォルダに骨董品レベルのMD5ライブラリが入っているのでその使い方です。まぁ、多分、すごく簡単に使えるので「MD5だけ」に用事がある人はこっちをリンクすればいいと思います。

まずヘッダーの場所です。以下をincludeします。

#include "external/md5/md5.h"

実装は以下の通りです。

void foo()
{
    string msg = "md5 target string.あああああ";
    
    md5_state_t md5;
    md5_init(&md5);
    md5_append(&md5, (md5_byte_t*)msg.c_str(), msg.length());

    md5_byte_t p[16]{}; // ハッシュ値がここに入る
    md5_finish(&md5, p);

    CCLOG("MD5 = %s", StringUtil::toHex(p, 16).c_str());
    // MD5 = 5594c8cb0b709eb9b7a9bf13ea6f673f
}

簡単ですね?

但しwin32の場合VisualStudioのプロジェクトに"md5.c"を追加する必要があります。(Androidは何もしないでOKです。

以下画像の通りファイルを追加してください。

f:id:Takachan:20190907014141p:plain

OpenSSLの付属のMD5生成ライブラリを使う

上記とは別にOpenSSLにハッシュ生成アルゴリズムが付属していてそこでもMD5があるのでそれを使用する場合以下のコードを記述します。

ちょっと面倒かもしれません。

また、Cocos2d-x上での使い方として説明していますが、OpenSSL自体はCocos2d-xでなくとも動くため使用方法は汎用的なものですのでこれ以外のほかのC++アプリケーションでも使用できます。

まずヘッダーのインクルードから。各プラットフォームで実装が異なるようなのでそれぞれ分岐してincludeを行います。

#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
  #include "external/openssl/include/android/openssl/md5.h"
#elif(CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
  #include "external/openssl/include/ios/openssl/md5.h"
#elif(CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)
  #include "external/openssl/include/win10/openssl/md5.h"
#endif

また、Androidはライブラリの参照が自動設定されていますが、Win32は手作業でスタティックライブラリを以下手順にて追加する必要があります。

// 以下のフォルダにあるライブラリをプロジェクトにwin32のプロジェクトにに追加する
external\openssl\prebuilt\win32

(1)ライブラリディレクトリの追加 VC++ ディレクトリ > [ライブラリディレクトリ] に以下を追加

D:\[${自分のCocos2dxのパス}]\Cocos2d-x\cocos2d-x-3.17.1\external\openssl\prebuilt\win32

(2)ライブラリ名の指定 リンカー > [入力] に以下を追加

libssl.lib
libcrypto.lib

ハッシュ計算のコードは以下の通りです。

void foo()
{
    string msg = "md5 target string.あああああ";
    
    if (true)
    {
        MD5_CTX md5;
        MD5_Init(&md5);

        for (int i = 0; i < msg.length(); i++)
        {
            // ファイルの内容を順次読み取って追加するときはこっちを使う
            // (今回は1文字づつ追加してみる)
            MD5_Update(&md5, &msg[i], 1);
        }

        //MD5_Update(&md5, msg.c_str(), msg.length()); 
        MD5_Final(hash, &md5);
    }
    else
    {
        MD5((unsigned char*)msg.c_str(), msg.length(), hash);
    }

    CCLOG("MD5(OpenSSL) = %s", StringUtil::toHex(hash, MD5_DIGEST_LENGTH).c_str());
    // MD5(OpenSSL) = 5594c8cb0b709eb9b7a9bf13ea6f673f
}

SHA-256

現在も通用するハッシュアルゴリズムで使用を推奨されています。最近は何も考えずこれ使っておけばOKという感じです。

これはOpenSSLに付属しているライブラリを使用するしかありません。

ただし使い方はOpenSSL版のMD5と全く同じです。MD5でリンク済みとして説明していきます。

void foo()
{
    string msg = "md5 target string.あああああ";

    unsigned char hash[SHA256_DIGEST_LENGTH/* #define 32*/]{}; // ハッシュ値がここに入る

    if (true)
    {
        SHA256_CTX sha256;
        SHA256_Init(&sha256);

        for (int i = 0; i < msg.length(); i++)
        {
            // ファイルの内容を順次読み取って追加するときはこっちを使う
            // (今回は1文字づつ追加してみる)
            SHA256_Update(&sha256, &msg[i], 1);
        }
        
        //SHA256_Update(&sha256, msg.c_str(), msg.length());
        SHA256_Final(hash, &sha256);
    }
    else
    {
        SHA256((unsigned char*)msg.c_str(), msg.length(), hash);
    }

    CCLOG("SHA-256(OpenSSL) = %s", StringUtil::toHex(hash, SHA256_DIGEST_LENGTH).c_str());
    // SHA-256(OpenSSL) = 4c9503d051d72c83f1299798a28165a7ab80f3628c9c39cc103793e2e493f92e
}

簡単に使えるようにライブラリ化

ただしこのままでは記述量が多く少し使いづらいので薄いライブラリを作成してみました。 とりあえずコンテンツが手元でメモリ上に全部展開されているケースしかないと思うのでメソッド一ひとつでハッシュ化する処理になります。

HashUtilクラス

HashUtil.hpp

ヘッダー以下の通りです。

// -+-+- HashUtil.hpp -+-+-

#pragma once
#pragma execution_character_set("utf-8")

#include <string>

/**
 * ハッシュ操作に関係する処理を定義する
 */
class HashUtil
{
public:

    // 指定した文字列からMD5ハッシュ値を取得する
    static void calc_md5(const std::string& msg, unsigned char hash[16]);
    static std::string calc_md5(const std::string& msg);

    // 指定した文字列からSHA-256ハッシュ値を取得する
    static void calc_sha256(const std::string& msg, unsigned char hash[32]);
    static std::string calc_sha256(const std::string& msg);
};
HashUtil.cpp

実装側です。

// -+-+- HashUtil.cpp -+-+-

#include "HashUtil.hpp"

#include "StringUtil.hpp"

#include <cocos2d.h>

// MD5用
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
    #include "external/openssl/include/android/openssl/md5.h"
    #include "external/openssl/include/android/openssl/sha.h"
#elif(CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
    #include "external/openssl/include/ios/openssl/md5.h"
    #include "external/openssl/include/ios/openssl/sha.h"
#elif(CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)
    #include "external/openssl/include/win10/openssl/md5.h"
    #include "external/openssl/include/win10/openssl/sha.h"
#endif

void HashUtil::calc_md5(const std::string& msg, unsigned char hash[16])
{
    MD5((unsigned char*)msg.c_str(), msg.length(), hash);
}

std::string HashUtil::calc_md5(const std::string& msg)
{
    unsigned char hash[MD5_DIGEST_LENGTH]{};
    calc_md5(msg, hash);
    return StringUtil::toHex(hash, MD5_DIGEST_LENGTH);
}

void HashUtil::calc_sha256(const std::string& msg, unsigned char hash[32])
{
    SHA256((unsigned char*)msg.c_str(), msg.length(), hash);
}

std::string HashUtil::calc_sha256(const std::string& msg)
{
    unsigned char hash[SHA256_DIGEST_LENGTH]{};
    calc_sha256(msg, hash);
    return StringUtil::toHex(hash, SHA256_DIGEST_LENGTH);
}

StringUtilクラス

たびたびコードに出てくるStringUtil::toHexの実装は以下の通りです。

StringUtil.hpp

ヘッダー側です。

// -+-+- StringUtil.hpp -+-+-

#include <sstream>

/**
 * 文字列の汎用操作を提供します。
 */
class StringUtil
{
public:
    // 16進変換 + 上位不足桁をゼロ埋め
    template<typename CNT>
    static std::string toHex(CNT value);
    static std::string toHex(unsigned char value[], int array_length/*配列の長さ*/);
};

template<typename CNT>
inline std::string StringUtil::toHex(CNT value)
{
    int size = sizeof(value);

    std::stringstream ss;

    if (value < 0x10)
    {
        ss << "0"; // 固定幅で2桁ほしいので不足している場合はゼロ埋めする
    }

    ss << std::hex << (int)value;
    std::string str = ss.str();

    std::string temp = "";
    if ((int)str.length() < size)
    {
        for (int i = 0; i < size - (int)str.length(); i++)
        {
            temp.append("0");
        }
    }

    return temp + str;
}
StringUtil.cpp

実装側以下の通りです。

// -+-+- StringUtil.cpp -+-+-

#include "StringUtil.hpp"

std::string StringUtil::toHex(unsigned char value[], int array_length)
{
    std::string str;
    //int len = sizeof(value) / sizeof(unsigned char);
    for (int i = 0; i < array_length; i++)
    {
        str.append(toHex(value[i]));
    }
    return str;
}

使い方

上記ライブラリの使い方ですが、以下の通り再処理は簡単化されているかと思います。

#include "HashUtil.hpp"

void foo()
{
    using namespace std;

    string msg = "md5 target string.あああああ";

    string md5_hash = HashUtil::calc_md5(msg);
    CCLOG("MD5(Util) = %s)", md5_hash.c_str());
    // MD5(Util) = 5594c8cb0b709eb9b7a9bf13ea6f673f

    string sha256_hash = HashUtil::calc_sha256(msg);
    CCLOG("SHA-256(Util) = %s)", sha256_hash.c_str());
    // SHA-256(Util) = 4c9503d051d72c83f1299798a28165a7ab80f3628c9c39cc103793e2e493f92e
}

めでたしめでたし。

以上です。