Hashids algorithm v1.0.0 implementation in Java

Related tags

Utility hashids-java
Overview

Hashids.java Version Stories in Ready CircleCI

A small Java class to generate YouTube-like hashes from one or many numbers.

Ported from javascript hashids.js by Ivan Akimov

What is it?

hashids (Hash ID's) creates short, unique, decodable hashes from unsigned (long) integers.

It was designed for websites to use in URL shortening, tracking stuff, or making pages private (or at least unguessable).

This algorithm tries to satisfy the following requirements:

  1. Hashes must be unique and decodable.
  2. They should be able to contain more than one integer (so you can use them in complex or clustered systems).
  3. You should be able to specify minimum hash length.
  4. Hashes should not contain basic English curse words (since they are meant to appear in public places - like the URL).

Instead of showing items as 1, 2, or 3, you could show them as U6dc, u87U, and HMou. You don't have to store these hashes in the database, but can encode + decode on the fly.

All (long) integers need to be greater than or equal to zero.

Usage

Add the dependency

hashids is available in Maven Central. If you are using Maven, add the following dependency to your pom.xml's dependencies:

<dependency>
  <groupId>org.hashids</groupId>
  <artifactId>hashids</artifactId>
  <version>1.0.3</version>
</dependency>

Alternatively, if you use gradle or are on android, add the following to your app's build.gradle file under dependencies:

compile 'org.hashids:hashids:1.0.3'

Import the package

import org.hashids;

Encoding one number

You can pass a unique salt value so your hashes differ from everyone else's. I use "this is my salt" as an example.

Hashids hashids = new Hashids("this is my salt");
String hash = hashids.encode(12345L);

hash is now going to be:

NkK9

Decoding

Notice during decoding, same salt value is used:

Hashids hashids = new Hashids("this is my salt");
long[] numbers = hashids.decode("NkK9");

numbers is now going to be:

[ 12345 ]

Decoding with different salt

Decoding will not work if salt is changed:

Hashids hashids = new Hashids("this is my pepper");
long[] numbers = hashids.decode("NkK9");

numbers is now going to be:

[]

Encoding several numbers

Hashids hashids = new Hashids("this is my salt");
String hash = hashids.encode(683L, 94108L, 123L, 5L);

hash is now going to be:

aBMswoO2UB3Sj

Decoding is done the same way

Hashids hashids = new Hashids("this is my salt");
long[] numbers = hashids.decode("aBMswoO2UB3Sj");

numbers is now going to be:

[ 683, 94108, 123, 5 ]

Encoding and specifying minimum hash length

Here we encode integer 1, and set the minimum hash length to 8 (by default it's 0 -- meaning hashes will be the shortest possible length).

Hashids hashids = new Hashids("this is my salt", 8);
String hash = hashids.encode(1L);

hash is now going to be:

gB0NV05e

Decoding

Hashids hashids = new Hashids("this is my salt", 8);
long[] numbers = hashids.decode("gB0NV05e");

numbers is now going to be:

[ 1 ]

Specifying custom hash alphabet

Here we set the alphabet to consist of only six letters: "0123456789abcdef"

Hashids hashids = new Hashids("this is my salt", 0, "0123456789abcdef");
String hash = hashids.encode(1234567L);

hash is now going to be:

b332db5

Encoding and decoding "MongoDB" ids

In addition to encoding and decoding long values Hashids provides functionality for encoding and decoding ids in a hex notation such as object ids generated by MongoDB.

Hashids hashids = new Hashids("This is my salt");
String hash = hashids.encodeHex("507f1f77bcf86cd799439011"); // goMYDnAezwurPKWKKxL2
String objectId = hashids.decodeHex(hash); // 507f1f77bcf86cd799439011

Note that the algorithm used for encoding and decoding hex values is not compatible with the algorthm for encoding and decoding long values. That means that you cannot use decodeHex to extract a hex representation of a long id that was encoded with encode.

Randomness

The primary purpose of hashids is to obfuscate ids. It's not meant or tested to be used for security purposes or compression. Having said that, this algorithm does try to make these hashes unguessable and unpredictable:

Repeating numbers

Hashids hashids = new Hashids("this is my salt");
String hash = hashids.encode(5L, 5L, 5L, 5L);

You don't see any repeating patterns that might show there's 4 identical numbers in the hash:

1Wc8cwcE

Same with incremented numbers:

Hashids hashids = new Hashids("this is my salt");
String hash = hashids.encode(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L);

hash will be :

kRHnurhptKcjIDTWC3sx

Incrementing number hashes:

Hashids hashids = new Hashids("this is my salt");
String hash1 = hashids.encode(1L); /* NV */
String hash2 = hashids.encode(2L); /* 6m */
String hash3 = hashids.encode(3L); /* yD */
String hash4 = hashids.encode(4L); /* 2l */
String hash5 = hashids.encode(5L); /* rD */

Bad hashes

I wrote this class with the intent of placing these hashes in visible places - like the URL. If I create a unique hash for each user, it would be unfortunate if the hash ended up accidentally being a bad word. Imagine auto-creating a URL with hash for your user that looks like this - http://example.com/user/a**hole

Therefore, this algorithm tries to avoid generating most common English curse words with the default alphabet. This is done by never placing the following letters next to each other:

c, C, s, S, f, F, h, H, u, U, i, I, t, T

Limitations

The original and reference implementation is the JS (Hashids Website) version. JS number limitation is (2^53 - 1). Our java implementation uses Long, but is limited to the same limits JS is, for the sake of compatibility. If a bigger number is provided, an IllegalArgumentException will be thrown.

Contributors

Contact

Follow me C.C.(@fanweixiao), @IvanAkimov, @0x3333.

License

MIT License. See the LICENSE file.

Comments
  • Should we throw an exception for negative numbers as well?

    Should we throw an exception for negative numbers as well?

    When encoding a number the library throw an exception if the number is loo long (greater than MAX_NUMBER) but return an empty string for negative numbers. IMHO this is a little unexpected, using two different behaviours for the same thing, input validation.

    Since negative numbers are also illegal arguments, shouldn't it throw a IllegalArgumentException?

    bug 
    opened by gabrielhora 10
  • ArrayIndexOutOfBoundsException issues

    ArrayIndexOutOfBoundsException issues

    Both:

    Hashids hashids = new Hashids("this is my salt");
    long[] numbers = hashids.decode("0");
    

    and:

    Hashids hashids = new Hashids("this is my salt");
    long[] numbers = hashids.decode("test.txt");
    

    are causing exceptions in various places.

    I realize that they are not valid hashes, but the exception should either be declared or handled.

    Hope this helps.

    bug 
    opened by ethauvin 7
  • Relax MAX_NUMBER restriction

    Relax MAX_NUMBER restriction

    I understand the desire to be completely compatible with the Javascript implementation/limitiations, as explained in #47 and in the docs, but is it possible to provide a toggle that disables this functionality?

    This would ensure backward compatibility with Javascript's limitations, but would allow this library to make use of the full potential that the Java platform provides.

    Other implementations (such as Scala and .Net) don't impose these restrictions and allow the library to be used with the native implementation of the number.

    opened by iggymoran 6
  • Why hashids has MAX_NUMBER limitation

    Why hashids has MAX_NUMBER limitation

    Hello,

    I was looking into this great library for my project, but I quickly found undocumented limitation that this library doesn't support any number > 9007199254740992L. Could you please explain the reasoning behind this limitation?

    ids.encode(new Random().nextLong()

    Exception in thread "main" java.lang.IllegalArgumentException: number can not be greater than 9007199254740992L at org.hashids.Hashids.encode(Hashids.java:178) at io.remotable.experiments.ids.IdsExperiment.main(IdsExperiment.java:21)

    opened by opensso 6
  • ArrayIndexOutOfBoundsException

    ArrayIndexOutOfBoundsException

    From issue Issue 43 - kmandeville:

    I have found two other occurrences where I get ArrayIndexOutOfBoundsException from deep in the HashIds code. Hashids hashids = new Hashids("this is my salt"); long[] numbers = hashids.decode("[]");

    and

    Hashids hashids = new Hashids("this is my salt"); long[] numbers = hashids.decode("()");

    Like the original submitter above, these aren't valid hashes, but I'm running into these errors in my web app because someone COULD put these characters in as an ID in a URL accidentally or on purpose. My app is just passing in these characters directly into HashIds. I would expect something other than ArrayIndexOutOfBoundsExceptions.

    bug 
    opened by 0x3333 6
  • Add additonal prefix/suffix for salt version

    Add additonal prefix/suffix for salt version

    Since we are using a salt, it might be a good idea to have a version as part of the actual id. That will allow us to change the salt later if we need to. I'm not sure if this is out of scope for this library or maybe should be added as a best practice. What do you guys think?

    Cheers, Itay

    opened by iyahimovitz 6
  • array index out of bound bug

    array index out of bound bug

    if you call the encode(number) with a random large number. then numberHashInt += (numbers[i] % (i+100)) has a chance to return a negative number, in my case -35. the next calculation use alphabet.toCharArray()[numberHashInt % alphabet.length()] could thow an ArrayIndexOutOfBoundsException.

    bug 
    opened by maxiwu 5
  • Performance Issues

    Performance Issues

    I ran a quick stress test on this library and I'm seeing very poor and unpredictable performance. Generating 1,000,000 ids from the same Hashids instance (same salt) takes anywhere between 15 and 90 seconds on a 2.9GHz i7 CPU using 1.8 GB of RAM. There's also a strange stall somewhere between 30k and 60k in the test loop, after which the rest of the test runs quickly.

    Here's my test case:

        // generates a random 32 character string
        Hashids h = new Hashids(RandomStringUtils.random(32));
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
          h.encode(i);
        }
        long end = System.currentTimeMillis();
        long duration = end - start;
        System.out.println("time taken: " + duration/1000 + " seconds");
        System.out.println("hashes per second: " + 1000000/(duration/1000.0));
    
    invalid 
    opened by nmickuli 5
  • StringIndexOutOfBoundsException when decoding a guard and minHashLength >= 3

    StringIndexOutOfBoundsException when decoding a guard and minHashLength >= 3

    I got this exception when trying to decode some bad data. This test case illustrates the exception.

    public class TestClass
    {
        @Test
        public void test()
        {
            Hashids hasher = new Hashids("this is my salt", 3);
            long[] numbers = hasher.decode("A");
        }
    }
    

    And it throws this exception.

    java.lang.StringIndexOutOfBoundsException: String index out of range: 2
    	at java.lang.String.charAt(String.java:658)
    	at org.hashids.Hashids._encode(Hashids.java:292)
    	at org.hashids.Hashids._decode(Hashids.java:350)
    	at org.hashids.Hashids.decode(Hashids.java:194)
    	at TestClass.test(TestClass.java:15)
    

    Looks like any character that is in HashIds.guards will throw this exception. Other input like "asdf" or any other character not in HashIds.guards seem to work ok.

    Debugging, I noticed that on the call to _encode, usually an array with 0L is passed, but when one of those chars is used, the call is made with an empty array.

    duplicate 
    opened by meiao 4
  • different salt can give random number or crash

    different salt can give random number or crash

    Hi, When using different salt it should return empty array. But I recognized the behavior is quite unpredictable, there is a probability that it decodes to a wrong number instead or crashes with an ArrayIndexOutOfBoundsException.

    Here and example that will decode to a wrong number:

        @Test
        public void encodeAndDecodeTokenWithWrongKey() {
            Hashids a = new Hashids("supersecret",4);
            Hashids b = new Hashids("differentsupersecret",4);
    
            long sampleId = 123l;
            String token = a.encode(sampleId);
    
            long[] ids = b.decode(token); // []long id = { 81143 }
            assertEquals(0, ids.length);
        }
    

    assertion will fail, id[0] will be 81143

    Same test with sampleId = 37l will cause "ArrayIndexOutOfBoundsException"

    opened by chrisport 4
  • Confusion about supported integer range

    Confusion about supported integer range

    The doc says:

    decodable hashes from unsigned (long) integers

    but then it says

    All (long) integers need to be greater than or equal to zero

    And the implementation caps the max number to 2^53 (0-9,007,199,254,740,992)

    https://github.com/10cella/hashids-java/blob/162a263c8007652472bf1821a26d33190730f8a5/src/main/java/org/hashids/Hashids.java#L24


    • IF unsigned 64-bit integer was support, it would accept: 2^64 or 0 - 18,446,744,073,709,551,616 (1.8x10^19)
    • IF signed 64-bit integer was support from zero up, it would accept: 2^63 or 0 - 9,223,372,036,854,775,807 (9.2x10^18)

    You currently support 53 bit long integers - I can only assume that is a bug? Java's primitives are always signed, so you have about 2^63 values in the plus and minus range. The most logical range would be 2^63 if you only want to support from 0 up and not make the caller convert to unsigned longs?

    opened by patrickfav 3
  • Add configurable hash mod base

    Add configurable hash mod base

    When generating hashes that are separated by 100 the hash strings are very similar. This is unavoidable with this implementation and perhaps not a huge deal, but maybe it would be best if the periodicity were a number that is not so "tidy". This PR adds the ability to construct the class with a user-specified hash mod base. I personally would use a similarly large prime number, like 97. The strings are very similar for every 97 ids, but this is probably less likely to be noticed.

    Also increase JUnit 4 version to avoid CVE flagging

    opened by jchennales 0
  • How to solve IllegalArgumentException in encode.

    How to solve IllegalArgumentException in encode.

    In encode, we passed a greater number(like 44913000000003047) than fixed MAX_LIMIT(9007199254740992L). How to solve this case and handle any number to encode.

    opened by SivaKarthik2120 0
  • Added

    Added "separators" customization, NULL pre-checks, and Javadoc comment blocks to all constructors

    Added a new Hashids constructor to support customized "separators" plus a pre-check for NULL that prevents a NullException by defaulting to DEFAULT_SEPS (this behaviour is modeled after the "salt" pre-check for NULL).

    Added a pre-check for NULL for the custom "alphabet" that prevents a NullException by defaulting to DEFAULT_ALPHABET (this behaviour is also modeled after the "salt" pre-check for NULL).

    Added rudimentary comments for existing constructors for better Javadoc output. Added relevant details about the new constructor that supports custom "separators" (should this also be mentioned in the documentation on the web site?).

    opened by randolf 1
  • V2 Proposal

    V2 Proposal

    Version 2.0 Proposal

    This issue will be a discussion point to exchange ideas about version 2.0.

    Big picture

    The community is urging for a more java idiom version, and the current version can't handle these changes, that's why I'm proposing a new version, incorporating all the needs from the community in a broad discussion.

    What about v2?

    The new version must be compatible with original implementation as much as it can, without sacrificing java idiom. We will discuss how it will be implemented and the design considerations will be made here.

    @cazacugmihai Proposal

    • [ ] Use of char arrays and StringBuilder instead of Strings
    • [ ] Precompute and cache recurrent operations
    • [ ] Helper class (CharUtils) for working with char arrays

    @iggymoran Proposal

    • [] Relax MAX_NUMBER restriction

    @KangoV Proposal

    • [] BigInteger as input/output

    Reference implementation PR #53.

    question 
    opened by 0x3333 13
Releases(v1.0.2)
  • v1.0.2(May 29, 2017)

    Version 1.0.2 - May 29, 2017

    Issues fixed:

    • Issue #18 - Thread Safety information
    • Issue #20 - ArrayIndexOutOfBoundsException when decoding invalid token
    • Issue #23 - Fixed separators initialization
    • Issue #30 - Fixed negative numbers encoding
    • Issue #31 - Fixed large number encoding resulting in an ArrayIndexOutOfBoundsException
    • Issue #33 - Create a new release 1.0.2
    • Issue #37 - Performance improvements
    • Issue #43 - Fixed decode of invalid data resulting in an ArrayIndexOutOfBoundsException

    PR merged:

    • PR #25 - Fixed SonarJava squid:S2131 and squid:S2325
    • PR #26 - Started to use StringBuilder instead of adding Strings
    • PR #27 - Fixed identation and Issue #20
    • PR #28 - Performance improvements and code organization
    • PR #34 - Reimplemented unhash using Horner's method(Internal commit, PR closed)
    • PR #39 - Added Gradle specification
    • PR #40 - Fix typo in test names
    Source code(tar.gz)
    Source code(zip)
    hashids-1.0.2-javadoc.jar(25.00 KB)
    hashids-1.0.2-sources.jar(3.75 KB)
    hashids-1.0.2.jar(7.65 KB)
Owner
CELLA
熹乐科技 Uber the Computing
CELLA
Java rate limiting library based on token/leaky-bucket algorithm.

Java rate-limiting library based on token-bucket algorithm. Advantages of Bucket4j Implemented on top of ideas of well known algorithm, which are by d

Vladimir Bukhtoyarov 1.7k Jan 8, 2023
a pug implementation written in Java (formerly known as jade)

Attention: jade4j is now pug4j In alignment with the javascript template engine we renamed jade4j to pug4j. You will find it under https://github.com/

neuland - Büro für Informatik 700 Oct 16, 2022
Java lib for monitoring directories or individual files via java.nio.file.WatchService

ch.vorburger.fswatch Java lib for monitoring directories or individual files based on the java.nio.file.WatchService. Usage Get it from Maven Central

Michael Vorburger ⛑️ 21 Jan 7, 2022
Tencent Kona JDK11 is a no-cost, production-ready distribution of the Open Java Development Kit (OpenJDK), Long-Term Support(LTS) with quarterly updates. Tencent Kona JDK11 is certified as compatible with the Java SE standard.

Tencent Kona JDK11 Tencent Kona JDK11 is a no-cost, production-ready distribution of the Open Java Development Kit (OpenJDK), Long-Term Support(LTS) w

Tencent 268 Dec 16, 2022
This repository contains Java programs to become zero to hero in Java.

This repository contains Java programs to become zero to hero in Java. Data Structure programs topic wise are also present to learn data structure problem solving in Java. Programs related to each and every concep are present from easy to intermidiate level

Sahil Batra 15 Oct 9, 2022
An open-source Java library for Constraint Programming

Documentation, Support and Issues Contributing Download and installation Choco-solver is an open-source Java library for Constraint Programming. Curre

null 607 Jan 3, 2023
Java Constraint Programming solver

https://maven-badges.herokuapp.com/maven-central/org.jacop/jacop/badge.svg [] (https://maven-badges.herokuapp.com/maven-central/org.jacop/jacop/) JaCo

null 202 Dec 30, 2022
Java Constraint Solver to solve vehicle routing, employee rostering, task assignment, conference scheduling and other planning problems.

OptaPlanner www.optaplanner.org Looking for Quickstarts? OptaPlanner’s quickstarts have moved to optaplanner-quickstarts repository. Quick development

KIE (Drools, OptaPlanner and jBPM) 2.8k Jan 2, 2023
Alibaba Java Diagnostic Tool Arthas/Alibaba Java诊断利器Arthas

Arthas Arthas is a Java Diagnostic tool open sourced by Alibaba. Arthas allows developers to troubleshoot production issues for Java applications with

Alibaba 31.5k Jan 4, 2023
Object-Oriented Java primitives, as an alternative to Google Guava and Apache Commons

Project architect: @victornoel ATTENTION: We're still in a very early alpha version, the API may and will change frequently. Please, use it at your ow

Yegor Bugayenko 691 Dec 27, 2022
Dex : The Data Explorer -- A data visualization tool written in Java/Groovy/JavaFX capable of powerful ETL and publishing web visualizations.

Dex Dex : The data explorer is a powerful tool for data science. It is written in Groovy and Java on top of JavaFX and offers the ability to: Read in

Patrick Martin 1.3k Jan 8, 2023
Google core libraries for Java

Guava: Google Core Libraries for Java Guava is a set of core Java libraries from Google that includes new collection types (such as multimap and multi

Google 46.5k Jan 1, 2023
Java regular expressions made easy.

JavaVerbalExpressions VerbalExpressions is a Java library that helps to construct difficult regular expressions. Getting Started Maven Dependency: <de

null 2.6k Dec 30, 2022
MinIO Client SDK for Java

MinIO Java SDK for Amazon S3 Compatible Cloud Storage MinIO Java SDK is Simple Storage Service (aka S3) client to perform bucket and object operations

High Performance, Kubernetes Native Object Storage 787 Jan 3, 2023
java port of Underscore.js

underscore-java Requirements Java 1.8 and later or Java 11. Installation Include the following in your pom.xml for Maven: <dependencies> <dependency

Valentyn Kolesnikov 411 Dec 6, 2022
(cross-platform) Java Version Manager

jabba Java Version Manager inspired by nvm (Node.js). Written in Go. The goal is to provide unified pain-free experience of installing (and switching

Stanley Shyiko 2.5k Jan 9, 2023
Manage your Java environment

Master your Java Environment with jenv Website : http://www.jenv.be Maintainers : Gildas Cuisinier Future maintainer in discussion: Benjamin Berman As

jEnv 4.6k Dec 30, 2022
The shell for the Java Platform

______ .~ ~. |`````````, .'. ..'''' | | | |'''|''''' .''```. .'' |_________| |

CRaSH Repositories 916 Dec 30, 2022
Lightweight Java State Machine

Maven <dependency> <groupId>com.github.stateless4j</groupId> <artifactId>stateless4j</artifactId> <version>2.6.0</version>

Stateless 4 Java 772 Dec 22, 2022