2
0

【Golang】Base62 に標準ライブラリで変換する【Base16 → Base62 のハッシュ値取得】

Last updated at Posted at 2022-03-24

Go 言語(以下 Golang)で、数値を Base62 でエンコードしたい。例えばハッシュ値や、バイナリ([]byte)データなど。

そうです。Base64 ではなく、Base62・・ です。

しかし、「"golang" base62 変換」でググっても、外部ライブラリを使う方法ばかりだったので自分のググラビリティとして。

  • Base62 @ Wikipedia
    • エンコードに使われる文字: [0-9A-Za-z]0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz の 62 文字)

TL; DR (今北産業)

  1. Go 標準モジュールの math/big パッケージを使う。

  2. big.Int 型には Text(n) 関数があり n 進数指定できる。
    62 進数の場合は、10 進数の 0〜9 --> 0-910〜35 --> a-z36〜61 --> A-Z を使い、合計 62 文字で Int を表現する。
    バイト・スライス([]byte)を n 進数に変換したい場合は TS; DR 参照。

  3. マスター、効くやつ動くものをくれ。

    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
    }
    
    文字列を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
    }
    
    

TS; DR マスター、もっと強いのくれ

ハッシュ値の頭 n 桁を文字列で DB のキーとして扱いたいのですが、HEX でなく Base62 で欲しかったのです。

つまり、ハッシュ値を Base16(16進数)でなく Base62(62進数)の文字列にして圧縮させることで、短くさせつつもコリジョンハッシュ値の衝突の可能性をなるべく和らげたいのです。

数値的にはエンコードを変えたところでコリジョン率は同じですが、文字列でデータをやりとりする場合、同じ文字数なら情報の多い方がいいからです。

xxHash3 アルゴリズムのハッシュ値を16進数&62進数で表示する例
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

【注意】例えば、元データのハッシュ値が 0000004b56abd80ec... のように偶然にも 0 で始まる場合、進数計算(変換)時にトリムされて 4b56abd80ec... と短くなってしまいます。ハッシュ値を固定長の文字列として Base62 で欲しい場合は注意します。例えば、n 桁の固定長で欲しい場合は「同数の連続 0 を頭に付け加えたのち、末尾 n 桁を利用する」などです。fmt.Printf("%032x", myValue) の利用を検討するといいでしょう。

参考文献

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0