├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── composer.lock ├── src └── Id.php └── tests └── index.php /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = tab 7 | trim_trailing_whitespace = true 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | indent_style = space 13 | indent_size = 4 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IntelliJ 2 | .idea/ 3 | 4 | # Composer 5 | vendor/ 6 | composer.phar 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) delight.im (https://www.delight.im/) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP-IDs 2 | 3 | Short, obfuscated and efficient IDs for PHP 4 | 5 | No database changes are required. The original (integer) IDs are all you need. 6 | 7 | No collisions. Reversible. 8 | 9 | ## Why do I need this? 10 | 11 | * Don't leak information to your competitors (e.g. number of orders, sign-ups per day) 12 | * Prevent resource enumeration by waiving sequential IDs 13 | * Mix up IDs a little bit in order to make guessing them harder 14 | * ~~Security through obscurity~~ 15 | 16 | ## Requirements 17 | 18 | * PHP 5.6.0+ 19 | * GMP extension 20 | 21 | ## Installation 22 | 23 | 1. Include the library via Composer [[?]](https://github.com/delight-im/Knowledge/blob/master/Composer%20(PHP).md): 24 | 25 | ``` 26 | $ composer require delight-im/ids 27 | ``` 28 | 29 | 1. Include the Composer autoloader: 30 | 31 | ```php 32 | require __DIR__ . '/vendor/autoload.php'; 33 | ``` 34 | 35 | ## Usage 36 | 37 | ### Creating an instance 38 | 39 | ```php 40 | $generator = new \Delight\Ids\Id(); 41 | ``` 42 | 43 | ### Encoding and decoding IDs 44 | 45 | ```php 46 | $generator->encode(6); // => "43Vht7" 47 | $generator->decode('43Vht7'); // => 6 48 | ``` 49 | 50 | ### Shortening a number without obfuscating it 51 | 52 | ```php 53 | $generator->shorten(3141592); // => "vJST" 54 | $generator->unshorten("vJST"); // => 3141592 55 | ``` 56 | 57 | ### Obfuscating a number without shortening it 58 | 59 | ```php 60 | $generator->obfuscate(42); // => 958870139 61 | $generator->deobfuscate(958870139); // => 42 62 | ``` 63 | 64 | ## Customization 65 | 66 | 1. Shuffle the characters of the alphabet that is used for the base conversion. Calling `\Delight\Ids\Id::createRandomAlphabet()` may be helpful for that purpose. You might also change the alphabet entirely, but there's usually no need to do that. 67 | 1. Pass your new alphabet to the constructor as the first argument. 68 | 1. Clone this repository and then execute the file [`tests/index.php`](tests/index.php) to generate your custom prime number, inverse prime and random number for Knuth's multiplicative hashing. 69 | 1. Pass your three new numbers to the constructor as the second, third and fourth argument, respectively. 70 | 71 | ## Contributing 72 | 73 | All contributions are welcome! If you wish to contribute, please create an issue first so that your feature, problem or question can be discussed. 74 | 75 | ## License 76 | 77 | This project is licensed under the terms of the [MIT License](https://opensource.org/licenses/MIT). 78 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "delight-im/ids", 3 | "description": "Short, obfuscated and efficient IDs for PHP", 4 | "require": { 5 | "php": ">=5.6.0", 6 | "ext-gmp": "*", 7 | "jenssegers/optimus": "^0.2.1" 8 | }, 9 | "type": "library", 10 | "keywords": [ "ids", "id", "obfuscation", "obfuscate", "shorten", "encode", "decode", "hash", "hashids", "hashid" ], 11 | "homepage": "https://github.com/delight-im/PHP-IDs", 12 | "license": "MIT", 13 | "autoload": { 14 | "psr-4": { 15 | "Delight\\Ids\\": "src/" 16 | } 17 | }, 18 | "require-dev": { 19 | "phpseclib/phpseclib": "^2.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "hash": "1f7656f3644cb5490321b0a7b68ab560", 8 | "content-hash": "9c4f3204a9278da713df1d9f9eb772a4", 9 | "packages": [ 10 | { 11 | "name": "jenssegers/optimus", 12 | "version": "v0.2.1", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/jenssegers/optimus.git", 16 | "reference": "86d94fb00a685769b743d5e3be2b18282830505c" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/jenssegers/optimus/zipball/86d94fb00a685769b743d5e3be2b18282830505c", 21 | "reference": "86d94fb00a685769b743d5e3be2b18282830505c", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "php": ">=5.4.0" 26 | }, 27 | "require-dev": { 28 | "phpseclib/phpseclib": "^2.0", 29 | "phpunit/phpunit": "^4.0|^5.0", 30 | "satooshi/php-coveralls": "^1.0", 31 | "symfony/console": "^2.6|^2.0" 32 | }, 33 | "suggest": { 34 | "ext-gmp": "Required for 32bit systems" 35 | }, 36 | "bin": [ 37 | "optimus" 38 | ], 39 | "type": "library", 40 | "autoload": { 41 | "psr-4": { 42 | "Jenssegers\\Optimus\\": "src" 43 | } 44 | }, 45 | "notification-url": "https://packagist.org/downloads/", 46 | "license": [ 47 | "MIT" 48 | ], 49 | "authors": [ 50 | { 51 | "name": "Jens Segers", 52 | "homepage": "https://jenssegers.com" 53 | } 54 | ], 55 | "description": "Id obfuscation based on Knuth's integer hash method", 56 | "homepage": "https://github.com/jenssegers/optimus", 57 | "keywords": [ 58 | "hashids", 59 | "id obfuscation", 60 | "ids", 61 | "obfuscation", 62 | "optimus" 63 | ], 64 | "time": "2016-02-20 22:12:18" 65 | } 66 | ], 67 | "packages-dev": [ 68 | { 69 | "name": "phpseclib/phpseclib", 70 | "version": "2.0.3", 71 | "source": { 72 | "type": "git", 73 | "url": "https://github.com/phpseclib/phpseclib.git", 74 | "reference": "41f85e9c2582b3f6d1b7d20395fb40c687ad5370" 75 | }, 76 | "dist": { 77 | "type": "zip", 78 | "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/41f85e9c2582b3f6d1b7d20395fb40c687ad5370", 79 | "reference": "41f85e9c2582b3f6d1b7d20395fb40c687ad5370", 80 | "shasum": "" 81 | }, 82 | "require": { 83 | "php": ">=5.3.3" 84 | }, 85 | "require-dev": { 86 | "phing/phing": "~2.7", 87 | "phpunit/phpunit": "~4.0", 88 | "sami/sami": "~2.0", 89 | "squizlabs/php_codesniffer": "~2.0" 90 | }, 91 | "suggest": { 92 | "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", 93 | "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.", 94 | "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.", 95 | "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations." 96 | }, 97 | "type": "library", 98 | "autoload": { 99 | "files": [ 100 | "phpseclib/bootstrap.php" 101 | ], 102 | "psr-4": { 103 | "phpseclib\\": "phpseclib/" 104 | } 105 | }, 106 | "notification-url": "https://packagist.org/downloads/", 107 | "license": [ 108 | "MIT" 109 | ], 110 | "authors": [ 111 | { 112 | "name": "Jim Wigginton", 113 | "email": "terrafrost@php.net", 114 | "role": "Lead Developer" 115 | }, 116 | { 117 | "name": "Patrick Monnerat", 118 | "email": "pm@datasphere.ch", 119 | "role": "Developer" 120 | }, 121 | { 122 | "name": "Andreas Fischer", 123 | "email": "bantu@phpbb.com", 124 | "role": "Developer" 125 | }, 126 | { 127 | "name": "Hans-Jürgen Petrich", 128 | "email": "petrich@tronic-media.com", 129 | "role": "Developer" 130 | }, 131 | { 132 | "name": "Graham Campbell", 133 | "email": "graham@alt-three.com", 134 | "role": "Developer" 135 | } 136 | ], 137 | "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", 138 | "homepage": "http://phpseclib.sourceforge.net", 139 | "keywords": [ 140 | "BigInteger", 141 | "aes", 142 | "asn.1", 143 | "asn1", 144 | "blowfish", 145 | "crypto", 146 | "cryptography", 147 | "encryption", 148 | "rsa", 149 | "security", 150 | "sftp", 151 | "signature", 152 | "signing", 153 | "ssh", 154 | "twofish", 155 | "x.509", 156 | "x509" 157 | ], 158 | "time": "2016-08-18 18:49:14" 159 | } 160 | ], 161 | "aliases": [], 162 | "minimum-stability": "stable", 163 | "stability-flags": [], 164 | "prefer-stable": false, 165 | "prefer-lowest": false, 166 | "platform": { 167 | "php": ">=5.6.0", 168 | "ext-gmp": "*" 169 | }, 170 | "platform-dev": [] 171 | } 172 | -------------------------------------------------------------------------------- /src/Id.php: -------------------------------------------------------------------------------- 1 | alphabet = empty($alphabet) ? self::ALPHABET_DEFAULT : ((string) $alphabet); 40 | $this->base = strlen($this->alphabet); 41 | $this->obfuscator = new \Jenssegers\Optimus\Optimus( 42 | empty($prime) ? self::PRIME_DEFAULT : ((int) $prime), 43 | empty($inverse) ? self::INVERSE_DEFAULT : ((int) $inverse), 44 | empty($random) ? self::RANDOM_DEFAULT : ((int) $random) 45 | ); 46 | } 47 | 48 | /** 49 | * Encodes an ID to a short, obfuscated identifier 50 | * 51 | * @param int $num the (integer) ID to encode 52 | * @return string the encoded identifier 53 | */ 54 | public function encode($num) { 55 | return $this->shorten($this->obfuscate($num)); 56 | } 57 | 58 | /** 59 | * Decodes an ID from a short, obfuscated identifier 60 | * 61 | * @param string $str the encoded identifier 62 | * @return int the (integer) ID to encode 63 | */ 64 | public function decode($str) { 65 | return $this->deobfuscate($this->unshorten($str)); 66 | } 67 | 68 | /** 69 | * Converts the specified integer ID to a short string 70 | * 71 | * This is done by performing a (bijective) base conversion 72 | * 73 | * @param int $num the integer ID to convert 74 | * @return string the short string 75 | */ 76 | public function shorten($num) { 77 | $str = ''; 78 | 79 | while ($num > 0) { 80 | $str = $this->alphabet[($num % $this->base)] . $str; 81 | $num = (int) ($num / $this->base); 82 | } 83 | 84 | return $str; 85 | } 86 | 87 | /** 88 | * Converts the specified short string to an integer ID 89 | * 90 | * This is done by performing a (bijective) base conversion 91 | * 92 | * @param string $str the short string to convert 93 | * @return int the integer ID 94 | */ 95 | public function unshorten($str) { 96 | $num = 0; 97 | $len = strlen($str); 98 | 99 | for ($i = 0; $i < $len; $i++) { 100 | $num = $num * $this->base + strpos($this->alphabet, $str[$i]); 101 | } 102 | 103 | return $num; 104 | } 105 | 106 | /** 107 | * Obfuscates the specified integer number 108 | * 109 | * This is done by performing Knuth's multiplicative hashing 110 | * 111 | * @param int $num the integer to obfuscate 112 | * @return int the obfuscated integer 113 | */ 114 | public function obfuscate($num) { 115 | return $this->obfuscator->encode($num); 116 | } 117 | 118 | /** 119 | * De-obfuscates the specified integer number 120 | * 121 | * This is done by performing Knuth's multiplicative hashing 122 | * 123 | * @param int $num the integer to de-obfuscate 124 | * @return int the de-obfuscated integer 125 | */ 126 | public function deobfuscate($num) { 127 | return $this->obfuscator->decode($num); 128 | } 129 | 130 | /** 131 | * Creates a random alphabet that may be used instead of the default one for customization 132 | * 133 | * @return string the new random alphabet 134 | */ 135 | public static function createRandomAlphabet() { 136 | return str_shuffle(self::ALPHABET_DEFAULT); 137 | } 138 | 139 | } 140 | -------------------------------------------------------------------------------- /tests/index.php: -------------------------------------------------------------------------------- 1 | randomPrime($min, $max); 27 | 28 | return (int) ((string) $prime); 29 | } 30 | 31 | function generateModularMultiplicativeInverse($prime) { 32 | $a = new \phpseclib\Math\BigInteger($prime); 33 | $m = new \phpseclib\Math\BigInteger(\Jenssegers\Optimus\Optimus::MAX_INT + 1); 34 | 35 | $x = $a->modInverse($m); 36 | 37 | return (int) ((string) $x); 38 | } 39 | 40 | function generateRandomInt() { 41 | // TODO: use `random_int(...)` in PHP 7.0.0+ 42 | return mt_rand(1, \Jenssegers\Optimus\Optimus::MAX_INT); 43 | } 44 | 45 | $generator = new \Delight\Ids\Id(); 46 | 47 | echo '$num | $generator->encode($num) | $generator->decode(...)'; 48 | echo "\n"; 49 | echo '---------------------------------------------------------'; 50 | echo "\n"; 51 | 52 | for ($i = 0; $i < 25; $i++) { 53 | $encoded = $generator->encode($i); 54 | $decoded = $generator->decode($encoded); 55 | 56 | echo sprintf('% 5s', $i); 57 | echo ' '; 58 | echo sprintf('% 25s', $encoded); 59 | echo ' '; 60 | echo sprintf('% 25s', $decoded); 61 | echo "\n"; 62 | } 63 | 64 | $numFirst = 3141592; 65 | echo "\n"; 66 | echo 'Shortening a number without obfuscating it:'; 67 | echo "\n"; 68 | echo "\t"; 69 | echo '$generator->shorten('; 70 | echo $numFirst; 71 | echo '); // => "'; 72 | echo $generator->shorten($numFirst); 73 | echo '"'; 74 | echo "\n"; 75 | echo "\t"; 76 | echo '$generator->unshorten("'; 77 | echo $generator->shorten($numFirst); 78 | echo '"); // => '; 79 | echo $generator->unshorten($generator->shorten($numFirst)); 80 | echo "\n"; 81 | 82 | $numSecond = 42; 83 | echo "\n"; 84 | echo 'Obfuscating a number without shortening it:'; 85 | echo "\n"; 86 | echo "\t"; 87 | echo '$generator->obfuscate('; 88 | echo $numSecond; 89 | echo '); // => '; 90 | echo $generator->obfuscate($numSecond); 91 | echo "\n"; 92 | echo "\t"; 93 | echo '$generator->deobfuscate('; 94 | echo $generator->obfuscate($numSecond); 95 | echo '); // => '; 96 | echo $generator->deobfuscate($generator->obfuscate($numSecond)); 97 | echo "\n"; 98 | 99 | $prime = generateRandomPrime(); 100 | echo "\n"; 101 | echo 'Random prime number:'; 102 | echo "\n"; 103 | echo "\t"; 104 | echo $prime; 105 | 106 | echo "\n"; 107 | echo 'Modular multiplicative inverse of random prime:'; 108 | echo "\n"; 109 | echo "\t"; 110 | echo generateModularMultiplicativeInverse($prime); 111 | 112 | echo "\n"; 113 | echo 'Random integer:'; 114 | echo "\n"; 115 | echo "\t"; 116 | echo generateRandomInt(); 117 | 118 | echo "\n"; 119 | echo 'Creating a random alphabet:'; 120 | echo "\n"; 121 | echo "\t"; 122 | echo '\Delight\Ids\Id::createRandomAlphabet(); // => "'; 123 | echo \Delight\Ids\Id::createRandomAlphabet(); 124 | echo '"'; 125 | echo "\n"; 126 | --------------------------------------------------------------------------------