TC kimlik numarası neden "checksum"lı bir numaradır?

Türkiye Cumhuriyeti Kimlik Numarası (TCKN), Nüfus ve Vatandaşlık İşleri Genel Müdürlüğü (NVİ) tarafından MERNİS sistemi üzerinden her vatandaşa bir kez atanan 11 haneli bir numaradır. Günlük konuşmada "TC kimlik no" ya da kısaca "TC" olarak geçer. Numaranın son iki hanesi rastgele değildir; ilk dokuz hanenin matematiksel fonksiyonu olarak hesaplanır. Bu tasarım, form üzerinde tek bir rakam yanlış yazıldığında numaranın büyük olasılıkla geçersiz hâle gelmesini sağlar — yani yerel bir "ilk seviye" doğrulama katmanı kurar.

Bu yazıda algoritmanın matematiksel mantığını adım adım çözeceğiz, tipik implementasyon tuzaklarını göstereceğiz ve beş programlama dilinde çalışan referans kodlar sunacağız. Amaç sadece formülü kopyala-yapıştır yapmak değil; TC kimlik numarasının arkasındaki tasarım tercihini de anlamak. Böylece üretim kodunuzda karşılaşacağınız uç durumlarda (leading zero, unicode rakam, trim eksikliği) algoritmanın nerede kırıldığını da göreceksiniz. Test verisi için kendi üretim rutininizi yazabilir ya da zaman kazanmak için hazır bir tc uret aracı kullanabilirsiniz.

Hemen belirtelim: bu formül sadece format doğrulaması yapar. Numaranın gerçek bir vatandaşa ait olup olmadığını ancak NVİ'nin TCKimlikNoDogrula servisi söyler. Bu ayrımı netleştirmek için NVİ servisi rehberini okumanızı öneririz.

TC kimlik algoritmasının resmi tanımı

TCKN'nin 11 hanesini d[0] d[1] d[2] ... d[10] olarak düşünelim (sıfır indeksli). Kurallar şunlar:

  1. d[0] != 0. İlk hane sıfır olamaz.
  2. d[9] (onuncu hane, kontrol hanesi): ((d[0]+d[2]+d[4]+d[6]+d[8]) * 7 - (d[1]+d[3]+d[5]+d[7])) mod 10
  3. d[10] (on birinci hane, toplam kontrol): (d[0]+d[1]+...+d[9]) mod 10

Tek indeksli (1 tabanlı) resmi dokümanlarda genelde şöyle yazılır: "1., 3., 5., 7., 9. hanelerin toplamının 7 katından, 2., 4., 6., 8. hanelerin toplamı çıkarılır; sonucun 10'a göre modu 10. haneyi verir." Aynı ifadedir, sadece indeksleme farklıdır. Koda dökerken dikkat edin.

Tek rakamlı bir örnek

10000000146 numarasını ele alalım:

  • Tek indeksli haneler (0, 2, 4, 6, 8): 1 + 0 + 0 + 0 + 0 = 1
  • Çift indeksli haneler (1, 3, 5, 7): 0 + 0 + 0 + 0 = 0
  • d[9] adayı: (1 * 7 - 0) mod 10 = 7

Ancak örnek numarada d[9] = 4. Demek ki bu TC kimlik no geçersiz. Geçerli olması için d[9] hanesinin 7 olması gerekirdi. Bu hızlı kontrol, üretim kodunda birim test yazarken pratik.

Negatif mod tuzağı

Formülde çıkarma işlemi var. (sumOdd * 7 - sumEven) negatif çıkabilir mi? Teorik olarak evet: sumOdd = 0, sumEven = 36 gibi. Ancak d[0] != 0 kısıtı ve rakamların 0-9 aralığında olması birlikte düşünüldüğünde pratikte nadirdir; yine de kodunuz bu durumu ele almalı.

C ve Java gibi dillerde % operatörü negatif sonuç verebilir (-3 % 10 == -3). Python'da ise -3 % 10 == 7. Diller arası taşıma yaparken bu farkı unutmayın. Savunmacı yazmak gerekirse:

let digit10 = ((sumOdd * 7) - sumEven) % 10;
if (digit10 < 0) digit10 += 10;

TC Kimlik Numarasının tasarım mantığı

Neden 7 ile çarpma? Neden 10'a mod? Kısa cevap: bu, "ISO 7064 benzeri" bir checksum şemasıdır. Küçük rakamları büyüterek pozisyonel hataları (iki rakamın yer değiştirmesi, tek bir rakamın yanlış yazılması) yüksek olasılıkla yakalar.

7 asal bir sayıdır ve 10 ile aralarında asaldır; bu sayede 7x mod 10 fonksiyonu 0-9 aralığında bire bir (bijeksiyon) davranır. {0,1,2,...,9} kümesini {0,7,4,1,8,5,2,9,6,3} kümesine eşler. Sonuç: tek bir rakamdaki değişiklik kontrol hanesinde mutlaka bir değişiklik yaratır. Eğer 7 yerine 2 seçilseydi (2 ile 10 aralarında asal değil), bazı hatalar yakalanmazdı.

On birinci hane (d[10]) basit bir toplam-mod-10'dur ve ikinci bir güvenlik ağı görevi görür. Özellikle iki rakamın yer değiştirmesi (transpozisyon) hatalarını yakalar.

Yaygın implementasyon hataları

Aşağıdaki hatalar production code review'lerinde sürekli karşımıza çıkar:

  • String değil de number tipinde taşımak: 02345678901 gibi leading zero içeren bir TC number'a cast edilince 2345678901 olur ve 10 haneye düşer. TCKN her zaman string olarak işlenmeli. Zaten algoritma gereği d[0] != 0 ama veri kaynağından (CSV, Excel) gelirken leading zero korunsun diye string kalmalı.
  • Regex'in §§T0§§ karakter sınıfı: JavaScript'te varsayılan olarak \d sadece ASCII 0-9'u yakalar, ama Python 3'te re.UNICODE bayrağı açıkken Arap-Hint, Bengal ve Devanagari rakamlarını da yakalar. Bu istenmeyen bir davranış. Güvenli seçim: [0-9]{11}.
  • Trim ve whitespace: Kullanıcı formda " 12345678901 " yazarsa length === 11 kontrolü patlar. input.trim() ilk satırdan önce gelir.
  • Negatif mod: Yukarıda anlattık. JavaScript ve Java için ((x % 10) + 10) % 10 kalıbı güvenlidir.
  • İlk hane kontrolünü atlamak: "00000000000" tüm checksum testlerinden geçer ama geçersiz bir TC kimlik numarasıdır. d[0] != 0 şartı ayrıca eklenmeli.

Bu tuzakların daha geniş bir listesi için TCKN/VKN doğrulamada sık yapılan hatalar yazımıza bakın.

Referans implementasyonlar

Aşağıda beş dilde minimal ama production-safe doğrulayıcılar yer alıyor. Her biri aynı sözleşmeyi takip eder: string girdi, boolean çıktı, hiçbir exception fırlatmaz — geçersiz girdi false döner.

JavaScript (ES2022+)

export function isValidTckn(input) {
  if (typeof input !== 'string') return false;
  const tckn = input.trim();
  if (!/^[1-9][0-9]{10}$/.test(tckn)) return false;

  const d = [...tckn].map(Number);
  const sumOdd = d[0] + d[2] + d[4] + d[6] + d[8];
  const sumEven = d[1] + d[3] + d[5] + d[7];
  const digit10 = ((sumOdd * 7 - sumEven) % 10 + 10) % 10;
  const digit11 = (sumOdd + sumEven + digit10) % 10;

  return d[9] === digit10 && d[10] === digit11;
}

Python (3.10+)

import re

_TCKN_RE = re.compile(r"^[1-9][0-9]{10}$")

def is_valid_tckn(value: str) -> bool:
    if not isinstance(value, str):
        return False
    tckn = value.strip()
    if not _TCKN_RE.match(tckn):
        return False

    d = [int(ch) for ch in tckn]
    sum_odd = d[0] + d[2] + d[4] + d[6] + d[8]
    sum_even = d[1] + d[3] + d[5] + d[7]
    digit10 = (sum_odd * 7 - sum_even) % 10
    digit11 = (sum_odd + sum_even + digit10) % 10
    return d[9] == digit10 and d[10] == digit11

Go (1.20+)

package tckn

import "regexp"

var tcknRe = regexp.MustCompile(`^[1-9][0-9]{10}$`)

func IsValid(input string) bool {
    if !tcknRe.MatchString(input) {
        return false
    }
    var d [11]int
    for i := 0; i < 11; i++ {
        d[i] = int(input[i] - '0')
    }
    sumOdd := d[0] + d[2] + d[4] + d[6] + d[8]
    sumEven := d[1] + d[3] + d[5] + d[7]
    digit10 := ((sumOdd*7-sumEven)%10 + 10) % 10
    digit11 := (sumOdd + sumEven + digit10) % 10
    return d[9] == digit10 && d[10] == digit11
}

Java (17+)

import java.util.regex.Pattern;

public final class Tckn {
    private static final Pattern RE = Pattern.compile("^[1-9][0-9]{10}$");

    public static boolean isValid(String input) {
        if (input == null) return false;
        String tckn = input.strip();
        if (!RE.matcher(tckn).matches()) return false;

        int[] d = new int[11];
        for (int i = 0; i < 11; i++) d[i] = tckn.charAt(i) - '0';

        int sumOdd = d[0] + d[2] + d[4] + d[6] + d[8];
        int sumEven = d[1] + d[3] + d[5] + d[7];
        int digit10 = Math.floorMod(sumOdd * 7 - sumEven, 10);
        int digit11 = (sumOdd + sumEven + digit10) % 10;
        return d[9] == digit10 && d[10] == digit11;
    }
}

Math.floorMod kullanımı Java'daki negatif mod sorununu çözer.

C# (.NET 8+)

using System.Text.RegularExpressions;

public static class Tckn
{
    private static readonly Regex Re = new(@"^[1-9][0-9]{10}$", RegexOptions.Compiled);

    public static bool IsValid(string? input)
    {
        if (string.IsNullOrWhiteSpace(input)) return false;
        var tckn = input.Trim();
        if (!Re.IsMatch(tckn)) return false;

        Span<int> d = stackalloc int[11];
        for (int i = 0; i < 11; i++) d[i] = tckn[i] - '0';

        int sumOdd = d[0] + d[2] + d[4] + d[6] + d[8];
        int sumEven = d[1] + d[3] + d[5] + d[7];
        int digit10 = ((sumOdd * 7 - sumEven) % 10 + 10) % 10;
        int digit11 = (sumOdd + sumEven + digit10) % 10;
        return d[9] == digit10 && d[10] == digit11;
    }
}

Test stratejisi

Her implementasyonun en azından şu vakaları kapsaması gerekir:

  • Bilinen geçerli bir TC kimlik numarası (üretici ile elde edilmiş)
  • İlk hanesi 0 olan input
  • 10 veya 12 haneli input
  • Boş string, null, whitespace
  • Harf içeren input
  • Unicode rakamlar ("١٢٣٤٥٦٧٨٩٠١" gibi)
  • Son haneyi +1 yaparak bozulmuş geçerli bir TC kimlik no

Test verisi üretimi için TC Üretici aracını veya kendi üretim fonksiyonunuzu kullanın. Bu konuda test verisi üretiminde en iyi pratikler yazımızda derin bir kapsam var.

Performans notu

11 hane üzerinde sabit sayıda aritmetik işlem yapıldığı için her bir doğrulama O(1). Modern bir x86 CPU'da yaklaşık 50-200 nanosaniye arasında tamamlanır — yani saniyede milyonlarca TC kimlik numarasını doğrulayabilirsiniz. Bu nedenle darboğaz neredeyse her zaman network, DB veya serialization katmanıdır, aritmetik değil. Yüksek hacimli senaryolar için toplu doğrulama rehberine göz atın.

Sonuç ve ileri okuma

TC kimlik algoritması basittir ama birçok implementasyonda aynı hatalar tekrarlanır: string/number karışıklığı, unicode regex, negatif mod, ilk hane kontrolü eksikliği. Yukarıdaki beş dildeki referans kodları kendi kod tabanınıza uyarlayabilirsiniz; testlerin eksiksiz olduğundan emin olun.

Aracı hemen deneyin: TC Doğrulayıcı ve TC Üretici.