Last active
January 18, 2020 22:25
-
-
Save tkaczenko/983615b2e5c6feaa1940069c1e28bf55 to your computer and use it in GitHub Desktop.
Чтение данных биометрического паспорта (на примере id-карты Украины) с использованием NFC ридера и JMRTD / Reading data from biometric passport (for example, Ukrainian ePassport or Ukrainian ID) using NFC reader with JMRTD
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.tkaczenko.cardreader.service; | |
import lombok.RequiredArgsConstructor; | |
import net.sf.scuba.smartcards.CardServiceException; | |
import org.jmrtd.PACEKeySpec; | |
import org.jmrtd.PassportService; | |
import org.jmrtd.lds.CardAccessFile; | |
import org.jmrtd.lds.PACEInfo; | |
import org.jmrtd.lds.SecurityInfo; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import org.springframework.stereotype.Service; | |
import org.springframework.validation.annotation.Validated; | |
import javax.validation.constraints.NotNull; | |
import java.io.IOException; | |
import java.util.Collection; | |
import java.util.List; | |
import java.util.stream.Collectors; | |
/** | |
* @author Andrii Tkachenko | |
*/ | |
@RequiredArgsConstructor | |
@Validated | |
@Service | |
public class DataConnection { | |
private static final Logger LOGGER = LoggerFactory.getLogger(DataConnection.class); | |
private final PassportService ps; | |
public boolean initConnection(@NotNull String can) throws CardServiceException, IOException { | |
ps.open(); | |
CardAccessFile cardAccessFile = new CardAccessFile(ps.getInputStream(PassportService.EF_CARD_ACCESS)); | |
Collection<SecurityInfo> securityInfos = cardAccessFile.getSecurityInfos(); | |
SecurityInfo securityInfo = securityInfos.iterator().next(); | |
LOGGER.info("ProtocolOIDString: " + securityInfo.getProtocolOIDString()); | |
LOGGER.info("ObjectIdentifier: " + securityInfo.getObjectIdentifier()); | |
List<PACEInfo> paceInfos = getPACEInfos(securityInfos); | |
LOGGER.debug("Found a card access file: paceInfos (" + (paceInfos == null ? 0 : paceInfos.size()) + ") = " + paceInfos); | |
if (paceInfos != null && paceInfos.size() > 0) { | |
PACEInfo paceInfo = paceInfos.get(0); | |
PACEKeySpec paceKey = PACEKeySpec.createCANKey(can); | |
ps.doPACE(paceKey, paceInfo.getObjectIdentifier(), PACEInfo.toParameterSpec(paceInfo.getParameterId()), paceInfo.getParameterId()); | |
ps.sendSelectApplet(true); | |
return true; | |
} else { | |
ps.close(); | |
return false; | |
} | |
} | |
private List<PACEInfo> getPACEInfos(Collection<SecurityInfo> securityInfos) { | |
return securityInfos.stream() | |
.filter(securityInfo -> securityInfo instanceof PACEInfo) | |
.map(securityInfo -> (PACEInfo) securityInfo) | |
.collect(Collectors.toList()); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.tkaczenko.cardreader.service; | |
import com.github.tkaczenko.cardreader.model.Person; | |
import lombok.RequiredArgsConstructor; | |
import net.sf.scuba.smartcards.CardServiceException; | |
import org.jmrtd.PassportService; | |
import org.jmrtd.lds.LDSFileUtil; | |
import org.jmrtd.lds.icao.*; | |
import org.jmrtd.lds.iso19794.FaceImageInfo; | |
import org.springframework.stereotype.Service; | |
import java.io.IOException; | |
import java.text.ParseException; | |
import java.text.SimpleDateFormat; | |
/** | |
* @author Andrii Tkachenko | |
*/ | |
@RequiredArgsConstructor | |
@Service | |
public class DataReader { | |
private static SimpleDateFormat date = new SimpleDateFormat("yyyyMMdd"); | |
private final PassportService ps; | |
public Person read() throws CardServiceException, IOException, ParseException { | |
DG1File dg1 = (DG1File) LDSFileUtil.getLDSFile(PassportService.EF_DG1, ps.getInputStream(PassportService.EF_DG1)); | |
DG11File dg11 = (DG11File) LDSFileUtil.getLDSFile(PassportService.EF_DG11, ps.getInputStream(PassportService.EF_DG11)); | |
DG12File dg12 = (DG12File) LDSFileUtil.getLDSFile(PassportService.EF_DG12, ps.getInputStream(PassportService.EF_DG12)); | |
DG2File dg2 = (DG2File) LDSFileUtil.getLDSFile(PassportService.EF_DG2, ps.getInputStream(PassportService.EF_DG2)); | |
FaceImageInfo faceInfo = dg2.getFaceInfos().stream() | |
.flatMap(fi -> fi.getFaceImageInfos().stream()) | |
.findFirst() | |
.orElse(null); | |
MRZInfo mrzInfo = dg1.getMRZInfo(); | |
return Person.builder() | |
.names(dg11.getNameOfHolder()) | |
.fathersName(dg11.getOtherNames()) | |
.dateOfBirth(date.parse(dg11.getFullDateOfBirth())) | |
.placeOfBirth(dg11.getPlaceOfBirth()) | |
.gender(mrzInfo.getGender().toString()) | |
.nationality(mrzInfo.getNationality()) | |
.docNumber(mrzInfo.getDocumentNumber()) | |
.docDateOExpiry(mrzInfo.getDateOfExpiry()) | |
.docIssuingAuthority(dg12.getIssuingAuthority()) | |
.docDateOfIssue(date.parse(dg12.getDateOfIssue())) | |
.faceInfo(faceInfo) | |
.build(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.tkaczenko.cardreader.service; | |
import org.jmrtd.lds.iso19794.FaceImageInfo; | |
import org.springframework.stereotype.Service; | |
import org.springframework.validation.annotation.Validated; | |
import javax.imageio.ImageIO; | |
import javax.validation.constraints.NotNull; | |
import java.awt.image.BufferedImage; | |
import java.io.DataInputStream; | |
import java.io.File; | |
import java.io.FileOutputStream; | |
import java.io.IOException; | |
/** | |
* @author Andrii Tkachenko | |
*/ | |
@Validated | |
@Service | |
public class ImageSaver { | |
public String saveFaceImage(@NotNull FaceImageInfo imageInfo, String destination) throws IOException { | |
String filePath = "%s/%s"; | |
int imageLength = imageInfo.getImageLength(); | |
DataInputStream dataInputStream = new DataInputStream(imageInfo.getImageInputStream()); | |
byte[] buffer = new byte[imageLength]; | |
dataInputStream.readFully(buffer, 0, imageLength); | |
FileOutputStream fileOut2 = new FileOutputStream(String.format(filePath, destination, "tmp.jp2")); | |
fileOut2.write(buffer); | |
fileOut2.flush(); | |
fileOut2.close(); | |
dataInputStream.close(); | |
File tempFile = new File(String.format(filePath, destination, "tmp.jp2")); | |
BufferedImage nImage = ImageIO.read(tempFile); | |
if (tempFile.exists()) { | |
tempFile.delete(); | |
} | |
File output = new File(String.format(filePath, destination, "facePhoto.jpg")); | |
ImageIO.write(nImage, "jpg", output); | |
return output.getAbsolutePath(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.tkaczenko.cardreader.model; | |
import lombok.Builder; | |
import lombok.Getter; | |
import lombok.ToString; | |
import org.jmrtd.lds.iso19794.FaceImageInfo; | |
import java.io.Serializable; | |
import java.util.Date; | |
import java.util.List; | |
import java.util.stream.Collectors; | |
/** | |
* @author Andrii Tkachenko | |
*/ | |
@Getter | |
@ToString | |
@Builder | |
public class Person implements Serializable { | |
private String firstName; | |
private String lastName; | |
private String ukFirstName; | |
private String ukLastName; | |
private String fathersName; | |
private Date dateOfBirth; | |
private String placeOfBirth; | |
private String gender; | |
private String nationality; | |
private String docNumber; | |
private String docDateOExpiry; | |
private Date docDateOfIssue; | |
private String docIssuingAuthority; | |
private FaceImageInfo faceInfo; | |
public static class PersonBuilder { | |
public PersonBuilder names(String names) { | |
String[] namesArr = names.split("<"); | |
ukFirstName = getNameByPos(namesArr, 1); | |
ukLastName = getNameByPos(namesArr, 0); | |
firstName = getNameByPos(namesArr, 4); | |
lastName = getNameByPos(namesArr, 3); | |
return this; | |
} | |
public PersonBuilder fathersName(List<String> otherNames) { | |
if (otherNames.size() > 0) { | |
fathersName = otherNames.get(0); | |
} | |
return this; | |
} | |
public PersonBuilder placeOfBirth(List<String> placeOfBirthList) { | |
placeOfBirth = placeOfBirthList.stream() | |
.map(String::toString) | |
.collect(Collectors.joining(" ")); | |
return this; | |
} | |
private String getNameByPos(String[] namesArr, int pos) { | |
return namesArr.length > pos ? namesArr[pos].replaceAll("<", "") : ""; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.tkaczenko.cardreader; | |
import com.github.tkaczenko.cardreader.model.Person; | |
import com.github.tkaczenko.cardreader.service.DataConnection; | |
import com.github.tkaczenko.cardreader.service.DataReader; | |
import com.github.tkaczenko.cardreader.service.ImageSaver; | |
import lombok.RequiredArgsConstructor; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import org.springframework.boot.CommandLineRunner; | |
import org.springframework.boot.SpringApplication; | |
import org.springframework.boot.autoconfigure.SpringBootApplication; | |
import org.springframework.context.annotation.Bean; | |
import java.io.File; | |
/** | |
* @author Andrii Tkachenko | |
*/ | |
@RequiredArgsConstructor | |
@SpringBootApplication | |
public class ReadUkrPassportApplication { | |
private static final Logger LOGGER = LoggerFactory.getLogger(ReadUkrPassportApplication.class); | |
private final DataConnection dataConnection; | |
private final DataReader dataReader; | |
private final ImageSaver imageSaver; | |
public static void main(String[] args) { | |
SpringApplication.run(ReadUkrPassportApplication.class, args); | |
} | |
@Bean | |
CommandLineRunner lookup() { | |
return args -> { | |
// Last 6 digits of Document No. | |
String can = args[0]; | |
if (dataConnection.initConnection(can)) { | |
Person person = dataReader.read(); | |
LOGGER.info("Read data: {}", person); | |
String path = imageSaver.saveFaceImage(person.getFaceInfo(), new File(".").getCanonicalPath()); | |
LOGGER.info("Saved photo image to {}", path); | |
} else { | |
LOGGER.info("Cannot establish a connection to read data with PACE protocol by CAN key"); | |
} | |
}; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.tkaczenko.cardreader.cfg; | |
import net.sf.scuba.smartcards.CardService; | |
import org.jmrtd.PassportService; | |
import org.springframework.beans.factory.annotation.Qualifier; | |
import org.springframework.context.annotation.Bean; | |
import org.springframework.context.annotation.Configuration; | |
import javax.smartcardio.CardException; | |
import javax.smartcardio.CardTerminal; | |
import javax.smartcardio.TerminalFactory; | |
/** | |
* @author Andrii Tkachenko | |
*/ | |
@Configuration | |
public class ReadUkrPassportConfig { | |
@Bean | |
public CardTerminal cardTerminal() throws CardException { | |
return TerminalFactory.getDefault().terminals().list().stream() | |
.filter(cardTerminal -> { | |
try { | |
return cardTerminal.isCardPresent(); | |
} catch (CardException e) { | |
e.printStackTrace(); | |
return false; | |
} | |
}) | |
.findFirst() | |
.orElseThrow(RuntimeException::new); | |
} | |
@Bean | |
public CardService cardService(@Qualifier("cardTerminal") CardTerminal cardTerminal) { | |
return CardService.getInstance(cardTerminal); | |
} | |
@Bean | |
public PassportService passportService(@Qualifier("cardService") CardService cardService) { | |
return new PassportService(cardService, 256, 224, false, true); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment