Go 言語(以下 Golang)で、数値を Base62 でエンコードしたい。例えばハッシュ値や、バイナリ(
[]byte
)データなど。
そうです。Base64 ではなく、Base62 です。
しかし、「"golang" base62 変換」でググっても、外部ライブラリを使う方法ばかりだったので自分のググラビリティとして。
-
Base62 @ Wikipedia
- エンコードに使われる文字:
[0-9A-Za-z]
(0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
の 62 文字)
- エンコードに使われる文字:
TL; DR (今北産業)
-
Go 標準モジュールの
math/big
パッケージを使う。 -
big.Int
型にはText(n)
関数がありn
進数指定できる。
62 進数の場合は、10 進数の0〜9
-->0-9
、10〜35
-->a-z
、36〜61
-->A-Z
を使い、合計 62 文字でInt
を表現する。
バイト・スライス([]byte
)をn
進数に変換したい場合は TS; DR 参照。 -
マスター、効くやつをくれ。
10進数の61を62進数に変換、またはその逆に変換する例package main import ( "fmt" "math/big" ) func main() { // エンコード { i := new(big.Int) input := 61 // 61 = 62 進数 - 1 = 1 桁の最大値 i.SetInt64(int64(input)) fmt.Println("Base62 encoded:", i.Text(62)) } // デコード { i := new(big.Int) input := "Z" i.SetString(input, 62) fmt.Println("Base62 decoded:", i) } // Output: // Base62 encoded: Z // Base62 decoded: 61 }
- オンラインで動作をみる @ Go Playground
文字列をBase62にエンコード&デコードpackage main import ( "fmt" "math/big" ) func main() { // エンコード { input := "string data to be encoded" fmt.Println("Original input:", input) bigInt := new(big.Int) inputBytes := []byte(input) // 文字列をバイト変換しておく bigInt.SetBytes(inputBytes) fmt.Println("Base62 encoded:", bigInt.Text(62)) } // デコード { bigInt := new(big.Int) input := "58T1aST0vUU7M3Sc09PDm2WoJRLf8HPS96" bigInt.SetString(input, 62) fmt.Printf("Base62 decoded bytes: %v\n", bigInt.Bytes()) fmt.Println("Decoded input as str:", string(bigInt.Bytes())) } // Output: // Original input: string data to be encoded // Base62 encoded: 58T1aST0vUU7M3Sc09PDm2WoJRLf8HPS96 // Base62 decoded bytes: [115 116 114 105 110 103 32 100 97 116 97 32 116 111 32 98 101 32 101 110 99 111 100 101 100] // Decoded input as str: string data to be encoded }
- オンラインで動作をみる @ Go Playground
TS; DR マスター、もっと強いのくれ
ハッシュ値の頭 n 桁を文字列で DB のキーとして扱いたいのですが、HEX でなく Base62 で欲しかったのです。
つまり、ハッシュ値を Base16(16進数)でなく Base62(62進数)の文字列にして圧縮させることで、短くさせつつもコリジョンの可能性をなるべく和らげたいのです。
数値的にはエンコードを変えたところでコリジョン率は同じですが、文字列でデータをやりとりする場合、同じ文字数なら情報の多い方がいいからです。
- ハッシュ?コリジョン?BLAKE3? 👇
- 最速のDBキー検索を求めて with SQLite3 | ハッシュ関数の基本と応用 @ Qiita
package main
import (
"fmt"
"math/big"
"math/rand"
"github.com/zeebo/xxh3"
)
func main() {
// ハッシュ化したいデータ
input := "Hello, 世界"
// ソルト値
salt := Salt()
// ハッシュ化 (xxHash3)
hashUint64 := xxh3.HashString(input + salt)
fmt.Printf("Base16: %x\n", hashUint64) // 通常の16進数表現
// big.Int型経由で任意進数に変換
var i big.Int
i.SetUint64(hashUint64)
fmt.Println("Base16:", i.Text(16)) // 16進数表現
fmt.Println("Base62:", i.Text(62)) // 62進数表現
}
// Salt はランダムな数値の文字列を返します。
func Salt() string {
//nolint:gosec // not for cryptographical use
return fmt.Sprintf("%d", rand.Int())
}
// 下記出力はランダムな salt 値により毎回変わります
// Output:
// Base16: 4b4e7561403341fc
// Base16: 4b4e7561403341fc
// Base62: 6sQZ9urnbbK
- オンラインで動作をみる @ Go Playground
- xxHash3 @ xxhash.com
【注意】例えば、元データのハッシュ値が 0000004b56abd80ec...
のように偶然にも 0
で始まる場合、進数計算(変換)時にトリムされて 4b56abd80ec...
と短くなってしまいます。ハッシュ値を固定長の文字列として Base62 で欲しい場合は注意します。例えば、n 桁の固定長で欲しい場合は「同数の連続 0
を頭に付け加えたのち、末尾 n 桁を利用する」などです。fmt.Printf("%032x", myValue)
の利用を検討するといいでしょう。
参考文献
- hashアルゴリズムとハッシュ値の長さ一覧 @ Qiita
- Int.Text() | Go v1.17.8 @ pkg.go.dev
- Multibase format | multiformats @ GitHub
- Multihash format | Multiformats @ multiformats.io