画像データとしてプログラムを保存する
はてなフォトライフなど大量の画像データを無料で預かるサービスが多い。そこで画像データとしてプログラムをはじめバイナリデータやテキストデータを保存するプログラムを作ってみた。コンセプトしては昔からあるので別に新しいものでもないし、Windowsでは色々なフリーソフトがるようだ。ここでは、あくまで個人が気楽に使う意味で(お遊びで)、簡単なシェルスクリプトとして組んでみた。
まだ、完成度がかなり低く、ブログに載せるのはどうかな、と思ったのだが、ちょっと忙しくなりそうで、今書いておかないと忘れてしいそうなので。まだまだバグが潜んでいそうだが、それは御愛嬌で。
■ 概要
tar(tape archive)をもじって“bar”(bitmap archive)という名前で作ったみた。例えば、
$ bar -cf files.png file1 file2 file3 ....
という風に使う。勿論、ディレクトリごと画像ファイルにアーカイブもできる。生成する画像データはPNGをデフォルトとして、指定でBMPも可能にした。純粋な画像データであれば不可逆圧縮であるJPEGなどでも構わないが、プログラムなどのデータを保存する場合は可逆圧縮である必要があるのでPNGにした。
アーカイブした画像ファイルから元のファイルを復元するには、
$ bar -xf files.png
とする。アーカイブ画像データは標準入力からも読み込めるので、
$ wget -O - http://img.f.hatena.ne.jp/images/fotolife/a/adsaria/20090305/20090305194844.png | bar -x
等と使うこともできる。
このbarというプログラム自身をビットマップにアーカイブすると となる。
■ 公開暗号鍵による中身の確認
最近は何かと物騒である。ネットに保管しているデータをダウンロードしてきたら中身が書き換えられていてウィルスだった、なんてことも考えられなくもない。そこで、barには公開鍵による中身の確認機能を付けておいた。
ファイルをアーカイブする時に秘密鍵を指定して、シグネチャ付きアーカイブを生成する。
$ bar -cf files.png -s adsaria_private.pem file1 file2 file3
そして、シグネチャ付きアーカイブファイルを復元する時には公開鍵を指定してシグネチャを確認する。
$ bar -xf files.png -p adsaria_public.pem bar: SIGNATURE was verified.
というメッセージが出てくる。
ただし、復元時に公開鍵を指定しなかったり、シグネチャが異なっていても、警告(WARNING)を出してファイルは復元する。あくまでも鍵によるシグネチャは暗号のためではなく、内容の確認のため。もし、暗号化して他人には公開したくないのであれば、アーカイブ前に暗号化しておく。
barを画像データとして保存した もシグネチャ付きで生成してあるので“adsariaの公開暗号鍵”でシグネチャを確認することができる。
■ 使い方
先ず、“Program List "bar"”にあるシェルスクリプトをコピー&ペーストして /usr/local/bin/bar というファイルとして保存する。それに対して実行権を与える。
$ sudo cat > /usr/local/bin/bar コピー&ペースト [Ctrl-D] $ sudo chmod +x /usr/local/bin/bar
次に、barは“convert”という画像フォーマットを変換するプログラムを使うため、convertがインストールされていない環境では、これをインストールしておく。Ubuntuの環境であれば次のようにしてインストールする。
$ sudo apt-get install imagemagick
Fedoraであれば
# yum install ImageMagick
あとは、実行するだけ。
オプション
-c | 画像ファイルにアーカイブ。-xとは排他。 |
-x | 画像ファイルからの解凍 -cとは排他。 |
-f ファイル名 | 画像ファイル名の指定。無いと標準入出力。 |
-b PNG または BMP | 画像ファイルのフォーマット指定。-cの時のみ。 |
-s RSA秘密鍵 | シグネチャ生成時の秘密鍵指定。-cの時のみ。 |
-p RSA公開鍵 | シグネチャ確認用の公開鍵指定。-xの時のみ。 |
Program List "bar"
#!/bin/bash # bitmap archiver: # bar Ver 0.003 (2009/03/01) # Copyright (C) 2009 Adsaria # This program is free software; you can redistribute it and/or modify it. # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY. ### Default Value definitions MAX_IMAGE_WIDTH=4096 BITMAP_FORMAT=png ### Constants BAR_ID=BAR1 BAR_VER=1 ARCHIVE_TYPE=TGZ_ SIGNATURE_TYPE=RSA_ END_OF_ARCHIVE=EBAR export LANG=en_US.UTF-8 CMD=`basename $0` USAGE="$CMD -x|-c [ -f|--file bitmap-archive ] [ -s|--secret secret_key | -p|--private public_key ] [ -b|--bitmap png|bmp ] [ file... ] " COLOR_DEPTH=24 ### Option processing GETOPT_FORM=`getopt -o cxvf:s:p:b: \ --long create,extract,verbose,help,file:,secret:,public:,bitmap: \ -n $CMD -- "$@"` eval set -- "$GETOPT_FORM" while true ; do case "$1" in -c|--create) OPER_C=Y ; OPERATION=create; shift ;; -x|--extract) OPER_X=Y ; OPERATION=extract; shift ;; -v|--verbose) TAR_OPT="-v" ; shift ;; -f|--file) ARCHIVE_FILE="$2" ; shift 2 ;; -s|--secret) PRIVATE_KEY="$2" ; shift 2 ;; -p|--public) PUBLIC_KEY="$2" ; shift 2 ;; -b|--bitmap) BITMAP_FORMAT_SPEC="$2" ; shift 2 ;; --help) echo "$USAGE" >&2 ; exit 1 ;; --) shift ; break ;; *) echo "Internal error!" >&2 ; exit 1 ;; esac done for arg do SRC_FILES="$SRC_FILES \"$arg\""; done ### Definition for temporally files TAR_TEMP_FILE=`mktemp "$CMD.TAR_XXXXXXXXXX.tgz"` SIGNATURE_TEMP_FILE=`mktemp "$CMD.SIG_XXXXXXXXXX.sig"` BMP_Temp_File=`mktemp "$CMD.BMP_XXXXXXXXXX.bmp"` PNG_Temp_File=`mktemp "$CMD.PNG_XXXXXXXXXX.png"` trap 'clean_exit 2' 0 INT QUIT TRAP USR1 PIPE TERM clean_exit () { trap - 0 INT QUIT TRAP USR1 PIPE TERM rm -rf "$TAR_TEMP_FILE" rm -rf "$SIGNATURE_TEMP_FILE" rm -rf "$BMP_Temp_File" rm -rf "$PNG_Temp_File" exit $1 } tohex8 () { DECIMAL=$1 UPPER=`expr \( $DECIMAL - \( $DECIMAL / 256 \* 256 \) \) / 16` LOWER=`expr $DECIMAL - \( $DECIMAL / 16 \* 16 \)` HEX_CHARS="0123456789ABCDEF" echo -n "\\x${HEX_CHARS:$UPPER:1}${HEX_CHARS:$LOWER:1}" } tohex16 () { DECIMAL=$1 BYTE_0=`expr \( $DECIMAL - \( $DECIMAL / 65536 \* 65536 \) \) / 256` BYTE_1=`expr $DECIMAL - \( $DECIMAL / 256 \* 256 \)` echo -n `tohex8 $BYTE_1;tohex8 $BYTE_0` } tohex32 () { DECIMAL=$1 BYTE_0=`expr \( $DECIMAL - \( $DECIMAL / 4294967296 \* 4294967296 \) \) / 16777216` BYTE_1=`expr \( $DECIMAL - \( $DECIMAL / 16777216 \* 16777216 \) \) / 65536` BYTE_2=`expr \( $DECIMAL - \( $DECIMAL / 65536 \* 65536 \) \) / 256` BYTE_3=`expr $DECIMAL - \( $DECIMAL / 256 \* 256 \)` echo -n `tohex8 $BYTE_3;tohex8 $BYTE_2;tohex8 $BYTE_1;tohex8 $BYTE_0` } exec_create () { eval tar zcf $TAR_TEMP_FILE "$TAR_OPT" $SRC_FILES 2> /dev/null if [ $? -ne 0 ]; then echo "${CMD}: arciving error." >&2 clean_exit 1 fi SIGNATURE_BYTES=0 if [ -n "$PRIVATE_KEY" ]; then MD5SUM=`md5sum -b < "$TAR_TEMP_FILE" | cut -f 1 -d " "` echo -n "$MD5SUM" | openssl rsautl -out "$SIGNATURE_TEMP_FILE" -sign -inkey "$PRIVATE_KEY" 2> /dev/null if [ $? -ne 0 ]; then echo "${CMD}: could not generate the SIGNATURE_TEMP_FILE." >&2 ; fi # in the case of failure of generating signature, make null file as dummy. touch "$SIGNATURE_TEMP_FILE" SIGNATURE_BYTES=`wc -c "$SIGNATURE_TEMP_FILE" | cut -f 1 -d " "` fi IMAGE_WIDTH=$MAX_IMAGE_WIDTH TAR_TEMP_BYTES=`wc -c "$TAR_TEMP_FILE" | cut -f 1 -d " "` IMAGE_BYTES=`expr 4 + 4 + 4 + $TAR_TEMP_BYTES + 4 + 4 + $SIGNATURE_BYTES + 4 + 4` IMAGE_PIXS=`expr \( $IMAGE_BYTES \* 8 + $COLOR_DEPTH - 1 \) / $COLOR_DEPTH` # Deciding the image width and height, not in excess of MAX_IMAGE_WIDTH if [ $IMAGE_PIXS -lt `expr $MAX_IMAGE_WIDTH \* $MAX_IMAGE_WIDTH` ]; then for (( WIDTH=1 ; WIDTH<=$MAX_IMAGE_WIDTH ; WIDTH*=2 )); do SQUARE=`expr $WIDTH \* $WIDTH` if [ $IMAGE_PIXS -lt $SQUARE ]; then break; fi done if [ $WIDTH -gt $MAX_IMAGE_WIDTH ]; then WIDTH=$MAX_IMAGE_WIDTH; fi IMAGE_WIDTH=$WIDTH fi IMAGE_HEIGHT=`expr \( $IMAGE_PIXS + $IMAGE_WIDTH - 1 \) / $IMAGE_WIDTH` PADDING_BYTES=`expr \( $IMAGE_WIDTH \* $IMAGE_HEIGHT \) \* $COLOR_DEPTH / 8 - $IMAGE_BYTES` BF_TYPE=BM BF_SIZE=`expr 54 + $IMAGE_BYTES + $PADDING_BYTES` BF_RESERVED_1=0 BF_RESERVED_2=0 BF_OFFSET=54 BI_HEADER_SIZE=40 BI_WIDTH=$IMAGE_WIDTH BI_HEIGHT=$IMAGE_HEIGHT BI_PLANES=1 BI_BITCOUNT=$COLOR_DEPTH BI_COMPRESSION=0 BI_IMAGE_SIZE=`expr $IMAGE_BYTES + $PADDING_BYTES` # 2834 = 72 dpi, 3779 = 96 dpi BI_X_BPM=2834 BI_Y_BPM=2834 BI_COLOR_USED=0 BI_COLOR_IMPORTANT=0 # RGB_QUAD_1="\\x00\\x00\\x00\\xFF" # RGB_QUAD_2="\\xFF\\xFF\\xFF\\xFF" ( exec 2> /dev/null exec > "$BMP_Temp_File" echo -n "$BF_TYPE" echo -n -e `tohex32 $BF_SIZE` echo -n -e `tohex16 $BF_RESERVED_1` echo -n -e `tohex16 $BF_RESERVED_2` echo -n -e `tohex32 $BF_OFFSET` echo -n -e `tohex32 $BI_HEADER_SIZE` echo -n -e `tohex32 $BI_WIDTH` echo -n -e `tohex32 $BI_HEIGHT` echo -n -e `tohex16 $BI_PLANES` echo -n -e `tohex16 $BI_BITCOUNT` echo -n -e `tohex32 $BI_COMPRESSION` echo -n -e `tohex32 $BI_IMAGE_SIZE` echo -n -e `tohex32 $BI_X_BPM` echo -n -e `tohex32 $BI_Y_BPM` echo -n -e `tohex32 $BI_COLOR_USED` echo -n -e `tohex32 $BI_COLOR_IMPORTANT` # echo -n -e $RGB_QUAD_1 # echo -n -e $RGB_QUAD_2 echo -n "$BAR_ID" echo -n -e `tohex32 4` echo -n -e `tohex32 $BAR_VER` echo -n "$ARCHIVE_TYPE" echo -n -e `tohex32 $TAR_TEMP_BYTES` cat $TAR_TEMP_FILE echo -n "$SIGNATURE_TYPE" echo -n -e `tohex32 $SIGNATURE_BYTES` if [ $SIGNATURE_BYTES -ne 0 ]; then cat "$SIGNATURE_TEMP_FILE" fi echo -n "$END_OF_ARCHIVE" echo -n -e `tohex32 0` head -c "$PADDING_BYTES" /dev/zero ) SOURCE_TEMP_FILE="$BMP_Temp_File" if [ "$BITMAP_FORMAT" = png ]; then convert "$BMP_Temp_File" "$PNG_Temp_File" SOURCE_TEMP_FILE="$PNG_Temp_File" fi if [ -z "$ARCHIVE_FILE" ]; then cat "$SOURCE_TEMP_FILE" else cp "$SOURCE_TEMP_FILE" "$ARCHIVE_FILE" fi } exec_extract () { if [ -z "$ARCHIVE_FILE" ]; then cat > "$PNG_Temp_File" else cp -f "$ARCHIVE_FILE" "$PNG_Temp_File" fi FILE_TYPE=`file -b "$PNG_Temp_File" | cut -f 1 -d ","` case "$FILE_TYPE" in "PNG image data") convert "$PNG_Temp_File" "$BMP_Temp_File" ;; "PC bitmap data") cp -f "$PNG_Temp_File" "$BMP_Temp_File" ;; *) echo "${CMD}: input data type \"$FILE_TYPE\" is unknown." >&2 clean_exit 1 ;; esac DEFAULT_OFFSET=54 BF_TYPE=`hexdump -s 0 -n 2 -e "\"%c\"" "$BMP_Temp_File"` if [ "$BF_TYPE" != "BM" ]; then echo "${CMD}: Archive format error." >&2 ; clean_exit 1; fi BF_SIZE=`hexdump -s 2 -n 4 -e "\"%u\"" "$BMP_Temp_File"` BF_RESERVED_1=`hexdump -s 6 -n 2 -e "\"%u\"" "$BMP_Temp_File"` BF_RESERVED_2=`hexdump -s 8 -n 2 -e "\"%u\"" "$BMP_Temp_File"` BF_OFFSET=`hexdump -s 10 -n 4 -e "\"%u\"" "$BMP_Temp_File"` if [ "$BF_OFFSET" -ne "$DEFAULT_OFFSET" ]; then echo "${CMD}: Archive format error." >&2 ; clean_exit 1; fi TOTAL_NET_SIZE=0 FRAME_OFFSET=`expr $BF_OFFSET` FRAME_NAME=`hexdump -s $FRAME_OFFSET -n 4 -e "\"%c\"" "$BMP_Temp_File"` FRAME_BYTES_OFFSET=`expr $FRAME_OFFSET + 4` FRAME_BYTES=`hexdump -s $FRAME_BYTES_OFFSET -n 4 -e "\"%u\"" "$BMP_Temp_File"` FRAME_BODY_OFFSET=`expr $FRAME_OFFSET + 8` TOTAL_NET_SIZE=`expr $TOTAL_NET_SIZE + 4 + 4 + $FRAME_BYTES` while [ "$FRAME_NAME" != "$END_OF_ARCHIVE" ]; do case "$FRAME_NAME" in "$BAR_ID") BAR_VAR=`hexdump -s $FRAME_BODY_OFFSET -n $FRAME_BYTES -e "\"%u\"" "$BMP_Temp_File"` ;; "$ARCHIVE_TYPE") tail -c +`expr $FRAME_BODY_OFFSET + 1` "$BMP_Temp_File" 2> /dev/null \ | head -c "$FRAME_BYTES" 2> /dev/null \ > "$TAR_TEMP_FILE" if [ $? -ne 0 ]; then echo "${CMD}: Extracting archive error." >&2 ; clean_exit 1; fi tar xf "$TAR_TEMP_FILE" "$TAR_OPT" if [ $? -ne 0 ]; then echo "${CMD}: Extracting file error." >&2 ; clean_exit 1; fi ;; "$SIGNATURE_TYPE") if [ $FRAME_BYTES -ne 0 ]; then tail -c +`expr $FRAME_BODY_OFFSET + 1` "$BMP_Temp_File" 2> /dev/null \ | head -c "$FRAME_BYTES" 2> /dev/null \ > "$SIGNATURE_TEMP_FILE" if [ $? -ne 0 ]; then echo "${CMD}: WARNING, Extracting signature error." >&2 ; return 1; fi if [ -n "$PUBLIC_KEY" ]; then SIGNATURE=`openssl rsautl -in "$SIGNATURE_TEMP_FILE" -verify -pubin -inkey "$PUBLIC_KEY" 2> /dev/null` if [ -n "$SIGNATURE" ]; then MD5SUM=`md5sum -b < "$TAR_TEMP_FILE" | cut -f 1 -d " "` if [ "$SIGNATURE" = "$MD5SUM" ]; then echo "${CMD}: SIGNATURE was verified." >&2 else echo "${CMD}: WARNING, SIGNATURE was incorrect." >&2 fi else echo "${CMD}: WARNING, unable to extract SIGNATURE." >&2 fi else echo "${CMD}: WARNING, SIGNATURE was not verified, Public key was missing." >&2 fi fi ;; *) echo "${CMD}: Unknown frame error." >&2 clean_exit 1 ;; esac FRAME_OFFSET=`expr $FRAME_OFFSET + 4 + 4 + $FRAME_BYTES` FRAME_NAME=`hexdump -s $FRAME_OFFSET -n 4 -e "\"%c\"" "$BMP_Temp_File"` FRAME_BYTES_OFFSET=`expr $FRAME_OFFSET + 4` FRAME_BYTES=`hexdump -s $FRAME_BYTES_OFFSET -n 4 -e "\"%u\"" "$BMP_Temp_File"` FRAME_BODY_OFFSET=`expr $FRAME_OFFSET + 8` TOTAL_NET_SIZE=`expr $TOTAL_NET_SIZE + 4 + 4 + $FRAME_BYTES` done if [ "$TOTAL_NET_SIZE" -gt "$BF_SIZE" ]; then echo "${CMD}: Format error." >&2 ; clean_exit 1; fi } ### Now start main program if [ "${OPER_C}${OPER_X}" != "Y" ];then echo "$USAGE" >&2 ; clean_exit 1; fi case $OPERATION in create) if [ -z "$SRC_FILES" ]; then echo "${CMD}: Cowardly refusing to create an empty archive" >&2 clean_exit 1 fi if [ -n "$ARCHIVE_FILE" ]; then if [ -e "$ARCHIVE_FILE" ]; then if [ ! -f "$ARCHIVE_FILE" ]; then echo "${CMD}: \"$ARCHIVE_FILE\": Cannot read: Is not a regular file" >&2 clean_exit 1; fi if [ ! -w "$ARCHIVE_FILE" ]; then echo "${CMD}: \"$ARCHIVE_FILE\": Cannot open: Permission denied" >&2 clean_exit 1; fi fi SUFFIX=`basename "$ARCHIVE_FILE" | tr "." "\n" | tail -1` case "$SUFFIX" in png|PNG) BITMAP_FORMAT=png ;; bmp|BMP) BITMAP_FORMAT=bmp ;; esac fi case "$BITMAP_FORMAT_SPEC" in png|PNG) BITMAP_FORMAT=png ;; bmp|BMP) BITMAP_FORMAT=bmp ;; "") ;; *) echo "${CMD}: Bitmap format \"$BITMAP_FORMAT\" is invalid." >&2 clean_exit 1 ;; esac if [ -n "$PRIVATE_KEY" -a -e "$PRIVATE_KEY" ]; then if [ ! -f "$PRIVATE_KEY" ]; then echo "${CMD}: \"$PRIVATE_KEY\": Cannot read: Is not a regular file" >&2 clean_exit 1; fi if [ ! -r "$PRIVATE_KEY" ]; then echo "${CMD}: \"$PRIVATE_KEY\": Cannot open: Permission denied" >&2 clean_exit 1; fi fi if [ -n "$PUBLIC_KEY" ]; then echo "${CMD}: \"secret (private)\" key is needed." >&2 clean_exit 1; fi exec_create ;; extract) if [ -n "$ARCHIVE_FILE" ]; then if [ ! -e "$ARCHIVE_FILE" ]; then echo "${CMD}: \"$ARCHIVE_FILE\": Cannot open: No such file or directory" >&2 clean_exit 1; fi if [ ! -f "$ARCHIVE_FILE" ]; then echo "${CMD}: \"$ARCHIVE_FILE\": Cannot read: Is not a regular file" >&2 clean_exit 1; fi if [ ! -r "$ARCHIVE_FILE" ]; then echo "${CMD}: \"$ARCHIVE_FILE\": Cannot open: Permission denied" >&2 clean_exit 1; fi fi if [ -n "$PRIVATE_KEY" ]; then echo "${CMD}: \"public\" key is needed." >&2 clean_exit 1; fi if [ -n "$BITMAP_FORMAT_SPEC" ]; then echo "${CMD}: Bitmap format specification is ignored." >&2 fi exec_extract ;; esac clean_exit 0