验证 IBAN PHP

19

在设计一个新平台时,我们尝试集成IBAN号码。我们必须确保IBAN被验证,并且存储到数据库中的IBAN始终是正确的。那么验证号码的适当方法是什么?


大多数没有特定于国家的检查,这是建议的。 - Peter Fox
Google Code托管了一个开源的php-iban验证器项目:https://code.google.com/p/php-iban/ - user3733632
抱歉,由于声望值较低,我暂时无法发表评论。以下是user3733632的答案链接:https://github.com/globalcitizen/php-iban(该项目自2015年3月起不再托管在Google Code上,并将在进行一些清理后推出新版本)。 - Shuhari
1
有趣的是:如果你点击@MarcinOrlowski的lmfgtfy链接,这个问题会作为第一个搜索结果弹出来 ;) - Frederik Kammer
@FrederikKammer 也许是因为首先有这个链接的缘故,所以在某种野外环境下出现了无限循环 :) - Marcin Orlowski
7个回答

54
根据我在另一个问题中解释的逻辑,我已经创建了一个函数。请参考维基百科文章中解释的逻辑,在下面找到适当的函数。每个国家的特定验证算法和字符长度可在以下网址获得:https://en.wikipedia.org/wiki/International_Bank_Account_Number#Validating_the_IBAN
function checkIBAN($iban)
{
    if(strlen($iban) < 5) return false;
    $iban = strtolower(str_replace(' ','',$iban));
    $Countries = array('al'=>28,'ad'=>24,'at'=>20,'az'=>28,'bh'=>22,'be'=>16,'ba'=>20,'br'=>29,'bg'=>22,'cr'=>21,'hr'=>21,'cy'=>28,'cz'=>24,'dk'=>18,'do'=>28,'ee'=>20,'fo'=>18,'fi'=>18,'fr'=>27,'ge'=>22,'de'=>22,'gi'=>23,'gr'=>27,'gl'=>18,'gt'=>28,'hu'=>28,'is'=>26,'ie'=>22,'il'=>23,'it'=>27,'jo'=>30,'kz'=>20,'kw'=>30,'lv'=>21,'lb'=>28,'li'=>21,'lt'=>20,'lu'=>20,'mk'=>19,'mt'=>31,'mr'=>27,'mu'=>30,'mc'=>27,'md'=>24,'me'=>22,'nl'=>18,'no'=>15,'pk'=>24,'ps'=>29,'pl'=>28,'pt'=>25,'qa'=>29,'ro'=>24,'sm'=>27,'sa'=>24,'rs'=>22,'sk'=>24,'si'=>19,'es'=>24,'se'=>24,'ch'=>21,'tn'=>24,'tr'=>26,'ae'=>23,'gb'=>22,'vg'=>24);
    $Chars = array('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);

    if(array_key_exists(substr($iban,0,2), $Countries) && strlen($iban) == $Countries[substr($iban,0,2)]){
                
        $MovedChar = substr($iban, 4).substr($iban,0,4);
        $MovedCharArray = str_split($MovedChar);
        $NewString = "";

        foreach($MovedCharArray AS $key => $value){
            if(!is_numeric($MovedCharArray[$key])){
                if(!isset($Chars[$MovedCharArray[$key]])) return false;
                $MovedCharArray[$key] = $Chars[$MovedCharArray[$key]];
            }
            $NewString .= $MovedCharArray[$key];
        }
        
        if(bcmod($NewString, '97') == 1)
        {
            return true;
        }
    }
    return false;
}

不错!你唯一忘了的是要将$NewString变量初始化为空字符串,否则会抛出错误。 - sucotronic
@PeterFox,你从哪里获取了国家及其IBAN长度的列表?这个列表是否包含所有国家? - BadHorsie
1
@BadHorsie 在这种情况下,维基百科是我的好朋友。你可以试试。 - Peter Fox
6
此外,您应该在检查 substr($iban,0,2) 是否实际上是 $Countries 数组中的一个键之前,先检查一下。这样可以防止用户输入无意义的内容导致 PHP 错误。如果不做这个检查,可能会出现问题。 - Wouter Florijn
2
你应该使用: if(array_key_exists(substr($iban,0,2), $Countries) && strlen($iban) == $Countries[substr($iban,0,2)]){ - AndiPower
如果您在使用bcmod时遇到配置问题,请尝试访问https://dev59.com/02PVa4cB1Zd3GeqP-OzS。对我来说效果非常好。 - Martin G

10
稍微修改@PeterFox的答案,包括对bcmath不可用时bcmod()的支持。
<?php

function isValidIBAN ($iban) {

  $iban = strtolower($iban);
  $Countries = array(
    'al'=>28,'ad'=>24,'at'=>20,'az'=>28,'bh'=>22,'be'=>16,'ba'=>20,'br'=>29,'bg'=>22,'cr'=>21,'hr'=>21,'cy'=>28,'cz'=>24,
    'dk'=>18,'do'=>28,'ee'=>20,'fo'=>18,'fi'=>18,'fr'=>27,'ge'=>22,'de'=>22,'gi'=>23,'gr'=>27,'gl'=>18,'gt'=>28,'hu'=>28,
    'is'=>26,'ie'=>22,'il'=>23,'it'=>27,'jo'=>30,'kz'=>20,'kw'=>30,'lv'=>21,'lb'=>28,'li'=>21,'lt'=>20,'lu'=>20,'mk'=>19,
    'mt'=>31,'mr'=>27,'mu'=>30,'mc'=>27,'md'=>24,'me'=>22,'nl'=>18,'no'=>15,'pk'=>24,'ps'=>29,'pl'=>28,'pt'=>25,'qa'=>29,
    'ro'=>24,'sm'=>27,'sa'=>24,'rs'=>22,'sk'=>24,'si'=>19,'es'=>24,'se'=>24,'ch'=>21,'tn'=>24,'tr'=>26,'ae'=>23,'gb'=>22,'vg'=>24
  );
  $Chars = array(
    '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
  );

  if (strlen($iban) != $Countries[ substr($iban,0,2) ]) { return false; }

  $MovedChar = substr($iban, 4) . substr($iban,0,4);
  $MovedCharArray = str_split($MovedChar);
  $NewString = "";

  foreach ($MovedCharArray as $k => $v) {

    if ( !is_numeric($MovedCharArray[$k]) ) {
      $MovedCharArray[$k] = $Chars[$MovedCharArray[$k]];
    }
    $NewString .= $MovedCharArray[$k];
  }
  if (function_exists("bcmod")) { return bcmod($NewString, '97') == 1; }

  // http://au2.php.net/manual/en/function.bcmod.php#38474
  $x = $NewString; $y = "97";
  $take = 5; $mod = "";

  do {
    $a = (int)$mod . substr($x, 0, $take);
    $x = substr($x, $take);
    $mod = $a % $y;
  }
  while (strlen($x));

  return (int)$mod == 1;
}

也许你可以添加一个条件,如果有人输入了不存在的国家代码。 如果(!isset($Countries [substr($iban,0,2)])|| strlen($ iban)!= $ Countries [substr($iban,0,2)]){返回false;} - FantomX1
增加了一些额外的检查:https://gist.github.com/Sroose/47f8ce5cf5fe36f52eba01dabc474d8c - S. Roose

7
接受的答案并不是首选的验证方式。根据规范,验证应按以下方式进行:
  1. 检查 IBAN 总长度是否符合国家标准。如果不符合,则该 IBAN 无效
  2. 用 00 替换两个校验数字(例如,英国为 GB00)
  3. 将前四个字符移动到字符串的末尾
  4. 将字符串中的字母替换为数字,并根据需要扩展字符串。即 A 或 a = 10,B 或 b = 11,Z 或 z = 35。每个字母字符都会被替换为 2 个数字
  5. 将字符串转换为整数(即忽略前导零)
  6. 计算新数的模 97,得到余数
  7. 从 98 中减去余数,并使用结果作为两个校验数字。如果结果是一位数字,则在其前面加上前导 0,以形成两位数字
我编写了一个类来根据规范验证、格式化和解析字符串。希望这可以帮助一些人节省自己编写代码所需的时间。
代码可以在 GitHub 上找到,在这里

1
这完全是不正确的。你所做的是计算校验位,这是完全可以的,但如果你只想验证IBAN,那么这并不是必要的。如果你不将校验位重置为零,并且在最后没有从98中减去,那么如果IBAN有效,你会得到1。如果你只想进行验证,那就足够了。 - inta

1
这个函数检查IBAN并需要激活GMP http://php.net/manual/en/book.gmp.php
function checkIban($string){
    $to_check = substr($string, 4).substr($string, 0,4);
    $converted = '';
    for ($i = 0; $i < strlen($to_check); $i++){
        $char = strtoupper($to_check[$i]);
        if(preg_match('/[0-9A-Z]/',$char)){
            if(!preg_match('/\d/',$char)){
                $char = ord($char)-55;
            }
            $converted .= $char;
        }
    }

    // prevent: "gmp_mod() $num1 is not an integer string" error
    $converted = ltrim($converted, '0');

    return strlen($converted) && gmp_strval(gmp_mod($converted, '97')) == 1;
}

享受!


1

顶级评分功能无法正常工作。

只需尝试一个包含“%”的字符串...

我正在使用这个:

function checkIBAN($iban) {

// Normalize input (remove spaces and make upcase)
$iban = strtoupper(str_replace(' ', '', $iban));

if (preg_match('/^[A-Z]{2}[0-9]{2}[A-Z0-9]{1,30}$/', $iban)) {
    $country = substr($iban, 0, 2);
    $check = intval(substr($iban, 2, 2));
    $account = substr($iban, 4);

    // To numeric representation
    $search = range('A','Z');
    foreach (range(10,35) as $tmp)
        $replace[]=strval($tmp);
    $numstr=str_replace($search, $replace, $account.$country.'00');

    // Calculate checksum
    $checksum = intval(substr($numstr, 0, 1));
    for ($pos = 1; $pos < strlen($numstr); $pos++) {
        $checksum *= 10;
        $checksum += intval(substr($numstr, $pos,1));
        $checksum %= 97;
    }

    return ((98-$checksum) == $check);
} else
    return false;
}

我对这个算法的理解:https://3v4l.org/fDgfo - mickmackusa

1
我在cakephp 3.7验证类中找到了这个解决方案。是一个简单美观的PHP实现。
/**
 * Check that the input value has a valid International Bank Account Number IBAN syntax
 * Requirements are uppercase, no whitespaces, max length 34, country code and checksum exist at right spots,
 * body matches against checksum via Mod97-10 algorithm
 *
 * @param string $check The value to check
 *
 * @return bool Success
 */
public static function iban($check)
{
    if (!preg_match('/^[A-Z]{2}[0-9]{2}[A-Z0-9]{1,30}$/', $check)) {
        return false;
    }

    $country = substr($check, 0, 2);
    $checkInt = intval(substr($check, 2, 2));
    $account = substr($check, 4);
    $search = range('A', 'Z');
    $replace = [];
    foreach (range(10, 35) as $tmp) {
        $replace[] = strval($tmp);
    }
    $numStr = str_replace($search, $replace, $account . $country . '00');
    $checksum = intval(substr($numStr, 0, 1));
    $numStrLength = strlen($numStr);
    for ($pos = 1; $pos < $numStrLength; $pos++) {
        $checksum *= 10;
        $checksum += intval(substr($numStr, $pos, 1));
        $checksum %= 97;
    }

    return ((98 - $checksum) === $checkInt);
}

看起来和 https://dev59.com/OWEi5IYBdhLWcg3wwegN#32612548 几乎一模一样。 - mickmackusa
就像我说的一样,所有的代码都来自于cakephp。 - Aivaras

0
我建议对校验和计算进行一些优化...
function calculateIBANChecksum($CIBAN){
    $CX = str_split($CIBAN);
    $checksum = $CX[0];
    array_shift($CX);
    foreach ($CX as $pos => $DIGIT) {
        $checksum *= 10;
        $checksum += $DIGIT * 1;
        $checksum %= 97;
    }
    return  (98 - $checksum);
}

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