`
w727ang
  • 浏览: 1578 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

HashIds.java

阅读更多
package org.hashids;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Hashids designed for Generating short hashes from numbers (like YouTube and Bitly), obfuscate
 * database IDs, use them as forgotten password hashes, invitation codes, store shard numbers.
 * <p>
 * This is implementation of http://hashids.org v1.0.0 version.
 *
 * This implementation is immutable, thread-safe, no lock is necessary.
 *
 * @author <a href="mailto:fanweixiao@gmail.com">fanweixiao</a>
 * @author <a href="mailto:terciofilho@gmail.com">Tercio Gaudencio Filho</a>
 * @since 0.3.3
 */
public class Hashids {
  /**
   * Max number that can be encoded with Hashids.
   */
  public static final long MAX_NUMBER = 9007199254740992L;

  private static final String DEFAULT_ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
  private static final String DEFAULT_SEPS = "cfhistuCFHISTU";
  private static final String DEFAULT_SALT = "";

  private static final int DEFAULT_MIN_HASH_LENGTH = 0;
  private static final int MIN_ALPHABET_LENGTH = 16;
  private static final double SEP_DIV = 3.5;
  private static final int GUARD_DIV = 12;

  private final String salt;
  private final int minHashLength;
  private final String alphabet;
  private final String seps;
  private final String guards;

  public Hashids() {
    this(DEFAULT_SALT);
  }

  public Hashids(String salt) {
    this(salt, 0);
  }

  public Hashids(String salt, int minHashLength) {
    this(salt, minHashLength, DEFAULT_ALPHABET);
  }

  public Hashids(String salt, int minHashLength, String alphabet) {
    this.salt = salt != null ? salt : DEFAULT_SALT;
    this.minHashLength = minHashLength > 0 ? minHashLength : DEFAULT_MIN_HASH_LENGTH;

    final StringBuilder uniqueAlphabet = new StringBuilder();
    for (int i = 0; i < alphabet.length(); i++) {
      if (uniqueAlphabet.indexOf(String.valueOf(alphabet.charAt(i))) == -1) {
        uniqueAlphabet.append(alphabet.charAt(i));
      }
    }

    alphabet = uniqueAlphabet.toString();

    if (alphabet.length() < MIN_ALPHABET_LENGTH) {
      throw new IllegalArgumentException(
          "alphabet must contain at least " + MIN_ALPHABET_LENGTH + " unique characters");
    }

    if (alphabet.contains(" ")) {
      throw new IllegalArgumentException("alphabet cannot contains spaces");
    }

    // seps should contain only characters present in alphabet;
    // alphabet should not contains seps
    String seps = DEFAULT_SEPS;
    for (int i = 0; i < seps.length(); i++) {
      final int j = alphabet.indexOf(seps.charAt(i));
      if (j == -1) {
        seps = seps.substring(0, i) + " " + seps.substring(i + 1);
      } else {
        alphabet = alphabet.substring(0, j) + " " + alphabet.substring(j + 1);
      }
    }

    alphabet = alphabet.replaceAll("\\s+", "");
    seps = seps.replaceAll("\\s+", "");
    seps = Hashids.consistentShuffle(seps, this.salt);

    if ((seps.isEmpty()) || (((float) alphabet.length() / seps.length()) > SEP_DIV)) {
      int seps_len = (int) Math.ceil(alphabet.length() / SEP_DIV);

      if (seps_len == 1) {
        seps_len++;
      }

      if (seps_len > seps.length()) {
        final int diff = seps_len - seps.length();
        seps += alphabet.substring(0, diff);
        alphabet = alphabet.substring(diff);
      } else {
        seps = seps.substring(0, seps_len);
      }
    }

    alphabet = Hashids.consistentShuffle(alphabet, this.salt);
    // use double to round up
    final int guardCount = (int) Math.ceil((double) alphabet.length() / GUARD_DIV);

    String guards;
    if (alphabet.length() < 3) {
      guards = seps.substring(0, guardCount);
      seps = seps.substring(guardCount);
    } else {
      guards = alphabet.substring(0, guardCount);
      alphabet = alphabet.substring(guardCount);
    }
    this.guards = guards;
    this.alphabet = alphabet;
    this.seps = seps;
  }

  /**
   * Encode numbers to string
   *
   * @param numbers
   *          the numbers to encode
   * @return the encoded string
   */
  public String encode(long... numbers) {
    if (numbers.length == 0) {
      return "";
    }

    for (final long number : numbers) {
      if (number < 0) {
        return "";
      }
      if (number > MAX_NUMBER) {
        throw new IllegalArgumentException("number can not be greater than " + MAX_NUMBER + "L");
      }
    }
    return this._encode(numbers);
  }

  /**
   * Decode string to numbers
   *
   * @param hash
   *          the encoded string
   * @return decoded numbers
   */
  public long[] decode(String hash) {
    if (hash.isEmpty()) {
      return new long[0];
    }
    
    String validChars = this.alphabet + this.guards + this.seps;
    for (int i = 0; i < hash.length(); i++) {
      if(validChars.indexOf(hash.charAt(i)) == -1) {
        return new long[0];
      }
    }

    return this._decode(hash, this.alphabet);
  }

  /**
   * Encode hexa to string
   *
   * @param hexa
   *          the hexa to encode
   * @return the encoded string
   */
  public String encodeHex(String hexa) {
    if (!hexa.matches("^[0-9a-fA-F]+$")) {
      return "";
    }

    final List<Long> matched = new ArrayList<Long>();
    final Matcher matcher = Pattern.compile("[\\w\\W]{1,12}").matcher(hexa);

    while (matcher.find()) {
      matched.add(Long.parseLong("1" + matcher.group(), 16));
    }

    // conversion
    final long[] result = new long[matched.size()];
    for (int i = 0; i < matched.size(); i++) {
      result[i] = matched.get(i);
    }

    return this.encode(result);
  }

  /**
   * Decode string to numbers
   *
   * @param hash
   *          the encoded string
   * @return decoded numbers
   */
  public String decodeHex(String hash) {
    final StringBuilder result = new StringBuilder();
    final long[] numbers = this.decode(hash);

    for (final long number : numbers) {
      result.append(Long.toHexString(number).substring(1));
    }

    return result.toString();
  }

  public static int checkedCast(long value) {
    final int result = (int) value;
    if (result != value) {
      // don't use checkArgument here, to avoid boxing
      throw new IllegalArgumentException("Out of range: " + value);
    }
    return result;
  }

  /* Private methods */

  private String _encode(long... numbers) {
    long numberHashInt = 0;
    for (int i = 0; i < numbers.length; i++) {
      numberHashInt += (numbers[i] % (i + 100));
    }
    String alphabet = this.alphabet;
    final char ret = alphabet.charAt((int) (numberHashInt % alphabet.length()));

    long num;
    long sepsIndex, guardIndex;
    String buffer;
    final StringBuilder ret_strB = new StringBuilder(this.minHashLength);
    ret_strB.append(ret);
    char guard;

    for (int i = 0; i < numbers.length; i++) {
      num = numbers[i];
      buffer = ret + this.salt + alphabet;

      alphabet = Hashids.consistentShuffle(alphabet, buffer.substring(0, alphabet.length()));
      final String last = Hashids.hash(num, alphabet);

      ret_strB.append(last);

      if (i + 1 < numbers.length) {
        if (last.length() > 0) {
          num %= (last.charAt(0) + i);
          sepsIndex = (int) (num % this.seps.length());
        } else {
          sepsIndex = 0;
        }
        ret_strB.append(this.seps.charAt((int) sepsIndex));
      }
    }

    String ret_str = ret_strB.toString();
    if (ret_str.length() < this.minHashLength) {
      guardIndex = (numberHashInt + (ret_str.charAt(0))) % this.guards.length();
      guard = this.guards.charAt((int) guardIndex);

      ret_str = guard + ret_str;

      if (ret_str.length() < this.minHashLength) {
        guardIndex = (numberHashInt + (ret_str.charAt(2))) % this.guards.length();
        guard = this.guards.charAt((int) guardIndex);

        ret_str += guard;
      }
    }

    final int halfLen = alphabet.length() / 2;
    while (ret_str.length() < this.minHashLength) {
      alphabet = Hashids.consistentShuffle(alphabet, alphabet);
      ret_str = alphabet.substring(halfLen) + ret_str + alphabet.substring(0, halfLen);
      final int excess = ret_str.length() - this.minHashLength;
      if (excess > 0) {
        final int start_pos = excess / 2;
        ret_str = ret_str.substring(start_pos, start_pos + this.minHashLength);
      }
    }

    return ret_str;
  }

  private long[] _decode(String hash, String alphabet) {
    final ArrayList<Long> ret = new ArrayList<Long>();

    int i = 0;
    final String regexp = "[" + this.guards + "]";
    String hashBreakdown = hash.replaceAll(regexp, " ");
    String[] hashArray = hashBreakdown.split(" ");

    if (hashArray.length == 3 || hashArray.length == 2) {
      i = 1;
    }

    if (hashArray.length > 0) {
      hashBreakdown = hashArray[i];
      if (!hashBreakdown.isEmpty()) {
        final char lottery = hashBreakdown.charAt(0);

        hashBreakdown = hashBreakdown.substring(1);
        hashBreakdown = hashBreakdown.replaceAll("[" + this.seps + "]", " ");
        hashArray = hashBreakdown.split(" ");

        String subHash, buffer;
        for (final String aHashArray : hashArray) {
          subHash = aHashArray;
          buffer = lottery + this.salt + alphabet;
          alphabet = Hashids.consistentShuffle(alphabet, buffer.substring(0, alphabet.length()));
          ret.add(Hashids.unhash(subHash, alphabet));
        }
      }
    }

    // transform from List<Long> to long[]
    long[] arr = new long[ret.size()];
    for (int k = 0; k < arr.length; k++) {
      arr[k] = ret.get(k);
    }

    if (!this.encode(arr).equals(hash)) {
      arr = new long[0];
    }

    return arr;
  }

  private static String consistentShuffle(String alphabet, String salt) {
    if (salt.length() <= 0) {
      return alphabet;
    }

    int asc_val, j;
    final char[] tmpArr = alphabet.toCharArray();
    for (int i = tmpArr.length - 1, v = 0, p = 0; i > 0; i--, v++) {
      v %= salt.length();
      asc_val = salt.charAt(v);
      p += asc_val;
      j = (asc_val + v + p) % i;
      final char tmp = tmpArr[j];
      tmpArr[j] = tmpArr[i];
      tmpArr[i] = tmp;
    }

    return new String(tmpArr);
  }

  private static String hash(long input, String alphabet) {
    String hash = "";
    final int alphabetLen = alphabet.length();

    do {
      final int index = (int) (input % alphabetLen);
      if (index >= 0 && index < alphabet.length()) {
        hash = alphabet.charAt(index) + hash;
      }
      input /= alphabetLen;
    } while (input > 0);

    return hash;
  }

  private static Long unhash(String input, String alphabet) {
    long number = 0, pos;

    for (int i = 0; i < input.length(); i++) {
      pos = alphabet.indexOf(input.charAt(i));
      number = number * alphabet.length() + pos;
    }

    return number;
  }

  /**
   * Get Hashid algorithm version.
   *
   * @return Hashids algorithm version implemented.
   */
  public String getVersion() {
    return "1.0.0";
  }
}

 

分享到:
评论

相关推荐

    hashids-java, 在Java中,Hashids算法v1.0.0实现.zip

    hashids-java, 在Java中,Hashids算法v1.0.0实现 Hashids.java 从一个或者多个数字生成youtube的一个小的Java类。来自 javascript hashids.js的端口,由 Ivan Akimov是什么?hashids ( 哈希标识's ) 通过无

    hashids.js:一个小JavaScript库,可从数字生成类似YouTube的ID

    当您不想向用户公开数据库ID时,请使用它: :入门通过以下方式安装Hashids: yarn add hashids (或只使用dist/hashids.js的代码)在与ESM兼容的环境(Webpack,现代浏览器)中使用import Hashids from 'hashids'...

    hashids.phpc:Hashids的php扩展

    [hashids]extension =hashids.so//default is emptyhashids.salt =cdoco//default: 0hashids.min_hash_length =20//default: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890//you

    Laravel开发-laravel-hashids .zip.zip

    在`config/services.php`或创建一个新的`config/hashids.php`文件中,添加以下配置: ```php return [ 'hashids' =&gt; [ 'connection' =&gt; env('HASHIDS_CONNECTION', 'default'), 'connections' =&gt; [ 'default' =...

    Laravel开发-laraveldoctrine-hashids .zip

    这个压缩包"Laravel开发-laraveldoctrine-hashids .zip"很可能包含了一个集成laravel-doctrine/hashids到Laravel项目中的示例或教程。 Laravel-doctrine/hashids是一个扩展,它允许你在Laravel应用中使用HashIDs,...

    hashids.php-master整数生成唯一字符串的加密库.zip

    namespace Hashids; class Hashids implements HashidsInterface {  const SEP_DIV = 3.5;  const GUARD_DIV = 12;  /**  * The alphabet string.  *  * @var string  */  ...

    hashids.pm:Hashids,移植到Perl

    在Perl中,通过模块`hashids.pm`,我们可以轻松地利用这一功能。 `hashids.pm` 是Hashids算法的一个Perl实现,它允许开发者在他们的Perl项目中使用这种加密技术。这个模块可以在CPAN(Comprehensive Perl Archive ...

    Python库 | hashids-1.2.0.tar.gz

    hashids = hashids.Hashids(salt='my_salt', alphabet='0123456789abcdef') ``` - **避免重复**:如果需要确保每次编码相同的整数时生成相同的哈希字符串,可以使用`separate`参数。 ```python hashids = hashids....

    hashids.github.io:Hashids网站

    Hashids的网站文档。 如何更新 获取 , 。 确保已安装和 。 这是一个静态网站,可以自动生成用于不同实现的页面。 有两个主要文件要更新: src/data.json src/template.html data.json包含填充网站的大多数实施...

    bashids:来自http:hashids.org的hashid算法的纯Bash实现

    描述部分再次确认了这个项目来源于http://hashids.org,并且是专门为Bash设计的。这意味着开发者可以利用这个工具在Linux或Unix系统中,通过命令行接口处理数据加密和解密,无需依赖其他的编程语言或库。 **标签...

    hashids:一个用于 http 的 Clojure 包装库

    安装 [hashobject/hashids "0.2.0"]用法 user=&gt; (use 'hashids.core)niluser=&gt; (encrypt 134 "super-secret-salt")"Lzn"user=&gt; (decrypt "Lzn" "super-secret-salt")134user=&gt; (encrypt 225 "super-secret-salt")"7...

    go-hashids:Go(golang)http的实现

    混蛋 MIT许可下的Go(golang... hd := hashids . NewData () hd . Salt = "this is my salt" hd . MinLength = 30 h , _ := hashids . NewWithData ( hd ) e , _ := h . Encode ([] int { 45 , 434 , 1313 , 99 })

    Laravel开发-hashids-laravel

    这将在 `config/hashids.php` 中创建一个配置文件,可以自定义盐值、长度等参数。 **3. 使用 Hashids-Laravel** 在 Laravel 中,你可以使用 Facade 或依赖注入来使用 Hashids。首先,需要在 `config/app.php` 的 `...

    Laravel开发-laravel-hashids

    config('services.hashids.length'), config('services.hashids.alphabet') ); }); } } ``` 现在,你可以在整个应用中使用`Hashids`服务了。例如,你可以在模型中创建一个方法来获取加密后的ID: ```php use ...

    hashid-rails:在Rails应用程序ActiveRecord模型中使用Hashids(http:hashids.orgruby)

    Hashid Rails 该宝石可让您轻松在Rails应用程序中使用 。 您的模型将使用唯一的短哈希,例如“ yLA6m0oM”,“ 5bAyD0LO”和“ wz3MZ49l”,而不是使用诸如1、2、3之类的序号的模型。 数据库仍然会使用整数,因此...

    hashids.rb:一种小的Ruby宝石,可以从一个或多个数字生成类似YouTube的哈希值。 当您不想向用户公开数据库ID时,请使用hashid

    Hashids 一种小的Ruby宝石,可从一个或多个数字生成类似YouTube的ID。 当您不想向用户公开数据库ID时,请使用hashid。 (2.6.2、2.5.5、2.4.5、2.3.8,jruby-9.2.6.0) 它是什么? hashids(哈希ID)从无符号整数...

    hashids-elixir:字符串化您的ID

    Hashids Hashids使您可以通过可逆映射混淆数字标识符。 这是来自JavaScript的。安装将Hashids添加为您的Mix项目的依赖项: defp deps do [ { :hashids , " ~&gt; 2.0 " } ]end用法Hashids将整数列表编码为字符串(技术...

    Laravel开发-hashids

    安装完成后,需要在 Laravel 的配置文件 `config/hashids.php` 中设置所需的参数,包括盐(salt)、长度(length)以及可能的预定义字符集(alphabet)。盐是一个随机字符串,用于增加加密的唯一性;长度决定生成的...

Global site tag (gtag.js) - Google Analytics