C/C++言語から暗号モジュールを扱う上でcryptライブラリはライセンス制約がありますが、public domainなCrypto++を使えばな制約もなくなります。
久々boostを触ってみようとリハビリがてらboost::asioとcrypto++を使って、暗号化通信をサポートしたGNTP Growlプログラムを書いてみた。
crypto++には、ハッシュアルゴリズムや暗号アルゴリズムがごった煮で含まれており、フィルタとして使ったりCBCモードで使ったりと、かなり便利になっています。
例えば乱数を得たいのであれば
CryptoPP::SecByteBlock salt(8);
CryptoPP::AutoSeededRandomPool rng;
rng.GenerateBlock(salt.begin(), salt.size());
こんな感じに、またMD5 Digestのhex文字列を取得したいのであれば
CryptoPP::SecByteBlock passtext(CryptoPP::Weak1::MD5::DIGESTSIZE);
CryptoPP::Weak1::MD5 hash;
hash.Update((byte*)password_.c_str(), password_.size());
hash.Update(salt.begin(), salt.size());
hash.Final(passtext);
CryptoPP::SecByteBlock digest(CryptoPP::Weak1::MD5::DIGESTSIZE);
hash.CalculateDigest(digest.begin(), passtext.begin(), passtext.size());
といった感じに。さらにCBCモードで文字列を暗号化したいならば
CryptoPP::CBC_Mode<CryptoPP::DES>::Encryption
encryptor(passtext.begin(), iv.size(), iv.begin());
std::string cipher_text;
CryptoPP::StringSource(text, true,
new CryptoPP::StreamTransformationFilter(encryptor,
new CryptoPP::StringSink(cipher_text)
) // StreamTransformationFilter
); // StringSource
こんな風に書くことも出来る。これだけそろえば出来たも同前。boost::asioの便利なiostreamを使って
asio::ip::tcp::iostream sock(hostname, port);
sock << "こんにちわこんにちわ!";
綺麗な書き方も出来る。ちなみにboost::asioを使えばWindowsやUNIX上での特有なコードが現れる事がないので、同じソースでWindowsやLinuxでもコンパイル出来てしまうので非常にありがたいですね。以下、全体のソースです。ヘッダファイルになってます。
#ifndef gntp_h
#define gntp_h
#define CRYPTOPP_ENABLE_NAMESPACE_WEAK 1
#include <sstream>
#include <iostream>
#include <string>
#include <cryptopp/osrng.h>
#include <cryptopp/files.h>
#include <cryptopp/hex.h>
#include <cryptopp/md5.h>
#include <cryptopp/des.h>
#include <cryptopp/aes.h>
#include <cryptopp/filters.h>
#include <cryptopp/modes.h>
#include <asio.hpp>
class gntp {
private:
static inline std::string to_hex(CryptoPP::SecByteBlock& in) {
std::string out;
CryptoPP::HexEncoder hex( NULL, true, 2, "" );
hex.Attach(new CryptoPP::StringSink(out));
hex.PutMessageEnd(in.begin(), in.size());
return out;
}
void send(const char* method, std::stringstream& stm) {
asio::ip::tcp::iostream sock(hostname_, port_);
if (!sock) return;
// initialize salt and iv
CryptoPP::SecByteBlock salt(8), iv(8);
rng.GenerateBlock(salt.begin(), salt.size());
rng.GenerateBlock(iv.begin(), iv.size());
if (!password_.empty()) {
// get digest of password+salt hex encoded
CryptoPP::SecByteBlock passtext(CryptoPP::Weak1::MD5::DIGESTSIZE);
CryptoPP::Weak1::MD5 hash;
hash.Update((byte*)password_.c_str(), password_.size());
hash.Update(salt.begin(), salt.size());
hash.Final(passtext);
CryptoPP::SecByteBlock digest(CryptoPP::Weak1::MD5::DIGESTSIZE);
hash.CalculateDigest(digest.begin(), passtext.begin(), passtext.size());
// initialize crypt
CryptoPP::CBC_Mode<CryptoPP::DES>::Encryption
encryptor(passtext.begin(), iv.size(), iv.begin());
std::string cipher_text;
CryptoPP::StringSource(stm.str(), true,
new CryptoPP::StreamTransformationFilter(encryptor,
new CryptoPP::StringSink(cipher_text)
) // StreamTransformationFilter
); // StringSource
sock << "GNTP/1.0 "
<< method
<< " DES:" << to_hex(iv)
<< " MD5:" << to_hex(digest) << "." << to_hex(salt)
<< "\r\n"
<< cipher_text << "\r\n\r\n";
} else {
sock << "GNTP/1.0 "
<< method
<< " NONE\r\n"
<< stm.str() << "\r\n";
}
while (1) {
std::string line;
if (!std::getline(sock, line)) {
break;
}
//std::cout << "[" << line << "]" << std::endl;
if (line.find("GNTP/1.0 -ERROR") == 0)
throw "failed to register notification";
if (line == "\r") break;
}
}
std::string application_;
std::string hostname_;
std::string port_;
std::string password_;
CryptoPP::AutoSeededRandomPool rng;
public:
gntp(std::string application = "gntp-send", std::string password = "",
std::string hostname = "localhost", std::string port = "23053") :
application_(application),
password_(password),
hostname_(hostname),
port_(port) { }
void regist(const char* name) {
std::stringstream stm;
stm << "Application-Name: " << application_ << "\r\n";
stm << "Notifications-Count: 1\r\n";
stm << "\r\n";
stm << "Notification-Name: " << name << "\r\n";
stm << "Notification-Display-Name: " << name << "\r\n";
stm << "Notification-Enabled: True\r\n";
stm << "\r\n";
send("REGISTER", stm);
}
void notify(const char* name, const char* title, const char* text, const char* icon = NULL) {
std::stringstream stm;
stm << "Application-Name: " << application_ << "\r\n";
stm << "Notification-Name: " << name << "\r\n";
if (icon) stm << "Notification-Icon: " << icon << "\r\n";
stm << "Notification-Title: " << title << "\r\n";
stm << "Notification-Text: " << text << "\r\n";
stm << "\r\n";
send("NOTIFY", stm);
}
};
#endif
使い方は非常に簡単。
#include "gntp.h"
int main(void) {
gntp client("my growl application", "my-password-is-secret");
client.regist("my-event");
client.notify("my-event", "タイトル", "本文", "http://mattn.kaoriya.net/images/logo.png");
return 0;
}
これだけで暗号化通信をサポートしたGNTP Growl通信が出来てしまいます。ちなみにDESな部分をAESに変えればそのままAES暗号通信する事が出来ます。CBC素晴らしい。本当はboost 1.40.0に含まれる新しい機能を触るつもりだったのですが、まったく触れてもいませんね...苦笑
追記 githubにソースあげておいた。
mattn's gntppp at master - GitHub
GNTP++: gntp client library writen in C++
http://github.com/mattn/gntppp/tree/master