PHP VIN Code Validation

P

Since 1981 Vehicle Identification Numbers in North America have followed a standard which provides a check digit to aid in the identification of transcription errors. Reproduced below is my PHP implementation that calculates a checksum and compares it to the check digit.

class VehicleUtilities{

    public static function validateVin($vin){
        $vin=strtoupper($vin);
        
        //17 Chars <> I,O,Q - Last 4 Numeric
        if(preg_match('/[A-HJ-NPR-Z0-9]{13}[0-9]{4}/', $vin) === 1){
            
            $sum = self::getNumVal($vin[0]) * 8;
            $sum += self::getNumVal($vin[1]) * 7;
            $sum += self::getNumVal($vin[2]) * 6;
            $sum += self::getNumVal($vin[3]) * 5;
            $sum += self::getNumVal($vin[4]) * 4;
            $sum += self::getNumVal($vin[5]) * 3;
            $sum += self::getNumVal($vin[6]) * 2;
            $sum += self::getNumVal($vin[7]) * 10;
            $sum += self::getNumVal($vin[9]) * 9;
            $sum += self::getNumVal($vin[10]) * 8;
            $sum += self::getNumVal($vin[11]) * 7;
            $sum += self::getNumVal($vin[12]) * 6;
            $sum += self::getNumVal($vin[13]) * 5;
            $sum += self::getNumVal($vin[14]) * 4;
            $sum += self::getNumVal($vin[15]) * 3;
            $sum += self::getNumVal($vin[16]) * 2;
            
            $checksum = $sum % 11; //Modulus
            
            return $checksum == $vin[8] || ($checksum == 10 && $vin[8] == 'X');
        }
        return false;
    }
    
    private static function getNumVal($char){
        if(in_array($char, ['A', 'J', '1'])) {return 1;}
        elseif(in_array($char, ['B', 'K', 'S', '2'])) {return 2;}
        elseif(in_array($char, ['C', 'L', 'T', '3'])) {return 3;}
        elseif(in_array($char, ['D', 'M', 'U', '4'])) {return 4;}
        elseif(in_array($char, ['E', 'N', 'V', '5'])) {return 5;}
        elseif(in_array($char, ['F', 'W', '6'])) {return 6;}
        elseif(in_array($char, ['G', 'P', 'X', '7'])) {return 7;}
        elseif(in_array($char, ['H', 'Y', '8'])) {return 8;}
        elseif(in_array($char, ['R', 'Z', '9'])) {return 9;}
        elseif($char == 0) {return 0;}
    }   
}

Author’s Note: The code above has been revised to replace the deprecated eregi function.

The code begins by converting the string to uppercase and checking that the passed value matches a regex looking for exactly 13 uppercase alphanumeric characters, excluding the letters I, O and Q, followed by exactly 4 digits. On success the code proceeds to implement the weighted checksum algorithm using the private helper function getNumVal($char) as a lookup table. Note that the 9th character is the checksum, and thus excluded from the calculation.

Once the total is calculated it is divided by 11 and its remainder becomes the checksum, with ‘X’ representing 10. It should be obvious that the mod 11 operation provides limited protection against errors since there is a 1 in 11 chance an invalid VIN code will pass the test. Nevertheless, the algorithm has saved countless invalid VIN codes from making into my databases.

As shown below, it is important to do some cursory error checking in the web form before passing the input to this method. This greatly assists the user in determining the most likely causes of error.

//Validate VIN
if(empty($VIN))
    $errors['VIN']="VIN cannot be blank";
else{
    $VIN=strtoupper($VIN);
    if(strlen($VIN) == 17){
        if(preg_match('/[A-HJ-NPR-Z0-9]{13}[0-9]{4}/',$VIN)){
            if(!VehicleUtilities::validateVin($VIN))
                $errors['VIN']='Invalid VIN Code.';
        } else $errors['VIN']='Letters I, O, Q invalid / Last 4 must be numeric.';
    } else $errors['VIN']='Requires exactly 17 characters.';
}

About the author

Chris Peterson

As a Web Application Developer & Elephant Trainer I have been putting the PHP mascot to work for more than a decade. I specialize in back-end development and use the LAMP stack to craft software that frees human beings to spend their time on more productive and rewarding things.

1 comment

  • This is fantastic. Thanks for the great tutorial and working code. I was able to add it to my small project seamlessly.

By Chris Peterson

Chris Peterson

As a Web Application Developer & Elephant Trainer I have been putting the PHP mascot to work for more than a decade. I specialize in back-end development and use the LAMP stack to craft software that frees human beings to spend their time on more productive and rewarding things.

Recent Posts

Recent Comments

Archives