2020年3月29日日曜日

ChaCha

はじめに

アルゴリズムやコードを使用するにあたり, 著作権や特許権の調査には手間がかかります.
ストリーム暗号ChaChaは, 特許的に訴えられる可能性がかなり低いアルゴリズムと思われます.
著作権的にもRFCやWikipediaからコードを実装すれば訴えられる可能性は低いです.
RFCやWikipediaから実装すると, コードが似てしまうのはどうしようもないと思います.

ストリーム暗号は, 入力された文をバイト単位などで逐次, 暗号化・複合する暗号アルゴリズムを指します. RC4またはARCFOURが有名です.
ストリーム暗号は, 事前に文の長さを知る必要がなく, 文の長さに制限もありません. ブロック暗号は, 文を固定長のブロックに分割して暗号化・複合する暗号アルゴリズムを指します. AESが有名です.
固定長のブロックごとに分割するため, 文が足りない場合に何かしらの規則でデータを埋める必要があります.

ChaChaの実装コードをここに書き留めておきますが, "これをコピーしました"と言っても, 簡単に使えないのだろうなと, 虚しい気持ちになります.
初期ベクトル, nonceを, 用途によって設定できるインターフェイスを用意すべきでしょう.
鍵の長さについても, 任意の長さの鍵を256bitに合わせるインターフェイスを用意すべきでしょう.

実装

実装
#ifndef INC_CHACHA_H_
/**
@file ChaCha.h
@author taqu

USAGE:
Put '#define CHACHA_IMPLEMENTATION' before including this file to create the implementation.
*/
#ifdef __cplusplus
#include <cstdint>
#else
#include <stdint.h>
#endif

#ifdef __cplusplus
namespace cppchacha
{
#endif

void chacha20_encrypt256(const uint32_t key[8], const uint32_t nonce[2], uint32_t size, uint8_t* message);

#ifdef __cplusplus
}
#endif
#endif //INC_CHACHA_H_

#ifdef CHACHA_IMPLEMENTATION

#define ROTL(a,b) (((a)<<(b)) | ((a)>>(32-(b))))
#define QR(a,b,c,d) (\
        a += b, d ^= a, d = ROTL(d,16),\
        c += d, b ^= c, b = ROTL(b,12),\
        a += b, d ^= a, d = ROTL(d, 8),\
        c += d, b ^= c, b = ROTL(b, 7))

#ifdef __cplusplus
namespace cppchacha
{
#endif

#ifdef __cplusplus
namespace
{
#define CHACHA_STATIC
#define CHACHA_CAST(type, ptr) reinterpret_cast<type>(ptr)

#else //__cplusplus
#define CHACHA_STATIC static
#define CHACHA_CAST(type, ptr) (type)(ptr)

#endif //__cplusplus

//'expa', 'nd 3', '2-by', 'te k'
static const uint32_t iv_32[4] = {0x65787061U, 0x6e642033U, 0x322d6279U, 0x7465206bU};

CHACHA_STATIC void chacha20_block(uint32_t out[16], const uint32_t in[16])
{
    uint32_t x[16];
    for(int32_t i=0; i<16; ++i){
        x[i] = in[i];
    }
    for(int32_t i=0; i<10; ++i){
        //Odd round
        QR(x[0], x[4],  x[8], x[12]); //Column 0
        QR(x[1], x[5],  x[9], x[13]); //Column 1
        QR(x[2], x[6], x[10], x[14]); //Column 2
        QR(x[3], x[7], x[11], x[15]); //Column 3
        //Even round
        QR(x[0], x[5], x[10], x[15]); //Diagonal 0
        QR(x[1], x[6], x[11], x[12]); //Diagonal 1
        QR(x[2], x[7],  x[8], x[13]); //Diagonal 2
        QR(x[3], x[4],  x[9], x[14]); //Diagonal 3
    }
    for(int32_t i=0; i<16; ++i){
        out[i] = x[i] + in[i];
    }
}

void chacha20_encrypt(uint32_t input[16], uint32_t size, uint8_t* message)
{
    uint32_t output[16];
    uint32_t* p = CHACHA_CAST(uint32_t*, message);
    uint64_t* counter = CHACHA_CAST(uint64_t*, &input[12]);
    for(;;){
        chacha20_block(output, input);
        *counter += 1;
        if(size<=64){
            uint8_t* pb = CHACHA_CAST(uint8_t*, p);
            uint8_t* po = CHACHA_CAST(uint8_t*, output);
            for(uint32_t i=0; i<size; ++i){
                pb[i] = pb[i] ^ po[i];
            }
            break;
        }else{
            for(uint32_t i = 0; i < 16; ++i){
                p[i] ^= output[i];
            }
            size -= 64;
            p += 16;
        }
    }
}

#ifdef __cplusplus
}
#endif

void chacha20_encrypt256(const uint32_t key[8], const uint32_t nonce[2], uint32_t size, uint8_t* message)
{
    uint32_t input[16];

    for(int32_t i=0; i<4; ++i){
        input[i] = iv_32[i];
    }

    for(int32_t i=0; i<8; ++i){
        input[4+i] = key[i];
    }

    input[12] = 0;
    input[13] = 0;
    input[14] = nonce[0];
    input[15] = nonce[1];
    chacha20_encrypt(input, size, message);
}

#ifdef __cplusplus
}
#endif

#endif //CHACHA_IMPLEMENTATION

使い方

使い方
#define CHACHA_IMPLEMENTATION
#include <string.h>
#include <assert.h>
#include <random>
#include <chrono>
#include <iostream>
#include "ChaCha.h"

int main(int, char**)
{
    using namespace cppchacha;
    const uint8_t key[8*4] = {
        0x23U, 0xADU, 0x52U, 0xB1U, 0x5FU, 0xA7U, 0xEBU, 0xDCU,
        0x46U, 0x72U, 0xD7U, 0x22U, 0x89U, 0x25U, 0x3DU, 0x95U,
        0xDCU, 0x9AU, 0x43U, 0x24U, 0xFCU, 0x36U, 0x9FU, 0x59U,
        0x3FU, 0xDCU, 0xC7U, 0x73U, 0x3AU, 0xD7U, 0x76U, 0x17};
    const uint8_t nonce[2*4] = {
        0x5AU, 0x5FU, 0x6CU, 0x13U, 0xC1U, 0xF1U, 0x26U, 0x53U};

    std::random_device seed_gen;
 std::mt19937 engine(seed_gen());

    int32_t length = 1025 * 1025;
    char* message0 = reinterpret_cast<char*>(malloc(length*sizeof(char)));
    char* message1 = reinterpret_cast<char*>(malloc(length*sizeof(char)));
    for(int32_t i=0; i<length; ++i){
        message0[i] = static_cast<char>(engine());
    }

    memcpy(message1, message0, length);

    std::chrono::high_resolution_clock::time_point begin;
    std::chrono::microseconds elapsed_time;

    begin = std::chrono::high_resolution_clock::now();
    chacha20_encrypt256(reinterpret_cast<const uint32_t*>(key), reinterpret_cast<const uint32_t*>(nonce), length, reinterpret_cast<uint8_t*>(message1));
    chacha20_encrypt256(reinterpret_cast<const uint32_t*>(key), reinterpret_cast<const uint32_t*>(nonce), length, reinterpret_cast<uint8_t*>(message1));
    elapsed_time = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - begin);
    std::cout << elapsed_time.count() << std::endl;

    for(int32_t i=0; i<length; ++i){
        assert(message0[i] == message1[i]);
    }
    free(message1);
    free(message0);
    return 0;
}