ls -al

仮想通貨やプログラミングに関する事などをつらつらと書き綴ります

Androidでファイルハッシュを計算する

Androidでファイルハッシュを計算する処理を実装しました。調べても、AndroidではなくJavaでやっているとか、文字列のハッシュを計算しているとかで、Androidを扱う上で面倒なURIの取扱まで通してカバーできる情報が見当たらなかったので、ここで共有します。


背景

 AndroidでNEMのApostille機能を実装するために、このような処理が必要となったため、調査、実装しました。


方法

 以下のようなコードで実装しました。culcFileHashにアルゴリズム名とSAFなどで得られたファイルのuriを与えるとハッシュが計算されます。

fun culcFileHash(algorithm: String, uri: Uri): String {
    val inputStream = context.contentResolver.openInputStream(uri)
    val buffer = readAll(inputStream)
    val sb = StringBuilder()

    lateinit var md: MessageDigest

    try {
        md = MessageDigest.getInstance(algorithm)
    } catch (e: NoSuchAlgorithmException) {
        e.printStackTrace()
    }

    md.update(buffer)

    for (b in md.digest()) {
        val hex = String.format("%02x", b)
        sb.append(hex)
    }
    return sb.toString()
}

fun readAll(inputStream: InputStream): ByteArray {
    val bOut = ByteArrayOutputStream()
    val buffer = ByteArray(1024*1024*20) //20MB
    while (true) {
        val len = inputStream.read(buffer)
        if (len < 0) {
            break
        }
        bOut.write(buffer, 0, len)
    }
    return bOut.toByteArray()
}

説明

 元々、UriからFileオブジェクトを得て、それからバイナリを取得しようとしていましたが、バイナリが必要なだけであれば、context.contentResolver.openInputStream(uri)を使うことができます。これはuriからInputStreamを取得できるメソッドです。まずはこれを使い、得たinputStreamからファイルのbyteArrayを取得します。その後、MessageDigestを使ってハッシュを計算し、16進数の文字列に変換します。


詰まった所

今回詰まった点は二箇所あります。

  • URIからバイナリを取得するのにFileオブジェクトを介す必要はない

    • context.contentResolver.openInputStream(uri) の存在を知らなかったので、バージョンによるURIの扱い方の違いなどに戸惑いながら、かなり苦戦していました。結局、そもそもそんなことしなくても良いということに後で気が付きました。
  • inputStream.read(buffer)が一度実行するだけで必ずファイルの全ての内容を読み出せているとは限らない

    • readAllメソッドで全て読み出せるようにしています。

参考

Qiita - Javaで暗号化(ハッシュ)
java.io.InputStreamからデータを全て読み込んでbyte配列に格納する方法