Mar
21
2013
C# // PHP

Unique Hash (Tiny Code) ported to C#

In my search for efficient algorithms that create unique indexes based on arbitrary values, I found this wonderful snippet of code on a Developer’s blog.

It seems useful when creating unique identifiers that need to be publicly visible while still creating some obfuscation as to how they are sequentially calculated. Since this solution uses base 62 numbering for the result it will also use less characters when dealing with larger values in a string.

This method is definitely not a replacement for encryption or hashing so don’t use for secure information like passwords. Also, keep in mind since it uses base 62 numbering the result is case-sensitive.

Anyways, I found the code so useful I ported it to C#. I kept the primes the same as they were in the PHP code, but treat the primes as your secret keys and change them up when you implement this code (keep the primes greater than .5*62^n). If you have any revisions for optimization or efficiency or find a bug, please let me know and I’ll revise it.

 C# |  copy code |? 
01
using System;
02
using System.Collections.Generic;
03
using System.Numerics;
04
 
05
public class PseudoCrypt
06
{
07
    private static long[][] golden_primes = new long[][]{
08
            new long[]{1,1},
09
            new long[]{41,59},
10
            new long[]{2377,1677},
11
            new long[]{147299,187507},
12
            new long[]{9132313,5952585},
13
            new long[]{566201239,643566407},
14
            new long[]{35104476161,22071637057},
15
            new long[]{2176477521929,294289236153},
16
            new long[]{134941606358731,88879354792675},
17
            new long[]{8366379594239857,7275288500431249},
18
            new long[]{518715534842869223,280042546585394647}
19
        };
20
 
21
    /* Ascii : 0  9, A  Z, a  z */
22
    private static int[] chars62 = new int[]
23
        {
24
            48,49,50,51,52,53,54,55,56,57,65,
25
            66,67,68,69,70,71,72,73,74,75,
26
            76,77,78,79,80,81,82,83,84,85,
27
            86,87,88,89,90,97,98,99,100,101,
28
            102,103,104,105,106,107,108,109,110,
29
            111,112,113,114,115,116,117,118,119,
30
            120,121,122
31
        };
32
 
33
    public static string base62( BigInteger i ) {
34
        List<char> key = new List<char>();
35
 
36
        while ( BigInteger.Compare( i, 0 ) > 0 ) {
37
            key.Add( (char)chars62[(int)( i % 62 )] );
38
            i = BigInteger.Divide( i, 62 );
39
        }
40
 
41
        key.Reverse();
42
        return new string( key.ToArray() );
43
    }
44
 
45
    public static string hash( long num, int len = 5 ) {
46
        BigInteger dec = BigInteger.Multiply( num, golden_primes[len][0] ) % BigInteger.Pow( 62, len );
47
        return base62( dec ).PadLeft( len, '0' );
48
    }
49
 
50
 
51
    public static long unbase62( string key ) {
52
        long val = 0;
53
 
54
        for ( int i = key.Length - 1; i >= 0; i-- ) {
55
            val += Array.IndexOf( chars62, (int)key[i] )
56
                * (long)Math.Pow( 62, key.Length - 1 - i );
57
        }
58
        return val;
59
    }
60
 
61
    public static long unhash( string hash ) {
62
        return (long)(
63
            BigInteger.Multiply( unbase62( hash ), golden_primes[hash.Length][1] )
64
                % BigInteger.Pow( 62, hash.Length ) );
65
    }
66
}
67

3 Comments + Add Comment

  • Old post, but still good code. I also had found the original site that seems to be gone now. I recently expanded the code to be (hopefully) a little more generic and mathematically more rigorous. BTW, I started the search for the multiplier “a” at the same Golden Ration distance, but starting at 0.5 * m as you suggest should be fine too. And according to LCG theory, multiplier “a” cannot be prime, and must meet the other defined criteria. I will try to attach the PHP code below:

    class UOID
    {
    // Initialize the class with up to 3 parameters:
    // $idc = new UOID( $baseN, $len, $ran );

    // $baseN is the desired numeric base system
    // 11 <= $baseN <= 64, default is 36.

    // $len is the desired string length of baseN characters
    // 2 <= $len <= 20, default is 10.

    // $ran is a randomizing parameter to change the calculation
    // of multiplier a below
    // 0 <= $ran < 0.5 * m * ( 1 – 1/phi ), an even integer, default is 0.

    private static $baseN = 36;
    private static $len = 10;
    private static $ran = 0;

    // Linear Congruential Generator (LCG)
    // X[n+1] = ( a*X[n] + c ) % m;
    // m = modulus, 0 < m = baseN ^ len
    // a = multiplier, 0 < a < m
    // c = increment, 0 <= c array( 11 ),
    12 => array( 2, 3 ),
    13 => array( 13 ),
    14 => array( 2, 7 ),
    15 => array( 3, 5 ),
    16 => array( 2 ),
    17 => array( 17 ),
    18 => array( 2, 3 ),
    19 => array( 19 ),
    20 => array( 2, 5 ),
    21 => array( 3, 7 ),
    22 => array( 2, 11 ),
    23 => array( 23 ),
    24 => array( 2, 3 ),
    25 => array( 5 ),
    26 => array( 2, 13 ),
    27 => array( 3 ),
    28 => array( 2, 7 ),
    29 => array( 29 ),
    30 => array( 2, 3, 5 ),
    31 => array( 31 ),
    32 => array( 2 ),
    33 => array( 3, 11 ),
    34 => array( 2, 17 ),
    35 => array( 5, 7 ),
    36 => array( 2, 3 ),
    37 => array( 37 ),
    38 => array( 2, 19 ),
    39 => array( 3, 13 ),
    40 => array( 2, 5 ),
    41 => array( 41 ),
    42 => array( 2, 3, 7 ),
    43 => array( 43 ),
    44 => array( 2, 11 ),
    45 => array( 3, 5 ),
    46 => array( 2, 23 ),
    47 => array( 47 ),
    48 => array( 2, 3 ),
    49 => array( 7 ),
    50 => array( 2, 5 ),
    51 => array( 3, 17 ),
    52 => array( 2, 13 ),
    53 => array( 53 ),
    54 => array( 2, 3 ),
    55 => array( 5, 11 ),
    56 => array( 2, 7 ),
    57 => array( 3, 19 ),
    58 => array( 2, 29 ),
    59 => array( 59 ),
    60 => array( 2, 3, 5 ),
    61 => array( 61 ),
    62 => array( 2, 31 ),
    63 => array( 3, 7 ),
    64 => array( 2 )
    );

    private static $chrmap = array
    (
    0 => ’0′,
    1 => ’1′,
    2 => ’2′,
    3 => ’3′,
    4 => ’4′,
    5 => ’5′,
    6 => ’6′,
    7 => ’7′,
    8 => ’8′,
    9 => ’9′,
    10 => ‘A’,
    11 => ‘B’,
    12 => ‘C’,
    13 => ‘D’,
    14 => ‘E’,
    15 => ‘F’,
    16 => ‘G’,
    17 => ‘H’,
    18 => ‘I’,
    19 => ‘J’,
    20 => ‘K’,
    21 => ‘L’,
    22 => ‘M’,
    23 => ‘N’,
    24 => ‘O’,
    25 => ‘P’,
    26 => ‘Q’,
    27 => ‘R’,
    28 => ‘S’,
    29 => ‘T’,
    30 => ‘U’,
    31 => ‘V’,
    32 => ‘W’,
    33 => ‘X’,
    34 => ‘Y’,
    35 => ‘Z’,
    36 => ‘a’,
    37 => ‘b’,
    38 => ‘c’,
    39 => ‘d’,
    40 => ‘e’,
    41 => ‘f’,
    42 => ‘g’,
    43 => ‘h’,
    44 => ‘i’,
    45 => ‘j’,
    46 => ‘k’,
    47 => ‘l’,
    48 => ‘m’,
    49 => ‘n’,
    50 => ‘o’,
    51 => ‘p’,
    52 => ‘q’,
    53 => ‘r’,
    54 => ‘s’,
    55 => ‘t’,
    56 => ‘u’,
    57 => ‘v’,
    58 => ‘w’,
    59 => ‘x’,
    60 => ‘y’,
    61 => ‘z’,
    62 => ‘-’,
    63 => ‘_’,
    ’0′ => 0,
    ’1′ => 1,
    ’2′ => 2,
    ’3′ => 3,
    ’4′ => 4,
    ’5′ => 5,
    ’6′ => 6,
    ’7′ => 7,
    ’8′ => 8,
    ’9′ => 9,
    ‘A’ => 10,
    ‘B’ => 11,
    ‘C’ => 12,
    ‘D’ => 13,
    ‘E’ => 14,
    ‘F’ => 15,
    ‘G’ => 16,
    ‘H’ => 17,
    ‘I’ => 18,
    ‘J’ => 19,
    ‘K’ => 20,
    ‘L’ => 21,
    ‘M’ => 22,
    ‘N’ => 23,
    ‘O’ => 24,
    ‘P’ => 25,
    ‘Q’ => 26,
    ‘R’ => 27,
    ‘S’ => 28,
    ‘T’ => 29,
    ‘U’ => 30,
    ‘V’ => 31,
    ‘W’ => 32,
    ‘X’ => 33,
    ‘Y’ => 34,
    ‘Z’ => 35,
    ‘a’ => 36,
    ‘b’ => 37,
    ‘c’ => 38,
    ‘d’ => 39,
    ‘e’ => 40,
    ‘f’ => 41,
    ‘g’ => 42,
    ‘h’ => 43,
    ‘i’ => 44,
    ‘j’ => 45,
    ‘k’ => 46,
    ‘l’ => 47,
    ‘m’ => 48,
    ‘n’ => 49,
    ‘o’ => 50,
    ‘p’ => 51,
    ‘q’ => 52,
    ‘r’ => 53,
    ‘s’ => 54,
    ‘t’ => 55,
    ‘u’ => 56,
    ‘v’ => 57,
    ‘w’ => 58,
    ‘x’ => 59,
    ‘y’ => 60,
    ‘z’ => 61,
    ‘-’ => 62,
    ‘_’ => 63
    );

    // Class initializer

    function __construct()
    {
    switch( func_num_args() )
    {
    case 0:
    break;
    case 1:
    self::$baseN = func_get_arg( 0 );
    break;
    case 2:
    self::$baseN = func_get_arg( 0 );
    self::$len = func_get_arg( 1 );
    default:
    self::$baseN = func_get_arg( 0 );
    self::$len = func_get_arg( 1 );
    self::$ran = func_get_arg( 2 );
    }
    if( self::$baseN 64 ) { self::$baseN = 64; }
    if( self::$len 20 ) { self::$len = 20; }

    self::$phi = bcdiv( bcadd( bcsqrt( 5, 24 ), 1, 24 ), 2, 20 );
    self::$m = bcpow( self::$baseN, self::$len );

    if( bccomp( self::$ran, 0 ) < 1 ) { self::$ran = 0; }
    else
    {
    $z = bcdiv( bcmul( self::$m, bcsub( 1, bcdiv( 1, self::$phi, 20 ), 20 ), 20 ), 2 );
    if( bccomp( $z, self::$ran ) 0 ) { self::$ran = bcadd( self::$ran, 1 ); }

    self::$c = self::calcC();
    self::$a = self::calcA();
    self::$mmi = self::calcMMI();
    }

    private static function calcC()
    {
    $z = count( self::$primes );
    for( $i = 0; $i < $z; $i++ )
    {
    $y = count( self::$factors[self::$baseN] );
    $f = 0;
    for( $j = 0; $j < $y; $j++ )
    {
    if( self::$primes[$i] !== self::$factors[self::$baseN][$j] ) { $f++; }
    }
    if( $f === $y ) { return self::$primes[$i]; }
    }
    return 0;
    }

    private static function calcA()
    {
    $a = bcadd( bcdiv( self::$m, self::$phi ), self::$ran );
    if( bccomp( bcmod( $a, 2 ), 0 ) < 1 ) { $a = bcadd( $a, 1 ); }

    while( bccomp( $a, self::$m ) < 0 )
    {
    if( self::hasFactors( bcsub( $a, 1 ), self::$factors[self::$baseN] ) )
    {
    if( bccomp( bcmod( self::$m, 4 ), 0 ) < 1 )
    {
    if( bccomp( bcmod( bcsub( $a, 1 ), 4 ), 0 ) < 1 ) { return $a; }
    }
    else { return $a; }
    }
    $a = bcadd( $a, 2 );
    }
    return 0;
    }

    private static function hasFactors( $num, $farray )
    {
    $z = count( $farray );
    for( $i = 0; $i < $z; $i++ )
    {
    if( bccomp( bcmod( $num, $farray[$i] ), 0 ) < 1 ) { continue; }
    else { return FALSE; }
    }
    return TRUE;
    }

    private static function calcMMI()
    {
    $a = self::$a;
    $m = self::$m;
    if( bccomp( $m, 0 ) < 0 ) { $m = bcsub( 0, $m ); }
    if( bccomp( $a, 0 ) 0 ) { return -1; }
    if( bccomp( $t, 0 ) 0 )
    {
    $rem = bcmod( $int, $N );
    $num .= self::$chrmap[$rem];
    $int = bcdiv( $int, $N );
    }
    return strrev( $num );
    }

    public static function baseN2dec( $num, $N )
    {
    $dec = 0;
    $rev = strrev( $num );
    foreach( str_split( $rev ) as $i => $chr )
    {
    $dec = bcadd( bcmul( self::$chrmap[$chr], bcpow( $N, $i ) ), $dec );
    }
    return $dec;
    }

    public static function encode( $num )
    {
    $val = bcmod( bcadd( bcmul( $num, self::$a ), self::$c ), self::$m );
    return str_pad( self::dec2baseN( $val, self::$baseN ), self::$len, ’0′, STR_PAD_LEFT );
    }

    public static function decode( $str )
    {
    $val = bcsub( self::baseN2dec( $str, self::$baseN ), self::$c );
    if( bccomp( $val, 0 ) < 0 ) { $val = bcadd( $val, self::$m ); }
    return bcmod( bcmul( $val, self::$mmi ), self::$m );
    }
    }

  • Hmmmmm. The code got slightly butchered. Here is the correction:

    // Linear Congruential Generator (LCG)
    // X[n+1] = ( a*X[n] + c ) % m;
    // m = modulus, 0 < m = baseN ^ len
    // a = multiplier, 0 < a < m
    // c = increment, 0 <= c array( 11 ),

  • Sorry. The post is screwing up the code and IDK how to escape it to prevent it from happening. One last attempt with just the code, no comments:

    private static $m = 0;
    private static $a = 0;
    private static $c = 0;
    private static $mmi = 0;

    // Golden Ratio, phi = ( 1 + sqrt( 5 ) ) / 2

    private static $phi = 0;

    // Lookup tables

    private static $primes = array( 5, 7, 11, 13, 17, 19, 23, 29 );

    private static $factors = array
    (
    11 => array( 11 ),
    .
    .
    .
    .
    .

Leave a comment