{"id":13878121,"url":"https://github.com/stevegeek/encoded_id","last_synced_at":"2025-04-16T03:48:42.958Z","repository":{"id":61236429,"uuid":"341448410","full_name":"stevegeek/encoded_id","owner":"stevegeek","description":"Turn numeric or hex IDs into reversible and human friendly obfuscated strings","archived":false,"fork":false,"pushed_at":"2025-04-09T13:29:29.000Z","size":197,"stargazers_count":9,"open_issues_count":2,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-09T13:44:05.761Z","etag":null,"topics":["friendlyid","hacktoberfest","obfuscate-strings","ruby"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/stevegeek.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-02-23T06:10:32.000Z","updated_at":"2025-04-09T13:28:29.000Z","dependencies_parsed_at":"2023-02-02T14:32:29.961Z","dependency_job_id":"bce86314-7cae-467e-87e6-ec240514c7d6","html_url":"https://github.com/stevegeek/encoded_id","commit_stats":{"total_commits":123,"total_committers":3,"mean_commits":41.0,"dds":0.05691056910569103,"last_synced_commit":"4f8af2f0c64e08ccbe446ad3b1e55b15d30efe38"},"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stevegeek%2Fencoded_id","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stevegeek%2Fencoded_id/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stevegeek%2Fencoded_id/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stevegeek%2Fencoded_id/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stevegeek","download_url":"https://codeload.github.com/stevegeek/encoded_id/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249192432,"owners_count":21227787,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["friendlyid","hacktoberfest","obfuscate-strings","ruby"],"created_at":"2024-08-06T08:01:40.417Z","updated_at":"2025-04-16T03:48:42.927Z","avatar_url":"https://github.com/stevegeek.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"# EncodedId\n\nEncode numerical or hex IDs into obfuscated strings that can be used in URLs. \n\n```ruby\ncoder = ::EncodedId::ReversibleId.new(salt: my_salt)\ncoder.encode(123)\n# =\u003e \"p5w9-z27j\"\ncoder.encode_hex(\"10f8c\")\n# =\u003e \"w72a-y0az\"\n```\n\nThe obfuscated strings are reversible (they decode them back into the original IDs). \n\nAlso supports encoding multiple IDs at once.\n\n```ruby\nmy_salt = \"salt!\"\ncoder = ::EncodedId::ReversibleId.new(salt: my_salt)\n\n# One of more values can be encoded\ncoder.encode([78, 45])\n# =\u003e \"z2j7-0dmw\"\n\n# The encoded string can then be reversed back into the original IDs\ncoder.decode(\"z2j7-0dmw\")\n# =\u003e [78, 45]\n\n# The decoder can be resilient to easily confused characters\ncoder.decode(\"z2j7-Odmw\") # (note the capital 'o' instead of zero)\n# =\u003e [78, 45]\n```\n\n## Features\n\n* 🔄 encoded IDs are reversible (uses Hashids, the old site is here https://github.com/hashids/hashids.github.io))\n* 👥 supports multiple IDs encoded in one encoded string (eg `7aq6-0zqw` decodes to `[78, 45]`)\n* 🔡 supports custom alphabets for the encoded string (at least 16 characters needed)\n  - by default uses a variation of the Crockford reduced character set (https://www.crockford.com/base32.html)\n  - 👓 easily confused characters (eg `i` and `j`, `0` and `O`, `1` and `I` etc) are mapped to counterpart characters, to help\n      avoid common readability mistakes when reading/sharing\n  - 🤬 build in profanity limitation\n* 🤓 encoded string can be split into groups of letters to improve human-readability\n  - eg `nft9hr834htu` as `nft9-hr83-4htu`\n* 🥽 supports limits on length to prevent resource exhaustion on encoding and decoding\n* configured with sensible defaults\n\nI aim for 100% test coverage and have fuzz tested quite extensively. But please report any issues!\n\n### Experimental\n\n* support for encoding of hex strings (eg UUIDs), including multiple IDs encoded in one string\n\n### Performance and benchmarking\n\nThis gem uses a custom HashId implementation that is significantly faster and more memory-efficient than the original `hashids` gem. \n\nFor detailed benchmarks and performance metrics, see the [Custom HashId Implementation](#custom-hashid-implementation) section at the end of this README.\n\n\n### Rails support `encoded_id-rails`\n\nTo use with **Rails** check out the [`encoded_id-rails`](https://github.com/stevegeek/encoded_id-rails) gem.\n\n```ruby\nclass User \u003c ApplicationRecord\n  include EncodedId::WithEncodedId\nend\n\nUser.find_by_encoded_id(\"p5w9-z27j\")\n# =\u003e #\u003cUser id: 78\u003e\n```\n\n### Note on security of encoded IDs (hashids)\n\n**Encoded IDs are not secure**. It maybe possible to reverse them via brute-force. They are meant to be used in URLs as \nan obfuscation. The algorithm is not an encryption.\n\nPlease read more on https://hashids.org/\n\n## Compare to alternate Gems\n\n- https://github.com/excid3/prefixed_ids\n- https://github.com/namick/obfuscate_id\n- https://github.com/norman/friendly_id\n- https://github.com/SPBTV/with_uid\n\n## Installation\n\nInstall the gem and add to the application's Gemfile by executing:\n\n    $ bundle add encoded_id\n\nIf bundler is not being used to manage dependencies, install the gem by executing:\n\n    $ gem install encoded_id\n\n## `EncodedId::ReversibleId.new`\n\nTo create an instance of the encoder/decoder use `.new` with the `salt` option:\n\n```ruby\ncoder = EncodedId::ReversibleId.new(\n  # The salt is required\n  salt: ...,\n  # And then the following options are optional\n  length: 8, \n  split_at: 4, \n  split_with: \"-\",\n  alphabet: EncodedId::Alphabet.modified_crockford,\n  hex_digit_encoding_group_size: 4 # Experimental\n)\n```\n\nNote the `salt` value is required and should be a string of some length (greater than 3 characters). This is used to generate the encoded string. \n\nIt will need to be the same value when decoding the string back into the original ID. If the salt is changed, the encoded\nstrings will be different and possibly decode to different IDs.\n\n### Options\n\nThe encoded ID is configurable. The following can be changed:\n\n- the length, eg 8 characters for `p5w9-z27j`\n- the alphabet used in it (min 16 characters)\n- and the number of characters to split the output into and the separator\n\n### `length`\n\n`length`: the minimum length of the encoded string. The default is 8 characters.\n\nThe actual length of the encoded string can be longer if the inputs cannot be represented in the minimum length.\n\n### `max_length`\n\n`max_length`: the maximum length of the encoded string. The default is 128 characters.\n\nThe maximum length represents both the longest encoded string that will be generated and also a limit on\nthe maximum input length that will be decoded. If the encoded string exceeds `max_length` then a\n`EncodedIdLengthError` will be raised. If the input exceeds `max_length` then a `InvalidInputError` will\nbe raised. If `max_length` is set to `nil`, then no validation, even using the default will be performed.\n\n### `max_inputs_per_id`\n\n`max_inputs_per_id`: the maximum amount of IDs to be encoded together. The default is 32.\n\nThis maximum amount is used to limit:\n- the length of array input passed to `encode`\n- the length of integer array encoded in hex string(s) passed to `encode_hex` function.\n`InvalidInputError` wil be raised when array longer than `max_inputs_per_id` is provided.\n\n### `alphabet`\n\n`alphabet`: the alphabet used in the encoded string. By default, it uses a variation of the Crockford reduced character set (https://www.crockford.com/base32.html).\n\n`alphabet` must be an instance of `EncodedId::Alphabet`.\n\nThe default alphabet is `EncodedId::Alphabet.modified_crockford`.\n\nTo create a new alphabet, use `EncodedId::Alphabet.new`:\n\n```ruby\nalphabet = EncodedId::Alphabet.new(\"0123456789abcdef\")\n```\n\n`EncodedId::Alphabet.new(characters, equivalences)`\n\n**characters**\n\n`characters`: the characters of the alphabet. Can be a string or array of strings.\n\nNote that the `characters` of the alphabet must be at least 16 _unique_ characters long and must not contain any\nwhitespace characters.\n\n\n```ruby\nalphabet = EncodedId::Alphabet.new(\"ςερτυθιοπλκξηγφδσαζχψωβνμ\")\ncoder = ::EncodedId::ReversibleId.new(salt: my_salt, alphabet: alphabet)\ncoder.encode(123)\n# =\u003e \"πφλχ-ψησω\"\n```\n\nNote that larger alphabets can result in shorter encoded strings (but remember that `length` specifies the minimum length\nof the encoded string).\n\n**equivalences**\n\nYou can optionally pass an appropriate character `equivalences` mapping. This is used to map easily confused characters \nto their counterpart. \n\n`equivalences`: a hash of characters keys, with their equivalent alphabet character mapped to in the values. \n\nNote that the characters to be mapped:\n- must not be in the alphabet, \n- must map to a character that is in the alphabet.\n\n`nil` is the default value which means no equivalences are used.\n\n```ruby\nalphabet = EncodedId::Alphabet.new(\"!@#$%^\u0026*()+-={}\", {\"_\" =\u003e \"-\"})\ncoder = ::EncodedId::ReversibleId.new(salt: my_salt, alphabet: alphabet)\ncoder.encode(123)\n# =\u003e \"}*^(-^}*=\"\n```\n\n### `split_at` and `split_with`\n\nFor readability, the encoded string can be split into groups of characters. \n\n`split_at`: specifies the number of characters to split the encoded string into. Defaults to 4. \n\n`split_with`: specifies the separator to use between the groups. Default is `-`.\n\nSet either to `nil` to disable splitting.\n\n### `hex_digit_encoding_group_size`\n\n**Experimental**\n\n`hex_digit_encoding_group_size`: specifies the number of hex digits to encode in a group. Defaults to 4. Can be\nbetween 1 and 32.\n\nCan be used to control the size of the encoded string when encoding hex strings. Larger values will result in shorter\nencoded strings for long inputs, and shorter values will result in shorter encoded strings for smaller inputs.\n\nBut note that bigger values will also result in larger markers that separate the groups so could end up increasing\nthe encoded string length undesirably.\n\nSee below section `Using with hex strings` for more details.\n\n## `EncodedId::ReversibleId#encode`\n\n`#encode(id)`: where `id` is an integer or array of integers to encode.\n\n```ruby\ncoder.encode(123)\n# =\u003e \"p5w9-z27j\"\n\n# One of more values can be encoded\ncoder.encode([78, 45])\n# =\u003e \"z2j7-0dmw\"\n```\n\n## `EncodedId::ReversibleId#decode`\n\n`#decode(encoded_id)`: where `encoded_id` is a string to decode.\n\n```ruby\n# The encoded string can then be reversed back into the original IDs\ncoder.decode(\"z2j7-0dmw\")\n# =\u003e [78, 45]\n```\n\n## Using with hex strings\n\n**Experimental** (subject to incompatible changes in future versions)\n\n```ruby\n# Hex input strings are also supported\ncoder.encode_hex(\"10f8c\")\n# =\u003e \"w72a-y0az\"\n```\n\nWhen encoding hex strings, the input is split into groups of hex digits, and each group is encoded separately as its\ninteger equivalent. In other words the input is converted into an array of integers and encoded as normal with the\n`encode` method.\n\neg with `hex_digit_encoding_group_size=1` and inpu `f1`, is split into `f` and `1`, and then encoded as `15` and `1` \nrespectively, ie `encode` is called with `[15, 1]`.\n\nTo encode multiple hex inputs the encoded string contains markers to indicate the start of a new hex input. This\nmarker is equal to an integer value which is 1 larger than the maximum value the hex digit encoding group size can\nrepresent (ie it is `2^(hex_digit_encoding_group_size * 4)`).\n\nSo for a hex digit encoding group size of 4 (ie group max value is `0xFFFF`), the marker is `65536`\n\nFor example with `hex_digit_encoding_group_size=1` for the inputs `f1` and `e2` encoded together, the \nactual encoded integer array is `[15, 1, 16, 14, 2]`.\n\n### `EncodedId::ReversibleId#encode_hex`\n\n`encode_hex(hex_string)` , where `hex_string` is a string of hex digits or an array of hex strings.\n\n```ruby\n# UUIDs will result in long output strings...\ncoder.encode_hex(\"9a566b8b-8618-42ab-8db7-a5a0276401fd\")\n# =\u003e \"5jjy-c8d9-hxp2-qsve-rgh9-rxnt-7nb5-tve7-bf84-vr\"\n# \n# but there is an option to help reduce this... \ncoder = ::EncodedId::ReversibleId.new(salt: my_salt, hex_digit_encoding_group_size: 32)\ncoder.encode_hex(\"9a566b8b-8618-42ab-8db7-a5a0276401fd\")\n# =\u003e \"vr7m-qra8-m5y6-dkgj-5rqr-q44e-gp4a-52\"\n```\n\n### `EncodedId::ReversibleId#decode_hex`\n\n`decode_hex(encoded_id)` , where the output is an array of hex strings.\n\n```ruby\ncoder.decode_hex(\"5jjy-c8d9-hxp2-qsve-rgh9-rxnt-7nb5-tve7-bf84-vr\")\n# =\u003e [\"9a566b8b-8618-42ab-8db7-a5a0276401fd\"]\n```\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies. \n\nRun `bin/console` for an interactive prompt that will allow you to experiment.\n\n### Running tests\n\nRun `bundle exec rake test` to run the tests.\n\n### Type check\n\nFirst install RBS dependencies:\n\n```bash\nrbs collection install\n```\n\nThen run:\n\n```bash\nsteep check\n```\n\n## See also\n\n- https://hashids.org\n- https://www.crockford.com/base32.html\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/stevegeek/encoded_id.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n\n## Custom HashId Implementation\n\nInternally, `encoded_id` uses its own HashId implementation (`EncodedId::HashId`) instead of the original `hashids` gem. This custom implementation was created to improve both performance and memory usage. \n\nRecent benchmarks show significant improvements:\n\n### Performance Comparison\n\n```\n| Test                      | Hashids (i/s) | EncodedId::HashId (i/s) | Speedup |\n| ------------------------- | ------------ | --------------------- | ------- |\n| #encode - 1 ID            |  131,000.979 |           197,586.231 |   1.51x |\n| #decode - 1 ID            |   65,791.334 |            92,425.571 |   1.40x |\n| #encode - 10 IDs          |   13,773.355 |            20,669.715 |   1.50x |\n| #decode - 10 IDs          |    6,911.872 |             9,990.078 |   1.45x |\n| #encode w YJIT - 1 ID     |  265,764.969 |           877,551.362 |   3.30x |\n| #decode w YJIT - 1 ID     |  130,154.837 |           348,000.817 |   2.67x |\n| #encode w YJIT - 10 IDs   |   27,966.457 |           100,461.237 |   3.59x |\n| #decode w YJIT - 10 IDs   |   14,187.346 |            43,974.011 |   3.10x |\n| #encode w YJIT - 1000 IDs |      268.140 |             1,077.855 |   4.02x |\n| #decode w YJIT - 1000 IDs |      136.217 |               464.579 |   3.41x |\n```\n\nWith YJIT enabled, the performance improvements are even more significant, with up to 4x faster operation for large inputs.\n\n### Memory Usage Comparison\n\n```\n| Test                | Implementation   | Allocated Memory | Allocated Objects | Memory Reduction |\n| ------------------- | ---------------- | ---------------- | ----------------- | ---------------- |\n| encode small input  | Hashids          |          7.28 KB |               120 |                - |\n|                     | EncodedId::HashId |            920 B |                 6 |           87.66% |\n| encode large input  | Hashids          |        403.36 KB |              5998 |                - |\n|                     | EncodedId::HashId |          8.36 KB |               104 |           97.93% |\n| decode large input  | Hashids          |        366.88 KB |              5761 |                - |\n|                     | EncodedId::HashId |         14.63 KB |               264 |           96.01% |\n```\n\nThe memory usage improvements are dramatic, with up to 98% reduction in memory allocation for large inputs.\n\nRun `bin/are_we_fast_yet` and `bin/memory_profile` in your environment to see the current performance difference.\n\n## keywords\nhash ID, friendly ID, obfuscate ID, rails, ActiveRecord, model, slug, vanity URL, friendly URL\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstevegeek%2Fencoded_id","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstevegeek%2Fencoded_id","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstevegeek%2Fencoded_id/lists"}