R で 文字列の SHA1 値を取得するなら openssl::sha1 、 オブジェクト対象なら digest::sha1 、似ているようで違うから気をつけよう

R で文字列の SHA1 値が欲しかったので検索してみたら digest っていうライブラリがヒット、ドキュメント読まずに適当に試してみたら、それらしい結果になったんだけど、ちょっと違和感を感じて確認してみたら私の期待する値ではないみたい。

でもってさらに検索したら openssl ライブラリがヒット。そっちは適当に書いても思った結果になったので、そっちを使うことにしたっていう話です。

なにがいけなかったのか忘れないようにのメモです。

## はじめに試した digest を使う方法
という訳で、まず始めに試した digest による SHA1 計算です。以下のような感じで実行しました。ドキュメントもほとんど読まずに…。

```R:digestのsha1によるSHA1計算
install.packages("digest")

digest::sha1("hello!")
# -> [1] "85ef2b23c7b82fb6bf0e019b64d9046cb66124ca

```

それっぽい結果が表示されたので、おー！簡単じゃんって思ったんですが…うん？うーーん。なんか値に違和感を感じたので、ためしに別の関数を使って計算してみると…。

```R:digestによるSHA1計算
digest::digest("hello!", algo="sha1")
# -> [1] "96cef7fabbc2bfb6d09965f40daa547d12f6d649"
```

あれ？値が違うようです。どっちが正しいの？っていうかどっちも間違ってない？って心配になってきます。

## 欲しい結果（値）は？
そもそもどんな結果になって欲しいのかを確認することとしました。

まずは思いついた Linux コマンドで試してみることに。

```bash:コマンドでSHA1を計算（実行結果）
#sha1sumを使った例
echo -n "hello!" | sha1sum
# -> 8f7d88e901a5ad3a05d8cc0de93313fd76028f8c  -

#opensslを使った例
echo -n "hello!" | openssl sha1
# -> (stdin)= 8f7d88e901a5ad3a05d8cc0de93313fd76028f8c
```

良い感じじゃないですか。でも心配なので nodejs でも計算。これもしっかりと確認することなく思いついたままを打って実行

```bash:nodejsでSHA1を計算（実行結果）
node -e 'console.log(require("crypto").createHash("sha1").update("hello!").digest("hex"))'
# -> 8f7d88e901a5ad3a05d8cc0de93313fd76028f8c
```

お一！さっきと一致しました（←ちょっと驚いている）。でもなんか心配なので python でも計算。これもやっぱり確認することなく思いついたままを打って実行

```bash:pythonでSHA1を計算（実行結果）
#2.7
python -c 'import hashlib;print(hashlib.sha1("hello!").hexdigest())'
# -> 8f7d88e901a5ad3a05d8cc0de93313fd76028f8c

#3.5
python3 -c 'import hashlib;print(hashlib.sha1("hello!".encode("utf-8")).hexdigest())'
# -> 8f7d88e901a5ad3a05d8cc0de93313fd76028f8c
```

お一！お一！また一致しました（←もの凄く驚いている）。てな訳で、私の求めている結果（値）、つまり直感で打って求められる値は、 `8f7d88e901a5ad3a05d8cc0de93313fd76028f8c` であることが分かりました。

## で、どうすれば良いの？
で色々と試してみたら `serialize=FALSE` を指定すると期待した結果になることが分かりました。

```R:digestのインストールによるSHA1計算（実行結果）
digest::digest("hello!", algo="sha1", serialize=FALSE)
# -> [1] "8f7d88e901a5ad3a05d8cc0de93313fd76028f8c"
```

どうやら digest ライブラリの digest や sha1 関数の第 1 引数は「文字列」ではなく「オブジェクト」のようで、デフォルトではオブジェクトをシリアライズしてから処理を行うようです。

digest::digest のヘルプにも冒頭からしっかりと書かれていました。

```text:digestのヘルプ（抜粋）
digest                 package:digest                  R Documentation
Create hash function digests for arbitrary R objects
Description:
     The ‘digest’ function applies a cryptographical hash function to
     arbitrary R objects. By default, the objects are internally
     serialized, and either one of the currently implemented MD5 and
     SHA-1 hash functions algorithms can be used to compute a compact
     digest of the serialized object.
（省略）
Usage:
     digest(object, algo=c("md5", "sha1", "crc32", "sha256", "sha512",
            "xxhash32", "xxhash64", "murmur32", "spookyhash"), serialize=TRUE, file=FALSE,
            length=Inf, skip="auto", ascii=FALSE, raw=FALSE, seed=0,
            errormode=c("stop","warn","silent"),
            serializeVersion=.getSerializeVersion())
```

digest::sha1 のヘルプにもしっかりと書かれているようです。また digest との違いも書かれているようです。

```
sha1                  package:digest                   R Documentation
Calculate a SHA1 hash of an object
Description:
     Calculate a SHA1 hash of an object. The main difference with
     ‘digest(x, algo = "sha1")’ is that ‘sha1()’ will give the same
（省略）
```

## つまり用途を理解せずに使った私が悪い？
そんなこともあって、ドキュメントを読まず、適当に使った私が悪いことが判明しました。相手（ digest ）のことを理解せずに強引に進めようとした私が悪かった、それは私だけでなく誰しもが認める事実でしょう。

反省しつつ振り返る中で、ふと、 Linux も nodejs も python も適当に打っても私の思う結果が得られたじゃないか。もしかしたら、そうゆうライブラリもあるんじゃないか、ということにようやくここで気付きました。

## openssl ライブラリに出会った
てなわけで、私と相性のよいライブラリを探す旅にでることになりました。ゴールがあるかも分からない果てしなく遠い旅に…、って思っていたら一瞬でみつかりました。 openssl っていうライブラリです。ネーミング的にもグットきます。早速ためしてみることにしました。また懲りずに直感で適当に打って実行です。

```R:opensslでsha1計算（実行結果）
install.packages("openssl")

openssl::sha1("hello!")
# -> [1] "8f7d88e901a5ad3a05d8cc0de93313fd76028f8c"
```

おーー！思い通りの結果になりましたー！適当に使っても期待した結果になったってことは digest よりも openssl の方が私には向いているみたいです。

実行速度も測定してみました。

```R:速度の比較
library(purrr)
library(dplyr)

system.time(
	seq(1,10000) %>%
		map(function(x) {
			return(digest::digest(as.character(x), algo="sha1", serialize=F))
			}
		)
)
#   user  system elapsed 
#  1.388   0.656   2.060

system.time(
	seq(1,10000) %>%
		map(function(x) {
			return(openssl::sha1(as.character(x)))
			}
		)
)
#   user  system elapsed 
#  1.096   0.000   1.097

```

速度の問題はなさそうです。

あと openssl::sha1 はベクトルも扱えるみたいです。

```R:ベクトルが扱える
openssl::sha1(c("1","2","3"))
# [1] "356a192b7913b04c54574d18c28d46e6395428ab"
# [2] "da4b9237bacccdf19c0760cab7aec4a8359010b0"
# [3] "77de68daecd823babbb58edb1c8e14d7106e83bb"

# digest 関数はベクトルが扱えない、
digest::digest(c("1","2","3"), algo="sha1", serialize=F)
# [1] "356a192b7913b04c54574d18c28d46e6395428ab"

#そうゆう時は map を使う
c("1","2","3") %>% map(function(x) {
	digest::digest(x,algo="sha1",serialize=F)
}) %>% as.character
# [1] "356a192b7913b04c54574d18c28d46e6395428ab"
# [2] "da4b9237bacccdf19c0760cab7aec4a8359010b0"
# [3] "77de68daecd823babbb58edb1c8e14d7106e83bb"
```

ということは、速度の比較も以下のようにシンプルに書けるし、速度も速くなりそう。

```R:速度の比較2
system.time(
	seq(1,10000) %>% as.character %>% openssl::sha1()
)
#   user  system elapsed 
#  0.052   0.000   0.054 

#又は
system.time(
	openssl::sha1(as.character(seq(1,10000)))
)
#   user  system elapsed 
#  0.052   0.000   0.054 
```

ということは dplyr の mutate とも相性が良いかな。

````R:ベクトルが扱える2
library(dplyr)
data.frame(n=seq(1,3)) %>%
	mutate(hash=openssl::sha1(as.character(n)))
'
  n                                     hash
1 1 356a192b7913b04c54574d18c28d46e6395428ab
2 2 da4b9237bacccdf19c0760cab7aec4a8359010b0
3 3 77de68daecd823babbb58edb1c8e14d7106e83bb
'

#digest はベクトルが扱えないからか結果が全部一緒になっちゃう
data.frame(n=seq(1,3)) %>%
	mutate(hash=digest::digest(as.character(n),algo="sha1",serialize=F))
'
  n                                     hash
1 1 356a192b7913b04c54574d18c28d46e6395428ab
2 2 356a192b7913b04c54574d18c28d46e6395428ab
3 3 356a192b7913b04c54574d18c28d46e6395428ab
'

#ベクトルが扱えないなら map を使う
library(purrr)
data.frame(n=seq(1,3)) %>%
	mutate(hash=map(n, function(x) {
		return(digest::digest(as.character(x),algo="sha1",serialize=F))
		}
	))
'
  n                                     hash
1 1 356a192b7913b04c54574d18c28d46e6395428ab
2 2 da4b9237bacccdf19c0760cab7aec4a8359010b0
3 3 77de68daecd823babbb58edb1c8e14d7106e83bb
'
```

## てな訳で、
私の場合、直感的に使えそうなのは openssl ライブラリのようです。もちろん digest でも文字列の SHA1 値求めることができますが、それよりは「文字列の SHA1 値を求めるなら openssl ライブラリ」、「オブジェクトの SHA1 値を求めるなら digest ライブラリ」ってな使い分けがシンプルだし、使い勝手も良さそうかな。

めでたしめでたし。