Hatena::ブログ(Diary)

IKB: 雑記帖 このページをアンテナに追加 RSSフィード Twitter

2016-05-16

tar.gz から .deb をつくって Ubuntu に Oracle Java をインストール

Oracle JavaLinux 向け公式インストーラーは RPM と tarball との二択。

alian というパッケージ変換ツールを使うと RPMDEB に変えられるというので Ubuntu で試してみたところ、インストール後のスクリプトで alternatives がないとエラーが(というのも Ubuntu では update-alternatives を使うからだが)発生してしまった。

どうしたものかと少々さぐったところ、tarball から alien を使って DEB を作るという記事 http://uchan.hateblo.jp/entry/2016/03/19/004153 が見つかった。

というので真似してみた。

http://www.oracle.com/technetwork/java/javase/downloads/index.html から最新 64bit 版 JDK をダウンロード。(これを書いている時点では 8u91)

/opt/oraclejdk_8-amd64 に配置するつもりでパッケージ化準備を進める。

$ DST="/opt/oraclejdk_8-amd64"
$ mkdir -p /tmp/jdk{/DEBIAN,$DST}
$ cat >/tmp/jdk/DEBIAN/postinst <<EOF
#!/bin/sh
cd "$DST/bin"
for f in *; do
  update-alternatives --install "/usr/bin/\$f" "\$f"\
 "$DST/bin/\$f" 1000
done
EOF
$ chmod 0755 /tmp/jdk/DEBIAN/postinst
$ tar xzf ~/Downloads/jdk-8u91-linux-x64.tar.gz\
 -C /tmp/jdk$DST --strip-component 1
$ cd /tmp/jdk
$ tar czf $(basename $DST).tar.gz opt/ DEBIAN/
$ fakeroot alien --version=1.8.0.91 --bump=0 $(basename $DST).tar.gz

これで /tmp/jdk の下に oraclejdk-8-jdk_1.8.0.91-1_all.deb ができあがる。

これをシステムに導入するときは dpkg コマンドを使う。:

$ sudo dpkg -i oraclejdk-8-jdk_1.8.0.91-1_all.deb

うん、できた。

念のため update-alternatives も実行して、インストールした JDK を使うよう選択しておく。:

$ sudo update-alternatives --config java

2016-01-20

領域を固定パターンで埋める memfill

ある値で int 配列を埋めたい、構造体配列初期化したい、なんて要求はざらにあるような気がするのだけれど、標準ライブラリーにはいってないのはなぜなんだぜ?

見たとおり、速度は O(log nmemb)。

void* memfill(void *ptr, const void *data, size_t size, size_t nmemb) {
  char *const head = ptr;
  if (nmemb > 0) {
    size_t n;
    memcpy(head, data, size);
    for (n = 1; 2*n < nmemb; n *= 2)
      memcpy(head + n * size, head, n * size);
    memcpy(head + n * size, head, (nmemb - n) * size);
  }
  return head + size * nmemb;
}

戻り値は埋めたメモリー領域の末尾のひとつ向こう。続けて操作をしたいときだとかに便利だし、あと STL の標準アルゴリズムを真似したいというのがあってだね。

2015-07-25

AES暗号ユーティリティ

AES 暗号をお手軽に使えるよう InputStream/OutputStream にかぶせて使うアダプター Stream をつくってみた。

暗号化アダプター

暗号化したいデータを格納した InputStream is があるとして、これに 16 バイト(あるいは 24、もしくは 32 バイト)の鍵データを適用すると IV 込みで暗号化したデータを読み出せる AesEncInputStream。

InputStream wrapped = new AesEncInputStream(key, is, null);

三つめの引数は IV を生成する Random オブジェクトオプションで渡せるようにしている。 null を渡すとパッケージローカルの SecureRandom オブジェクトを使う。

もしくは OutputStream os に書き込むときに暗号化を施すアダプター AesEncOutputStream。

OutputStream wrapped = new AesOutputStream(key, os, null);

三つめの引数は同様に IV 生成につかうオプション Random。パディングは AesEncOutputStream#close() を呼び出したときに付け足すので #close() 忘れに注意。

復号アダプター

冒頭に IV 16 バイトが付された暗号化データを読み出せる InputStream is があるとして、これに鍵データを適用すると、復号したデータを読み出せる AesDecInputStream。

InputStream wrapped = new AesDecInputStream(key, is);

もしくは OutputStream os に書き込むときに復号を実行する AesDecOutputStream。

OutputStream wrapped = new AesDecOutputStream(key, os);

最後のパディングを処理は AesDecOutputStream#close() を呼び出したときにするため、ちょっと使いにくいかもしれない。

実装

以下実装。

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.SequenceInputStream;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Enumeration;
import java.util.Random;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import static javax.crypto.Cipher.DECRYPT_MODE;
import static javax.crypto.Cipher.ENCRYPT_MODE;

class Local {
    static final SecureRandom random = new SecureRandom();
}

class AesEncInputStream extends SequenceInputStream {

    public AesEncInputStream(byte[] key, InputStream is, Random random)
            throws GeneralSecurityException {
        super(new Helper(key, is, random));
    }

    private static class Helper implements Enumeration<InputStream> {
        final InputStream[] iss = new InputStream[2];
        int pos = 0;

        Helper(byte[] key, InputStream is, Random random) throws GeneralSecurityException {
            final byte[] iv = new byte[16];
            (random != null ? random : Local.random).nextBytes(iv);

            final Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
            c.init(ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));

            iss[0] = new ByteArrayInputStream(iv);
            iss[1] = new CipherInputStream(is, c);
        }

        @Override
        public boolean hasMoreElements() {
            return pos < 2;
        }

        @Override
        public InputStream nextElement() {
            return iss[pos++];
        }

    }

}

class AesEncOutputStream extends CipherOutputStream {

    public AesEncOutputStream(byte[] key, OutputStream os, Random random)
            throws GeneralSecurityException {
        super(os, getCipher(key, os, random));
    }

    private static Cipher getCipher(byte[] key, OutputStream os, Random random)
            throws GeneralSecurityException {
        final byte[] iv = new byte[16];
        (random != null ? random : Local.random).nextBytes(iv);
        try {
            os.write(iv, 0, 16);
        } catch (IOException e) {
            throw new GeneralSecurityException("Cannot write IV (16bytes)", e);
        }

        final Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
        c.init(ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));
        return c;
    }

}

class AesDecInputStream extends CipherInputStream {

    public AesDecInputStream(byte[] key, InputStream is)
            throws GeneralSecurityException {
        super(is, getCipher(key, is));
    }

    private static Cipher getCipher(byte[] key, InputStream is) throws GeneralSecurityException {
        Throwable t = null;
        try {
            final byte[] iv = new byte[16];
            if (is.read(iv) == 16) {
                final Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
                c.init(DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));
                return c;
            }
        } catch (IOException e) {
            t = e;
        }
        throw new GeneralSecurityException("Cannot read IV (16bytes)", t);
    }

}

class AesDecOutputStream extends OutputStream {

    private final Cipher c;
    private final OutputStream os;
    private final byte[] key;
    private final byte[] iv = new byte[16];
    private int pos = 0;

    public AesDecOutputStream(byte[] key, OutputStream os) throws GeneralSecurityException {
        c = Cipher.getInstance("AES/CBC/PKCS5Padding");
        this.key = key;
        this.os = os;
    }

    @Override
    public void write(int oneByte) throws IOException {
        final byte[] buffer = {(byte) oneByte};
        write(buffer, 0, 1);
    }

    @Override
    public void write(byte[] buffer, int offset, int count) throws IOException {
        final int n = pos == 16 ? 0 : Math.min(16 - pos, count);
        if (n > 0) {
            System.arraycopy(buffer, offset, iv, pos, n);
            if ((pos += n) == 16) {
                try {
                    c.init(DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));
                } catch (GeneralSecurityException e) {
                    throw new IOException("Failed to initialize cipher", e);
                }
            }
        }
        if (count - n > 0) os.write(c.update(buffer, offset + n, count - n));
    }

    @Override
    public void flush() throws IOException {
        os.flush();
    }

    @Override
    public void close() throws IOException {
        try {
            if (pos == 16) os.write(c.doFinal());
        } catch (GeneralSecurityException e) {
            throw new IOException("Failed to finalize cipher", e);
        }
    }

}
使用例
    public void testInPair() throws Exception {
        final byte[] key = new SeucreRandom().get(16);
        final String expect = "hello, world! hello, world! hello, world! hello, world!!";

        final byte[] encrypted;
        {
            final InputStream is = new AesEncInputStream(key, new ByteArrayInputStream(expect.getBytes()), null);
            final ByteArrayOutputStream os = new ByteArrayOutputStream();
            final byte[] buffer = new byte[8];
            for (int read; (read = is.read(buffer, 0, Local.random.nextInt(8) + 1)) != -1; )
                os.write(buffer, 0, read);
            encrypted = os.toByteArray();
        }

        final byte[] decrypted;
        {
            final InputStream is = new AesDecInputStream(key, new ByteArrayInputStream(encrypted));
            final ByteArrayOutputStream os = new ByteArrayOutputStream();
            final byte[] buffer = new byte[8];
            for (int read; (read = is.read(buffer, 0, Local.random.nextInt(8) + 1)) != -1; )
                os.write(buffer, 0, read);
            decrypted = os.toByteArray();
        }

        assertEquals(expect, new String(decrypted));
    }

ブロック暗号の簡単な解説

以下のキーワードの関係をできるだけ平易に説明します。

ブロック暗号

AES や DES と呼ばれる暗号はブロック暗号、特定の長さのデータを「鍵」と呼ばれるデータで暗号化する方式です。

データと鍵の組み合わせに対して暗号化されるデータは唯一定まります。このため鍵と暗号化データからもとのデータを解読(復号)できます。

ストリーム暗号

一般的な応用では、処理するデータの長さは暗号方式の要求する長さ(ブロック長)にならないので対象データをブロックに分割します。そして最後のブロックはぴったりの長さになるよう詰め物(パディング)をします。

ストリーム暗号とは、このようにして任意の長さのデータを暗号化できるようにしたブロック暗号のことです。

暗号化モード

さてストリーム暗号では一般に、暗号化するブロックに前段のブロックの暗号化結果をかき混ぜて簡単に解読されないようにします。 *1

けれど最初のブロックにはかき混ぜるべき前の暗号化結果がないので、暗号化開始時に IV (initial vector) と呼ばれるランダムなデータ列を生成して使います。

ストリーム暗号でのデータのかき混ぜ方は暗号化モードと呼ばれていて、主流のモードは前段の "CBC" です。

*1:というのはデータと鍵が同じなら暗号化結果が唯一定まるため、なにもしなければ解読者へのヒントになってしまうためです。たとえばプロトコルの最初の "HELO" が "IFMP" に写されるとして、メッセージのやりとりを継続的に盗聴している人は "IFMP" を "HELO" と解読できなくてもセッションの特定のメッセージが発せられたことはわかります。

2015-05-14

一般的なInputStreamからChannelを生成する

FileInputStream には #getChannel() があるけれど、一般的な InputStream を Channel にしたいときはこうする。:

Channel ch = java.nio.channels.Channels#newChannle(InputStream);

StringをInputStreamに変換する

InputStream を受け取る API に、文字列から生成した InputStream を渡したいことがある。

このために StringBufferInputStream があったのだけれど deprecated であり、 StringReader を使うよう推奨されていて困る、ことがある。

自前で String から InputStream を生成する方法はこう。:

new ByteArrayInputStream(string.getBytes(Charset.forName("UTF-8")));

なお Charset.forName("UTF-8") は StandardCharsets.UTF_8 とすることもできる。(ただし Android の場合 API 19 以降)

巨大ドメインに参加している PC での /etc/passwd, /etc/group ファイルのつくりかた

先日 Cygwin を何度か入れなおす羽目になって、そのときにコンソールに表示されるこのメッセージ。:

Your group is currently "mkpasswd".  This indicates that your
gid is not in /etc/group and your uid is not in /etc/passwd.

The /etc/passwd (and possibly /etc/group) files should be rebuilt.
See the man pages for mkpasswd and mkgroup then, for example, run

mkpasswd -l [-d] > /etc/passwd
mkgroup  -l [-d] > /etc/group

Note that the -d switch is necessary for domain users.

パスワードとグループがデフォルト設定のままになってるから mkpasswd と mkgroup を使って設定をするがいいじゃんよ」ってことなんだけれど、アクティブディレクトリーで膨大なユーザー・グループ情報が管理されている状況下で mkpasswd -d などすると実際死ぬ。延々とリスト取得していて、ちっとも終わりゃあしない!

で、対処法。現在のユーザー情報とホストマシンのローカル情報だけを反映した passwd と group ファイルを作ればよいので、:

$ mkpasswd -l -c > /etc/passwd
$ mkgroup -l -c > /etc/group

複数のドメインユーザーで共有する環境なら初回利用者は上記を実行して、それ以降に使うユーザーは自分のユーザー情報だけ追記更新ってのでもよさそう。:

$ mkpasswd -c >> /etc/passwd

ドメインユーザーに対して mkgroup -c が返す値は Domain Users:... のため、こちらは改めて追加する必要はない。

Windows 共有を Linux から利用する

CIFS UTILS を使って Linux 上のディレクトリーにマウントする。

準備

1. CIFS UTILS をインストールする

sudo apt-get install cifs-utils

2. Windows 共有の認証情報を保存する

cat > $HOME/.cifs-cred <<EOF
username=YOUR ID
password=YOUR PASSWORD
EOF
chmod 600 $HOME/.cifs-cred

3. マウント先ディレクトリーを作成する

mkdir -p $HOME/shared

マウント

mount -t cifs -o <オプション> <Windows共有名> <マウント先>

sudo mount -t cifs\
 -o noexec,nounix,uid=$(id -u),gid=$(id -g),credentials=$HOME/.cifs-cred\
 //Windows-Shared/ResourceName\
 $HOME/shared

アンマウント

umount [-l] <マウント先>

sudo umount $HOME/shared

-l オプションは、コマンドから即座に制御を戻しシステムにバックグラウンドでアンマウントさせる Lazy モード指定。

共有との制御通信に時間がかかる場合などに便利。

2015-03-16

マングルされたシンボルのデマングル

アセンブルリストに出てくるマングルされたシンボルの羅列があまりにつからったので、ちょろっと調べてデマングルするためのコードを書いてみた。…だがしかし…

#include <cxxabi.h>
#include <cstdio>
#include <cstdlib>

int main(int argn, char **args) {
  if (argn > 1) {
    int status = 0 ;
    char *realname = abi::__cxa_demangle(args[1], 0, 0, &status);
    if (realname != NULL) {
      puts(realname);
      free(realname);
      return 0;
    } else {
      return 1;
    }
  } else {
    return 2;
  }
}

こんなんわざわざ書かなくても c++filt という便利コマンドがあった。

$ echo _ZN7android16LoadNativeBridgeEPKcPKNS_28NativeBridgeRuntimeCallbacksE |c++filt 
android::LoadNativeBridge(char const*, android::NativeBridgeRuntimeCallbacks const*)

objdump -d でディスアセンブルした出力をパイプで c++filt に流せば、可能な範囲でぜんぶデマングルしてくれる。