30 votes

How to validate a CURP from Mexico

Question: How can I verify that the format of a CURP Mexican is it valid?

What is the CURP? The Unique Population Registry Code (CURP) is a unique 18-character alphanumeric identity code used to officially identify both residents and citizens of Mexico, issued by the Mexican government. RENAPO .

It is formed from the letters of the first and last names, the date and state of birth, and the sex. In addition, the 17th character is to avoid duplicates, and the last character is a digit used as an error detector-corrector. The syntax is detailed in the Regulatory Instructions for the Assignment of the Unique Population Registry Code (Clave Única de Registro de Población) .

Context: I am only interested in validating that a key could be valid. I am not interested at this point in whether or not that CURP exists.

So far, I am only verifying that it is 18 alphanumeric characters with the regex:

/^[A-Z\d]{18}$/

but I am also interested in considering that the characters are valid, and that the check digit matches.

0 votes

Very good Mariano, just one doubt, what do you use the pcre tag for?

0 votes

Thanks @jasilva. All questions from [regex] have to specify the language/dialect used . I used PCRE as it is one of the most popular and least restrictive.

0 votes

34voto

Mariano Points 21056

Regular expression

The following regular expression verifies:

  • The positions where letters, vowels and consonants of the first and last names are expected.
  • Valid date (although for simplicity, months with less than 31 days are not being validated).
  • Listing valid of federal entities.
  • And it generates references to separate the first 17 digits (group 1) from the last digit (group 2).

    /^([A-Z][AEIOUX][A-Z]{2}\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])HM[B-DF-HJ-NP-TV-Z]{3}[A-Z\d])(\d)$/

Full validation

I publish the code in JavaScript to be able to run it here, but I'm sure it's very easy to port to any other language.

//Función para validar una CURP
function curpValida(curp) {
    var re = /^([A-Z][AEIOUX][A-Z]{2}\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])[HM](?:AS|B[CS]|C[CLMSH]|D[FG]|G[TR]|HG|JC|M[CNS]|N[ETL]|OC|PL|Q[TR]|S[PLR]|T[CSL]|VZ|YN|ZS)[B-DF-HJ-NP-TV-Z]{3}[A-Z\d])(\d)$/,
        validado = curp.match(re);

    if (!validado)  //Coincide con el formato general?
        return false;

    //Validar que coincida el dígito verificador
    function digitoVerificador(curp17) {
        //Fuente https://consultas.curp.gob.mx/CurpSP/
        var diccionario  = "0123456789ABCDEFGHIJKLMNÑOPQRSTUVWXYZ",
            lngSuma      = 0.0,
            lngDigito    = 0.0;
        for(var i=0; i<17; i++)
            lngSuma = lngSuma + diccionario.indexOf(curp17.charAt(i)) * (18 - i);
        lngDigito = 10 - lngSuma % 10;
        if (lngDigito == 10) return 0;
        return lngDigito;
    }

    if (validado[2] != digitoVerificador(validado[1])) 
        return false;

    return true; //Validado
}

//Handler para el evento cuando cambia el input
//Lleva la CURP a mayúsculas para validarlo
function validarInput(input) {
    var curp = input.value.toUpperCase(),
        resultado = document.getElementById("resultado"),
        valido = "No válido";

    if (curpValida(curp)) { //  Acá se comprueba
        valido = "Válido";
        resultado.classList.add("ok");
    } else {
        resultado.classList.remove("ok");
    }

    resultado.innerText = "CURP: " + curp + "\nFormato: " + valido;
}

#resultado {
    background-color: red;
    color: white;
    font-weight: bold;
}
#resultado.ok {
    background-color: green;
}

<label>CURP:
    <input type="text" id="curp_input" oninput="validarInput(this)" style="width:100%;" placeholder="Ingrese su CURP">
</label>
<pre id="resultado"></pre>

Description

Taking into account the structure with which a CURP is formed:

Descripción de cómo se forma cada caracter

  • The first character is the initial of the first surname [A-Z]
    (if it were another letter, use a X ).
  • Followed by the first internal vowel of the surname [AEIOUX]
    (or a X if I didn't have one).
  • And the initials of the second surname and first name [A-Z]{2}
  • Date of birth.
    • \d{2} year.
    • (?:0[1-9]|1[0-2]) month.
    • (?:0[1-9]|[12]\d|3[01]) day.
  • Gender (Male or Female) [HM]
  • The federal state in which you were born (listed in the pdf )
    (?:AS|B[CS]|C[CLMSH]|D[FG]|G[TR]|HG|JC|M[CNS]|N[ETL]|OC|PL|Q[TR]|S[PLR]|T[CSL]|VZ|YN|ZS)
  • First internal consonants of surnames and given names [B-DF-HJ-NP-TV-Z]{3}
  • The homoclave (homonymy and century) [A-Z\d]
  • The check digit (captured in group 2) (\d)

After seeing if it matches the regex, we check that the check digit is valid. That is, if it matches the one calculated from the first 17 characters:

if (validado[2] != digitoVerificador(validado[1]))

To calculate the digit, first add the value of each of the 17 characters, which have a value from 0 to 36 in this order (dictionary):

0123456789ABCDEFGHIJKLMNÑOPQRSTUVWXYZ

for(var i=0; i<17; i++)
    lngSuma = lngSuma + diccionario.indexOf(curp17.charAt(i)) * (18 - i);

And the 10's complement of the last digit of this sum is taken (or 0 if it gives 10).

lngDigito = 10 - lngSuma % 10;
if (lngDigito == 10) return 0;
return lngDigito;

2 votes

Good question and answer. Just a note: the regular expression you put in admits incorrect dates (apart from the note with months of less than 31 days) because it accepts 00 as a valid month and day when they are not. Although I understand that this would make the regular expression a bit more complicated.

2 votes

@Alvaro That's true, I had meant to be lax on that validation. I added the validation so that it doesn't accept 00 (doesn't make the regex much more complicated). However, if one wanted to correctly validate date and months with less than 31 days, I would recommend doing it with the functions of the language used (although it could also be done with regex). It should be clarified that, in these cases, an input error in the date would be validated by the check digit.

0 votes

An or | is needed for the day on the date. This is the correct one: /^([A-Z][AEIOUX][A-Z]{2} \d {2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]‌​ \d |3[01])[HM](?:AS|B[CS]|C[CLMSH]|D[FG]|G[TR]|HG|JC|M[CNS]|N[ETL]|OC|PL|Q[TR]|S[PLR]|T[CSL]|VZ|YN|ZS)[B-DF-HJ-NP-TV-Z]{3}[A-Z \d ])( \d )$/

3voto

ArCiGo Points 51

My answer is a little late, but some time ago I did this method to validate the RFC, both for individuals and companies. I got the regex from the official SAT site to validate RFC. I hope someone will find it useful. The function is made in JS

function validateRFC(rfc) {

        var patternPM = "^(([A-ZÑ&]{3})([0-9]{2})([0][13578]|[1][02])(([0][1-9]|[12][\\d])|[3][01])([A-Z0-9]{3}))|" +
            "(([A-ZÑ&]{3})([0-9]{2})([0][13456789]|[1][012])(([0][1-9]|[12][\\d])|[3][0])([A-Z0-9]{3}))|" +
            "(([A-ZÑ&]{3})([02468][048]|[13579][26])[0][2]([0][1-9]|[12][\\d])([A-Z0-9]{3}))|" +
            "(([A-ZÑ&]{3})([0-9]{2})[0][2]([0][1-9]|[1][0-9]|[2][0-8])([A-Z0-9]{3}))$";
        var patternPF = "^(([A-ZÑ&]{4})([0-9]{2})([0][13578]|[1][02])(([0][1-9]|[12][\\d])|[3][01])([A-Z0-9]{3}))|" +
            "(([A-ZÑ&]{4})([0-9]{2})([0][13456789]|[1][012])(([0][1-9]|[12][\\d])|[3][0])([A-Z0-9]{3}))|" +
            "(([A-ZÑ&]{4})([02468][048]|[13579][26])[0][2]([0][1-9]|[12][\\d])([A-Z0-9]{3}))|" +
            "(([A-ZÑ&]{4})([0-9]{2})[0][2]([0][1-9]|[1][0-9]|[2][0-8])([A-Z0-9]{3}))$";

        if (rfc.match(patternPM) || rfc.match(patternPF)) {
            return true;
        } else {
            alert("La estructura de la clave de RFC es incorrecta.");
            return false;
        }
    }

2 votes

This publication talks about CURP, not RFC! Greetings.

2voto

Jorhel Reyes Points 71

Thank you very much. This helped me to create a validator for PHP, I leave the code in case someone can use it.

    function is_curp( $string = '' ){
// By @JorhelR
// TRANSFORMARMOS EN STRING EN MAYÚSCULAS RESPETANDO LAS Ñ PARA EVITAR ERRORES
        $string = mb_strtoupper($string, "UTF-8");
// EL REGEX POR @MARIANO
        $pattern = "/^([A-Z][AEIOUX][A-Z]{2}\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])[HM](?:AS|B[CS]|C[CLMSH]|D[FG]|G[TR]|HG|JC|M[CNS]|N[ETL]|OC|PL|Q[TR]|S[PLR]|T[CSL]|VZ|YN|ZS)[B-DF-HJ-NP-TV-Z]{3}[A-Z\d])(\d)$/";
        $validate = preg_match($pattern, $string, $match);
        if( $validate === false ){
// SI EL STRING NO CUMPLE CON EL PATRÓN REQUERIDO RETORNA FALSE
            return false;
        }
// ASIGNAMOS VALOR DE 0 A 36 DIVIDIENDO EL STRING EN UN ARRAY
        $ind = preg_split( '//u', '0123456789ABCDEFGHIJKLMNÑOPQRSTUVWXYZ', null, PREG_SPLIT_NO_EMPTY );
// REVERTIMOS EL CURP Y LE COLOCAMOS UN DÍGITO EXTRA PARA QUE EL VALOR DEL PRIMER CARACTER SEA 0 Y EL DEL PRIMER DIGITO DE LA CURP (INVERSA) SEA 1
        $vals = str_split( strrev( $match[0]."?" ) );
// ELIMINAMOS EL CARACTER ADICIONAL Y EL PRIMER DIGITO DE LA CURP (INVERSA)
        unset( $vals[0] );
        unset( $vals[1] );
        $tempSum = 0;
        foreach( $vals as $v => $d ){
// SE BUSCA EL DÍGITO DE LA CURP EN EL INDICE DE LETRAS Y SU CLAVE(VALOR) SE MULTIPLICA POR LA CLAVE(VALOR) DEL DÍGITO. TODO ESTO SE SUMA EN $tempSum
            $tempSum = (array_search( $d, $ind ) * $v)+$tempSum;
        }
// ESTO ES DE @MARIANO NO SUPE QUE ERA
        $digit = 10 - $tempSum % 10;
// SI EL DIGITO CALCULADO ES 10 ENTONCES SE REASIGNA A 0
        $digit = $digit == 10 ? 0 : $digit;
// SI EL DIGITO COINCIDE CON EL ÚLTIMO DÍGITO DE LA CURP RETORNA TRUE, DE LO CONTRARIO FALSE
        return $match[2] == $digit;
    }

0 votes

I don't know much about regexp, in fact, I always get them from stackoverflow, so I don't know if you are validating that a curp must have 18 characters, I did test with the curp: 'MCCTEST' and it gives me true, it's wrong, so I just added a trim at the beginning of the function and an if strlen != 18, cheers

0 votes

There is another detail, it is necessary to validate if $match contains result, this is the way it is at the end: paiza.io/projects/3NjCP4LT8T3OUq97x5M8Fg

1voto

A REST API to validate CURP and obtain basic information about the person. The API is Free (unlimited requests), Fast (200 ms latency on average) and Standardized according to ISO. More information using Postman

Example of request:

GET https://domain.com/api/curp/CAHF620818HMNLNL09?apiKey=bpT32rai

Example of a successful response:

{
    "curp": "CAHF620818HMNLNL09",
    "fatherName": "CALDERON",
    "motherName": "HINOJOSA",
    "name": "FELIPE DE JESUS",
    "gender": "1",
    "birthday": "1962-01-01T00:08:00.000Z",
    "birthState": "MX-MIC"
}

Documentation

0voto

acrogenesis Points 116

You can validate a CURP and the person's information by using this API https://rapidapi.com/acrogenesis/api/curp-renapo .

For example to use it in php it would be like this:

<?php

$client = new http\Client;
$request = new http\Client\Request;

$request->setRequestUrl('https://curp-renapo.p.rapidapi.com/v1/curp/CURP_DE_TU_USUARIO');
$request->setRequestMethod('GET');

$request->setHeaders(array(
    'x-rapidapi-host' => 'curp-renapo.p.rapidapi.com',
    'x-rapidapi-key' => 'api-key'
));

$client->enqueue($request)->send();
$response = $client->getResponse();

echo $response->getBody();

And I would return the information to you in this form:

{
  "curp":"CURP_DE_TU_USUARIO"
  "renapo_valid":true
  "paternal_surname":"CALDERON"
  "mothers_maiden_name":"HINOJOSA"
  "names":"FELIPE DE JESUS"
  "sex":"HOMBRE"
  "birthdate":"18/08/1962"
  "nationality":"MEXICO"
  "entity_birth":"MN"
  "probation_document":"ACTA DE NACIMIENTO"
  "probation_document_data":{
    "Entidad":"16"
    "Municipio":"053"
    "Año":"1963"
    "Número de acta":"00145"
  }
  "rfc":"RFC_DE_TU_USUARIO"
  "sat_valid":true
}

0 votes

@Aprendiz is a former president of Mexico and all of the above is public information .

1 votes

It seems to me that a validation with an API is much more valuable than a validation with regex. This validation tells you if the CURP really exists or not and the real data of that CURP.

0 votes

I already censored the CURP and RFC and gave an example of how to use the API.

HolaDevs.com

HolaDevs is an online community of programmers and software lovers.
You can check other people responses or create a new question if you don't find a solution

Powered by:

X