2015年1月の読書メーター

2015年1月の読書メーター
読んだ本の数:3冊
読んだページ数:998ページ
ナイス数:7ナイス

「納品」をなくせばうまくいく ソフトウェア業界の“常識"を変えるビジネスモデル「納品」をなくせばうまくいく ソフトウェア業界の“常識"を変えるビジネスモデル感想
何がうまくいって、何がうまくいかないのか、それはやってみないと分からない。そうなると「納品」を無くし、常に改善し続けるというのは非常に理にかなっている。本来あれば企業内に技術者を置くべき所ではあるが、本にも書かれている通り、いろいろな理由で難しい物がある。このやり方は技術者だけでなく、新しくビジネスを始めたいと思っている人にも助けになると思う。☆×4
読了日:1月24日 著者:倉貫義人
嫌われる勇気―――自己啓発の源流「アドラー」の教え嫌われる勇気―――自己啓発の源流「アドラー」の教え感想
☆×4
読了日:1月18日 著者:岸見一郎,古賀史健
マネー・ボール〔完全版〕 (ハヤカワ・ノンフィクション文庫)マネー・ボール〔完全版〕 (ハヤカワ・ノンフィクション文庫)感想
読もうと思ったきっかけは、WEB+DB PRESS Vol.84に統計分析の特集があり、そこに取り上げられていたから。/それまで常識だと思われていたものを、新しい尺度で測り直して評価するというのは、非常に有効なんだと思う。ただし、その新しい尺度を見つけ出すのは簡単では無いだろうし、またそれまでの常識から懸け離れていればいるほど、すぐには受け入れられない。だからこそチャンスなんだとも思う。 ☆×4
読了日:1月10日 著者:マイケル・ルイス

読書メーター

opensslによる暗号化/復号化

自分用のメモ。

概要

  • OpenSSLを使ってファイルの暗号化と復号化を行う
  • C++にて実装
  • 自前のプログラムとopensslコマンドで双方でファイルの暗号化/復号化が出来るようにする

単純に自前のプログラム内で暗号化/復号化する方法はGoogleで検索すれば見つかったけど、opensslコマンドとの間でもやりとりしたかった。サンプルでは自前のプログラムで暗号化と復号化を一度にやっている。暗号化したファイルがopensslコマンドで復号化出来ることを確認。

ソースコード(OpensslSample.cpp。https://gist.github.com/asakawajunya/bb3098fc49b4507fbe31)

サンプルなので暗号化に使用するパスワードはソースにべた書き。

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <openssl/evp.h>
#include <openssl/aes.h>
#include <openssl/rand.h>

#include <cstdlib>
#include <ctime>
#include <iostream>
#include <unistd.h>

unsigned char password[128] = "defaultpass";

void encrypt(const char* ifile, const char* ofile) {

    // salt key
    unsigned char salt_key[] = "xxxxxxxx";
    // salt string
    unsigned char salt[] = "Salted__xxxxxxxx";

    // saltをランダムに生成
    for (int i = 0; i < PKCS5_SALT_LEN; i++) {
        salt[i + PKCS5_SALT_LEN] = salt_key[i] =
                (int) ((unsigned char) std::rand());
    }

    struct stat ifile_stat;
    if (0 == stat(ifile, &ifile_stat)) {
        if (ifile_stat.st_size > 0) {
            unsigned char *indata = (unsigned char *) malloc(
                    ifile_stat.st_size);
            unsigned char *outdata = (unsigned char *) malloc(
                    ifile_stat.st_size + 32);
            unsigned char key[EVP_MAX_KEY_LENGTH], iv[EVP_MAX_IV_LENGTH];

            const EVP_CIPHER* ci = EVP_aes_256_cbc();
            EVP_BytesToKey(ci, EVP_md5(), salt_key, password,
                    strlen((const char *) password), 1, key, iv);

            FILE* i_file = fopen(ifile, "rb");
            fread(indata, sizeof(char), ifile_stat.st_size, i_file);

            int outdata1_len = 0;
            int outdata2_len = 0;

            EVP_CIPHER_CTX ctx;
            EVP_EncryptInit(&ctx, ci, key, iv);
            EVP_EncryptUpdate(&ctx, outdata, &outdata1_len, indata,
                    ifile_stat.st_size);
            EVP_EncryptFinal(&ctx, outdata + outdata1_len, &outdata2_len);
            EVP_CIPHER_CTX_cleanup(&ctx);

            FILE* o_file = fopen(ofile, "wb");

            // salt書き込み
            fwrite(salt, sizeof(char), strlen((const char *) salt), o_file);
            // データ書き込み
            fwrite(outdata, sizeof(char), outdata1_len + outdata2_len, o_file);

            fclose(i_file);
            fclose(o_file);

            free(indata);
            free(outdata);
            indata = NULL;
            outdata = NULL;
        }
    }
}

void decrypt(const char* ifile, const char* ofile) {

    struct stat ifile_stat;
    if (0 == stat(ifile, &ifile_stat)) {
        FILE* i_file = fopen(ifile, "rb");

        unsigned char salt_key[PKCS5_SALT_LEN];
        unsigned char salt[16];

        // salt読み込み
        fread(salt, sizeof(char), 16, i_file);
        for (int i = 0; i < PKCS5_SALT_LEN; i++) {
            salt_key[i] = salt[i + PKCS5_SALT_LEN];
        }

        int outdata1_len = 0;
        int outdata2_len = 0;

        unsigned char *indata = (unsigned char *) malloc(ifile_stat.st_size);
        unsigned char *outdata = (unsigned char *) malloc(ifile_stat.st_size);
        unsigned char key[EVP_MAX_KEY_LENGTH], iv[EVP_MAX_IV_LENGTH];

        // データ読み込み
        fread(indata, sizeof(char), ifile_stat.st_size, i_file);

        const EVP_CIPHER* ci = EVP_aes_256_cbc();
        EVP_BytesToKey(ci, EVP_md5(), salt_key, password,
                strlen((const char *) password), 1, key, iv);

        EVP_CIPHER_CTX ctx;
        EVP_DecryptInit(&ctx, ci, key, iv);
        EVP_DecryptUpdate(&ctx, outdata, &outdata1_len, indata,
                ifile_stat.st_size);
        EVP_DecryptFinal(&ctx, outdata + outdata1_len, &outdata2_len);
        EVP_CIPHER_CTX_cleanup(&ctx);

        FILE* o_file = fopen(ofile, "wb");
        // padding部分を削って書き込む。書き込みデータの一番最後のデータから削る分を特定する。
        fwrite(outdata, sizeof(char),
                outdata1_len - (int) outdata[ifile_stat.st_size - 16 - 1],
                o_file);

        fclose(i_file);
        fclose(o_file);

        free(indata);
        free(outdata);
        indata = NULL;
        outdata = NULL;
    }
}

int main(int argc, char *argv[]) {
    if (2 != argc) {
        fprintf(stderr, "Usage: %s original_file", argv[0]);
        return EXIT_FAILURE;
    }
    // 元ファイル
    std::string original_file = argv[1];
    // 暗号化ファイル
    std::string encrypt_file = std::string(original_file) + ".encrypt";
    // 復号化ファイル
    std::string decrypt_file = std::string(original_file) + ".decrypt";

    encrypt(original_file.c_str(), encrypt_file.c_str());
    decrypt(encrypt_file.c_str(), decrypt_file.c_str());

    return EXIT_SUCCESS;
}

ビルド方法

Mac OS 10.9.5だと結構warningが出るが、ひとまずそのまま。OpenSSLではなくCommonCryptoを使わないとダメな模様。Linuxなら平気かも(未確認)

$ c++ OpensslSample.cpp -o OpensslSample -lcrypto

確認

インラインで説明を追記。

# 暗号化対象のファイルを作成
/Users/junya/develop/OpensslSample% echo "HelloWorld" > test.txt

# md5確認
/Users/junya/develop/OpensslSample% md5 test.txt
MD5 (test.txt) = 6df4d50a41a5d20bc4faad8a6f09aa8f

# 暗号化および復号化
/Users/junya/develop/OpensslSample% ./OpensslSample test.txt

# OpensslSampleを実行すると、"元ファイル名.decrypt"と"元ファイル名.encrypt"というファイルを出力する
/Users/junya/develop/OpensslSample% ls -l
total 72
-rwxr-xr-x  1 junya  staff  15372  9 22 14:00 OpensslSample
-rw-r--r--  1 junya  staff   4193  9 22 13:58 OpensslSample.cpp
-rw-r--r--  1 junya  staff     11  9 22 14:00 test.txt
-rw-r--r--  1 junya  staff     11  9 22 14:01 test.txt.decrypt
-rw-r--r--  1 junya  staff     32  9 22 14:01 test.txt.encrypt

# md5確認。元ファイルと復号化したファイルのハッシュ値が一致することを確認
/Users/junya/develop/OpensslSample% md5 test.txt*
MD5 (test.txt) = 6df4d50a41a5d20bc4faad8a6f09aa8f
MD5 (test.txt.decrypt) = 6df4d50a41a5d20bc4faad8a6f09aa8f
MD5 (test.txt.encrypt) = df58fbb119ceea14dd9e109773fc73f9

# opensslコマンドを使って、暗号化ファイルを復号化する。パスワードにはサンプルソースに書かれている物を入力する
/Users/junya/develop/OpensslSample% openssl aes-256-cbc -d -in test.txt.encrypt
enter aes-256-cbc decryption password:
HelloWorld

その他

  • opensslコマンドでは、デフォルトではファイルの先頭部分に"Salted__xxxxxxxx"という16バイトのデータを書き込む("xxxxxxxx"部分はランダム。これによってファイルとパスワードが同一であっても、毎回異なる暗号化ファイルが出来上がる。nosaltオプションもあるが、今回は使わないようにした。
  • 暗号化すると、ファイルサイズは必ず16の倍数になる。足りない場合はpadding。自前のプログラムで復号化する場合、padding部分を取り除かないと元ファイルと一致しなくなる。書き込みデータの一番最後にはどれだけpaddingされたか書かれているので、その分だけファイルに出力しないようにしている。1バイトpaddingしている場合は"01"x1、2バイトpaddingされている場合は"02"x2、元ファイルが16の倍数であっても必ずpaddingされ、その場合は"10"x16が記述される。
  • 暗号化するのに必要なファイルサイズは元ファイル +32バイト("Salted__xxxxxxxx"とpadding分)あれば足りる。"Salted__xxxxxxxx"を除けば16バイト削れる、padding分も元ファイルサイズを16で割ったあまりを使えば正確になる。

2014年11月28日ソースコード修正

  • mallocに対応するfreeを追加
  • EVP_CIPHER_CTX_cleanupを追加

Amazon SES SMTP Credentialsをpythonやrubyで作ってみる

http://docs.aws.amazon.com/ses/latest/DeveloperGuide/smtp-credentials.html の下の方に書いてあるJavaのコードをスクリプト言語で書くとどうなるかというのを試してみる。AWS SECRET ACCESS KEYをそのまま書くわけにはいかないので、そこは適当な文字で。
実行したバージョンが分かるようにしておく。バージョン変わるとAPIも変わるかも。

pythonの場合

python 2.7.5 (default, Sep 12 2013, 21:33:34)
[GCC 4.2.1 Compatible Apple LLVM 5.0 (clang-500.0.68)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import base64
>>> import hashlib
>>> import hmac
>>> base64.b64encode(chr(2) + hmac.new('AWS_SECRET_ACCESS_KEY','SendRawEmail',hashlib.sha256).digest())
'Aiqnxngua+8IzceF3cMNnR+Tdt0Vo0bXw6z8Q+3U2ls7'

rubyの場合

irb(main):001:0> RUBY_VERSION
=> "2.0.0"
irb(main):002:0> require 'openssl'
=> true
irb(main):003:0> require 'base64'
=> true
irb(main):004:0> Base64.encode64(2.chr + OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), 'AWS_SECRET_ACCESS_KEY', 'SendRawEmail')).chop
=> "Aiqnxngua+8IzceF3cMNnR+Tdt0Vo0bXw6z8Q+3U2ls7"

rubyの方は実行したら後ろに改行コードが付いてしまったので、chopにて除去。
Javaで実行した結果と一致したので合っているっぽい。

vmstatで時刻を表示する

sarコマンドを実行すると、先頭に時刻を表示する。

# sar 1
Linux 3.2.0-4-amd64 (linux) 	20140214日 	_x86_64_	(8 CPU)

201702秒     CPU     %user     %nice   %system   %iowait    %steal     %idle
201703秒     all      0.00      0.00      0.00      0.00      0.00    100.00
201704秒     all      0.00      0.00      0.00      0.00      0.00    100.00

vmstatでは時刻を表示しない。

# vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa
 0  0      0 704064 136996  88860    0    0     0     0    4    4  0  0 100  0
 0  0      0 704064 136996  88860    0    0     0     0   57   31  0  0 100  0

vmstatでもsar同様に時刻を表示させたかったので調べていたら、以下のようにすれば良いことが分かった。

# vmstat 1 | awk '{print strftime("%H:%M:%S"), $0; fflush()}'
20:25:32 procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----
20:25:32  r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa
20:25:32  1  0      0 703740 136996  88864    0    0     0     0    4    4  0  0 100  0
20:25:33  0  0      0 703864 136996  88864    0    0     0     0   55   31  0  0 100  0

キモはfflushでバッファしないようにしていること。これをしておかないと、ファイルにリダイレクトしている時、ctrl+cで停止してもバッファされている分がファイルに出力されなくなる。開始数秒でctrl+cで停止すると、なぜかファイルサイズが0バイトで偉く悩んだ。

0から2147483647までの間の乱数を取得する

bashだと$RANDOMで乱数は取得できるけど、0〜32767までしか取得できない。それ以上の乱数を手っ取り早く取得できないかと思い探している内に以下に落ち着いた。

コード

#!/usr/bin/env bash

while :;
do
  random32=$((($RANDOM & 1)<<30 | $RANDOM<<15 | $RANDOM))
  echo ${random32}
done

これを、random32.shみたいな名前で保存しておく。
実際に使う場合は以下のようになる。無限ループさせているので、延々出力し続ける。

$ ./random32.sh
2062042316
1359327322
286514404
1256198400
1891772729
以下省略・・・

一つだけ取り出した場合は、"./random32.sh | head -n 1"みたいにする。

$ ./random32.sh | head -n 1
695679400

一定の値より小さい乱数を取り出したい場合は以下のようにする。

$ ./random32.sh | xargs -n 1 -I {} expr {} % 65536
42128
52300
58834
22831
13975
52221
21935
62329
以下省略・・・

xargsを使って65536で割ったあまりを出力する。$RANDOMだと上限が32767のため、65536で割っても32767以上の値が取り出せないが、これなら取り出せる。

bash用ロガースクリプト

単純にechoでメッセージ表示するだけでも良かったのだけど、環境変数でログレベルを切り替えて表示出来るように実装してみた。DEBUG、INFO、WARN、ERRORのみ指定出来、それ以外だとINFO扱いにする。

logger.sh

# LOG_LEVELの設定
declare -A _LOG_LEVEL
_LOG_LEVEL[DEBUG]=10
_LOG_LEVEL[INFO]=30
_LOG_LEVEL[WARN]=40
_LOG_LEVEL[ERROR]=50

# LOG_LEVELが指定されていた場合はそのログレベルを使用する
# LOG_LEVELが指定されていなかった場合はINFOを指定
if [ "${LOG_LEVEL}" != "" ];
then
  LOG_LEVEL=${_LOG_LEVEL[${LOG_LEVEL}]}
fi
if [ "${LOG_LEVEL}" = "" ];
then
  LOG_LEVEL=${_LOG_LEVEL[INFO]}
fi

function log_impl() {
  local target=$1;shift;
  local level=$1;shift;
  if [ ${_LOG_LEVEL[${level}]} -ge ${target} ];
  then
    printf "$(date +'%Y/%m/%d %T') %-5s $@\n" ${level}
  fi
}

function log_debug() {
  log_impl ${LOG_LEVEL} DEBUG "$@"
}

function log_info() {
  log_impl ${LOG_LEVEL} INFO "$@"
}

function log_warn() {
  log_impl ${LOG_LEVEL} WARN "$@"
}

function log_error() {
  log_impl ${LOG_LEVEL} ERROR "$@"
}

log_sample.sh

#!/usr/bin/env bash

source logger.sh

log_debug "HOGE"
log_info  "HOGE"
log_warn  "HOGE"
log_error "HOGE"

実行結果

/Users/junya/develop/bash_sample% export LOG_LEVEL=DEBUG;./log_sample.sh
2013/10/09 20:51:27 DEBUG HOGE
2013/10/09 20:51:27 INFO  HOGE
2013/10/09 20:51:27 WARN  HOGE
2013/10/09 20:51:27 ERROR HOGE
/Users/junya/develop/bash_sample% export LOG_LEVEL=DEBUG;./log_sample.sh
2013/10/09 20:54:30 DEBUG HOGE
2013/10/09 20:54:30 INFO  HOGE
2013/10/09 20:54:30 WARN  HOGE
2013/10/09 20:54:30 ERROR HOGE
/Users/junya/develop/bash_sample% export LOG_LEVEL=INFO;./log_sample.sh
2013/10/09 20:54:37 INFO  HOGE
2013/10/09 20:54:37 WARN  HOGE
2013/10/09 20:54:37 ERROR HOGE
/Users/junya/develop/bash_sample% export LOG_LEVEL=WARN;./log_sample.sh
2013/10/09 20:54:42 WARN  HOGE
2013/10/09 20:54:42 ERROR HOGE
/Users/junya/develop/bash_sample% export LOG_LEVEL=ERROR;./log_sample.sh
2013/10/09 20:54:49 ERROR HOGE
/Users/junya/develop/bash_sample%

netcatによるワンライナーWebサーバ

起動方法

/bin/bash -c "while :; do echo -ne 'HTTP/1.0 200 OK\r\nContent-Length: 0\r\n\r\n' | nc -l -p 8080 -w 30; done"

終了方法

psコマンドでpidを確認後、killコマンドで終了させる。

kill -9 プロセスID

終了直後にncコマンドのプロセスが残っていることがあるが、しばらくすると消える。
もう少しスマートに出来ると良いのだが。

whileで無限ループさせているため、ncコマンドだけをkillしようとしてもうまくいかない。すぐに次のncが起動してしまうので。そこでbash -cのとして一枚かぶせている。

確認方法

curlwgetなどを使ってアクセスすれば動作確認可能。ブラウザでも可能だが、自分のMac環境だとGoogle chromeは接続できなかった。Firefoxsafariは平気だった。

curl http://localhost:8080

上記コマンドを投げると、nc側のコンソールには、以下のように出力されている。

GET / HTTP/1.1
User-Agent: curl/7.24.0 (x86_64-apple-darwin12.0) libcurl/7.24.0 OpenSSL/0.9.8y zlib/1.2.5
Host: localhost:8080
Accept: */*