Validate IBAN PHP
Asked Answered
F

8

19

As designing a new platform we tried to integrate the IBAN numbers. We have to make sure that the IBAN is validated and the IBAN stored to the database is always correct. So what would be a proper way to validate the number?

Feasible answered 7/1, 2014 at 22:31 Comment(5)
Most do not have the country specific check, as recommended.Feasible
Google Code hosts a project of an open source php-iban validator: code.google.com/p/php-ibanCalc
Sorry, I can not yet comment due to low Rep, so the new link to user3733632s answer: github.com/globalcitizen/php-iban (the project is no longer hosted at google code as of March 2015, and the project will get a new version after some cleanup.)Timetable
The funny thing is: If you click on @MarcinOrlowski lmfgtfy link, this question pops up as the first search result ;)Irenairene
@FrederikKammer perhaps it's because of this link in the first place, so some sort of infinite loop in the wild :)Aircraft
F
55

As the logic was explained in my other question, I've created a function myself. Based on the logic explained in the Wikipedia article find a proper function below. Country specific validation.

Algorithm and character lengths per country at 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;
}
Feasible answered 7/1, 2014 at 22:31 Comment(6)
Nice! The only thing you missed it to initialize the $NewString variable to an empty string, if not, it'll throw an error.Lucic
@PeterFox Where did you get the list of countries and their IBAN lengths, and does the list include every country?Caudex
@Caudex in that case, Wikipedia was my friend. Try themFeasible
Also, you should check if substr($iban,0,2) is actually a key in the $Countries array before you check if(strlen($iban) == $Countries[substr($iban,0,2)]). Prevents PHP errors if users enter nonsense input.Dukey
You should use: if(array_key_exists(substr($iban,0,2), $Countries) && strlen($iban) == $Countries[substr($iban,0,2)]){Rodent
If you end up with configuration issues when using bcmod, try #10626777. Worked perfectly well for me.Vitellus
R
12

Slight modification of @PeterFox answer including support for bcmod() when bcmath is not available,

<?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;
}
Rabia answered 14/11, 2014 at 9:12 Comment(2)
perhaps you could also add an condition, if somebody put in code of not existing country if (!isset($Countries[ substr($iban,0,2) ]) || strlen($iban) != $Countries[ substr($iban,0,2) ]) { return false; }`Legofmutton
Added some extra checks: gist.github.com/Sroose/47f8ce5cf5fe36f52eba01dabc474d8cCornett
C
8

The accepted answer is not the preferred way of validation. The specification dictates the following:

  1. Check that the total IBAN length is correct as per the country. If not, the IBAN is invalid
  2. Replace the two check digits by 00 (e.g. GB00 for the UK)
  3. Move the four initial characters to the end of the string
  4. Replace the letters in the string with digits, expanding the string as necessary, such that A or a = 10, B or b = 11, and Z or z = 35. Each alphabetic character is therefore replaced by 2 digits
  5. Convert the string to an integer (i.e. ignore leading zeroes)
  6. Calculate mod-97 of the new number, which results in the remainder
  7. Subtract the remainder from 98, and use the result for the two check digits. If the result is a single digit number, pad it with a leading 0 to make a two-digit number

I've written a class that validates, formats and parses strings according to the spec. Hope this helps some save the time required to roll their own.

The code can be found on GitHub here.

Clemmie answered 16/3, 2016 at 9:26 Comment(3)
This is simply not true. What you are doing is calculating the check digits, thats totally fine but not necessary if you just want to validate the IBAN. If you do not reset the check digits to zero and do not subtract from 98 at the end, then you get 1 if the IBAN is valid. That is sufficient if you only want to verify.Minnich
@inta, try validate the iban GB00HLFX11016111455365 with your method, you will get a valid result, but is not according to the site iban.com/testibans . Standard generates check digit in range 02-98, but validation does not catch this 00 vs 97 overlap.Brennan
You are correct, the test IBANs may not. Check digits of "00", "01" or "99" should not appear, but they do or at least did in the past. It's the responsibility of credit institutions to assign IBANs so the check digits might differ from what you calculate, but as long as the validation succeeds the IBANs are valid.Minnich
R
1

top rated function does NOT work.

Just try a string with '%' in it...

I'm using this one :

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;
}
Reger answered 16/9, 2015 at 15:13 Comment(1)
My interpretation of this algorithm: 3v4l.org/fDgfoSoutheasterly
P
1

This function check the IBAN and need GMP activate 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;
}

enjoy !

Plagiary answered 23/12, 2017 at 7:15 Comment(0)
R
1

I found this solution in cakephp 3.7 validation class. Plain beautiful php realization.

/**
 * 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);
}
Ruprecht answered 25/1, 2019 at 12:4 Comment(2)
looks virtually identical to https://mcmap.net/q/627654/-validate-iban-phpSoutheasterly
It is. As I said all code is from cakephp.Ruprecht
M
0

i'd suggest a bit of optimalization for checksum calc ...

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);
}
Mycobacterium answered 19/5, 2023 at 11:28 Comment(0)
P
0

Improve from "mpapec" solution to avoid cases of countries not set, example: IBAN jhasksssa (jh not exists as country and php fails with: Undefined array key "jh"

    public static function isValidIBAN($iban)
    {
        $iban = strtolower($iban);
        $countries = [
            '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 = [
            '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 (!isset($countries[substr($iban, 0, 2)]) || 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;
    }
Poverty answered 10/6 at 10:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.