Decode Drivers Licence Barcode

For anyone still interested, I have written an open source Dart package which provides the means to gather the information from a Driver's license barcode: https://pub.dev/packages/rsa_identification

Please leave a like on the package if you find it useful.

For those interested in flutter development I have built on-top of this package with a flutter plugin (see here) which provides the ability to scan barcodes of driver's licenses, ID cards and ID books. The repository for the plugin also includes an example app but you'll need to set up a Firebase project for it to work since the plugin makes use of Google's ML Vision :) Also note that due to an unresolved issue with the Flutter Camera plugin scanning may not be smooth on some phones (the Samsung Galaxy S8 is one that I've noticed) but with most phones everything should work fine. Again, leave a like on the plugin if you find it useful :)

Also, if you'd just like to download an app from the Play store or App store and see the functionality in action then you can download an access-control app that I developed on both stores. To be specific, the scanning functionality is available within the "My Profile" page in the app by tapping one of the camera icons.
 
Hey guys. Does anyone understand if this tech / service is commercially available via api and who in South Africa provides the barcode decode service. I have a commercial use case but I need it to be legal and legit. I know Regula has an iOS app that reads the barcode on an ID quite efficiently but their costs at $8000 is prohibitive. Any insights would be appreciated.
Would you like to handle the scanning natively on the android devices?

I Have a camera scanning library capable of scanning the pdf417 on the back of the sadl efficiently. License free and legal ;-)
 
Hi Marcus,

Do you get a 100% decode rate, as for some reason, some licenses that i scan fail to decode.
They also start with 01 9b 09 45 followed by 00 but the rest does not decode according to the script.

By any chance do you know what could be the issue or have you encountered this as well?
So far I've gotten a 100% decode rate but admittedly the is on quite a small sample of around 10 driver's licences that I've tried :)
 
So far I've gotten a 100% decode rate but admittedly the is on quite a small sample of around 10 driver's licences that I've tried :)

I have a working version capable of decrypting the barcodes with the following headers:

Version 1: 01 e1 02 45
Version 2: 01 9b 09 45
Version 3: 01 87 06 45

I have a c++ version

  1. Android studio aar arm 32bit/arm 64 bit (No image decoding)
  2. Android studio aar arm 32bit (Image decoding included)
  3. Visual studio (Windows forms demo including image decoding x86/x64)
  4. Visual Studio Xamarin Android Version (arm 32bit/arm 64 bit No image decoding)
  5. Visual Studio Xamarin Android Version (arm 32bit Image decoding included)
I also have the entire decryption and parsing ported to c#(portable to Xamarin android/ xamarin ios and linux)
 
I was thinking more in the line of selling copies of it. PM me your email address and i will send along a screen recoding of the decryption and parsing with my visual studio open.

Not worth it, new licenses coming in. Few will part with money for the old drivers license.
 
Hi,

I am also trying to get this problem solved on C#. I can get the sample that is given to work but as soon as I use my own I get gibberish again, would you mind 'testing' the hex I received and check if it decodes?

Raw:
019b09450000afc6a4c2ebd0768005365d9cdb4dba5bf649ab4084ac3abee3ec2ffe9a6502a3785daa72f0b766ca26067799ba7c7006b4febd906b7c92d72cf104f6bab63676fb0c861c24a1abe30177e4b54e6889a274aead70689d25e15077f53e3ee26c065208fdb3fe5a5502f228b0728414b7d5f5d7ee97af3ee39a5535c7955ab77e374453a3f5224e717684e716099453d5167dab4824c8a98acdea3630c805960ca147092b00169f28ce69feb07309d5272151fe69d74c89c1e6ea8ad94983bd744b741de582c9d202bc83f9e95ac79864d58ed225e4204b12083c4b198dfb5e14364ca362a0583cb66a73dc567e22c5c6c75963e47c8b9db69e36024b6669f20bc0aa56ea9b6b4a0f2fe4ae8144eb5d49a5af4a672904ede4230a82f4fc807b858e167038fd780d23a84311abbf243a8b845dfc180aa1b999b675c0ffe0b27419ec5d5b99ae0242850a074a3604e763c6a76bb5ce2389967e39ad5dfc240a2312038ac8f18808a11be7bdfc5b7cf546845d77ece574751a47296eb61b09c8d94ba261fbc5b0de393e465d2080b3bff9838953b8acd3c072247216970c979d93b3996112fe3ac28a96fec91b019afdeb71d186fd55e6766089ef560924d59ac0787b7ea634d5887febfb32c1770f8adb50e3e516b5b42d9cdecd39217c60373805ec20080a442533a3ce58cf1d5fea2fae0db971ac71a5deb5b00c06c37cfc171d5e8e36887ad1a160e7de6778114f8eb0dd88e5c2a0286466b6aadf8cf0ec61a57734023054d265b6c2494071ae82fe2632a020b1fedc418650407bf02e238ff5c5fa28cfbb649bd0c71b8040a4a5bb850b31f333b3e8a5d93df03b28d6c8d6bbe8d4123393a74545b26c77c489d30b0af9cea5425795ef66fdc2e8fcb5ea3e20d1445e73906b82bcaa91fab9d22dbc8bbf55fbb2d2a20792fbfd8464b85ee9e6ef33993ea4f14dbe4f261fc7cc2a95070e34ff1763b9596460cd0725c54dfc1880b70de453a470b3496ca5

Any help will be greatly appreciated!
I know this is a bit old but this is what is inside your raw string (I have replaced some of it with "x" so that I can obfuscate some "sensitive" information:

ss = 019b09450000afc6a4c2ebd0768005365d9cdb4dba5bf649ab4084ac3abee3ec2ffe9a6502a3785daa72f0b766ca26067799ba7c7006b4febd906b7c92d72cf104f6bab63676fb0c861c24a1abe30177e4b54e6889a274aead70689d25e15077f53e3ee26c065208fdb3fe5a5502f228b0728414b7d5f5d7ee97af3ee39a5535c7955ab77e374453a3f5224e717684e716099453d5167dab4824c8a98acdea3630c805960ca147092b00169f28ce69feb07309d5272151fe69d74c89c1e6ea8ad94983bd744b741de582c9d202bc83f9e95ac79864d58ed225e4204b12083c4b198dfb5e14364ca362a0583cb66a73dc567e22c5c6c75963e47c8b9db69e36024b6669f20bc0aa56ea9b6b4a0f2fe4ae8144eb5d49a5af4a672904ede4230a82f4fc807b858e167038fd780d23a84311abbf243a8b845dfc180aa1b999b675c0ffe0b27419ec5d5b99ae0242850a074a3604e763c6a76bb5ce2389967e39ad5dfc240a2312038ac8f18808a11be7bdfc5b7cf546845d77ece574751a47296eb61b09c8d94ba261fbc5b0de393e465d2080b3bff9838953b8acd3c072247216970c979d93b3996112fe3ac28a96fec91b019afdeb71d186fd55e6766089ef560924d59ac0787b7ea634d5887febfb32c1770f8adb50e3e516b5b42d9cdecd39217c60373805ec20080a442533a3ce58cf1d5fea2fae0db971ac71a5deb5b00c06c37cfc171d5e8e36887ad1a160e7de6778114f8eb0dd88e5c2a0286466b6aadf8cf0ec61a57734023054d265b6c2494071ae82fe2632a020b1fedc418650407bf02e238ff5c5fa28cfbb649bd0c71b8040a4a5bb850b31f333b3e8a5d93df03b28d6c8d6bbe8d4123393a74545b26c77c489d30b0af9cea5425795ef66fdc2e8fcb5ea3e20d1445e73906b82bcaa91fab9d22dbc8bbf55fbb2d2a20792fbfd8464b85ee9e6ef33993ea4f14dbe4f261fc7cc2a95070e34ff1763b9596460cd0725c54dfc1880b70de453a470b3496ca5


License type 1: B

License type 2:

License Type 3:

License Type 4:

Surname: GROBLER

Initials: SF

PrDP Code:

ID Country of Issue: ZA

License Country of Issue: ZA

Vehicle Restriction L1: 0

Vehicle Restriction L2:

Vehicle Restriction L3:

Vehicle Restriction L4:

License Number: 4062x005BNxZ

ID Number: 791x28x02x087

ID Number Type: 02

Issue Date L1: 20150216

Issue Date L2: ........

Issue Date L3: ........

Issue Date L4: ........

Driver Restrictions: 00

PrDP Expiry Date: ........

License Issue Number: 01

Birth Date: 19791x28

Valid From: 20150217

Valid To: 20200216

Gender: Male
 
For Windows OS

Attached is a ZIP with a jar and an exe (both named decodeDriverLicense.xxx). Extract the JAR and EXE from the zipped file. Copy them into a folder (make a folder on your desktop and extract them inside the folder). Double click on the "APPLICATION - it will run the JAR). Copy your HEX string into the TextArea and click on "Decode". The data in the hex string should be extracted and displayed in the ListView below the TextArea

It works with Version 2 licences.

The JAR was created by compiling my B4J project (see reference to B4J further down this post)

You need to have Java Open JDK 11.0.1 installed and have to set a system environmental variable. In my case it looks as follows:

1638698474864.png




1638697849652.png

I have done the code using B4J - it is for free! Get it from here:


Very powerful, a great community with tons of support. It also has B4A (Android), B4R (Arduino), and B4I (Apple). With B4J and B4A you can use inline Java code. With B4R and B4I you can use inline C code. Believe me - B4X (B4J, B4I, B4R, B4A) is extremely powerful.

I have also attached the B4J project for those that want to explore it (the smaller - in size - of the attached projects)

I have used inline Java code in the B4J project. It looks like this:

#If Java import java.lang.StringBuffer; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.io.StringReader; import java.math.BigInteger; import java.security.interfaces.RSAPublicKey; import org.bouncycastle.openssl.PEMReader; import org.bouncycastle.crypto.params.RSAKeyParameters; import java.util.Base64; import org.bouncycastle.util.BigIntegers; import org.bouncycastle.util.encoders.Hex; import org.bouncycastle.jce.provider.BouncyCastleProvider; import java.security.Provider; import java.security.Security; //import org.bouncycastle.jce.provider.JDKKeyFactory; //import org.bouncycastle.jce.provider.JDKKeyFactory.RSA; static{ Provider BC = new BouncyCastleProvider(); Security.addProvider(BC); } private static RSAPublicKey pubKey; //Unsigned integer types have no direct equivalent in Java: public static byte[] GetPaddedValue(BigInteger value, int length) { //Unsigned integer types have no direct equivalent in Java: byte[] result = value.toByteArray(); if (result[0] == 0) { byte[] padded = new byte[length]; for (int i = 0; i < 128; i++) { padded[i] = result[i+1]; } return padded; } else { byte[] padded = new byte[length]; System.arraycopy(result, 0, padded, (length - result.length), result.length); return padded; } } public static byte[] EncryptValue(byte[] rgb, BigInteger e, BigInteger n, int size) { BigInteger input = new BigInteger(rgb); BigInteger output = input.modPow(e, n); byte[] result = GetPaddedValue(output, size); return result; } //Version 2 code for first 5 blocks of 128 bytes public static String decodeVersion2First5Blocks(byte[] mybyte) { String longKey = "-----BEGIN RSA PUBLIC KEY-----" + "\r\n" + "MIGWAoGBAMqfGO9sPz+kxaRh/qVKsZQGul7NdG1gonSS3KPXTjtcHTFfexA4MkGA" + "\r\n" + "mwKeu9XeTRFgMMxX99WmyaFvNzuxSlCFI/foCkx0TZCFZjpKFHLXryxWrkG1Bl9+" + "\r\n" + "+gKTvTJ4rWk1RvnxYhm3n/Rxo2NoJM/822Oo7YBZ5rmk8NuJU4HLAhAYcJLaZFTO" + "\r\n" + "sYU+aRX4RmoF" + "\r\n" + "-----END RSA PUBLIC KEY-----"; String base64 = null; try { StringReader reader = new StringReader(longKey); PEMReader pem = new PEMReader(reader); pubKey = (RSAPublicKey) new PEMReader(reader).readObject(); RSAKeyParameters o = new RSAKeyParameters(false, pubKey.getModulus(), pubKey.getPublicExponent()); //Unsigned integer types have no direct equivalent in Java: byte[] data = mybyte; //Unsigned integer types have no direct equivalent in Java: byte[] decrypted = EncryptValue(data, o.getExponent(), o.getModulus(), 128); base64 = Base64.getEncoder().encodeToString(decrypted); } catch (Exception e) { BA.Log("RSA decryption error : " + e); } return base64; } #End If

I have this working on Android too with a B4A project and with a camera scanner.

Now only need to figure out how to get the photo from the hex string.....
 

Attachments

Last edited:
This is the B4J code excluding the inline Java code:
#Region Project Attributes #MainFormWidth: 600 #MainFormHeight: 600 #End Region #AdditionalJar: bcprov-jdk16-1.43 Sub Process_Globals Private fx As JFX Private MainForm As Form Private xui As XUI Dim su As StringUtils Dim dl As String = "" Dim nativeMe As JavaObject Private TextArea1 As TextArea Private Button1 As Button Private ListView1 As ListView End Sub Sub AppStart (Form1 As Form, Args() As String) MainForm = Form1 MainForm.RootPane.LoadLayout("Layout1") MainForm.Show nativeMe = Me TextArea1.Text = "019b0945000060e9a27a1e475f89af17cb3a5ae86bd91152d22fbca1f462ba8bd39e9341bb26ad82e3ab1b4e68069247b75286edb64fed476c1a444bba7ffeca8ac9ddcf629ac582a865b8eefec682f33de40e68a6fc08265df86ad058376db56e4a9d2a2da285d11f408493fb4706abc346632a156b517c87b58372a3afda8be11dbae4a8f5a3d1881559ce0c69acd026770fd9e75c28c4abd0b2796e70eca894d8546bd8456bdcd9af0aa112f78b56523471b46805ae844a66108ba2f12a28180e5ddded48436239eae212c6193d351201e6a24613d2482ddae552bf7e21729246667a9cd5c730ba4b80736586f5f90894b1017fcea7b299cd6241a266c4b7b28b704de1f0266025358d134686e1cac2ba9af74ea16b9524f4fca20792d2618beec8499dbab596f5a56531f26462c996dad5e1278400a5266c5b5aed2dd3cae135821148bdfd3d564ac127c90d3a125031cb263e63879dfdd10725dc80225a700e5d259a5a1d2340d7bd2d3fecc9b078e1726661e2c51b945b56d762fa3067f2b926dd2707a74e1535abf1a7ee09b0ec40689cd3b3b9995dfdda9b5f33482f1441afa48304a68b24b942464d0334d4b20c17a6f09d5db119257212f7b2d48860e22c64924dc53c03557624a020b175ef47553946ce2e60c3f2ba95865d73b2d440c2fd3eb3c9850447c249c869444dbc103d8197c5556355e6c46455b61aa0b176bcc73290b532726d4339bb35b144fecf5c5d4c5e90aef23fc9c9375ae7206f9ea5a22255365f7e0728e3360cb78562c805a4f9a2b4a674eb5af30b598b98b16227a223505e2e013e426296e8f3d1ff03d6506fa431162438afabbc4e8b9626e8821d6bd264ff9d35a2df90e2be27e0297c162a0a6bbc937f349d31413484b52ca27255b2263abde2793cb946387685de9662777cdf6808a18c3a25759ed4e6ab6410f7881b655421be87e3f3df100062f12ae806059ca3e11248557080a64e4ec6e0a46697cae4c5c5197e3defdc" End Sub Private Sub Button1_Click Dim ss As String ss = TextArea1.Text.Trim ListView1.Items.Clear Log("ss = " & ss) Log("ss length = " & ss.Length) Dim block1 As String = ss.SubString2(10,268) Dim conv As ByteConverter Dim mybyte() As Byte = conv.HexToBytes(block1) Log("MYSTRING LENGTH = " & block1.Length) Dim returnedstring As String returnedstring = nativeMe.RunMethod("decodeVersion2First5Blocks", Array(mybyte)) Dim MyBase64String As String = returnedstring Dim MyBase64StringAsBytes() As Byte = su.DecodeBase64(MyBase64String) Log("LENGTE = " & MyBase64StringAsBytes.Length) Dim s As String = BytesToString(MyBase64StringAsBytes, 0, MyBase64StringAsBytes.Length, "ISO8859_1") Dim karak(128) As String Dim finalstring As String = "" For i = 0 To s.Length - 1 Dim c As Char = s.CharAt(i) Dim ub As Int = Bit.And(0xff, MyBase64StringAsBytes(i)) karak(i) = Bit.ToHexString(ub) If karak(i).Length = 1 Then karak(i) = "0" & karak(i) finalstring = finalstring & karak(i) Log("char = " & c & " karak = " & karak(i) & " MyBase64StringAsBytes(" & i & ") = " & karak(i)) Next Dim section1_length As Int = Bit.ParseInt(karak(10), 16) Dim section2_length As Int = Bit.ParseInt(karak(12), 16) Dim section1 As String = "" For i = 15 To (15 + section1_length) If karak(i).Trim = "e0" Then karak(i) = "," section1 = section1 & karak(i) else if karak(i).Trim = "e1" Then karak(i) = "[]" section1 = section1 & karak(i) Else Dim c As Char = s.CharAt(i) section1 = section1 & c End If Next section1 = section1 Log("section1 = " & section1) section1 = section1.Replace("[][][]", ",,,,") section1 = section1.Replace("[][]", ",,,") section1 = section1.Replace("[]", ",,") Log("section1 = " & section1) Dim headings() As String = Array As String("License type 1: ", "License type 2: ", "License Type 3: ", "License Type 4: ", _ "Surname: ", "Initials: ", "PrDP Code: ", "ID Country of Issue: ", "License Country of Issue: ", _ "Vehicle Restriction L1: ", "Vehicle Restriction L2: ", "Vehicle Restriction L3: ", "Vehicle Restriction L4: ", _ "License Number: ", "ID Number: ") DateTime.DateFormat = "yyyy/MM/dd" ListView1.Items.Add("Date: " & DateTime.Date(DateTime.Now)) ListView1.Items.Add("Time: " & DateTime.Time(DateTime.Now)) Dim Lines() As String = Regex.Split(",",section1) For i = 0 To Lines.Length - 1 ListView1.Items.Add(headings(i) & Lines(i)) Log(""& headings(i) & " " & Lines(i)) Next Dim section2 As String = "" For i = (15 + section1_length) To (15 + section1_length + section2_length - 1) If i = 15 + section1_length Then section2 = section2 & karak(i).Trim End If If i > 15 + section1_length Then If karak(i).Trim.SubString2(0,1) = "a" Then section2 = section2 & "." Else section2 = section2 & karak(i).Trim.SubString2(0,1) End If If karak(i).Trim.SubString2(1,2) = "a" Then section2 = section2 & "." Else section2 = section2 & karak(i).Trim.SubString2(1,2) End If End If Next If section2.EndsWith(".") = True Then section2 = section2.SubString2(0, section2.Length - 1) End If Dim amendedSection2 As String = "" For i = 0 To section2.Length - 1 If section2.SubString2(i, i+1) <> "." Then amendedSection2 = amendedSection2 & section2.SubString2(i, i+1) Else amendedSection2 = amendedSection2 & "........" End If Next Dim idType As String = amendedSection2.SubString2(0,2) Dim issueDate1 As String = amendedSection2.SubString2(2, 10) Dim issueDate2 As String = amendedSection2.SubString2(10,18) Dim issueDate3 As String = amendedSection2.SubString2(18, 26) Dim issueDate4 As String = amendedSection2.SubString2(26, 34) Dim driverRestrictions As String = amendedSection2.SubString2(34, 36) Dim PrDPdate As String = amendedSection2.SubString2(36, 44) Dim licenseIssueNumber As String = amendedSection2.SubString2(44, 46) Dim birthDate As String = amendedSection2.SubString2(46, 54) Dim validFrom As String = amendedSection2.SubString2(54, 62) Dim validTo As String = amendedSection2.SubString2(62, 70) Dim gender As String = amendedSection2.SubString2(70, 72) ListView1.Items.Add("ID Number Type: " & idType) Log("ID Number Type: " & idType) ListView1.Items.Add("Issue Date L1: " & issueDate1) Log("Issue Date L1: " & issueDate1) ListView1.Items.Add("Issue Date L2: " & issueDate2) Log("Issue Date L2: " & issueDate2) ListView1.Items.Add("Issue Date L3: " & issueDate3) Log("Issue Date L3: " & issueDate3) ListView1.Items.Add("Issue Date L4: " & issueDate4) Log("Issue Date L4: " & issueDate4) ListView1.Items.Add("Driver Restrictions: " & driverRestrictions) Log("Driver Restrictions: " & driverRestrictions) ListView1.Items.Add("PrDP Expiry Date: " & PrDPdate) Log("PrDP Expiry Date: " & PrDPdate) ListView1.Items.Add("License Issue Number: " & licenseIssueNumber) Log("License Issue Number: " & licenseIssueNumber) ListView1.Items.Add("Birth Date: " & birthDate) Log("Birth Date: " & birthDate) ListView1.Items.Add("Valid From: " & validFrom) Log("Valid From: " & validFrom) ListView1.Items.Add("Valid To: " & validTo) Log("Valid To: " & validTo) If gender = "01" Then gender = "Male" Else gender = "Female" ListView1.Items.Add("Gender: " & gender) Log("Gender: " & gender) End Sub
 
Hi,

I am also trying to get this problem solved on C#. I can get the sample that is given to work but as soon as I use my own I get gibberish again, would you mind 'testing' the hex I received and check if it decodes?

Raw:
019b09450000afc6a4c2ebd0768005365d9cdb4dba5bf649ab4084ac3abee3ec2ffe9a6502a3785daa72f0b766ca26067799ba7c7006b4febd906b7c92d72cf104f6bab63676fb0c861c24a1abe30177e4b54e6889a274aead70689d25e15077f53e3ee26c065208fdb3fe5a5502f228b0728414b7d5f5d7ee97af3ee39a5535c7955ab77e374453a3f5224e717684e716099453d5167dab4824c8a98acdea3630c805960ca147092b00169f28ce69feb07309d5272151fe69d74c89c1e6ea8ad94983bd744b741de582c9d202bc83f9e95ac79864d58ed225e4204b12083c4b198dfb5e14364ca362a0583cb66a73dc567e22c5c6c75963e47c8b9db69e36024b6669f20bc0aa56ea9b6b4a0f2fe4ae8144eb5d49a5af4a672904ede4230a82f4fc807b858e167038fd780d23a84311abbf243a8b845dfc180aa1b999b675c0ffe0b27419ec5d5b99ae0242850a074a3604e763c6a76bb5ce2389967e39ad5dfc240a2312038ac8f18808a11be7bdfc5b7cf546845d77ece574751a47296eb61b09c8d94ba261fbc5b0de393e465d2080b3bff9838953b8acd3c072247216970c979d93b3996112fe3ac28a96fec91b019afdeb71d186fd55e6766089ef560924d59ac0787b7ea634d5887febfb32c1770f8adb50e3e516b5b42d9cdecd39217c60373805ec20080a442533a3ce58cf1d5fea2fae0db971ac71a5deb5b00c06c37cfc171d5e8e36887ad1a160e7de6778114f8eb0dd88e5c2a0286466b6aadf8cf0ec61a57734023054d265b6c2494071ae82fe2632a020b1fedc418650407bf02e238ff5c5fa28cfbb649bd0c71b8040a4a5bb850b31f333b3e8a5d93df03b28d6c8d6bbe8d4123393a74545b26c77c489d30b0af9cea5425795ef66fdc2e8fcb5ea3e20d1445e73906b82bcaa91fab9d22dbc8bbf55fbb2d2a20792fbfd8464b85ee9e6ef33993ea4f14dbe4f261fc7cc2a95070e34ff1763b9596460cd0725c54dfc1880b70de453a470b3496ca5

Any help will be greatly appreciated!
1638707034732.png
 
A bit of a resurrection...

As of 2025, which library/SDK is preferable open source or free ideally, but paid is fine too if the pricing is realistic / affordable. It should work with .NET Standard minimally, though .NET Core is preferred:

Looking for something that can read the initial values from a PDF417 strip before decryption and decoding. ZXing still doesn’t work, and I even tried the C++ version.
 
The decoder handles issued v1 and v2 formats and returns the encoded identity fields, licence number, valid-from and expiry dates, vehicle categories, restrictions, PrDP fields and the low-resolution portrait (image) stored inside the barcode.
Encoded ≠ encrypted.
How did VerifyNow obtain the encryption keys?
 
Top
Sign up to the MyBroadband newsletter
X