在PHP中检测base64编码?

36

有没有办法在PHP中检测字符串是否已经进行了base64编码?

我们正在将一些存储从纯文本转换为base64,并且其中的一部分存储在需要更新的Cookie中。如果文本尚未编码,我想重置它们的Cookie,否则保持不变。

12个回答

26

对于已经回答过的问题,我很抱歉回复晚了。但是我不认为base64_decode($x,true)是解决这个问题的好方法。实际上,可能没有一个非常好的方法可以针对任何输入都有效。例如,我可以将许多错误的值放入$x中,并且不会得到错误的返回值。

var_dump(base64_decode('wtf mate',true));
string(5) "���j�"

var_dump(base64_decode('This is definitely not base64 encoded',true));
string(24) "N���^~)��r��[jǺ��ܡם"

我认为除了严格的返回值检查外,您还需要进行解码后的验证。最可靠的方法是,如果您能对已知的一组可能值进行解码,然后进行检查。

另一个更普遍的解决方案(长字符串更接近100%准确,对于短字符串不准确)是检查输出是否有很多超出正常范围的 utf-8(或任何编码方式)字符。

请参见以下示例:

<?php
$english = array();
foreach (str_split('az019AZ~~~!@#$%^*()_+|}?><": Iñtërnâtiônàlizætiøn') as $char) {
  echo ord($char) . "\n";
  $english[] = ord($char);
}
  echo "Max value english = " . max($english) . "\n";

$nonsense = array();
echo "\n\nbase64:\n";
foreach (str_split(base64_decode('Not base64 encoded',true)) as $char) {
  echo ord($char) . "\n";
  $nonsense[] = ord($char);
}

  echo "Max nonsense = " . max($nonsense) . "\n";

?>

结果:

Max value english = 195
Max nonsense = 233

所以你可以这样做:
if ( $maxDecodedValue > 200 ) {} //decoded string is Garbage - original string not base64 encoded

else {} //decoded string is useful - it was base64 encoded

你应该使用解码值的mean()而不是max(),我在这个示例中只是使用了max(),因为PHP中没有内置的mean()函数。您使用哪种度量(mean、max等)针对什么阈值(例如200)取决于您的预估使用情况。

总之,唯一的胜利之举是不参与其中。我会尽量避免首先辨别base64。


25
function is_base64_encoded($data)
{
    if (preg_match('%^[a-zA-Z0-9/+]*={0,2}$%', $data)) {
       return TRUE;
    } else {
       return FALSE;
    }
};

is_base64_encoded("iash21iawhdj98UH3"); // true
is_base64_encoded("#iu3498r"); // false
is_base64_encoded("asiudfh9w=8uihf"); // false
is_base64_encoded("a398UIhnj43f/1!+sadfh3w84hduihhjw=="); // false

http://php.net/manual/zh/function.base64-decode.php#81425


这非常有用,但是你的第四个例子is_base64_encoded("a398UIhnj43f/1!+sadfh3w84hduihhjw=="); // true在我的测试中返回 FALSE。 - Dylan
3
@Dylan,那是因为那不是有效的base64编码。他只是评论错了。 - Digital Human
1
这只是将一个字符串与任意长度匹配,并以=结尾或不以=结尾。它无法区分普通字符串和base64编码的字符串。 - renatoaraujoc
如果base64_decode无法解析base64编码的字符串,则返回false,因此您只需要执行以下操作:return base64_decode($str)!== false。 - renatoaraujoc

23

我也遇到了同样的问题,最终找到了这个解决方案:

if ( base64_encode(base64_decode($data)) === $data){
    echo '$data is valid';
} else {
    echo '$data is NOT valid';
}

10
如果我执行 $data='iujhklsc',它会返回有效,但实际上并不是。 - Mohit
2
很好的测试@Mohit - 我可以重现那个问题。这是一个聪明的解决方案,但显然也不起作用。问题在于base64_decode()会“解码”非base64数据,然后base64_encode()只是简单地反转该函数。 - chrishiestand
2
这个无法工作。我在另一个答案中也看到过...买家要小心。 - But those new buttons though..
1
无法处理 '123412341234'。为什么我总是看到这个答案? - catbadger
1
我认为这个答案不值得考虑,因为上面评论中提到的原因。 - Sean the Bean
显示剩余5条评论

12

迟做总比不做好:你可以使用mb_detect_encoding()函数来判断编码后的字符串是否为文本类型:

function is_base64_string($s) {
  // first check if we're dealing with an actual valid base64 encoded string
  if (($b = base64_decode($s, TRUE)) === FALSE) {
    return FALSE;
  }

  // now check whether the decoded data could be actual text
  $e = mb_detect_encoding($b);
  if (in_array($e, array('UTF-8', 'ASCII'))) { // YMMV
    return TRUE;
  } else {
    return FALSE;
  }
}

更新 对于那些喜欢简短的人

function is_base64_string_s($str, $enc=array('UTF-8', 'ASCII')) {
  return !(($b = base64_decode($str, TRUE)) === FALSE) && in_array(mb_detect_encoding($b), $enc);
}

1
真是太棒了!这可能是最好的,因为它允许程序员选择有效的编码列表(当然可以作为第二个参数传递以增加灵活性)。 - Fr0zenFyr
1
真的是一个被低估的解决方案。我归咎于答案的时差。 - lilHar
如果编码的字符串既不是UTF-8也不是ASCII,该怎么办... - Tiamiyu Saheed Oluwatosin
如果我输入值 555,那么这个函数将返回 true,而 555 不是有效的 base 64 编码。 - vee
是的,base64_decode()确实会解码错误,但OP询问如何检测base64编码字符串,而5555不是base64编码字符串(抱歉,我错了)。因此,该函数无法检测到这一点。我发现有很多无效字符是asciiutf-8格式的。在这种情况下,mb_detect_encoding()不能用于100%检测已解码的base64字符串。 - vee
显示剩余5条评论

11

我们可以将三件事情合并成一个函数来检查给定的字符串是否是有效的base64编码。

function validBase64($string)
{
 $decoded = base64_decode($string, true);
 $result = false;
    
 // Check if there is no invalid character in string
 if (!preg_match('/^[a-zA-Z0-9\/\r\n+]*={0,2}$/', $string)) {$result = false;}
        
 // Decode the string in strict mode and send the response
 if (!$decoded) {$result = false;}
        
 // Encode and compare it to original one
 if (base64_encode($decoded) != $string) {$result = false;}
        
 return $result;
}

我认为第二行应该是"$string"而不是"$str"。 - Wireblue
3
你是否应该检查输入的长度模4等于0? - frumbert
@frumbert 并非所有的实现都需要填充.. 但如果你要测试 mod 4,你首先需要去除所有的空格。 - Brad Kent

5

我正准备在php中创建一个base64开关,这是我的做法:

function base64Toggle($str) {
    if (!preg_match('~[^0-9a-zA-Z+/=]~', $str)) {
        $check = str_split(base64_decode($str));
        $x = 0;
        foreach ($check as $char) if (ord($char) > 126) $x++;
        if ($x/count($check)*100 < 30) return base64_decode($str);
    }
    return base64_encode($str);
}

这对我来说完美无缺。 以下是我的完整想法:http://www.albertmartin.de/blog/code.php/19/base64-detection 你可以在这里试一下:http://www.albertmartin.de/tools

我个人非常喜欢这个解决方案,因为它最接近 OP 的完美解决方案(在 return base64_encode($str) 的位置使用 return false,在 return base64_decode($str) 的位置使用 return true)。我很感激你在博客上对此进行了解释。 - Fr0zenFyr
我认为你也应该看一下 (Marki 的解决方案)[https://dev59.com/GXE85IYBdhLWcg3w436o#51877882]。它更加灵活,出错的可能性更小。 - Fr0zenFyr

3

如果输入不是有效的base64编码数据,base64_decode()将不会返回FALSE。使用imap_base64()代替,它会在$text中包含Base64字母表外的字符时返回FALSE。imap_base64()参考资料


它与base64_decode($string, true)相同,即严格形式。 - Fr0zenFyr

3
这是我的解决方案: if(empty(htmlspecialchars(base64_decode($string, true)))) { return false; } 如果解码的$string无效,例如:"node"、"123"、""等,则会返回false。请注意保留html标记。

不错。比大多数其他的都要好。 - Fr0zenFyr

2
$is_base64 = function(string $string) : bool {
    $zero_one = ['MA==', 'MQ=='];
    if (in_array($string, $zero_one)) return TRUE;

    if (empty(htmlspecialchars(base64_decode($string, TRUE))))
        return FALSE;

    return TRUE;
};

var_dump('*** These yell false ***');
var_dump($is_base64(''));
var_dump($is_base64('This is definitely not base64 encoded'));
var_dump($is_base64('node'));
var_dump($is_base64('node '));
var_dump($is_base64('123'));
var_dump($is_base64(0));
var_dump($is_base64(1));
var_dump($is_base64(123));
var_dump($is_base64(1.23));

var_dump('*** These yell true ***');
var_dump($is_base64(base64_encode('This is definitely base64 encoded')));
var_dump($is_base64(base64_encode('node')));
var_dump($is_base64(base64_encode('123')));
var_dump($is_base64(base64_encode(0)));
var_dump($is_base64(base64_encode(1)));
var_dump($is_base64(base64_encode(123)));
var_dump($is_base64(base64_encode(1.23)));
var_dump($is_base64(base64_encode(TRUE)));

var_dump('*** Should these yell true? Might be edge cases ***');
var_dump($is_base64(base64_encode('')));
var_dump($is_base64(base64_encode(FALSE)));
var_dump($is_base64(base64_encode(NULL)));

1
也许这不完全是您所要求的,但希望对某些人有用。
在我的情况下,解决方案是使用json_encode对所有数据进行编码,然后再使用base64_encode。
$encoded=base64_encode(json_encode($data));

这个值可以被存储或使用,具体取决于您的需求。如果要检查此值是否仅为文本字符串而非编码数据,则只需使用:
function isData($test_string){
   if(base64_decode($test_string,true)&&json_decode(base64_decode($test_string))){
      return true;
   }else{
    return false;
   }

或者,另一种选择。
function isNotData($test_string){
   if(base64_decode($test_string,true)&&json_decode(base64_decode($test_string))){
      return false;
   }else{
    return true;
   }

感谢本帖中之前所有回答者的贡献 :)


如果不先使用json_encode(),这里就会出现问题。aGVsbG8=hello的base64编码字符串。isData('aGVsbG8=')应该为true,但却得到了falsejson_decode()无法正确检测解码后的base64字符串。 - vee

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