What's wrong with my implementation of 2 Factor Authorization? - php

I'm trying to implement my own PHP function to generate codes for Google Authenticator. I do it for fun and to learn something new. Here's what I did:
function twoFactorAuthorizationCode(string $secretBase32, int $digitsCount): string {
$counter = (int) (time() / 30);
$secret = Base32::decode($secretBase32);
$hash = hash_hmac('sha1', $counter, $secret, true); // 20 binary characters
$hexHash = unpack('H*', $hash)[1]; // 40 hex characters
$offset = hexdec($hexHash[-1]); // last 4 bits of $hash
$truncatedHash = hexdec(substr($hexHash, $offset * 2, 8)) & 0x7fffffff; // last 31 bits
$code = $truncatedHash % (10 ** $digitsCount);
return str_pad($code, $digitsCount, '0', STR_PAD_LEFT);
}
I'm not sure which step is wrong, but it doesn't generate the same results as Google Authenticator. Obviously, I tried to play with time offsets in case my clock is not in sync with Google Authenticator's.
Some of the things I'm not sure are:
Should the secret be decoded from Base32, or should it stay the Base32 string?
Is the counter a value or a key for SHA1 hash?
I did a lot of experiments and I can't get my algorithm to generate a valid result. Any advice is highly appreciated.

I have found the answer by trials and errors. So, the problem was in the $counter value that I've been hashing directly:
$hash = hash_hmac('sha1', $counter, $secret, true);
Instead, it should be a 64-bit binary string made from the $counter:
$packedCounter = pack('J', $counter);
$hash = hash_hmac('sha1', $packedCounter, $secret, true);
Explanation
Let's say our Unix timestamp is 1578977176.
That makes the counter as follows: (int) (1578977176 / 30) = 52632572.
The value used for hashing needs to be a 64-bit, big endian byte order string. It means that we need to left-pad it with zeros to make it 64-bit.
52632572 is 11001000110001101111111100 in binary. That's just 26 bits, so we need 38 more. What we have now is:
0000000000000000000000000000000000000011001000110001101111100010.
Every character is one byte, so we split it into the groups of 8:
00000000 00000000 00000000 00000000 00000011 00100011 00011011 11100010
We can now convert every group to a character by its code:
$packedCounter = chr(0b00000000)
. chr(0b00000000)
. chr(0b00000000)
. chr(0b00000000)
. chr(0b00000011)
. chr(0b00100011)
. chr(0b00011011)
. chr(0b11100010);
And that's the string we want to hash, which is exactly what pack('J', $string) does.
Voilà!

Related

Expiry time on php openssl?

I'm not very familiar with encryption, and we are now using PHP's openssl_encrypt/decrypt in our application.
Is it possible to make the encryption/decryption work only before an expiry time? e.g. maybe the keys expire?
Yes, it is possible, you have to append the creation timestamp as bytes before what you need to encrypt:
$time = pack('N', time());
$enc = openssl_encrypt($time . $other_data, ...);
When you decrypt:
$dec = openssl_decrypt($encrypted, ...);
$time = unpack('N', substr($dec, 0, 4));
$other_data = substr($dec, 4);
if (time() - $time[1] > $EXPIRY_SECONDS)
die('Expired');
The N flag i've used in pack/unpack is for Big Endian byte order, you can also use V for little endian or L for machine-dependent, because the timestamp fit in 32 bit integer (4 bytes).

PHP mt_srand function

I've written a code that should generate pseudo-random strings.
I tried to improve the randomness by gathering entropy from user's mouse movements.
Here is my code :
// As described in the PHP documentation
function make_seed() {
list($usec, $sec) = explode(' ', microtime());
return (float) $sec + ((float) $usec * 100000);
}
function rand_string($entropy, $length, $chars) {
mt_srand($entropy . make_seed()); // Here is the important line
$return = '';
$charlen = strlen($chars);
for ($i=0;$i<$length;$i++) {
$rand = mt_rand(0, $charlen) - 1;
$return .= substr($chars, $rand, 1);
}
return $return;
}
$entropy = '18421828841384386426948169412548'; // Mouse movements, changes everytime
echo rand_string($entropy, 20, 'abcdefghijklmnopqrstuvwxz');
I ran the function a couple of times. Some values show up very frequently, so this is a very weak function. I can't understand why. Is there a limit on mt_srand's parameter ? Does it have to be a number ?
Edit : mt_srand() seed must be an INT.
mt_srand() takes an unsigned 32 bit integer to initialize the mersenne twister.
http://svn.php.net/viewvc/php/php-src/trunk/ext/standard/rand.c?revision=321634&view=markup:
194 /* {{{ php_mt_srand
195 */
196 PHPAPI void php_mt_srand(php_uint32 seed TSRMLS_DC)
197 {
198 /* Seed the generator with a simple uint32 */
199 php_mt_initialize(seed, BG(state));
200 php_mt_reload(TSRMLS_C);
201
202 /* Seed only once */
203 BG(mt_rand_is_seeded) = 1;
204 }
205 /* }}} */
I'd suggest searching for means of the underlying system to gather entropy/random bits.
That would be rngd + /dev/random on a *nix machine and CryptGenRandom or (simpler to reach but slower) CAPICOM Utilities.GetRandom() under windows.
Depending on your needs mcrypt_create_iv() can also be a good choice (maybe in combination with something that creates a "readable" string from the iv).
I wrote my own random string generator without using php's rand() function.
function rs($length,$chars)
{
$hex = sha1(microtime()); //contains hexadecimal string
$return = '';
$seedLen = strlen($chars); //length of the source characters string
$posLen = strlen($hex); //length of the hex string
for($i=0;$i<$length;$i++){
$idx_hex = $i % ($posLen-1); //make sure the address is in the hex string (if $i is too big)
$pos = hexdec($hex[$idx_hex].$hex[$idx_hex+1]);
$return .= $chars[$pos % $seedLen];
}
return $return;
}
The way it works is: it generates sha1 hash of current time (a string that looks like 06009da3e0d26f8569b65cb50a774bb6b431a777) then it takes 2 values at a time from the hash and uses that as a hexadecimal "address" of a character in the $chars string.
i.e. in this example if the $chars string is "abcdefghijklmnopqrstuvwxyz" then the first character in the "random" string will be "g" - the letter with index 06 in the chars string.
Limitations:
(1) only first 256 characters will be used from the $chars string (if it's longer than 256 characters)
(2) due to modulo operators, this function is slower than the mt_rand().
edit: mt_rand() uses modulo operators as well, so the speed might be on the same order as this function. i didn't run any comparisons.

shorter php cipher than md5?

For a variety of stupid reasons, the maximum length of a given form variable that we are posting to an external server is 12 characters.
I wanted to obscure that value with md5, but obviously with 12 characters that isn't going to work. Is there a cipher with an already-made PHP function which will result in something 12 characters or less?
The security and integrity of the cipher isn't super important here. My last resort is to just write a function which moves each letter up or down an ascii value by x. So the goal isn't to obscure it from a cryptography expert, but just to not post it in plain text so a non-technical worker looking at it won't know what it is.
Thanks for any advice.
maybe this will help you generate a 12 char string that you can pass in a URL, without increasing the risk of collisions
substr(base_convert(md5($string), 16,32), 0, 12);
This is an addition to this answer.
The answer proposes to take the first twelve characters from a 32 character representation of md5. Thus 20 characters of information will be lost - this will result in way more possible collisions.
You can reduce the loss of information by taking the first twelve characters of a 16 character representation (the raw form):
substr(md5($string, true), 0, 12);
This will maintain 75% of the data, whereas the use of the 32 char form only maintains 37.5% of the data.
Try crc32() maybe?
If you just need a hash, you can still use the first 12 characters from the md5 hash.
substr(md5($yourString), 0, 12);
All the answers are suggesting loosing some of the data (higher collision possibility), but looks like using using base conversion is a better approach:
e.g. like described here http://proger.i-forge.net/Short_MD5/OMF
You may also generate any random string and insert it into database, checking if not already exists prior to saving. This will allow you to have short hashes, and ensure there are no collisions.
I have to put this suggestion across as I have to assume you are in control of the script that your encrypted value is sent to....
I also have to assume that you can create many form fields but they can't have a length larger than 12 characters each.
If that's the case, could you not simply create more than one form field and spread the md5 string across multiple hidden fields?
You could just split the md5 string into chunks of 8 and submit each chunk in a hidden form field and then join them together at the other end.
Just a thought...
You can make use of a larger alphabet and make hash shorter but still reversible to original value.
I implemented it here - for example, hash ee45187ab28b4814cf03b2b4224eb974 becomes 7fBKxltZiQd7TFsUkOp26w - it goes from 32 to 22 characters. And it can become even less if you use a larger alpahabet. If you use unicode, you can even encode hash with emoji...
This probably won't be of use to the OP since they were looking for 2 way function but may help someone looking for a shorter hash than md5. Here is what I came up with for my needs (thanks to https://rolandeckert.com/notes/md5 for highlighting the base64_encode function). Encode the md5 hash as base(64) and remove any undesirable base(64) characters. I'm removing vowels + and / so reducing the effective base from 64 to 52.
Note if you truncate a base(b) encoded hash after c characters it will allow for b ^ c unique hashes. Is this robust enough to avoid collisions? It depends on how many items (k) you are hashing. The probability of collision is roughly (k * k) / (b ^ c) / 2, so if you used the function below to hash k = 1 million items with base b = 52 encoding truncated after c = 12 characters the probability of collision is < 1 in 750 million. Compare to truncating the hex encoded (b = 16) hash after c = 12 characters. The probability of collision is roughly 1 in 500! Just say no to truncating hex encoded hashes. :)
I'll go out on a limb and say the function below (with length 12) is reasonably safe for 10 million items (< 1 in 7.5 million probability of collision), but if you want to be extra safe use base(64) encoding (comment out the $remove array) and/or truncate fewer characters.
// convert md5 to base64, remove undesirable characters and truncate to $length
function tinymd5($str, $length) { // $length 20-22 not advised unless $remove = '';
// remove vowels to prevent undesirable words and + / which may be problematic
$remove = array('a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U', '+', '/');
$salt = $str;
do { // re-salt and loop if rebase removes too many characters
$salt = $base64 = base64_encode(md5($salt, TRUE));
$rebase = substr(str_replace($remove, '', $base64), 0, $length);
} while ($length < 20 && substr($rebase, -1) == '=');
return str_pad($rebase, min($length, 22), '='); // 22 is max possible length
}
$str = 'Lorem ipsum dolor sit amet 557726776';
echo '<br />' . md5($str); // 565a0bf7e0ba474fdaaec57b82e6504a
$x = md5($str, TRUE);
echo '<br />' . base64_encode($x); // VloL9+C6R0/arsV7guZQSg==
echo '<br />' . tinymd5($str, 12); // VlL9C6R0rsV7
echo '<br />' . tinymd5($str, 17); // VlL9C6R0rsV7gZQSg
$x = md5(base64_encode($x), TRUE); // re-salt triggered < 20
echo '<br />' . base64_encode($x); // fmkPW/OQLqp7PTex0nK3NQ==
echo '<br />' . tinymd5($str, 18); // fmkPWQLqp7PTx0nK3N
echo '<br />' . tinymd5($str, 19); // fmkPWQLqp7PTx0nK3NQ
echo '<br />' . tinymd5($str, 20); // fmkPWQLqp7PTx0nK3NQ=
echo '<br />' . tinymd5($str, 22); // fmkPWQLqp7PTx0nK3NQ===
$hashlen = 4;
$cxtrong = TRUE;
$sslk = openssl_random_pseudo_bytes($hashlen, $cxtrong);
$rand = bin2hex($sslk);
echo $rand;
You can change the hash length (in multiples of two) by changing the value of the variable $hashlen
I came up with base 90 for reducing md5 to 20 multi-byte characters (that I tested to fit properly in a mysql's varchar(20) column). Unfortunately this actually makes the string potentially larger than even the 32 bytes from php's md5, with the only advantage that they can be stored in varchar(20) columns. Of course you could just replace the alphabet with single-byte ones if your worries are about storage...
There are a couple of rules that are important to have in mind if your idea is to use this reduced hash as a lookup key in something like mysql and for other kinds of processing:
By default MySQL does not differentiate Upper Case from Lower Case in a typical where clause which takes out a lot of characters right out of the possible target alphabets. This include not only english character but also almost all characters in other languages.
It's important that your hash can be upper-cased and lower-cased transparently since many systems uppercase these keys, so to keep it consistent with md5 in that sense you should use only lowercase when using case-able characters.
This is the alphabet I used (I handpicked each character to make the hashes as nice as possible):
define('NICIESTCHARS', [
"0","1","2","3","4","5","6","7","8","9",
"a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z",
"¢","£","¥","§","¶","ø","œ","ƒ","α","δ","ε","η","θ","ι","λ","μ","ν","π","σ","τ","φ","ψ","ω","ћ","џ","ѓ","ѝ","й","ќ","ў","ф","э","ѣ","ѷ","ѻ","ѿ","ҁ","∂","∆","∑","√","∫",
"!","#","$","%","&","*","+","=","#","~","¤","±"
]);
Here is the code in PHP (I suppose it's not the best code but does the job). And keep in mind that it only works for strings in hexa (0-F) that are a multiple of 8 in length like md5 in php which is 32 0-f bytes:
function mbStringToArray ($string) {
$strlen = mb_strlen($string);
while ($strlen) {
$array[] = mb_substr($string,0,1,"UTF-8");
$string = mb_substr($string,1,$strlen,"UTF-8");
$strlen = mb_strlen($string);
}
return $array;
}
class Base90{
static function toHex5($s){
// Converts a base 90 number with a multiple of 5 digits to hex (compatible with "hexdec").
$chars = preg_split('//u', $s, null, PREG_SPLIT_NO_EMPTY);
$map = array_flip(NICIESTCHARS);
$rt = '';
$part = [];
$b90part = '';
foreach($chars as $c){
$b90part .= $c;
$part[] = $map[$c];
if(count($part) == 5){
$int = base90toInt($part);
$rt .= str_pad(dechex($int), 8, "0", STR_PAD_LEFT);
$part = [];
$b90part = '';
}
}
return $rt;
}
static function fromHex8($m){
// Converts an hexadecimal number compatible with "hexdec" to base 90
$parts = [];
$part = '';
foreach(str_split($m) as $i => $c){
$part.= $c;
if(strlen($part) === 8){
$parts[] = intToBase90(hexdec($part));
$part = '';
}
}
return implode('', $parts);
}
}
function intToBase90($int){
$residue = $int;
$result = [];
while($residue){
$digit = $residue % 90;
$residue -= $digit;
$residue = $residue / 90;
array_unshift($result, NICIESTCHARS[$digit]);
}
$result = implode('', $result);
return $result;
}
function base90toInt($digits){
$weight = 1;
$rt = 0;
while(count($digits)){
$rt += array_pop($digits)*$weight;
$weight *= 90;
}
return $rt;
}

PHP - Generate an 8 character hash from an integer

Is there a way to take any number, from say, 1 to 40000 and generate an 8 character hash?
I was thinking of using base_convert but couldn't figure out a way to force it to be an 8 character hash.
Any help would be appreciated!
Why don't you just run md5 and take the first 8 characters?
Because you are wanting a hash, it doesn't matter whether portions are discarded, but rather that the same input will produce the same hash.
$hash = substr(md5($num), 0, 8);
>>> math.exp(math.log(40000)/8)
3.7606030930863934
Therefore you need 4 digit-symbols to produce a 8-character hash from 40000:
sprintf("%08s", base_convert($n, 10, 4))
For php:
$seed = 'JvKnrQWPsThuJteNQAuH';
$hash = sha1(uniqid($seed . mt_rand(), true));
# To get a shorter version of the hash, just use substr
$hash = substr($hash, 0, 10);
http://snipplr.com/view.php?codeview&id=20236
As of PHP 8.1 you can use xxHash to get 8 characters hash of any string or number variable.
$number = random_int(1, 40_000);
$hash = hash('xxh32', (string) $number);
var_dump($hash); // Output is string(8) "af863d37"
there are many ways ...
one example
$x = ?
$s = '';
for ($i=0;$i<8;++$i)
{
$s .= chr( $x%26 + ord('a') );
$x /= 26;
}
$hash = substr(hash("sha256",$num), 0, 8);
So you want to convert a 6 digit number into a 8 digit string reproducibly?
sprintf("%08d", $number);
Certainly a hash is not reversible - but without a salt / IV it might be a bit easy to hack. A better solution might be:
substr(sha1($number . $some_secret),0,8);
C.

Short unique id in php

I want to create a unique id but uniqid() is giving something like '492607b0ee414'. What i would like is something similar to what tinyurl gives: '64k8ra'. The shorter, the better. The only requirements are that it should not have an obvious order and that it should look prettier than a seemingly random sequence of numbers. Letters are preferred over numbers and ideally it would not be mixed case. As the number of entries will not be that many (up to 10000 or so) the risk of collision isn't a huge factor.
Any suggestions appreciated.
Make a small function that returns random letters for a given length:
<?php
function generate_random_letters($length) {
$random = '';
for ($i = 0; $i < $length; $i++) {
$random .= chr(rand(ord('a'), ord('z')));
}
return $random;
}
Then you'll want to call that until it's unique, in pseudo-code depending on where you'd store that information:
do {
$unique = generate_random_letters(6);
} while (is_in_table($unique));
add_to_table($unique);
You might also want to make sure the letters do not form a word in a dictionnary. May it be the whole english dictionnary or just a bad-word dictionnary to avoid things a customer would find of bad-taste.
EDIT: I would also add this only make sense if, as you intend to use it, it's not for a big amount of items because this could get pretty slow the more collisions you get (getting an ID already in the table). Of course, you'll want an indexed table and you'll want to tweak the number of letters in the ID to avoid collision. In this case, with 6 letters, you'd have 26^6 = 308915776 possible unique IDs (minus bad words) which should be enough for your need of 10000.
EDIT:
If you want a combinations of letters and numbers you can use the following code:
$random .= rand(0, 1) ? rand(0, 9) : chr(rand(ord('a'), ord('z')));
#gen_uuid() by gord.
preg_replace got some nasty utf-8 problems, which causes the uid somtimes to contain "+" or "/".
To get around this, you have to explicitly make the pattern utf-8
function gen_uuid($len=8) {
$hex = md5("yourSaltHere" . uniqid("", true));
$pack = pack('H*', $hex);
$tmp = base64_encode($pack);
$uid = preg_replace("#(*UTF8)[^A-Za-z0-9]#", "", $tmp);
$len = max(4, min(128, $len));
while (strlen($uid) < $len)
$uid .= gen_uuid(22);
return substr($uid, 0, $len);
}
Took me quite a while to find that, perhaps it's saves somebody else a headache
You can achieve that with less code:
function gen_uid($l=10){
return substr(str_shuffle("0123456789abcdefghijklmnopqrstuvwxyz"), 0, $l);
}
Result (examples):
cjnp56brdy
9d5uv84zfa
ih162lryez
ri4ocf6tkj
xj04s83egi
There are two ways to obtain a reliably unique ID: Make it so long and variable that the chances of a collision are spectacularly small (as with a GUID) or store all generated IDs in a table for lookup (either in memory or in a DB or a file) to verify uniqueness upon generation.
If you're really asking how you can generate such a short key and guarantee its uniqueness without some kind of duplicate check, the answer is, you can't.
Here's the routine I use for random base62s of any length...
Calling gen_uuid() returns strings like WJX0u0jV, E9EMaZ3P etc.
By default this returns 8 digits, hence a space of 64^8 or roughly 10^14,
this is often enough to make collisions quite rare.
For a larger or smaller string, pass in $len as desired. No limit in length, as I append until satisfied [up to safety limit of 128 chars, which can be removed].
Note, use a random salt inside the md5 [or sha1 if you prefer], so it cant easily be reverse-engineered.
I didn't find any reliable base62 conversions on the web, hence this approach of stripping chars from the base64 result.
Use freely under BSD licence,
enjoy,
gord
function gen_uuid($len=8)
{
$hex = md5("your_random_salt_here_31415" . uniqid("", true));
$pack = pack('H*', $hex);
$uid = base64_encode($pack); // max 22 chars
$uid = ereg_replace("[^A-Za-z0-9]", "", $uid); // mixed case
//$uid = ereg_replace("[^A-Z0-9]", "", strtoupper($uid)); // uppercase only
if ($len<4)
$len=4;
if ($len>128)
$len=128; // prevent silliness, can remove
while (strlen($uid)<$len)
$uid = $uid . gen_uuid(22); // append until length achieved
return substr($uid, 0, $len);
}
Really simple solution:
Make the unique ID with:
$id = 100;
base_convert($id, 10, 36);
Get the original value again:
intval($str,36);
Can't take credit for this as it's from another stack overflow page, but I thought the solution was so elegant and awesome that it was worth copying over to this thread for people referencing this.
You could use the Id and just convert it to base-36 number if you want to convert it back and forth. Can be used for any table with an integer id.
function toUId($baseId, $multiplier = 1) {
return base_convert($baseId * $multiplier, 10, 36);
}
function fromUId($uid, $multiplier = 1) {
return (int) base_convert($uid, 36, 10) / $multiplier;
}
echo toUId(10000, 11111);
1u5h0w
echo fromUId('1u5h0w', 11111);
10000
Smart people can probably figure it out with enough id examples. Dont let this obscurity replace security.
I came up with what I think is a pretty cool solution doing this without a uniqueness check. I thought I'd share for any future visitors.
A counter is a really easy way to guarantee uniqueness or if you're using a database a primary key also guarantees uniqueness. The problem is it looks bad and and might be vulnerable. So I took the sequence and jumbled it up with a cipher. Since the cipher can be reversed, I know each id is unique while still appearing random.
It's python not php, but I uploaded the code here:
https://github.com/adecker89/Tiny-Unique-Identifiers
Letters are pretty, digits are ugly.
You want random strings, but don't want "ugly" random strings?
Create a random number and print it in alpha-style (base-26), like the reservation "numbers" that airlines give.
There's no general-purpose base conversion functions built into PHP, as far as I know, so you'd need to code that bit yourself.
Another alternative: use uniqid() and get rid of the digits.
function strip_digits_from_string($string) {
return preg_replace('/[0-9]/', '', $string);
}
Or replace them with letters:
function replace_digits_with_letters($string) {
return strtr($string, '0123456789', 'abcdefghij');
}
You can also do it like tihs:
public static function generateCode($length = 6)
{
$az = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$azr = rand(0, 51);
$azs = substr($az, $azr, 10);
$stamp = hash('sha256', time());
$mt = hash('sha256', mt_rand(5, 20));
$alpha = hash('sha256', $azs);
$hash = str_shuffle($stamp . $mt . $alpha);
$code = ucfirst(substr($hash, $azr, $length));
return $code;
}
You can do that without unclean/costy stuff like loops, String concatenations or multiple calls to rand(), in a clean and easy to read way. Also, it is better to use mt_rand():
function createRandomString($length)
{
$random = mt_rand(0, (1 << ($length << 2)) - 1);
return dechex($random);
}
If you need the String to have the exact length in any case, just pad the hex number with zeros:
function createRandomString($length)
{
$random = mt_rand(0, (1 << ($length << 2)) - 1);
$number = dechex($random);
return str_pad($number, $length, '0', STR_PAD_LEFT);
}
The "theoretical backdraw" is, that you are limited to PHPs capabilities - but this is more a philosophical issue in that case ;) Let's go through it anyways:
PHP is limited in what it can represent as a hex number doing it like this. This would be $length <= 8 at least on a 32bit system, where PHPs limitation for this should be 4.294.967.295 .
PHPs random number generator also has a maximum. For mt_rand() at least on a 32bit system, it should be 2.147.483.647
So you are theoretically limited to 2.147.483.647 IDs.
Coming back to the topic - the intuitive do { (generate ID) } while { (id is not uniqe) } (insert id) has one drawback and one possible flaw that might drive you straight to darkness...
Drawback: The validation is pessimistic. Doing it like this always requires a check at the database. Having enough keyspace (for example length of 5 for your 10k entries) will quite unlikely cause collisions as often, as it might be comparably less resource consuming to just try to store the data and retry only in case of a UNIQUE KEY error.
Flaw: User A retrieves an ID that gets verified as not taken yet. Then the code will try to insert the data. But in the meantime, User B entered the same loop and unfortunately retrieves the same random number, because User A is not stored yet and this ID was still free. Now the system stores either User B or User A, and when attempting to store the second User, there already is the other one in the meantime - having the same ID.
You would need to handle that exception in any case and need to re-try the insertion with a newly created ID. Adding this whilst keeping the pessimistic checking loop (that you would need to re-enter) will result in quite ugly and hard to follow code. Fortunately the solution to this is the same like the one to the drawback: Just go for it in the first place and try to store the data. In case of a UNIQUE KEY error just retry with a new ID.
Take a lookt at this article
Create short IDs with PHP - Like Youtube or TinyURL
It explains how to generate short unique ids from your bdd ids, like youtube does.
Actually, the function in the article is very related to php function base_convert which converts a number from a base to another (but is only up to base 36).
10 chars:
substr(uniqid(),-10);
5 binary chars:
hex2bin( substr(uniqid(),-10) );
8 base64 chars:
base64_encode( hex2bin( substr(uniqid(),-10) ) );
function rand_str($len = 12, $type = '111', $add = null) {
$rand = ($type[0] == '1' ? 'abcdefghijklmnpqrstuvwxyz' : '') .
($type[1] == '1' ? 'ABCDEFGHIJKLMNPQRSTUVWXYZ' : '') .
($type[2] == '1' ? '123456789' : '') .
(strlen($add) > 0 ? $add : '');
if(empty($rand)) $rand = sha1( uniqid(mt_rand(), true) . uniqid( uniqid(mt_rand(), true), true) );
return substr(str_shuffle( str_repeat($rand, 2) ), 0, $len);
}
If you do like a longer version of unique Id use this:
$uniqueid = sha1(md5(time()));
Best Answer Yet: Smallest Unique "Hash Like" String Given Unique Database ID - PHP Solution, No Third Party Libraries Required.
Here's the code:
<?php
/*
THE FOLLOWING CODE WILL PRINT:
A database_id value of 200 maps to 5K
A database_id value of 1 maps to 1
A database_id value of 1987645 maps to 16LOD
*/
$database_id = 200;
$base36value = dec2string($database_id, 36);
echo "A database_id value of 200 maps to $base36value\n";
$database_id = 1;
$base36value = dec2string($database_id, 36);
echo "A database_id value of 1 maps to $base36value\n";
$database_id = 1987645;
$base36value = dec2string($database_id, 36);
echo "A database_id value of 1987645 maps to $base36value\n";
// HERE'S THE FUNCTION THAT DOES THE HEAVY LIFTING...
function dec2string ($decimal, $base)
// convert a decimal number into a string using $base
{
//DebugBreak();
global $error;
$string = null;
$base = (int)$base;
if ($base < 2 | $base > 36 | $base == 10) {
echo 'BASE must be in the range 2-9 or 11-36';
exit;
} // if
// maximum character string is 36 characters
$charset = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
// strip off excess characters (anything beyond $base)
$charset = substr($charset, 0, $base);
if (!ereg('(^[0-9]{1,50}$)', trim($decimal))) {
$error['dec_input'] = 'Value must be a positive integer with < 50 digits';
return false;
} // if
do {
// get remainder after dividing by BASE
$remainder = bcmod($decimal, $base);
$char = substr($charset, $remainder, 1); // get CHAR from array
$string = "$char$string"; // prepend to output
//$decimal = ($decimal - $remainder) / $base;
$decimal = bcdiv(bcsub($decimal, $remainder), $base);
} while ($decimal > 0);
return $string;
}
?>

Categories