バージョン管理された人

subversionで管理されてます

C++とOpenSSLライブラリを利用してデータの暗号化・復号化をAES-CBCで行う

C++でまともに暗号化・復号化するサンプルをやっている例を見ないので書く。 実際にはsaltがどうだとかパディングがどうだとかストレッチングがどうだとか暗号化する前にデータを圧縮する話だとかは出さない。 運用上は重要だけど、そういうのは後付けできる。 問題はOpenSSLをC++でまともにあつかう例がない方なので、ここではこのはなしをざっくり切ってどのように記述するのかを解説していく。

方針

OpenSSLライブラリを利用する小さなAES-CBCモードに限定したopensslコマンドを実装する。 小さいといっても興が乗って300行程になってしまったが、問題はないだろう。 実際に重要なところは合わせても100行も行かない。 また、ここではC++17前提で実装を進める。 それ以前のC++を利用する方は適宜読み替えて欲しい。 ただ、OpenSSLのライブラリはCで書かれているので、中核となる部分はC++11までならコンパイルできるはず(試していない)。 ただ、それもcstdintライブラリ内にあるuint8_tを用いるためなので、おそらくunsigned charに変更すれば(charが8 bitな環境であれば)C++98でもコンパイルできる...かもしれない。 興味があれば試して欲しい。

準備

さて、まずはいくつかデータ型を定義しておく。 まずはbyte型を定義する

#include <cstdint>
using byte = uint8_t;

C++17ではstd::byteという型があるが、これよりは符号なし8 bit整数を利用することを明確にしたいのでuint8_tの方を採用する。

さらに、この型を用いてバイトストリーム用の型を定義する。

#include <vector>
#include <cstdint>
using byte_stream = std::vector<byte>;

8 bitな動的配列は全てこのbyte_streamから作成する。

また、AES-CBCにおける鍵を表す型を定義する

#include <type_traits>
#include <array>
#include <cstddef>
template <
  size_t key_size,
  std::enable_if_t<key_size == 128 || key_size == 192 || key_size == 256, std::nullptr_t> = nullptr
>
using key = std::array<byte, key_size / 8>;

templatestd::enable_if_tがはいってきて何やらややこしい。 しかし、やりたいことは単純で、鍵長key_sizeが128 bitか192 bitか256 bitなものしか鍵の型を生成できないように制限しているだけだ。 また、key_size / 8をしているのはkey_sizeの単位がbitでarrayが格納する要素の型は8 bit長なため、要素数key_size bitを要素の型の長さ8 bitで割ったものとなるためだ。

最後にAES-CBCにはIVが必要となるので、これを表現する型を導入する。

#include <array>
const auto BLOCK_SIZE = 16;
using iv = std::array<byte, BLOCK_SIZE>;

BLOCK_SIZE == 16なのはAESではブロック長が128 bit固定であり、IVもそのブロック長にする必要があるので、byte == uint8_tからその要素数128 / 8 == 16となるため。

コマンドのインターフェース

今回用意するコマンドのインターフェースは次のようにしようと思う。

  • 標準入力からデータを受け取って、受け取ったデータを暗号化した上で標準出力へ出力するサブコマンドencを持つ
  • 標準入力からデータを受け取って、受け取ったデータを復号化した上で標準出力へ出力するサブコマンドdecを持つ

どちらも標準入力から受け取るので、main関数はこんな感じでいいだろう。

#include <iostream>

int
main(int argc, char* argv[]) {
  if (argc < 1) {
    std::cerr << "no subcommand" << std::endl;
  }

  auto subcommand = std::string(argv[1]);
  if (subcommand == "enc") {
  } else if (subcommand = "dec") {
  }
}

ただ、これだけだとターミナルに移ったときに使い方がわからないので、helpサブコマンドとついでで-hオプションをコマンドに追加しておこう。

#include <iostream>
#include <unistd.h>
#include <string>

int
main(int argc, char* argv[]) {
  int opt;
  // -hオプションを追加するためにgetopt関数を使った
  while ((opt = getopt(argc, argv, "h")) != -1) {
    switch (opt) {
      case 'h':
        usage(program_path);
        return 0;
      default:
        return 1;
    }
  }

  if (argc < 1) {
    std::cerr << "no subcommand" << std::endl;
  }

  auto subcommand = std::string(argv[1]);
  if (subcommand == "help") {
    usage(argv[0]);
    return 0;
  }
  if (subcommand == "enc") {
  } else if (subcommand = "dec") {
  }
}

usageという関数はプログラムのパスargv[0]を受け取ってその使い方を表示してくれる。

#include <filesystem>
#include <iostream>
auto
usage(const std::filesystem::path& program_path) -> void {
  const auto program_name = program_path.filename().generic_string();

  std::cout
    << "OpenSSLライブラリの使用方法のサンプル"        << std::endl
    << "Usage:"                                       << std::endl
    << "  " << program_name << " enc"                 << std::endl
    << "  " << program_name << " dec"                 << std::endl
    << "  " << program_name << " help"                << std::endl
    << "  " << program_name << " -h"                  << std::endl
    << "Arguments:"                                   << std::endl
    << "  enc"                                        << std::endl
    << "    標準入力から受け取ったデータを暗号化し、" << std::endl
    << "    標準出力へ出力する"                       << std::endl
    << "  dec"                                        << std::endl
    << "    標準入力から受け取ったデータを復号化し、" << std::endl
    << "    標準出力へ出力する"                       << std::endl
    << "  help"                                       << std::endl
    << "    ヘルプを表示する"                         << std::endl
    << "Options:"                                     << std::endl
    << "  -h"                                         << std::endl
    << "    ヘルプを表示する"                         << std::endl;
}

AESの暗号化と復号化のインターフェースはいずれも鍵とIV、そしてデータを必要とするので、次のような形で良いだろう。

#include <type_traits>
#include <cstddef>
template <
  size_t key_size,
  std::enable_if_t<key_size == 128 || key_size == 192 || key_size == 256, std::nullptr_t> = nullptr
>
auto
encrypt(
  const key<key_size>& key,
  const byte_stream& data,
  const iv& iv
) -> byte_stream;
template <
  size_t key_size,
  std::enable_if_t<key_size == 128 || key_size == 192 || key_size == 256, std::nullptr_t> = nullptr
>
auto
decrypt(
  const key<key_size>& key,
  const byte_stream& data,
  const iv& iv
) -> byte_stream;

いずれも鍵、IV、データを受け取ってデータを暗号化・復号化した結果であるbyte_stream型なデータを返す。 インターフェースはどちらも同じなので、データを暗号化・復号化した結果を標準出力に返すために次のような関数を用意する

#include <type_traits>
#include <functional>
#include <stdexcept>
#include <algorithm>
#include <cstddef>
template <
  size_t key_size,
  std::enable_if_t<key_size == 128 || key_size == 192 || key_size == 256, std::nullptr_t> = nullptr
>
auto
print_data_while_encoding(
  const byte_stream& original_key,
  const byte_stream& data,
  const byte_stream& original_iv,
  const std::function<byte_stream(const key<key_size>&, const byte_stream&, const iv&)>& encoder
) -> void {
  iv iv;
  key<key_size> key;
  if (iv.size() != original_iv.size()) {
    throw std::invalid_argument("iv length must be 128 bit ");
  }
  if (original_key.size() != key.size()) {
    throw std::invalid_argument("invalid key size");
  }
  std::copy(original_iv.begin(), original_iv.end(), iv.begin());
  std::copy(original_key.begin(), original_key.end(), key.begin());

  auto encoded_data = encoder(key, data, iv);
  for (const auto byte_value : encoded_data) {
    std::cout << byte_value;
  }
}

これは鍵、IV、データだけでなく、そららのデータを受け取って暗号化・復号化を行う関数encoderを受け取っている。 途中、byte_stream型で受け取った鍵とIVのデータ型を変換しているが、それ以外はencoderに通し、その結果を表示する部分と引数のチェックを行っている部分とわかれている。

OpenSSLによるAES-CBC暗号化・復号化

本題のOpenSSLによる暗号化・復号化に入っていく。

暗号化

最初に暗号化を行うencrypt関数の実装を行う。

まずは、次のように書く。

#include <openssl/evp.h>
#include <type_traits>
#include <cstddef>
template <
  size_t key_size,
  std::enable_if_t<key_size == 128 || key_size == 192 || key_size == 256, std::nullptr_t> = nullptr
>
auto
encrypt(
  const key<key_size>& key,
  const byte_stream& data,
  const iv& iv
) -> byte_stream {
  EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
}

このctxを暗号化が終わるまで一貫して用いる。 このctxに対して暗号化モードや必要な設定・鍵・IVなどを保存した上でデータを暗号化・復号化するという仕組みになっている。

初期化コードを書いたらctxに暗号化スイートを設定しておくようにコーディングする。

#include <openssl/evp.h>
#include <type_traits>
#include <cstddef>
template <
  size_t key_size,
  std::enable_if_t<key_size == 128 || key_size == 192 || key_size == 256, std::nullptr_t> = nullptr
>
auto
encrypt(
  const key<key_size>& key,
  const byte_stream& data,
  const iv& iv
) -> byte_stream {
  EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
  const EVP_CIPHER* cipher_suite;
  switch (key_size) {
    case 128:
      cipher_suite = EVP_aes_128_cbc();
      break;
    case 192:
      cipher_suite = EVP_aes_192_cbc();
      break;
    case 256:
      cipher_suite = EVP_aes_256_cbc();
      break;
  }
}

EVP_aes_(key-size)_cbcという関数がAES-CBC暗号化スイートの設定を作成し、返してくれる。

スイートの設定を作成したら、その設定と鍵、IVをctxに設定する。

#include <openssl/evp.h>
#include <type_traits>
#include <cstddef>
template <
  size_t key_size,
  std::enable_if_t<key_size == 128 || key_size == 192 || key_size == 256, std::nullptr_t> = nullptr
>
auto
encrypt(
  const key<key_size>& key,
  const byte_stream& data,
  const iv& iv
) -> byte_stream {
  EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
  const EVP_CIPHER* cipher_suite;
  switch (key_size) {
    case 128:
      cipher_suite = EVP_aes_128_cbc();
      break;
    case 192:
      cipher_suite = EVP_aes_192_cbc();
      break;
    case 256:
      cipher_suite = EVP_aes_256_cbc();
      break;
  }
  EVP_EncryptInit(ctx, cipher_suite, key.data(), iv.data());
}

以上でAES-CBCによる暗号化を行う準備はととのった。 これから実際に暗号化処理を行う部分を実装するが、実は次のようなコードを追加するだけで大部分の実装が終わる。

#include <openssl/evp.h>
#include <type_traits>
#include <cstddef>
template <
  size_t key_size,
  std::enable_if_t<key_size == 128 || key_size == 192 || key_size == 256, std::nullptr_t> = nullptr
>
auto
encrypt(
  const key<key_size>& key,
  const byte_stream& data,
  const iv& iv
) -> byte_stream {
  // 初期化処理は省略

  auto encrypted_data = byte_stream(
    raw_data.size() + (BLOCK_SIZE - raw_data.size() % BLOCK_SIZE)
  );
  int len = 0;
  EVP_EncryptUpdate(ctx, encrypted_data.data(), &len, data.data(), raw_data.size()
}

encrypted_dataのデータサイズの調整をおこなっている式についてはすぐ後で触れるので、ここでは忘れて欲しい。

最後に、データの後処理を行う。

#include <openssl/evp.h>
#include <type_traits>
#include <cstddef>
template <
  size_t key_size,
  std::enable_if_t<key_size == 128 || key_size == 192 || key_size == 256, std::nullptr_t> = nullptr
>
auto
encrypt(
  const key<key_size>& key,
  const byte_stream& data,
  const iv& iv
) -> byte_stream {
  // 初期化処理と暗号化処理は省略

  EVP_EncryptFinal(ctx, encrypted_data.data() + len, &len)
  EVP_CIPHER_CTX_free(ctx);
  return encrypted_data;
}

最後にこのような処理をしなければならないのはAESではデータサイズがブロック長単位になっている必要があり、その部分の暗号化処理を行う必要があるからだ。 AESではブロック長は128 bitであることを思いだそう。 AESでは暗号化・復号化処理は1ブロック毎に行われる。 つまり、AESで処理されるデータ長は128 bit単位になっていなければならない。 しかし、データ長のbit数がAESで処理する128 bit長で割り切れない、すなわちデータ長が128 bit単位になっていないとき、最後1つ手前の128 bit長のブロックを処理したあとにはみ出した分のデータを処理するときにそのはみ出したデータの長さはちょうど128 bitでない場合がある。 そもそもほとんどのデータはちょうどそのデータ長が128 bitで割り切れるということは稀だろう。 そこで、パディングというある一定のルールに乗っ取ったデータをデータの末尾に追加するという処理を施してデータ長を128 bit長に帳尻合わせする必要がでてくる。 しかし、ここではOpenSSLがどのパディングを使用するかやそのパディングの詳細については述べない。 各自興味があれば調べて欲しい。

実はデータ長がピッタリ128 bitのながさであってももう1ブロック分のパディング処理が行われる。 これには理由があって、パディングがあるデータとないデータの区別をするのを頑張るよりは、128 bit長ちょうどのデータに対しても1ブロック分のパディング処理を施しておくことで処理を共通して行える上にどこからどこまでがパディングでデータであるかの区別を見分けやすくできるというメリットがある。

とにかく、最後のはみだしたデータはパディングという処理を施した上でまだ暗号化されていないはみだしたデータを暗号化する必要がある。 そこで、lenに暗号化できたデータ長を保存しておき、encrypted_dataの先頭アドレスencrypted_data.data()len分足した、パディングが施されたはみだしているデータに対して暗号化するという形をとる。 先程のencrypted_dataのサイズを調整しているのはこの処理のためである。 元々はみだすデータサイズはデータサイズ % ブロック長(128 bit)でわかるので、パディングされるデータはブロック長(128 bit) - データサイズ % ブロック長(128 bit)で求められるので、パディング分だけ保存する配列を伸ばしておく必要があったのだ。 最後に、ctrは動的にメモリー確保されてているのでEVP_CIPHER_CTX_freeでメモリーを開放する。 このとき、設定された鍵やIV以外のOpenSSL側で用意するEVP_aes_key-size_cbcのような動的に確保されているメモリーが開放される。

まとめると、encrypt関数は次のような形になる。

template <
  size_t key_size,
  std::enable_if_t<key_size == 128 || key_size == 192 || key_size == 256, std::nullptr_t> = nullptr
>
auto
encrypt(
  const key<key_size>& key,
  const byte_stream& raw_data,
  const iv& iv
) -> byte_stream {
  auto ctx = EVP_CIPHER_CTX_new();
  if (ctx == nullptr) {
    throw "cannot initialize OpenSSL cipher context";
  }
  const EVP_CIPHER* cipher_suite;
  switch (key_size) {
    case 128:
      cipher_suite = EVP_aes_128_cbc();
      break;
    case 192:
      cipher_suite = EVP_aes_192_cbc();
      break;
    case 256:
      cipher_suite = EVP_aes_256_cbc();
      break;
  }
  if (cipher_suite == nullptr) {
    EVP_CIPHER_CTX_cleanup(ctx);
    throw "cannot get cipher suite";
  }
  if (EVP_EncryptInit(ctx, cipher_suite, key.data(), iv.data()) == 0) {
    EVP_CIPHER_CTX_cleanup(ctx);
    throw "cannot construct OpenSSL cipher context";
  }

  auto encrypted_data = byte_stream(
    raw_data.size() + (BLOCK_SIZE - raw_data.size() % BLOCK_SIZE)
  );
  int len = 0;
  if (EVP_EncryptUpdate(ctx, encrypted_data.data(), &len, raw_data.data(), raw_data.size()) == 0) {
    EVP_CIPHER_CTX_cleanup(ctx);
    throw "cannot encrypt data";
  }

  if (EVP_EncryptFinal(ctx, encrypted_data.data() + len, &len) == 0) {
    EVP_CIPHER_CTX_cleanup(ctx);
    throw "cannot padding";
  }
  EVP_CIPHER_CTX_free(ctx);
  return encrypted_data;
}

解説では省いていたエラー処理をここでは入れてある。

復号化

実は暗号化処理と復号化処理はほとんど同じような操作でできるので、解説することは少ない。 なので、いきなりコードを載せてしまう。

template <
  size_t key_size,
  std::enable_if_t<key_size == 128 || key_size == 192 || key_size == 256, std::nullptr_t> = nullptr
>
auto
decrypt(
  const key<key_size>& key,
  const byte_stream& encrypted_data,
  const iv& iv
) -> byte_stream {
  EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
  if (ctx == nullptr) {
    throw "cannot initialize OpenSSL cipher context";
  }
  const EVP_CIPHER* cipher_suite;
  switch (key_size) {
    case 128:
      cipher_suite = EVP_aes_128_cbc();
      break;
    case 192:
      cipher_suite = EVP_aes_192_cbc();
      break;
    case 256:
      cipher_suite = EVP_aes_256_cbc();
      break;
  }
  if (cipher_suite == nullptr) {
    EVP_CIPHER_CTX_cleanup(ctx);
    throw "cannot get cipher suite";
  }
  if (EVP_DecryptInit(ctx, cipher_suite, key.data(), iv.data()) == 0) {
    EVP_CIPHER_CTX_cleanup(ctx);
    throw "cannot construct OpenSSL cipher context";
  }

  auto plain_data = byte_stream(encrypted_data.size());
  int plain_data_len = 0;
  if (EVP_DecryptUpdate(ctx, plain_data.data(), &plain_data_len, encrypted_data.data(), encrypted_data.size()) == 0) {
    EVP_CIPHER_CTX_cleanup(ctx);
    throw "cannot encrypt data";
  }

  int remain_plain_data_len;
  if (EVP_DecryptFinal(ctx, plain_data.data() + plain_data_len, &remain_plain_data_len) == 0) {
    EVP_CIPHER_CTX_cleanup(ctx);
    throw "cannot padding";
  }
  plain_data.resize(plain_data_len + remain_plain_data_len);
  EVP_CIPHER_CTX_free(ctx);
  return plain_data;
}

ほとんどEVP_EncryptXXXEVP_DecryptXXXになっただけである。 ただ、最後にデータからパディングをはがす処理が異なっているだけである。 ここではそのパディングをはがす処理について解説する。 まず、plain_data_lenには暗号化時にははみだしていなかったデータの復号化後の長さが入っている。 また、remain_plain_data_lenにはパデイングデータをのぞいた暗号化時にはみだしていたデータ分の長さがはいる。 つまり、暗号化前のデータ長はplain_data_len + remain_plain_data_lenである。 パディング処理によってデータの末尾には余計なデータがくっついているので、byte_streamvetctorであることを利用してresizeメソッドによってデータ長を暗号化前のデータ長に合わせてやる。 これによって真の復号化データを得ることができる。

コマンドの拡充

暗号化と復号化はできるようになったが、コマンドは最低限のインターフェースを供えただけで標準入力からデータを読み込んだり、出力したりIVや鍵を設定したりなどの部分が抜け落ちている。 したがってここではこれらを実装していくことにしよう。

まず、IVや鍵を設定しよう。 ただ、これは確認のためであれば全部を0埋めしたデータにしておけばよいだろうし、0埋めだけだと不安なので、デフォルトは0埋めにしておいて、あとでオプションから設定できれば良いだろう。 そこで-iオプションでIVを受け取り、-kで鍵を受け取るようにしよう。 ただし、これらはuint8_tな配列でなければならないので、これらオプションに指定する引数はhex string(16進数な数字の羅列)で受け取るようにする。

#include <iostream>
#include <cstddef>
#include <cstdint>
#include <type_traits>
#include <unistd.h>
#include <string>
#include <stdexcept>
#include <algorithm>

auto
convert_hex_string(const std::string& hex_string) -> byte_stream {
  if (hex_string.size() % 2 != 0) {
    throw std::invalid_argument("invalid hex string");
  }

  byte_stream byte_array(hex_string.size() / 2);
  for (std::size_t i = 0; i < hex_string.size(); i += 2) {
    auto&& byte_value = static_cast<uint8_t>(
      std::stoi(hex_string.substr(i, 2), nullptr, 16)
    );
    byte_array[i / 2] = byte_value;
  }

  return byte_array;
}

int
main(int argc, char* argv[]) {
  const auto program_path = argv[0];
  byte_stream iv(BLOCK_SIZE, 0);
  byte_stream key(128 / 8, 0);
  int opt;
  while ((opt = getopt(argc, argv, "hi:k:V")) != -1) {
    switch (opt) {
      case 'h':
        usage(program_path);
        return 0;
      case 'i':
        try {
          iv = convert_hex_string(optarg);
        } catch (const std::invalid_argument& error) {
          std::cerr << "invalid iv format" << std::endl;
          return 1;
        } catch (const std::out_of_range& error) {
          std::cerr << error.what() << std::endl;
          return 1;
        }
        if (iv.size() != BLOCK_SIZE) {
          std::cerr << "iv must be 128 bit" << std::endl;
        }
        break;
      case 'k':
        try {
          key = convert_hex_string(optarg);
        } catch (const std::invalid_argument& error) {
          std::cout << "invalid key format" << std::endl;
          return 1;
        } catch (const std::out_of_range& error) {
          std::cerr << error.what() << std::endl;
          return 1;
        }
        if (!(key.size() == 128 / 8 || key.size() == 192 / 8 || key.size() == 256 / 8)) {
          std::cerr << "key size must be 128, 192 or 256 bit" << std::endl;
        }
        break;
      default:
        return 1;
    }
  }

  if (argc - optind < 1) {
    std::cerr << "no subcommand" << std::endl;
    return 1;
  }
  const auto subcommand = std::string(argv[optind]);
  if (subcommand == "help") {
    usage(program_path);
    return 0;
  }

  if (subcommand == "enc") {
  } else if (subcommand == "dec") {
  } else {
    std::cerr << "unknown command: " << subcommand << std::endl;
    return 1;
  }
}

convert_hex_stringが受け取ったhex stringを符号無し8 bit整数配列に直す関数である。 この関数は2文字ずつ読んでいき、その2文字を8 bit整数を表す16進数文字列だと見なして符号無し8 bit整数に変換し、それをvectorにつめていくというものになっている。 また、-i-vはそれぞれhex stringを引数として受け取り、convert_hex_stringにかけて符号無し8 bit整数配列に変換したうえで、各鍵長、IV長に当っているかをみている。

-i-vオプションを追加したので、usage関数をアップデートする。

auto
usage(const std::filesystem::path& program_path) -> void {
  const auto program_name = program_path.filename().generic_string();

  std::cout
    << "OpenSSLライブラリの使用方法のサンプル"        << std::endl
    << "Usage:"                                       << std::endl
    << "  " << program_name << " enc"                 << std::endl
    << "  " << program_name << " dec"                 << std::endl
    << "  " << program_name << " help"                << std::endl
    << "  " << program_name << " -h"                  << std::endl
    << "Arguments:"                                   << std::endl
    << "  enc"                                        << std::endl
    << "    標準入力から受け取ったデータを暗号化し、" << std::endl
    << "    標準出力へ出力する"                       << std::endl
    << "  dec"                                        << std::endl
    << "    標準入力から受け取ったデータを復号化し、" << std::endl
    << "    標準出力へ出力する"                       << std::endl
    << "  help"                                       << std::endl
    << "    ヘルプを表示する"                         << std::endl
    << "Options:"                                     << std::endl
    << "  -h"                                         << std::endl
    << "    ヘルプを表示する"                         << std::endl
    << "  -i <iv>"                                    << std::endl
    << "    <iv>で指定された値でIVを作成する。"       << std::endl
    << "    <iv>はHex String形式(例: AB01DF93BC)で"   << std::endl
    << "    長さが128bitになるようにする必要がある"   << std::endl
    << "  -k <key>"                                   << std::endl
    << "    <key>で指定された値で鍵を作成する。"      << std::endl
    << "    <key>はhex String形式で長さが128 bit,"    << std::endl
    << "    192 bit, 256 bitのいずれかの"             << std::endl
    << "    長さになる必要がある。"                   << std::endl;
}

最後に、データを標準入力からうけとる部分と、暗号化・復号化したものを標準出力へ出力する部分を実装しよう。 データは符号無し8 bit配列に収めてやる。

#include <cstdio>
#include <cstdint>

int
main(int argc, char* argv[]) {
  // オプション処理部分等は省略

  byte_stream data;
  while (true) {
    auto byte_value = getc(stdin);
    if (byte_value == EOF) {
      break;
    }
    data.emplace_back(byte_value);
  }


  // データの暗号化・復号化部分は省略
}

データを暗号化・復号化した上で標準出力に出力するようにしてやる。

#include <iostream>
#include <cstdint>

int
main(int argc, char* argv[]) {
  // オプション処理部分やデータの入力部分は省略

    if (subcommand == "enc") {
    switch (key.size() * 8) {
      case 128:
        print_data_while_encoding<128>(key, data, iv, encrypt<128>);
        break;
      case 192:
        print_data_while_encoding<192>(key, data, iv, encrypt<192>);
        break;
      case 256:
        print_data_while_encoding<256>(key, data, iv, encrypt<256>);
        break;
      default:
        std::cout << "invalid key type" << std::endl;
        return 1;
    }
  } else if (subcommand == "dec") {
    switch (key.size() * 8) {
      case 128:
        print_data_while_encoding<128>(key, data, iv, decrypt<128>);
        break;
      case 192:
        print_data_while_encoding<192>(key, data, iv, decrypt<192>);
        break;
      case 256:
        print_data_while_encoding<256>(key, data, iv, decrypt<256>);
        break;
      default:
        std::cout << "invalid key type" << std::endl;
        return 1;
    }
  } else {
    std::cerr << "unknown command: " << subcommand << std::endl;
    return 1;
  }
}

コードのまとめ

以上のコードをまとめると、最終的には次のようなコードができている

#include <openssl/evp.h>
#include <iostream>
#include <vector>
#include <array>
#include <cstdint>
#include <cstddef>
#include <type_traits>
#include <unistd.h>
#include <string>
#include <filesystem>
#include <stdexcept>
#include <algorithm>
#include <functional>
#include <cstdio>

const auto BLOCK_SIZE = 16;

using byte = uint8_t;
using byte_stream = std::vector<byte>;
template <
  size_t key_size,
  std::enable_if_t<key_size == 128 || key_size == 192 || key_size == 256, std::nullptr_t> = nullptr
>
using key = std::array<byte, key_size / 8>;
using iv = std::array<byte, BLOCK_SIZE>;

template <
  size_t key_size,
  std::enable_if_t<key_size == 128 || key_size == 192 || key_size == 256, std::nullptr_t> = nullptr
>
auto
encrypt(
  const key<key_size>& key,
  const byte_stream& raw_data,
  const iv& iv
) -> byte_stream {
  auto ctx = EVP_CIPHER_CTX_new();
  if (ctx == nullptr) {
    throw "cannot initialize OpenSSL cipher context";
  }
  const EVP_CIPHER* cipher_suite;
  switch (key_size) {
    case 128:
      cipher_suite = EVP_aes_128_cbc();
      break;
    case 192:
      cipher_suite = EVP_aes_192_cbc();
      break;
    case 256:
      cipher_suite = EVP_aes_256_cbc();
      break;
  }
  if (cipher_suite == nullptr) {
    EVP_CIPHER_CTX_cleanup(ctx);
    throw "cannot get cipher suite";
  }
  if (EVP_EncryptInit(ctx, cipher_suite, key.data(), iv.data()) == 0) {
    EVP_CIPHER_CTX_cleanup(ctx);
    throw "cannot construct OpenSSL cipher context";
  }

  auto encrypted_data = byte_stream(
    raw_data.size() + (BLOCK_SIZE - raw_data.size() % BLOCK_SIZE)
  );
  int len = 0;
  if (EVP_EncryptUpdate(ctx, encrypted_data.data(), &len, raw_data.data(), raw_data.size()) == 0) {
    EVP_CIPHER_CTX_cleanup(ctx);
    throw "cannot encrypt data";
  }

  if (EVP_EncryptFinal(ctx, encrypted_data.data() + len, &len) == 0) {
    EVP_CIPHER_CTX_cleanup(ctx);
    throw "cannot padding";
  }
  EVP_CIPHER_CTX_free(ctx);
  return encrypted_data;
}

template <
  size_t key_size,
  std::enable_if_t<key_size == 128 || key_size == 192 || key_size == 256, std::nullptr_t> = nullptr
>
auto
decrypt(
  const key<key_size>& key,
  const byte_stream& encrypted_data,
  const iv& iv
) -> byte_stream {
  EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
  if (ctx == nullptr) {
    throw "cannot initialize OpenSSL cipher context";
  }
  const EVP_CIPHER* cipher_suite;
  switch (key_size) {
    case 128:
      cipher_suite = EVP_aes_128_cbc();
      break;
    case 192:
      cipher_suite = EVP_aes_192_cbc();
      break;
    case 256:
      cipher_suite = EVP_aes_256_cbc();
      break;
  }
  if (cipher_suite == nullptr) {
    EVP_CIPHER_CTX_cleanup(ctx);
    throw "cannot get cipher suite";
  }
  if (EVP_DecryptInit(ctx, cipher_suite, key.data(), iv.data()) == 0) {
    EVP_CIPHER_CTX_cleanup(ctx);
    throw "cannot construct OpenSSL cipher context";
  }

  auto plain_data = byte_stream(encrypted_data.size());
  int plain_data_len = 0;
  if (EVP_DecryptUpdate(ctx, plain_data.data(), &plain_data_len, encrypted_data.data(), encrypted_data.size()) == 0) {
    EVP_CIPHER_CTX_cleanup(ctx);
    throw "cannot encrypt data";
  }

  int remain_plain_data_len;
  if (EVP_DecryptFinal(ctx, plain_data.data() + plain_data_len, &remain_plain_data_len) == 0) {
    EVP_CIPHER_CTX_cleanup(ctx);
    throw "cannot padding";
  }
  plain_data.resize(plain_data_len + remain_plain_data_len);
  EVP_CIPHER_CTX_free(ctx);
  return plain_data;
}

auto
convert_hex_string(const std::string& hex_string) -> byte_stream {
  if (hex_string.size() % 2 != 0) {
    throw std::invalid_argument("invalid hex string");
  }

  byte_stream byte_array(hex_string.size() / 2);
  for (std::size_t i = 0; i < hex_string.size(); i += 2) {
    auto&& byte_value = static_cast<uint8_t>(
      std::stoi(hex_string.substr(i, 2), nullptr, 16)
    );
    byte_array[i / 2] = byte_value;
  }

  return byte_array;
}

auto
usage(const std::filesystem::path& program_path) -> void {
  const auto program_name = program_path.filename().generic_string();

  std::cout
    << "OpenSSLライブラリの使用方法のサンプル"        << std::endl
    << "Usage:"                                       << std::endl
    << "  " << program_name << " enc"                 << std::endl
    << "  " << program_name << " dec"                 << std::endl
    << "  " << program_name << " help"                << std::endl
    << "  " << program_name << " -h"                  << std::endl
    << "Arguments:"                                   << std::endl
    << "  enc"                                        << std::endl
    << "    標準入力から受け取ったデータを暗号化し、" << std::endl
    << "    標準出力へ出力する"                       << std::endl
    << "  dec"                                        << std::endl
    << "    標準入力から受け取ったデータを復号化し、" << std::endl
    << "    標準出力へ出力する"                       << std::endl
    << "  help"                                       << std::endl
    << "    ヘルプを表示する"                         << std::endl
    << "Options:"                                     << std::endl
    << "  -h"                                         << std::endl
    << "    ヘルプを表示する"                         << std::endl
    << "  -i <iv>"                                    << std::endl
    << "    <iv>で指定された値でIVを作成する。"       << std::endl
    << "    <iv>はHex String形式(例: AB01DF93BC)で"   << std::endl
    << "    長さが128bitになるようにする必要がある"   << std::endl
    << "  -k <key>"                                   << std::endl
    << "    <key>で指定された値で鍵を作成する。"      << std::endl
    << "    <key>はhex String形式で長さが128 bit,"    << std::endl
    << "    192 bit, 256 bitのいずれかの"             << std::endl
    << "    長さになる必要がある。"                   << std::endl;
}

template <
  size_t key_size,
  std::enable_if_t<key_size == 128 || key_size == 192 || key_size == 256, std::nullptr_t> = nullptr
>
auto
print_data_while_encoding(
  const byte_stream& original_key,
  const byte_stream& data,
  const byte_stream& original_iv,
  const std::function<byte_stream(const key<key_size>&, const byte_stream&, const iv&)>& encoder
) -> void {
  iv iv;
  key<key_size> key;
  if (iv.size() != original_iv.size()) {
    throw std::invalid_argument("iv length must be 128 bit ");
  }
  if (original_key.size() != key.size()) {
    throw std::invalid_argument("invalid key size");
  }
  std::copy(original_iv.begin(), original_iv.end(), iv.begin());
  std::copy(original_key.begin(), original_key.end(), key.begin());

  auto encoded_data = encoder(key, data, iv);
  for (const auto byte_value : encoded_data) {
    std::cout << byte_value;
  }
}

int
main(int argc, char* argv[]) {
  const auto program_path = argv[0];
  byte_stream iv(BLOCK_SIZE, 0);
  byte_stream key(128 / 8, 0);
  int opt;
  while ((opt = getopt(argc, argv, "hi:k:V")) != -1) {
    switch (opt) {
      case 'h':
        usage(program_path);
        return 0;
      case 'i':
        try {
          iv = convert_hex_string(optarg);
        } catch (const std::invalid_argument& error) {
          std::cerr << "invalid iv format" << std::endl;
          return 1;
        } catch (const std::out_of_range& error) {
          std::cerr << error.what() << std::endl;
          return 1;
        }
        if (iv.size() != BLOCK_SIZE) {
          std::cerr << "iv must be 128 bit" << std::endl;
        }
        break;
      case 'k':
        try {
          key = convert_hex_string(optarg);
        } catch (const std::invalid_argument& error) {
          std::cout << "invalid key format" << std::endl;
          return 1;
        } catch (const std::out_of_range& error) {
          std::cerr << error.what() << std::endl;
          return 1;
        }
        if (!(key.size() == 128 / 8 || key.size() == 192 / 8 || key.size() == 256 / 8)) {
          std::cerr << "key size must be 128, 192 or 256 bit" << std::endl;
        }
        break;
      default:
        return 1;
    }
  }

  if (argc - optind < 1) {
    std::cerr << "no subcommand" << std::endl;
    return 1;
  }
  const auto subcommand = std::string(argv[optind]);
  if (subcommand == "help") {
    usage(program_path);
    return 0;
  }

  byte_stream data;
  while (true) {
    auto byte_value = getc(stdin);
    if (byte_value == EOF) {
      break;
    }
    data.emplace_back(byte_value);
  }
  try {
    if (subcommand == "enc") {
      switch (key.size() * 8) {
        case 128:
          print_data_while_encoding<128>(key, data, iv, encrypt<128>);
          break;
        case 192:
          print_data_while_encoding<192>(key, data, iv, encrypt<192>);
          break;
        case 256:
          print_data_while_encoding<256>(key, data, iv, encrypt<256>);
          break;
        default:
          std::cout << "invalid key type" << std::endl;
          return 1;
      }
    } else if (subcommand == "dec") {
      switch (key.size() * 8) {
        case 128:
          print_data_while_encoding<128>(key, data, iv, decrypt<128>);
          break;
        case 192:
          print_data_while_encoding<192>(key, data, iv, decrypt<192>);
          break;
        case 256:
          print_data_while_encoding<256>(key, data, iv, decrypt<256>);
          break;
        default:
          std::cout << "invalid key type" << std::endl;
          return 1;
      }
    } else {
      std::cerr << "unknown command: " << subcommand << std::endl;
      return 1;
    }
  } catch (const char* error) {
    std::cerr << error << std::endl;
    return 1;
  }
}

ビルド

最後にビルドするが、ファイル名をmain.cppとして保存した上で次のようなコマンドでビルドできる。

$ g++ -std=c++17 -Wall -Wextra -pedantic -O2 -lcrypto -lssl main.cpp

OpenSSL 1.1ではlibssllibcryptoをリンクしなければならないが、OpenSSL 1.0では次のコマンドのようにlibsslだけリンクするようにしてビルドすれば良い。

$ g++ -std=c++17 -Wall -Wextra -pedantic -O2 -lssl main.cpp

もしlibsslをリンクできなければfind / -name 'libssl.so*'を、libcryptoをリンクできなければfind / -name 'libcrypto.so*'を実行し、次のように実行することでリンクできる。

# libsslがリンクできなければ`-L libssl.so*のファイルがあるディレクトリへのパス`をコマンドに追加する(`*`はワイルドカード)
$ g++ -std=c++17 -Wall -Wextra -pedantic -O2 -lcrypto -lssl main.cpp -L libssl.so*のファイルがあるディレクトリのパス
# libcryptoがリンクできなければ`-L libcrypto.so*のファイルがあるディレクトリへのパス`をコマンドに追加する
$ g++ -std=c++17 -Wall -Wextra -pedantic -O2 -lcrypto -lssl main.cpp -L libcrypto.so*のファイルがあるディレクトリのパス
# どちらもなければ`-L libssl.so*のファイルがあるディレクトリへのパス -L libcrypto.so*のファイルがあるディレクトリのパス`をコマンドに追加する
$ g++ -std=c++17 -Wall -Wextra -pedantic -O2 -lcrypto -lssl main.cpp -L libcrypto.so*のファイルがあるディレクトリのパス -L libssl.so*のファイルがあるディレクトリへのパス

こうしてできたバイナリを実行する際には先程追加したディレクトリへのパスをLD_LIBRARY_PATHに指定してやる必要がある(LD_LIBRARY_PATHは場合によっては優先度の高いディレクトリに差し替え用のライブラリを差し込まれて攻撃に利用される可能性があるため、十分に注意してとりあつかう)。

# libsslがリンクできなければこのコマンド
$ env LD_LIBRARY_PATH=libssl.so*のファイルのあるディレクトリへのパス:$LD_LIBRARY_PATH ビルドしたコマンド
# libcryptoがリンクできなければこのコマンド
$ env LD_LIBRARY_PATH=libcrypto.so*のファイルのあるディレクトリへのパス:$LD_LIBRARY_PATH ビルドしたコマンド
# どちらもない場合
$ env LD_LIBRARY_PATH=libssl.so*のファイルのあるディレクトリへのパス:libcrypto.so*のファイルのあるディレクトリへのパス:$LD_LIBRARY_PATH ビルドしたコマンド

もし、openssl/evp.hをインクルードできなければ、find / -name 'evp.h'を実行し、出力されたパスを元に-I openssl/evp.hのあるディレクトリのパスというオプションを追加してビルドする

g++ -std=c++17 -Wall -Wextra -pedantic -O2 -lcrypto -lssl main.cpp -I openssl/evp.hのあるディレクトリのパス

opensslコマンドによるテスト

最後に、実装した暗号化・復号化するコマンドの出力がopensslコマンドによって暗号化・復号化をできるかを試してみる。

まず暗号化。

$ bash
$ iv="$(head -c 16 /dev/urandom | od -x -A n | tr -d ' \n')"; key="$(head -c 32 /dev/urandom | od -x -A n | tr -d ' \n')"; head -c 1000 /dev/urandom | base64 | tr -d '\n' | xargs echo | tee /dev/stderr | ./a.out -i "$iv" -k "$key" enc | openssl aes-256-cbc -nosalt -d -iv "$iv" -K "$key" | xargs echo
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

次に復号化。

$ bash
$ iv="$(head -c 16 /dev/urandom | od -x -A n | tr -d ' \n')"; key="$(head -c 32 /dev/urandom | od -x -A n | tr -d ' \n')"; head -c 1000 /dev/urandom | base64 | tr -d '\n' | xargs echo | tee /dev/stderr | openssl aes-256-cbc -nosalt -e -iv "$iv" -K "$key" | ./a.out -i "$iv" -k "$key" dec | xargs echo
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

あっていそうだ。

スクリプトでやってることは簡単で

  1. /dev/urandomから16 byte(128 bit)とってhex stringにしてIVにする
  2. /dev/urandomから32 byte(256 bit)とってhex stringにして鍵にする
  3. /dev/urandomから1000バイトとってbase64エンコードしたものを表示しつつ、そのbase64エンコードしたものを
    • 作成した暗号化・復号化コマンドで暗号化してopensslコマンドで復号化して表示
    • opensslコマンドで暗号化して作成した暗号化・復号化コマンドで復号化して表示