4

is there any way to make 2 way encryption/decryption for an integer (or string) Please note that I am not looking for encoding

i need something like this

crypting (100) --> 24694

crypting (101) --> 9564jh4 or 45216 or gvhjdfT or whatever ...

decrypting (24694) --> 100

I don't need encoding because it`s bijective

base64_encode(100) -->MTAw

base64_encode(101) -->MTAx

I hope I will find a way here to encrypt/decrypt PURE NUMBERS (computer love numbers, it's faster)

rock321987
  • 10,942
  • 1
  • 30
  • 43
Kratos
  • 43
  • 1
  • 1
  • 5
  • 2
    Both answers currently given are not cryptographically safe. Could you specify the possible output format and level of security that you require? – Maarten Bodewes Jun 22 '14 at 13:23
  • 1
    Normally encryption *is* bijective. You can make it more random by using a unique IV in most modes of encryption. – Maarten Bodewes Jun 22 '14 at 13:39
  • I just need a simple way to encrypt/decrypt in two ways but not with bijection like "encoding" does crypt(1) -> 15 decrypt(15) -> 1 encoding is bijective and doesn't prevent user from guessing the next id/code/or..whatever 1-> a 2-> b 10-> j – Kratos Jun 22 '14 at 13:40
  • is there any simple md5() or hash() alike function that could be reversed – Kratos Jun 22 '14 at 13:42
  • Cryptographic hashes are created specifically to be one way. You can of course keep a table though, but in that case you may as well use a large enough random number. – Maarten Bodewes Jun 22 '14 at 13:56
  • @Kratos just use AES, DES or another encryption method instead of trying custom encryption schemes. And your definition of bijective is off. A bijective method does not mean it is predictable. It means each input has a unique output. (which is a requirement for decryption) – EWit Jun 23 '14 at 06:11

7 Answers7

11
function decrypt($string, $key) {
$result = '';
$string = base64_decode($string);
for($i=0; $i<strlen($string); $i++) {
$char = substr($string, $i, 1);
$keychar = substr($key, ($i % strlen($key))-1, 1);
$char = chr(ord($char)-ord($keychar));
$result.=$char;
}
return $result;
}

function encrypt($string, $key) {
$result = '';
for($i=0; $i<strlen($string); $i++) {
$char = substr($string, $i, 1);
$keychar = substr($key, ($i % strlen($key))-1, 1);
$char = chr(ord($char)+ord($keychar));
$result.=$char;
}
return base64_encode($result);
}
Muhammad Ali
  • 1,992
  • 1
  • 13
  • 20
9

Have you tried looking into ROT-13?

More serious answer: from this SO answer, you can use:

function numhash($n) {
    return (((0x0000FFFF & $n) << 16) + ((0xFFFF0000 & $n) >> 16));
}

numhash(42);           // 2752512
numhash(numhash(42));   // 42
Petr Cibulka
  • 2,452
  • 4
  • 28
  • 45
Andreas
  • 622
  • 4
  • 11
  • Yes, but I changed it to PHP -- the operators are the same any way! – Andreas Jun 23 '14 at 10:48
  • 2
    It would seem, that this function stops decode properly if number is more than 10 digits long. Is that correct? If so, it might be good to add to the answer. `numhash(numhash(1111111111)); // 2521176519` – Petr Cibulka Nov 14 '17 at 04:03
  • 1
    Also, a noob question - will resulting hash ever start with zero? Thanks! – Petr Cibulka Nov 14 '17 at 04:14
2

64bit support. negative number support. and a little bit security salt.

@Petr Cibulka

class NumHash {

  private static $SALT = 0xd0c0adbf;

  public static function encrypt($n) {
    return (PHP_INT_SIZE == 4 ? self::encrypt32($n) : self::encrypt64($n)) ^ self::$SALT;
  }

  public static function decrypt($n) {
    $n ^= self::$SALT;
    return PHP_INT_SIZE == 4 ? self::decrypt32($n) : self::decrypt64($n);
  }

  public static function encrypt32($n) {
    return ((0x000000FF & $n) << 24) + (((0xFFFFFF00 & $n) >> 8) & 0x00FFFFFF);
  }

  public static function decrypt32($n) {
    return ((0x00FFFFFF & $n) << 8) + (((0xFF000000 & $n) >> 24) & 0x000000FF);
  }

  public static function encrypt64($n) {
    /*
    echo PHP_EOL . $n . PHP_EOL;
    printf("n   :%20X\n", $n);
    printf("<<  :%20X\n", (0x000000000000FFFF & $n) << 48);
    printf(">>  :%20X\n", (0xFFFFFFFFFFFF0000 & $n) >> 16);
    printf(">>& :%20X\n", ((0xFFFFFFFFFFFF0000 & $n) >> 16) & 0x0000FFFFFFFFFFFF);
    printf("=   :%20X\n", ((0x000000000000FFFF & $n) << 48) + (((0xFFFFFFFFFFFF0000 & $n) >> 16) & 0x0000FFFFFFFFFFFF));
    /* */
    return ((0x000000000000FFFF & $n) << 48) + (((0xFFFFFFFFFFFF0000 & $n) >> 16) & 0x0000FFFFFFFFFFFF);
  }

  public static function decrypt64($n) {
    /*
    echo PHP_EOL;
    printf("n   :%20X\n", $n);
    printf("<<  :%20X\n", (0x0000FFFFFFFFFFFF & $n) << 16);
    printf(">>  :%20X\n", (0xFFFF000000000000 & $n) >> 48);
    printf(">>& :%20X\n", ((0xFFFF000000000000 & $n) >> 48) & 0x000000000000FFFF);
    printf("=   :%20X\n", ((0x0000FFFFFFFFFFFF & $n) << 16) + (((0xFFFF000000000000 & $n) >> 48) & 0x000000000000FFFF));
    /* */
    return ((0x0000FFFFFFFFFFFF & $n) << 16) + (((0xFFFF000000000000 & $n) >> 48) & 0x000000000000FFFF);
  }
}

var_dump(NumHash::encrypt(42));
var_dump(NumHash::encrypt(NumHash::encrypt(42)));
var_dump(NumHash::decrypt(NumHash::encrypt(42)));

echo PHP_EOL;


// stability test

var_dump(NumHash::decrypt(NumHash::encrypt(0)));
var_dump(NumHash::decrypt(NumHash::encrypt(-1)));
var_dump(NumHash::decrypt(NumHash::encrypt(210021200651)));
var_dump(NumHash::decrypt(NumHash::encrypt(210042420501)));

Here's the step by step(remove the comments):

210042420501
n   :          30E780FD15
<<  :    FD15000000000000
>>  :              30E780
>>& :              30E780
=   :    FD1500000030E780

n   :    FD1500000030E780
<<  :          30E7800000
>>  :    FFFFFFFFFFFFFD15
>>& :                FD15
=   :          30E780FD15
int(210042420501)
Gavin Kwok
  • 98
  • 6
1

This may be more than what you are looking for, but I thought it would be fun to construct as an answer. Here is a simple format-preserving encryption which takes any 16-bit number (i.e. from 0 to 65535) and encrypts it to another 16-bit number and back again, based on a 128-bit symmetric key. You can build something like this.

It's deterministic, in that any input always encrypts to the same output with the same key, but for any number n, there is no way to predict the output for n + 1.

# Written in Ruby -- implement in PHP left as an exercise for the reader
require 'openssl'

def encrypt_block(b, k)
    cipher = OpenSSL::Cipher::Cipher.new 'AES-128-ECB'
    cipher.encrypt
    cipher.key = k
    cipher.update(b) + cipher.final
end

def round_key(i, k)
    encrypt_block(i.to_s, k)
end

def prf(c, k)
    encrypt_block(c.chr, k)[0].ord
end

def encrypt(m, key)
    left = (m >> 8) & 0xff
    right = m & 0xff
    (1..7).each do |i|
        copy = right
        right = left ^ prf(right, round_key(i, key))
        left = copy
    end
    (left << 8) + right
end

def decrypt(m, key)
    left = (m >> 8) & 0xff
    right = m & 0xff
    (1..7).each do |i|
        copy = left
        left = right ^ prf(left, round_key(8 - i, key))
        right = copy
    end
    (left << 8) + right
end

key = "0123456789abcdef"

# This shows no fails and no collisions
x = Hash.new
(0..65535).each do |n|
    c = encrypt(n, key)
    p = decrypt(c, key)
    puts "FAIL" if n != p
    puts "COLLISION" if x.has_key? c
    x[c] = n
end

# Here are some samples
(0..10).each do |n|
    c = encrypt(n, key)
    p = decrypt(c, key)
    puts "#{n} --> #{c}"
end
(0..10).each do
    n = rand(65536)
    c = encrypt(n, key)
    p = decrypt(c, key)
    puts "#{n} --> #{c}"
end

Some examples:

0 --> 39031
1 --> 38273
2 --> 54182
3 --> 59129
4 --> 18743
5 --> 7628
6 --> 8978
7 --> 15474
8 --> 49783
9 --> 24614
10 --> 58570
1343 --> 19234
19812 --> 18968
6711 --> 31505
42243 --> 29837
62617 --> 52334
27174 --> 56551
3624 --> 31768
38685 --> 40918
27826 --> 42109
62589 --> 25562
20377 --> 2670
Jim Flood
  • 8,144
  • 3
  • 36
  • 48
  • if I have to handle bigger numbers (32bits) gow does I have to modify the algorithm? – Luca C. May 10 '18 at 08:36
  • @Luca C. For 32 bits it is simple. Where you see 16 bits split into left and right of 8 bits each, you would split into left and right of 16 bits each. (m >> 16) & 0xffff and m & 0xffff, and also the prf has to output a 16-bit value. Maybe [0].ord << 8 + [1].ord ?? – Jim Flood May 10 '18 at 19:15
  • Check this Coursera video on format-preserving encryption: https://www.coursera.org/learn/crypto/lecture/aFRSZ/format-preserving-encryption. It's a little dense, but perhaps gives you ideas of where to look next. – Jim Flood May 10 '18 at 19:17
1

a simply function that mangles integers keeping the smaller numbers small (if you need to preserve magnitude):

    function switchquartets($n){
        return ((0x0000000F & $n) << 4) + ((0x000000F0& $n)>>4)
        + ((0x00000F00 & $n) << 4) + ((0x0000F000& $n)>>4)
        + ((0x000F0000 & $n) << 4) + ((0x00F00000& $n)>>4)
        + ((0x0F000000 & $n) << 4) + ((0xF0000000& $n)>>4);
    }
Luca C.
  • 11,714
  • 1
  • 86
  • 77
0

You can simply use 3DES CBC mode encryption to perform the operation. If you want to only accept values that you've generated, you can add a HMAC to the ciphertext. If the HMAC is not enough, you could rely on the format of the numbers for this particular scheme. If you want users not to be able to copy the values to each other, you can use a random IV.

So basically you store the number as a 8 byte or 8 ASCII character string by left-padding with zero values. Then you perform an encryption of a single block. This allows you to have 2^64 or 10^8 numbers. You can base 64 encrypt the result, replacing the + and / characters with the URL-safe - and _ characters.

Note that this encryption/decryption is of course bijective (or a permutation, as it is usually called in crypto). That's OK though, as the output is large enough for an attacker to have trouble guessing a value.

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
0

method "double square":

function dsCrypt($input,$decrypt=false) {
            $o = $s1 = $s2 = array(); // Arrays for: Output, Square1, Square2
            // формируем базовый массив с набором символов
            $basea = array('?','(','@',';','$','#',"]","&",'*'); // base symbol set
            $basea = array_merge($basea, range('a','z'), range('A','Z'), range(0,9) );
            $basea = array_merge($basea, array('!',')','_','+','|','%','/','[','.',' ') );
            $dimension=9; // of squares
            for($i=0;$i<$dimension;$i++) { // create Squares
                for($j=0;$j<$dimension;$j++) {
                    $s1[$i][$j] = $basea[$i*$dimension+$j];
                    $s2[$i][$j] = str_rot13($basea[($dimension*$dimension-1) - ($i*$dimension+$j)]);
                }
            }
            unset($basea);
            $m = floor(strlen($input)/2)*2; // !strlen%2
            $symbl = $m==strlen($input) ? '':$input[strlen($input)-1]; // last symbol (unpaired)
            $al = array();
            // crypt/uncrypt pairs of symbols
            for ($ii=0; $ii<$m; $ii+=2) {
                $symb1 = $symbn1 = strval($input[$ii]);
                $symb2 = $symbn2 = strval($input[$ii+1]);
                $a1 = $a2 = array();
                for($i=0;$i<$dimension;$i++) { // search symbols in Squares
                    for($j=0;$j<$dimension;$j++) {
                        if ($decrypt) {
                            if ($symb1===strval($s2[$i][$j]) ) $a1=array($i,$j);
                            if ($symb2===strval($s1[$i][$j]) ) $a2=array($i,$j);
                            if (!empty($symbl) && $symbl===strval($s2[$i][$j])) $al=array($i,$j);
                        }
                        else {
                            if ($symb1===strval($s1[$i][$j]) ) $a1=array($i,$j);
                            if ($symb2===strval($s2[$i][$j]) ) $a2=array($i,$j);
                            if (!empty($symbl) && $symbl===strval($s1[$i][$j])) $al=array($i,$j);
                        }
                    }
                }
                if (sizeof($a1) && sizeof($a2)) {
                    $symbn1 = $decrypt ? $s1[$a1[0]][$a2[1]] : $s2[$a1[0]][$a2[1]];
                    $symbn2 = $decrypt ? $s2[$a2[0]][$a1[1]] : $s1[$a2[0]][$a1[1]];
                }
                $o[] = $symbn1.$symbn2;
            }
            if (!empty($symbl) && sizeof($al)) // last symbol
                $o[] = $decrypt ? $s1[$al[1]][$al[0]] : $s2[$al[1]][$al[0]];
            return implode('',$o);
        }
    echo dsCrypt('586851105743');
    echo '<br />'.dsCrypt('tdtevmdrsdoc', 1);