github.com/johnnyeven/libtools@v0.0.0-20191126065708-61829c1adf46/crypto/go-hashids/hashids.go (about)

     1  // Go implementation of http://www.hashids.org under MIT license
     2  // Generates hashes from an array of integers, eg. for YouTube like hashes
     3  // Setup: go get github.com/speps/go-hashids
     4  // Original implementations by Ivan Akimov at https://github.com/ivanakimov
     5  // Thanks to Rémy Oudompheng and Peter Hellberg for code review and fixes
     6  
     7  package hashids
     8  
     9  import (
    10  	"errors"
    11  	"fmt"
    12  	"math"
    13  )
    14  
    15  const (
    16  	// Version is the version number of the library
    17  	Version string = "1.0.0"
    18  
    19  	// DefaultAlphabet is the default alphabet used by go-hashids
    20  	DefaultAlphabet string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
    21  
    22  	minAlphabetLength int     = 16
    23  	sepDiv            float64 = 3.5
    24  	guardDiv          float64 = 12.0
    25  )
    26  
    27  var sepsOriginal = []rune("cfhistuCFHISTU")
    28  
    29  // HashID contains everything needed to encode/decode hashids
    30  type HashID struct {
    31  	alphabet  []rune
    32  	minLength int
    33  	salt      []rune
    34  	seps      []rune
    35  	guards    []rune
    36  }
    37  
    38  // HashIDData contains the information needed to generate hashids
    39  type HashIDData struct {
    40  	// Alphabet is the alphabet used to generate new ids
    41  	Alphabet string
    42  
    43  	// MinLength is the minimum length of a generated id
    44  	MinLength int
    45  
    46  	// Salt is the secret used to make the generated id harder to guess
    47  	Salt string
    48  }
    49  
    50  // NewData creates a new HashIDData with the DefaultAlphabet already set.
    51  func NewData() *HashIDData {
    52  	return &HashIDData{Alphabet: DefaultAlphabet}
    53  }
    54  
    55  // New creates a new HashID
    56  func New() *HashID {
    57  	return NewWithData(NewData())
    58  }
    59  
    60  // NewWithData creates a new HashID with the provided HashIDData
    61  func NewWithData(data *HashIDData) *HashID {
    62  	if len(data.Alphabet) < minAlphabetLength {
    63  		panic(fmt.Errorf("alphabet must contain at least %d characters", minAlphabetLength))
    64  	}
    65  	// Check if all characters are unique in Alphabet
    66  	uniqueCheck := make(map[rune]bool, len(data.Alphabet))
    67  	for _, a := range data.Alphabet {
    68  		if _, found := uniqueCheck[a]; found {
    69  			panic(errors.New("duplicate character in alphabet"))
    70  		}
    71  		uniqueCheck[a] = true
    72  	}
    73  
    74  	alphabet := []rune(data.Alphabet)
    75  	salt := []rune(data.Salt)
    76  
    77  	seps := make([]rune, len(sepsOriginal))
    78  	copy(seps, sepsOriginal)
    79  
    80  	// seps should contain only characters present in alphabet; alphabet should not contains seps
    81  	for i := 0; i < len(seps); i++ {
    82  		foundIndex := -1
    83  		for j, a := range alphabet {
    84  			if a == seps[i] {
    85  				foundIndex = j
    86  				break
    87  			}
    88  		}
    89  		if foundIndex == -1 {
    90  			seps = append(seps[:i], seps[i+1:]...)
    91  			i--
    92  		} else {
    93  			alphabet = append(alphabet[:foundIndex], alphabet[foundIndex+1:]...)
    94  		}
    95  	}
    96  	seps = consistentShuffle(seps, salt)
    97  
    98  	if len(seps) == 0 || float64(len(alphabet))/float64(len(seps)) > sepDiv {
    99  		sepsLength := int(math.Ceil(float64(len(alphabet)) / sepDiv))
   100  		if sepsLength == 1 {
   101  			sepsLength++
   102  		}
   103  		if sepsLength > len(seps) {
   104  			diff := sepsLength - len(seps)
   105  			seps = append(seps, alphabet[:diff]...)
   106  			alphabet = alphabet[diff:]
   107  		} else {
   108  			seps = seps[:sepsLength]
   109  		}
   110  	}
   111  	alphabet = consistentShuffle(alphabet, salt)
   112  
   113  	guardCount := int(math.Ceil(float64(len(alphabet)) / guardDiv))
   114  	var guards []rune
   115  	if len(alphabet) < 3 {
   116  		guards = seps[:guardCount]
   117  		seps = seps[guardCount:]
   118  	} else {
   119  		guards = alphabet[:guardCount]
   120  		alphabet = alphabet[guardCount:]
   121  	}
   122  
   123  	return &HashID{
   124  		alphabet:  alphabet,
   125  		minLength: data.MinLength,
   126  		salt:      salt,
   127  		seps:      seps,
   128  		guards:    guards,
   129  	}
   130  }
   131  
   132  // Encode hashes an array of int to a string containing at least MinLength characters taken from the Alphabet.
   133  // Use Decode using the same Alphabet and Salt to get back the array of int.
   134  func (h *HashID) Encode(numbers []int) (string, error) {
   135  	numbers64 := make([]int64, 0, len(numbers))
   136  	for _, id := range numbers {
   137  		numbers64 = append(numbers64, int64(id))
   138  	}
   139  	return h.EncodeInt64(numbers64)
   140  }
   141  
   142  // EncodeInt64 hashes an array of int64 to a string containing at least MinLength characters taken from the Alphabet.
   143  // Use DecodeInt64 using the same Alphabet and Salt to get back the array of int64.
   144  func (h *HashID) EncodeInt64(numbers []int64) (string, error) {
   145  	if len(numbers) == 0 {
   146  		return "", errors.New("encoding empty array of numbers makes no sense")
   147  	}
   148  	for _, n := range numbers {
   149  		if n < 0 {
   150  			return "", errors.New("negative number not supported")
   151  		}
   152  	}
   153  
   154  	alphabet := make([]rune, len(h.alphabet))
   155  	copy(alphabet, h.alphabet)
   156  
   157  	numbersHash := int64(0)
   158  	for i, n := range numbers {
   159  		numbersHash += (n % int64(i+100))
   160  	}
   161  
   162  	result := make([]rune, 0, h.minLength)
   163  	lottery := alphabet[numbersHash%int64(len(alphabet))]
   164  	result = append(result, lottery)
   165  
   166  	for i, n := range numbers {
   167  		buffer := append([]rune{lottery}, append(h.salt, alphabet...)...)
   168  		alphabet = consistentShuffle(alphabet, buffer[:len(alphabet)])
   169  		hash := hash(n, alphabet)
   170  		result = append(result, hash...)
   171  
   172  		if i+1 < len(numbers) {
   173  			n %= int64(hash[0]) + int64(i)
   174  			result = append(result, h.seps[n%int64(len(h.seps))])
   175  		}
   176  	}
   177  
   178  	if len(result) < h.minLength {
   179  		guardIndex := (numbersHash + int64(result[0])) % int64(len(h.guards))
   180  		result = append([]rune{h.guards[guardIndex]}, result...)
   181  
   182  		if len(result) < h.minLength {
   183  			guardIndex = (numbersHash + int64(result[2])) % int64(len(h.guards))
   184  			result = append(result, h.guards[guardIndex])
   185  		}
   186  	}
   187  
   188  	halfLength := len(alphabet) / 2
   189  	for len(result) < h.minLength {
   190  		alphabet = consistentShuffle(alphabet, alphabet)
   191  		result = append(alphabet[halfLength:], append(result, alphabet[:halfLength]...)...)
   192  		excess := len(result) - h.minLength
   193  		if excess > 0 {
   194  			result = result[excess/2 : excess/2+h.minLength]
   195  		}
   196  	}
   197  
   198  	return string(result), nil
   199  }
   200  
   201  // DEPRECATED: Use DecodeWithError instead
   202  // Decode unhashes the string passed to an array of int.
   203  // It is symmetric with Encode if the Alphabet and Salt are the same ones which were used to hash.
   204  // MinLength has no effect on Decode.
   205  func (h *HashID) Decode(hash string) []int {
   206  	result, err := h.DecodeWithError(hash)
   207  	if err != nil {
   208  		panic(err)
   209  	}
   210  	return result
   211  }
   212  
   213  // Decode unhashes the string passed to an array of int.
   214  // It is symmetric with Encode if the Alphabet and Salt are the same ones which were used to hash.
   215  // MinLength has no effect on Decode.
   216  func (h *HashID) DecodeWithError(hash string) ([]int, error) {
   217  	result64, err := h.DecodeInt64WithError(hash)
   218  	if err != nil {
   219  		return nil, err
   220  	}
   221  	result := make([]int, 0, len(result64))
   222  	for _, id := range result64 {
   223  		result = append(result, int(id))
   224  	}
   225  	return result, nil
   226  }
   227  
   228  // DEPRECATED: Use DecodeInt64WithError instead
   229  // DecodeInt64 unhashes the string passed to an array of int64.
   230  // It is symmetric with EncodeInt64 if the Alphabet and Salt are the same ones which were used to hash.
   231  // MinLength has no effect on DecodeInt64.
   232  func (h *HashID) DecodeInt64(hash string) []int64 {
   233  	result, err := h.DecodeInt64WithError(hash)
   234  	if err != nil {
   235  		panic(err)
   236  	}
   237  	return result
   238  }
   239  
   240  // DecodeInt64 unhashes the string passed to an array of int64.
   241  // It is symmetric with EncodeInt64 if the Alphabet and Salt are the same ones which were used to hash.
   242  // MinLength has no effect on DecodeInt64.
   243  func (h *HashID) DecodeInt64WithError(hash string) ([]int64, error) {
   244  	hashes := splitRunes([]rune(hash), h.guards)
   245  	hashIndex := 0
   246  	if len(hashes) == 2 || len(hashes) == 3 {
   247  		hashIndex = 1
   248  	}
   249  
   250  	result := make([]int64, 0)
   251  
   252  	hashBreakdown := hashes[hashIndex]
   253  	if len(hashBreakdown) > 0 {
   254  		lottery := hashBreakdown[0]
   255  		hashBreakdown = hashBreakdown[1:]
   256  		hashes = splitRunes(hashBreakdown, h.seps)
   257  		alphabet := []rune(h.alphabet)
   258  		for _, subHash := range hashes {
   259  			buffer := append([]rune{lottery}, append(h.salt, alphabet...)...)
   260  			alphabet = consistentShuffle(alphabet, buffer[:len(alphabet)])
   261  			number, err := unhash(subHash, alphabet)
   262  			if err != nil {
   263  				return nil, err
   264  			}
   265  			result = append(result, number)
   266  		}
   267  	}
   268  
   269  	return result, nil
   270  }
   271  
   272  func splitRunes(input, seps []rune) [][]rune {
   273  	splitIndices := make([]int, 0)
   274  	for i, inputRune := range input {
   275  		for _, sepsRune := range seps {
   276  			if inputRune == sepsRune {
   277  				splitIndices = append(splitIndices, i)
   278  			}
   279  		}
   280  	}
   281  
   282  	result := make([][]rune, 0, len(splitIndices)+1)
   283  	inputLeft := input[:]
   284  	for _, splitIndex := range splitIndices {
   285  		splitIndex -= len(input) - len(inputLeft)
   286  		subInput := make([]rune, splitIndex)
   287  		copy(subInput, inputLeft[:splitIndex])
   288  		result = append(result, subInput)
   289  		inputLeft = inputLeft[splitIndex+1:]
   290  	}
   291  	result = append(result, inputLeft)
   292  
   293  	return result
   294  }
   295  
   296  func hash(input int64, alphabet []rune) []rune {
   297  	result := make([]rune, 0)
   298  	for {
   299  		r := alphabet[input%int64(len(alphabet))]
   300  		result = append(result, r)
   301  		input /= int64(len(alphabet))
   302  		if input == 0 {
   303  			break
   304  		}
   305  	}
   306  	reversed := make([]rune, len(result))
   307  	for i, r := range result {
   308  		reversed[len(result)-i-1] = r
   309  	}
   310  	return reversed
   311  }
   312  
   313  func unhash(input, alphabet []rune) (int64, error) {
   314  	result := int64(0)
   315  	for _, inputRune := range input {
   316  		alphabetPos := -1
   317  		for pos, alphabetRune := range alphabet {
   318  			if inputRune == alphabetRune {
   319  				alphabetPos = pos
   320  				break
   321  			}
   322  		}
   323  		if alphabetPos == -1 {
   324  			return 0, errors.New("alphabet used for hash was different")
   325  		}
   326  
   327  		result = result*int64(len(alphabet)) + int64(alphabetPos)
   328  	}
   329  	return result, nil
   330  }
   331  
   332  func consistentShuffle(alphabet, salt []rune) []rune {
   333  	if len(salt) == 0 {
   334  		return alphabet
   335  	}
   336  
   337  	result := make([]rune, len(alphabet))
   338  	copy(result, alphabet)
   339  	for i, v, p := len(result)-1, 0, 0; i > 0; i-- {
   340  		p += int(salt[v])
   341  		j := (int(salt[v]) + v + p) % i
   342  		result[i], result[j] = result[j], result[i]
   343  		v = (v + 1) % len(salt)
   344  	}
   345  
   346  	return result
   347  }