Bazı bankacılık uygulamaları müşterinin kimliğini, kimlik kartındaki ya da pasaportundaki çipi NFC ile okuyarak doğrular. Böyle bir onboarding akışı için NFC kimlik doğrulama Android SDK'sını geliştirdim; kısa bir teknik özetini Çalışmalar sayfamın NFC bölümünde bulabilirsiniz. Hedefimiz satır satır anladığımız bir SDK'ydı: okuma başarısız olduğunda nereye bakacağımızı bilmek istiyorduk.
Bu yazıda kurum içi kod yok. Anlattığım her şey herkese açık: ICAO Doc 9303, ISO/IEC 7816-4 ve BSI TR-03110 ile bunların Android'in NFC API'lerine nasıl oturduğu. Standartlar iyi belgelenmiş durumda. Asıl iş, onları gerçek cihazlarda düzgün çalıştırmak.
Çipte neler var
Çipli kimlik kartları ve pasaportlar ICAO Doc 9303 standardını izler. Çipin içinde LDS (Logical Data Structure) denilen küçük bir dosya sistemi bulunur. LDS1'de tipik bir doğrulama akışının ilgilendiği dosyalar şunlar:
- EF.COM: LDS sürümü ve çipte bulunan veri gruplarının listesi. SOD imzasının kapsamında değildir, o yüzden ona yalnızca ipucu gözüyle bakın. Güvenilir veri grubu listesi EF.SOD'dan gelir.
- DG1: MRZ verisi, yani belgenin üzerindeki makine tarafından okunabilir satırlarla aynı içerik (ad, belge numarası, uyruk, doğum tarihi, son geçerlilik tarihi).
- DG2: biyometrik bir veri bloğunun içinde yüz görüntüsü. Genellikle okuyacağınız en büyük dosya budur. Görüntü JPEG ya da JPEG 2000 olabilir; Android'in
BitmapFactory'si JPEG 2000'i çözemediği için bir decoder planlamak gerekir. - EF.SOD: Document Security Object. Her veri grubunun hash'ini tutan, veren devletin Document Signer'ı tarafından imzalanmış bir CMS SignedData yapısı.
- DG14 / DG15: Chip Authentication ve Active Authentication için açık anahtarlar. Yalnızca çip bu mekanizmaları destekliyorsa bulunur.
DG3 (parmak izi) ve DG4 (iris) Extended Access Control ile korunur ve devletin verdiği terminal sertifikalarını gerektirir. Yani sıradan bir uygulama bunları okuyamaz.
Veriyi okumak ile ona güvenmek iki ayrı iş. DG1'i çipten almak size çipte ne yazdığını söyler, verinin gerçek olduğunu değil. O kanıt daha sonra Passive Authentication'dan gelir.
Akış
Aşağıdaki her adım bir öncekine bağlı.
1. Reader mode ve IsoDep
Çip radyo seviyesinde ISO/IEC 14443 konuşur, onun üzerinde de ISO/IEC 7816-4 APDU'ları gider gelir. Android'de bunun karşılığı IsoDep tag teknolojisi. Tag'e foreground dispatch ile ya da enableReaderMode ile ulaşabilirsiniz; bu iş için reader mode daha uygun. Activity ön plandayken NFC kontrolcüsü yalnızca okuyucu olarak çalışır (card emulation kapanır), tag'ler dispatch sistemine değil doğrudan sizin callback'inize gelir ve FLAG_READER_SKIP_NDEF_CHECK ile sistem önce çipten NDEF okumaya çalışmaz.
class EidScanActivity : ComponentActivity(), NfcAdapter.ReaderCallback {
private val nfcAdapter: NfcAdapter? by lazy { NfcAdapter.getDefaultAdapter(this) }
override fun onResume() {
super.onResume()
nfcAdapter?.enableReaderMode(
this,
this,
NfcAdapter.FLAG_READER_NFC_A or
NfcAdapter.FLAG_READER_NFC_B or
NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK,
null,
)
}
override fun onPause() {
super.onPause()
nfcAdapter?.disableReaderMode(this)
}
override fun onTagDiscovered(tag: Tag) {
// Main thread'de çağrılmaz, bu yüzden burada bloklayan I/O sorun değil.
val isoDep = IsoDep.get(tag) ?: return // ISO 14443-4 çip değil
try {
isoDep.use { chip ->
chip.connect()
chip.timeout = 5_000 // ms, gerçek kart ve cihazlarda ayarlayın
readDocument(chip)
}
} catch (e: TagLostException) {
// Kart kaydı ya da alan koptu: kullanıcıdan kartı sabit tutup tekrar denemesini isteyin.
} catch (e: IOException) {
// Zaman aşımı ya da iletim hatası.
}
}
}
Yazının geri kalanı readDocument içinde geçiyor. Bu kodda iki detay önemli. Kimlik kartları ve pasaportlar Type A da olabilir Type B de, o yüzden ikisini birden açıyoruz. Varsayılan IsoDep timeout'u, PACE anahtar anlaşması ya da Active Authentication gibi ağır işlemlerde bazı çipler için kısa kalır; bu yüzden setTimeout ile artırmak yaygındır. Doğru değeri gerçek kart ve telefonlarla test ederek bulursunuz.
2. APDU'lar ve uygulamayı seçmek
Bir komut APDU'su CLA INS P1 P2 ile başlar; ardından isteğe bağlı olarak Lc ve veri, son olarak da Le (kaç bayt yanıt beklediğiniz) gelir. Yanıt, veriden ve onun ardından gelen iki baytlık status word'den oluşur: 90 00 başarı demektir; 69 82 (güvenlik koşulu sağlanmadı) ya da 6A 82 (dosya bulunamadı) gibi değerler neyin ters gittiğini söyler.
Kısa formda Lc ve Le birer bayttır, yani tek bir yanıt en fazla 256 bayt taşır. Extended length bunu 65.536'ya çıkarır ama hem çipin hem telefonun desteklemesi gerekir. Telefon tarafını IsoDep.isExtendedLengthApduSupported() ve getMaxTransceiveLength() ile öğrenebilirsiniz. Kısa APDU'lar her yerde çalıştığı için güvenli varsayılan onlardır.
İlk komut, eMRTD uygulamasını AID'si (A0 00 00 02 47 10 01) ile seçer:
// 00 A4 04 0C 07 A0 00 00 02 47 10 01
// AID ile SELECT; P2 = 0C, yanıt verisi beklenmiyor demek.
private val SELECT_EMRTD_APP = byteArrayOf(
0x00, 0xA4.toByte(), 0x04, 0x0C, 0x07,
0xA0.toByte(), 0x00, 0x00, 0x02, 0x47, 0x10, 0x01,
)
private fun ByteArray.statusWord(): Int {
require(size >= 2) { "Response is shorter than a status word" }
return ((this[size - 2].toInt() and 0xFF) shl 8) or (this[size - 1].toInt() and 0xFF)
}
fun IsoDep.selectEmrtdApplication() {
val sw = transceive(SELECT_EMRTD_APP).statusWord()
check(sw == 0x9000) { "SELECT eMRTD failed: %04X".format(sw) }
}
BAC kullanıyorsanız bu SELECT, erişim kontrolünden önce düz (şifresiz) gönderilir. PACE'te sıra farklı: önce EF.CardAccess okunur, PACE çalıştırılır, uygulama ondan sonra secure messaging altında seçilir.
3. Erişim kontrolü: BAC ve PACE
Çip, okuma yetkiniz olduğunu kanıtlamadan veri gruplarını vermez. İki mekanizma da belgenin üzerinde basılı veriyi kullanır; böylece belgenin fiziksel olarak okuyucunun önünde olduğu gösterilmiş olur.
- BAC (Basic Access Control): anahtar tohumu MRZ'den gelir: belge numarası, doğum tarihi ve son geçerlilik tarihi, her biri kendi check digit'iyle. Bunlar SHA-1 ile hash'lenir, sonuçtan 3DES şifreleme ve MAC anahtarları türetilir, ardından GET CHALLENGE / MUTUAL AUTHENTICATE alışverişiyle oturum anahtarları üretilir. Zayıf noktası entropi: MRZ alanları büyük ölçüde tahmin edilebilir olduğundan kaydedilmiş bir BAC oturumu çevrimdışı kaba kuvvetle kırılabilir.
- PACE (Password Authenticated Connection Establishment): parola ya aynı MRZ verisi ya da kartın üzerinde basılı kısa numara olan CAN'dir. PACE bir Diffie-Hellman anahtar anlaşması (klasik ya da eliptik eğri) yürüttüğü için parola zayıf olsa da oturum anahtarları güçlü olur. Yeni belgeler PACE'i destekler, çoğu zaman BAC ile birlikte.
Hangisini kullanacağınıza karar vermek için önce EF.CardAccess'i okuyun; bu dosya erişim kontrolü olmadan okunabilir. Varsa ve PACE parametrelerini listeliyorsa PACE kullanın; çip PACE desteklemiyorsa BAC'e geri dönün. ICAO da çip destekliyorsa PACE'in tercih edilmesini önerir.
MRZ konusunda iki pratik not. Çipe dokunmadan önce check digit'leri doğrulayın ve kısa bir belge numarasını < ile dokuz karaktere tamamlayın. Yanlış bir MRZ (çoğu zaman bir OCR hatası) BAC'in MUTUAL AUTHENTICATE adımında başarısız olmasına yol açar. Bu durumu ayrı raporlamazsanız kullanıcı onu sıradan bir NFC okuma hatası olarak görür.
BAC ya da PACE başarılı olduktan sonra her komutun secure messaging'den (SM, ISO/IEC 7816-4) geçmesi gerekir. Komut verisi şifrelenir, komut üzerinden bir MAC hesaplanır ve iki tarafın da artırdığı bir send sequence counter (SSC) bu MAC'in parçasıdır. Yanıtlar da aynı şekilde gelir, siz de MAC'lerini kontrol edersiniz. Bu noktada düz bir APDU gönderirseniz çip onu reddeder ve genellikle güvenli oturumu sonlandırır. SSC senkronu kaybolursa da aynısı olur. Pratikte her SM hatası, erişim kontrolüne baştan başlamak demektir.
İki mekanizmayı BAC ve PACE: eID Erişim Kontrolünü Anlamak yazısında daha ayrıntılı anlattım.
4. Veri gruplarını okumak
Uygulama seçilip SM çalışmaya başladıktan sonra bir dosyayı READ BINARY (INS B0) ile okursunuz. Dosyayı adreslemenin iki yolu var:
- Dosyayı file ID'siyle SELECT edip (EF.COM
01 1E, EF.SOD01 1D, DG101 01, DG201 02) ardından offset'i P1-P2'de vererek READ BINARY göndermek. - Short EF identifier (SFI) kullanmak: P1'i
0x80 | SFI, P2'yi offset yaparsınız ve çip seçme ile okumayı tek komutta yapar. EF.COM'un SFI'ı1E, EF.SOD1D, DG101, DG202. Bir round trip kazandırır ama yalnızca 255'e kadar olan offset'leri kapsar. İlk okumadan sonra dosya current EF olur, sonraki parçalar normal offset formuyla okunur.
Dosyalar BER-TLV ile kodlanmıştır. Yaygın yaklaşım, ilk birkaç baytı okuyup tag ve length'i çözmek, sonra geri kalanını parça parça okumaktır. SM altında her yanıt padding ve MAC de taşıdığı için okuma başına gelen veri 256 baytın biraz altında kalır. P1-P2'deki offset 15 bittir; 32.767 baytı aşan kısımlar için offset'i komut verisinde taşıyan, tek sayılı INS kullanan varyant (INS B1) gerekir. DG1 bu boyuta hiç yaklaşmaz, DG2 de nadiren ulaşır; ama okuma döngünüz bunu bilmeli.
Bu kısım için açık kaynak jMRTD kütüphanesi iyi bir referans: parçaların nasıl TLV yapısına dönüştüğünü, DG1 ve DG2'nin nasıl ayrıştırıldığını orada görebilirsiniz.
5. Passive Authentication: veriye güvenmek
Passive Authentication (PA) iki kısımdan oluşur:
- EF.SOD üzerindeki imzayı, genellikle SOD'un içinde gelen Document Signer (DS) sertifikasıyla doğrulamak. Sonra DS sertifikasını veren ülkenin CSCA sertifikasına karşı doğrulamak. CSCA sertifikaları çipten gelmez; onları ayrıca, örneğin ICAO PKD üzerinden ya da yayımlanan master list'lerden temin edersiniz. İptal kontrolü (CRL) de bu adıma aittir.
- Okuduğunuz her veri grubunu SOD'da belirtilen algoritmayla hash'leyip saklanan değerle karşılaştırmak.
İkisi de geçerse veri o devlet tarafından düzenlenmiş ve imzalandığından beri değişmemiştir. PA çipin kendisinin orijinal olduğunu kanıtlamaz: dosyaları kopyalanmış bir klon da PA'dan aynı şekilde geçer. Bunu iki mekanizma karşılar:
- Active Authentication (AA): okuyucu rastgele bir challenge gönderir (INTERNAL AUTHENTICATE), çip bunu dışarı okunamayan bir özel anahtarla imzalar. İmzayı DG15'teki açık anahtarla doğrularsınız.
- Chip Authentication (CA): ICAO 9303 Part 11 ve BSI TR-03110'da tanımlıdır. Çipin DG14'teki statik anahtarıyla yapılan bir anahtar anlaşmasıdır ve BAC/PACE oturumunu yeni bir secure messaging oturumuyla değiştirir. Çip özel anahtara sahip olduğunu dolaylı olarak, o oturumu sürdürebilmesiyle kanıtlar.
Hangisini kullanabileceğiniz çipin neyi desteklediğine (DG15, DG14 ya da ikisi) ve güvenlik gereksinimlerinize bağlıdır. İkisi de PA'ya dayanır: DG14 ve DG15'teki anahtarlara, hash'leri imzalı SOD'da bulunduğu için güvenebilirsiniz. Ayrıntılar Passive Authentication: eID Belgesinin Bütünlüğünü Doğrulamak yazısında.
SDK'yı bölmek: okuma, doğrulama, entegrasyon
eID SDK'sını üç parçaya böldüm ve sınırları katı tuttum:
- Okuma: çip iletişimi, erişim kontrolü, secure messaging, APDU'lar, veri gruplarını baytlardan modele çevirmek.
- Doğrulama: imza ve hash kontrolleri, sertifika zinciri, güven kararları.
- Entegrasyon: SDK'nın uygulama akışına bağlanması, callback'ler, ilerleme ve hata bildirimi.
Sebebi bakım. Okuma katmanı cihaz kaprisleriyle, doğrulama katmanı kriptografiyle uğraşıyor. İkisi karışırsa bir NFC timeout'unu düzeltirken imza koduna dokunmak zorunda kalabilirsiniz. Ayrı tutulduklarında her katman kendi başına test edilebilir (doğrulama katmanı düz byte array'lerle çalışır, telefon gerekmez) ve uygulama yalnızca küçük bir API görür.
Teşhis de baştan tasarlanmaya değer. Sahada bir okuma başarısız olduğunda hangi aşamanın koptuğunu (bağlantı, erişim kontrolü, okuma ya da doğrulama), hangi cihaz modelinde ve ne kadar süre sonra koptuğunu bilmek istersiniz. Bunu sonradan eklemek çok daha zor.
Cihaz gerçekliği
Standartlar net. Zor olan, aynı kartı birbirinden çok farklı Android telefonlarda güvenilir şekilde okuyabilmek.
- Anten her telefonda farklı yerde. Kullanıcı kartı nereye tutacağını bilmez. Net yönlendirme (kartı nereye koyacağı, sabit tutması gerektiği, yaklaşık ne kadar süreceği) başarı oranını doğrudan etkiler.
- Bağlantı okumanın ortasında kopar.
TagLostExceptionsonrasında SM oturumu da gitmiştir; yeniden bağlanıp erişim kontrolünü tekrar yaparsınız. Tamamen okunmuş veri gruplarını yeniden okumak gerekmez ve kullanıcı bütün akışa baştan başlamak zorunda kalmamalı. - Hata mesajı ürünün parçasıdır. "Okuma başarısız" kimsenin işine yaramaz. "MRZ bu kartla eşleşmiyor", "Bağlantı koptu, kartı sabit tutun" ve "Veri okundu ama doğrulama geçmedi" farklı çözümleri olan farklı sorunlardır.
Sık görülen hata durumlarını ve nasıl toparlanacağını Android'de NFC Okuma Neden Başarısız Olur, Nasıl Toparlanır? yazısında anlattım.
Sık sorulan sorular
- Kimlik kartı ve pasaport çipleri hangi standarda göre çalışır, çipte neler bulunur?
- ICAO Doc 9303 standardına göre çalışırlar. Çipte LDS denilen küçük bir dosya sistemi vardır. EF.COM mevcut veri gruplarını listeler, DG1 MRZ verisini, DG2 yüz görüntüsünü taşır; EF.SOD ise her veri grubunun hash'ini tutar ve bu yapı veren devletin Document Signer'ı tarafından imzalanır. Çip destekliyorsa DG14 ve DG15, Chip Authentication ve Active Authentication için açık anahtarları taşır.
- Çipten veri grubunu okumak neden kimliğin gerçek olduğunu kanıtlamaz?
- Veriyi okumak yalnızca çipte ne yazdığını gösterir. Güven Passive Authentication'dan gelir: EF.SOD imzası Document Signer sertifikasıyla, bu sertifika da ülkenin CSCA sertifikasıyla doğrulanır, ardından her veri grubunun hash'i SOD'daki değerle karşılaştırılır. Kopyalanmış bir çip bile bu kontrolden geçer; Active Authentication ve Chip Authentication bunun için vardır.
- BAC ile PACE erişim kontrolü arasındaki fark nedir?
- BAC anahtarlarını MRZ alanlarından (belge numarası, doğum tarihi, son geçerlilik tarihi) türetir. Bu alanlar büyük ölçüde tahmin edilebilir olduğu için kaydedilmiş bir BAC oturumu çevrimdışı kaba kuvvetle kırılabilir. PACE parola olarak MRZ'yi ya da CAN'i kullanır ama Diffie-Hellman anahtar anlaşması yaptığı için oturum anahtarları yine güçlü olur. Önce EF.CardAccess okunur; PACE parametreleri varsa PACE, yoksa BAC kullanılır.
- Aynı çipi farklı Android cihazlarda güvenilir okumak neden zor?
- NFC anteninin yeri her telefonda farklıdır ve bağlantı okumanın ortasında kopabilir. Kopunca secure messaging oturumu da gider; yeniden bağlanıp erişim kontrolünü baştan yapmak gerekir. Net hata mesajları da önemlidir: yanlış MRZ, kopan bağlantı ve geçmeyen doğrulama farklı çözümleri olan farklı sorunlardır.