如何在PHP中加密/解密整数

4
有没有办法对整数(或字符串)进行双向加密/解密?请注意,我不是在寻找编码。
我需要像这样的东西:
加密 (100) --> 24694 加密 (101) --> 9564jh4 或 45216 或 gvhjdfT 或其他... 解密 (24694) --> 100
我不需要编码,因为它是双射的。
base64_encode(100) -->MTAw base64_encode(101) -->MTAx
我希望能在这里找到一种加密/解密纯数字的方法(计算机喜欢数字,速度更快)。

2
目前给出的两个答案都不是加密安全的。您能否指定所需的可能输出格式和安全级别? - Maarten Bodewes
1
通常加密双射的。你可以在大多数加密模式下使用唯一的IV使其更随机。 - Maarten Bodewes
我只需要一种简单的加密/解密方式,但不像“编码”那样具有双射性。 crypt(1) -> 15 decrypt(15) -> 1编码是双射的,并且不能防止用户猜测下一个ID /代码/或其他内容。 1-> a 2-> b 10-> j - Kratos
有没有类似于md5()或hash()的简单函数可以被反转? - Kratos
密码散列函数是专为单向而创建的。当然,您可以保留一张表,但在这种情况下,您可能会使用足够大的随机数。 - Maarten Bodewes
@Kratos,您应该使用AES、DES或其他加密方法来代替尝试自定义加密方案。而且您对双射的定义有偏差。双射方法并不意味着它是可预测的,而是意味着每个输入都有一个唯一的输出,这是解密所需的要求。 - EWit
7个回答

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);
}

完美,正是我所寻找的。 - Louwki
看起来很有趣,但 $string 和 $key 是什么?有人能解释一下吗? - rahul
"String" 是您想要加密的关键字,"$key" 应该是解密该关键字的密码。 - Muhammad Ali

9

你尝试过了解ROT-13吗?

更严肃的回答:从这个SO答案中,可以使用以下方法:

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

numhash(42);           // 2752512
numhash(numhash(42));   // 42

是的,但我改成了PHP - 无论如何运算符都是相同的! - Andreas
2
如果数字超过10位数,似乎这个函数停止解码。是这样吗?如果是的话,将其添加到答案中可能会很好。numhash(numhash(1111111111)); // 2521176519 - Petr Cibulka
1
还有一个新手问题 - 生成的哈希值会以零开头吗?谢谢! - Petr Cibulka

2

64位支持。负数支持。稍微加点安全盐。

@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)));

以下是逐步操作(删除注释):
210042420501
n   :          30E780FD15
<<  :    FD15000000000000
>>  :              30E780
>>& :              30E780
=   :    FD1500000030E780

n   :    FD1500000030E780
<<  :          30E7800000
>>  :    FFFFFFFFFFFFFD15
>>& :                FD15
=   :          30E780FD15
int(210042420501)

谢谢,看起来是最快的方法。 - magallanes

1
一个简单的函数,对整数进行处理,保持较小的数字不变(如果需要保留幅度):
    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);
    }

很好,谢谢,正是我所需要的。 - Softmixt

1

这可能超出了您的需求,但我认为构建它作为答案会很有趣。这是一种基于128位对称密钥的简单格式保留加密,将任何16位数字(即从0到65535)加密为另一个16位数字,然后再解密回来。您可以构建类似这样的东西。

它是确定性的,也就是说,使用相同的密钥进行加密时,任何输入始终加密为相同的输出,但对于任何数字n,无法预测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

一些例子:

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

如果我需要处理更大的数字(32位),我需要如何修改算法? - Luca C.
@Luca C. 对于32位来说很简单。在16位被分成左右两个8位的情况下,你需要将其分成左右两个16位。 (m >> 16) & 0xffff 和 m & 0xffff,而且prf还必须输出一个16位的值。也许是 [0].ord << 8 + [1].ord ?? - Jim Flood
请查看Coursera关于格式保留加密的视频:https://www.coursera.org/learn/crypto/lecture/aFRSZ/format-preserving-encryption。虽然有些难懂,但或许能为你提供下一步寻找方向的灵感。 - Jim Flood

0

您可以使用3DES CBC模式加密来执行操作。如果您只想接受您生成的值,可以在密文中添加HMAC。如果HMAC不够安全,您可以依赖于数字的格式特定方案。如果您希望用户无法相互复制这些值,可以使用随机IV。

因此,基本上您将数字存储为8字节或8个ASCII字符字符串,并通过左填充零值进行加密。然后,您执行单个块的加密。这使您可以拥有2^64或10^8个数字。您可以对结果进行基于64的加密,将+/字符替换为URL安全的-_字符。

请注意,此加密/解密当然是双射的(或者通常在加密中称为置换)。虽然如此,输出足够大,以至于攻击者很难猜测一个值。


0

方法 "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);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接