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 }