2using System.Security.Cryptography;
3using System.Text.RegularExpressions;
4using System.Threading.Tasks;
27 Match M = td2_mrz_nr9charsplus.Match(MRZ);
30 Info = AssembleInfo2(M);
31 return Info is not
null;
34 M = td2_mrz_nr9chars.Match(MRZ);
37 Info = AssembleInfo1(M);
38 return Info is not
null;
41 M = td1_mrz_nr9charsplus.Match(MRZ);
44 Info = AssembleInfo2(M);
45 return Info is not
null;
48 M = td1_mrz_nr9chars.Match(MRZ);
51 Info = AssembleInfo1(M);
52 return Info is not
null;
64 Result.DocumentNumber = M.Groups[
"Nr1"].Value + M.Groups[
"Nr2"].Value;
66 return CalcMrzInfo(Result, M) ? Result :
null;
69 private static DocumentInformation? AssembleInfo1(Match M)
71 DocumentInformation Result = AssembleInfo(M);
72 Result.DocumentNumber = M.Groups[
"Nr"].Value;
74 return CalcMrzInfo(Result, M) ? Result :
null;
77 private static bool CalcMrzInfo(DocumentInformation Info, Match M)
79 if (Info.DocumentNumber is
null || Info.DateOfBirth is
null || Info.ExpiryDate is
null)
82 string NrCheck = M.Groups[
"NrCheck"].Value;
83 if (NrCheck != CalcCheckDigit(Info.DocumentNumber))
86 string BirthCheck = M.Groups[
"BirthCheck"].Value;
87 if (BirthCheck != CalcCheckDigit(Info.DateOfBirth))
90 string ExpiryCheck = M.Groups[
"ExpiryCheck"].Value;
91 if (ExpiryCheck != CalcCheckDigit(Info.ExpiryDate))
94 if (!
string.IsNullOrEmpty(Info.OptionalData))
96 string s = Info.OptionalData!.Replace(
"<",
string.Empty);
98 if (!
string.IsNullOrEmpty(s))
100 string OptionalCheck = M.Groups[
"OptionalCheck"].Value;
101 if (OptionalCheck != CalcCheckDigit(Info.OptionalData))
105 Info.OptionalData = s;
110 Info.MRZ_Information = Info.DocumentNumber + NrCheck +
111 Info.DateOfBirth + BirthCheck + Info.ExpiryDate + ExpiryCheck;
113 Info.DocumentNumber = Info.DocumentNumber.Replace(
"<",
string.Empty);
118 private static string CalcCheckDigit(
string Value)
126 foreach (
char ch
in Value)
128 if (ch >=
'0' && ch <=
'9')
130 else if (ch >=
'A' && ch <=
'Z')
132 else if (ch >=
'a' && ch <=
'z')
144 return new string((
char)(
'0' + Sum % 10), 1);
147 private static readonly
int[] weights = [7, 3, 1];
149 private static DocumentInformation AssembleInfo(Match M)
151 return new DocumentInformation()
154 IssuingState = M.Groups[
"Issuer"].Value,
155 Nationality = M.Groups[
"Nationality"].Value,
156 PrimaryIdentifier = M.Groups[
"PID"].Value.Split(
'<'),
157 SecondaryIdentifier = M.Groups[
"SID"].Value.Split(
'<'),
158 Gender = M.Groups[
"Gender"].Value,
159 DocumentNumber = M.Groups[
"Nr"].Value,
160 DateOfBirth = M.Groups[
"Birth"].Value,
161 ExpiryDate = M.Groups[
"Expires"].Value,
162 OptionalData = M.Groups[
"Optional"].Value
167 private static readonly Regex td2_mrz_nr9charsplus =
new(
@"^(?'DocType'.{1,2})<(?'Issuer'\w{3})(?'PID'[^<]+(<[^<]+)*)<<(?'SID'[^<]+(<[^<]+)*)<*\n(?'Nr1'[^<]{9})<(?'Nationality'\w{3})(?'Birth'[^<]*)(?'BirthCheck'\d)(?'Gender'[MF])(?'Expires'[^<]{6})(?'ExpiryCheck'\d)(?'Nr2'[^<]*)(?'NrCheck'\d)((?'Optional'.*)(?'OptionalCheck'\d))?<*(?'OverallCheck'\d)$", RegexOptions.Multiline);
168 private static readonly Regex td2_mrz_nr9chars =
new(
@"^(?'DocType'.{1,2})<(?'Issuer'\w{3})(?'PID'[^<]+(<[^<]+)*)<<(?'SID'[^<]+(<[^<]+)*)<*\n(?'Nr'.{9})(?'NrCheck'\d)(?'Nationality'\w{3})(?'Birth'[^<]{6})(?'BirthCheck'\d)(?'Gender'[MF])(?'Expires'[^<]{6})(?'ExpiryCheck'\d)((?'Optional'.*)(?'OptionalCheck'\d))?<*(?'OverallCheck'\d)$", RegexOptions.Multiline);
171 private static readonly Regex td1_mrz_nr9charsplus =
new(
@"^(?'DocType'.{1,2})<(?'Issuer'\w{3})(?'Nr1'[^<]{9})<(?'Nr2'.{3})(?'NrCheck'\d)((?'Optional'.*)(?'OptionalCheck'\d))?<*\n(?'Birth'[^<]{6})(?'BirthCheck'\d)(?'Gender'[MF])(?'Expires'[^<]{6})(?'ExpiryCheck'\d)(?'Nationality'\w{3})<*(?'OverallCheck'\d)\n(?'PID'[^<]+(<[^<]+)*)<<(?'SID'[^<]+(<[^<]+)*).*$", RegexOptions.Multiline);
172 private static readonly Regex td1_mrz_nr9chars =
new(
@"^(?'DocType'.{1,2})<(?'Issuer'\w{3})(?'Nr'.{9})(?'NrCheck'.)((?'Optional'.*)(?'OptionalCheck'\d))?<*\n(?'Birth'[^<]{6})(?'BirthCheck'\d)(?'Gender'[MF])(?'Expires'[^<]{6})(?'ExpiryCheck'\d)(?'Nationality'\w{3})<*(?'OverallCheck'\d)\n(?'PID'[^<]+(<[^<]+)*)<<(?'SID'[^<]+(<[^<]+)*).*$", RegexOptions.Multiline);
182 Array.Resize<
byte>(ref H, 16);
191 return CalcKey(Info, 1);
199 return CalcKey(Info, 2);
204 byte[]
KSeed = Info.KSeed();
205 byte[] D =
new byte[20];
206 Array.Copy(
KSeed, 0, D, 0, 16);
209 for (i = 19; i >= 16; i--)
211 D[i] = (byte)(Counter);
216 Array.Resize<
byte>(ref H, 16);
222 private static void OddParity(
byte[] H)
224 int i, j, c = H.Length;
227 for (i = 0; i < c; i++)
249 public static byte[]
CONCAT(
this byte[] Bytes, params
byte[][] MoreBytes)
251 int c = Bytes.Length;
254 foreach (
byte[] A
in MoreBytes)
257 byte[] Result =
new byte[c];
259 Array.Copy(Bytes, 0, Result, 0, i);
261 foreach (
byte[] A
in MoreBytes)
263 Array.Copy(A, 0, Result, i, c = A.Length);
282 byte[] S = Rnd1.CONCAT(Challenge, Rnd2);
286 using (TripleDES Cipher = TripleDES.Create())
288 Cipher.Mode = CipherMode.CBC;
289 Cipher.Padding = PaddingMode.None;
291 using ICryptoTransform Encryptor = Cipher.CreateEncryptor(
KEnc,
new byte[8]);
292 EIFD = Encryptor.TransformFinalBlock(S, 0, 32);
298 using (DES Cipher = DES.Create())
300 Cipher.Mode = CipherMode.CBC;
301 Cipher.Padding = PaddingMode.None;
307 byte[] Data =
new byte[c + 8];
308 Array.Copy(EIFD, 0, Data, 0, c);
311 byte[] Ka =
new byte[8];
312 byte[] Kb =
new byte[8];
314 Array.Copy(
KMac, 0, Ka, 0, 8);
315 Array.Copy(
KMac, 8, Kb, 0, 8);
317 byte[] Block =
new byte[8];
321 using (ICryptoTransform Encryptor2 = Cipher.CreateEncryptor(Ka,
new byte[8]))
325 Array.Copy(Data, i, Block, 0, 8);
330 for (j = 0; j < 8; j++)
334 H = Encryptor2.TransformFinalBlock(Block, 0, 8);
337 using (ICryptoTransform FinalDecryptor = Cipher.CreateDecryptor(Kb,
new byte[8]))
339 H = FinalDecryptor.TransformFinalBlock(H, 0, 8);
342 H = Encryptor2.TransformFinalBlock(H, 0, 8);
348 return EIFD.CONCAT(MIFD);
359 byte[] Rnd1 =
new byte[8];
360 byte[] Rnd2 =
new byte[16];
362 using (RandomNumberGenerator Rnd = RandomNumberGenerator.Create())
388 if (Response.Length != 10 || Response[8] != 0x90 || Response[9] != 0x00)
391 byte[] Challenge =
new byte[8];
392 Array.Copy(Response, 0, Challenge, 0, 8);
404 byte[] ChallengeResponse)
406 byte Lc = (byte)ChallengeResponse.Length;
407 byte[] Command =
new byte[]
414 }.CONCAT(ChallengeResponse,
419 byte[] Response = await TagInterface.ExecuteCommand(Command);
420 if (Response.Length != 10 || Response[8] != 0x90 || Response[9] != 0x00)
423 byte[] Challenge =
new byte[8];
424 Array.Copy(Response, 0, Challenge, 0, 8);
Contains NFC Extensions for Basic Access Control.
static byte[] KEnc(this DocumentInformation Info)
3DES Encryption Key (§D.1)
static byte[] KSeed(this DocumentInformation Info)
Seed for computing cryptographic keys (§D.2)
static async Task< byte[]?> ExternalAuthenticate(this IIsoDepInterface TagInterface, byte[] ChallengeResponse)
Send Response to challenge (§7.1.5.4, §D.3)
static byte[] CalcChallengeResponse(byte[] Challenge, byte[] Rnd1, byte[] Rnd2, byte[] KEnc, byte[] KMac)
Calculates a response to a challenge.
static byte[] KMac(this DocumentInformation Info)
DES MAC Key (§D.1)
static byte[] CONCAT(this byte[] Bytes, params byte[][] MoreBytes)
Concatenates a series of byte arrays.
static async Task< byte[]?> GetChallenge(this IIsoDepInterface TagInterface)
Get Challenge (§7.1.5.4, §D.3)
static byte[] CalcChallengeResponse(this DocumentInformation Info, byte[] Challenge)
Calculates a response to a challenge.
static bool ParseMrz(string MRZ, out DocumentInformation? Info)
Derives Basic Access Control Keys from the second row of the Machine-Readable string in passport (MRZ...
Helps with parsing of commong data types.
static Encoding ISO_8859_1
ISO-8859-1 encoding.
Contains methods for simple hash calculations.
static byte[] ComputeSHA1Hash(byte[] Data)
Computes the SHA-1 hash of a block of binary data.
ISO DEP interface, for communication with an NFC Tag.
Task< byte[]> ExecuteCommand(byte[] Command)
Executes an ISO 14443-4 command on the tag.
DocumentType
Type of markdown document.