package ge.eid.card.mifare;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.List;
import javax.smartcardio.Card;
import javax.smartcardio.CardChannel;
import javax.smartcardio.CardException;
import javax.smartcardio.CardTerminal;
import javax.smartcardio.CommandAPDU;
import javax.smartcardio.TerminalFactory;
/**
* ეს პროგრამული კოდი კითხულობს ყველა სექტორის ყველა ბლოკს MIFARE Classic 1k
ემულატორიდან * ემულატორიდან პროგრამა გატესტილია ბარათის უკონტაქტო წამკითხველზე HID OMNIKEY
* 5321 USB მოდელის წამკითხველებზე * როგორც ორმაგი (კონტაქტური და უკონტაქტო) ისე
* მხოლოდ უკონტაქტო ინტერფეისით.
*
* პროგრამა იყენებს PC/SC ინტერფეისს, რომელიც სტანდარტულია როგორც Windows-ში,
* ისე სხვა ოპერაციულ * სისტემებში. ამ უკანასკნელებში pcsc lite-ს გამოყენებით
*
* @author mikheil
*/
public class Main {
// IOCTL ბრძანება რომლითაც შესაძლებელია ბარათის წამკითხველში MIFARE
// ემულაციის გამორთვა - ემულაციას აკეთებს თავად ბარათი
public static final int CM_IOCTL_SET_RFID_CONTROL_FLAGS = scardCtlCode(3213);
private static final boolean EMULATED = true;
private static final boolean FACTORY_KEYS = false;
private static final boolean ZERO_KEYS = false;
/**
* PC/SC Control Code (საკონტროლო კოდი) სხვადასხვანაირად ითვლება
*
* @param code
* @return
*/
public static int scardCtlCode(int code) {
String osName = System.getProperty("os.name").toLowerCase();
if (osName.indexOf("windows") > -1) {
return 0x31 << 16 | code << 2;
} else {
return 0x42000000 + code;
}
}
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
// ავირჩიოთ ტერმინალი
CardTerminal terminal = selectCardTerminal();
// დაველოდოთ ბარათს
System.out.println("Waiting for a card..");
terminal.waitForCardPresent(0);
// დავუკავშირდეთ ბარათს. იხ დოკუმენტაცია თავად მეთოდზე
Card card = getCardConnection(terminal, EMULATED);
try {
// ავიღოთ კავშირის არხი
CardChannel channel = card.getBasicChannel();
// შეგვიძლია წავიკითხოთ ბარათის UID
byte[] baReadUID = new byte[5]; baReadUID readUIDCommand = new byte[] { (byte) 0xFF, (byte) 0xCA, (byte) 0x00,
(byte) 0x00, (byte) 0x00 };
System.out.println("UID: " + send(baReadUIDreadUIDCommand, channel, true));
// წარმატებული წაკითხვის შემთხვევაში UID-ს შემდეგ ეწერება 9000
// ჩავტვირთოთ A გასაღები წამკითხველის მეხსიერებაში
// მაგალითად, 0x07 ზონაში
byte[] baLoadKeyloadKeyCommand = createKeyLoadingCommand((byte) 0x07);
System.out.println("LOAD KEY: " + send(baLoadKeyloadKeyCommand, channel, true));
// წარმატების შემთხვევაში გამოვა 9000
byte[] baReadreadCommand;
// გადავუაროთ ყველა ბლოკს (16 სექტორი, 4 ბლოკი თითოეულში)
for (int blockId = 0; blockId < 4 * 16; blockId++) {
// თითოეული სექტორისათვის საკმარისია ერთ ბლოკზე იდენტიფიკაცია
// ამიტომაც ჩავატაროთ პროცესი დასაწყისში და ყოველ მეოთხე ბლოკზე
if (blockId % 4 == 0) {
System.out.println();
System.out.println("Authenticating Sector " + (blockId / 4)
+ " block 3");
byte authBlockId = (byte) (blockId + 3);
byte keyAddress = (byte) 0x07;
byte[] authDateBytesauthCommand = createBlockAuthenticationCommand(
authBlockId, keyAddress);
System.out.println("AUTHENTICATE: "
+ send(authDateBytesauthCommand, channel, true));
// წარმატებული აუთენტიფიკაციის შემთხვევაში ეწერება 9000
}
// თუ იდენტიფიკაცია გავლილია, შეგვიძლია წავიკითხოთ ბლოკი
baRead readCommand = new byte[] { (byte) 0xFF, (byte) 0xB0, (byte) 0x00,
(byte) (blockId), (byte) 0x10 };
System.out.println("READ: " + send(baReadreadCommand, channel, false));
// წარმატებული წაკითხვის შემთხვევაში მონაცემების შემდეგ ეწერება
// 9000
}
} finally {
card.disconnect(true);
}
}
/**
* ამ მეთოდით ხდება იდენტიფიკაციის ბრძანების აწყობა
*
* @param blockId
* ბლოკი, რომლის მიმართაც უნდა გავიაროთ აუთენტიფიკაცია
* @param keyAddress
* მისამართი, სადაც ჩატვირთული იყო გასაღები
*/
private static byte[] createBlockAuthenticationCommand(byte blockId,
byte keyAddress) {
byte keyTypeId = (byte) 0x60; // A გასაღები
byte[] authData = new byte[] { (byte) 0x01, 0, blockId, keyTypeId,
keyAddress };
// ავაწყოთ აუთენტიფიკაციის ბრძანება
CommandAPDU c1 = new CommandAPDU(0xFF, 0x86, 0x00, 0x00, authData);
byte[] authDateBytes = c1.getBytes();
return authDateBytes;
}
/**
* ამ მეთოდით ხდება წამკითხველის მეხსიერებაში გასაღების ჩატვირთვის ბრძანების
* ბრძანების აწყობა
*
* @param keyAddress
* მისამართი, სადაც ჩატვირთული იყო გასაღები
*/
private static byte[] createKeyLoadingCommand(byte loadKeyTo) {
byte[] baLoadKey = new byte[12]loadKeyCommand;
if (FACTORY_KEYS) {
// მწარმოებლის გასაღებები. მოჰყვება ცარიელ ბარათებს
baLoadKeyloadKeyCommand = new byte[] { (byte) 0xFF, (byte) 0x82, (byte) 0x20,
loadKeyTo, (byte) 0x06, (byte) 0xFF, (byte) 0xFF,
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF };
} else {
if (ZERO_KEYS) {
// ნულოვანი გასაღებები
baLoadKeyloadKeyCommand = new byte[] { (byte) 0xFF, (byte) 0x82, (byte) 0x20,
loadKeyTo, (byte) 0x06, (byte) 0x00, (byte) 0x00,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 };
} else {
// MAD სტანდარტული A გასაღებები, რომელიც ადევს MAD სექტორს.
// ID ბარათზე ყველა ცარიელ საქტორს სექტორს იგივე გასაღები უყენია
// გასაღების მნიშვნელობაა A0A1A2A3A4A5
baLoadKeyloadKeyCommand = new byte[] { (byte) 0xFF, (byte) 0x82, (byte) 0x20,
loadKeyTo, (byte) 0x06, (byte) 0xA0, (byte) 0xA1,
(byte) 0xA2, (byte) 0xA3, (byte) 0xA4, (byte) 0xA5 };
}
}
return baLoadKeyloadKeyCommand;
}
/**
* ამ მეთოდით ხდება წამკითხველთან დაკავშირება. OMNIKEY 5321 USB ავტომატურად
* ავტომატურად ააქტიურებს MIFARE ემულატორს თავად წამკითხველში, ამიტომ * ის არ მუშაობს ID
ბარათთან. * ბარათთან. იმისათვის, რომ წამკითხველმა იმუშაოს ID ბარათთან, აუცილებელია
* ემულაციის რეჟიმის გამორთვა. MIFARE ემულაციას თვითონ ბარათი აკეთებს.
*
* ემულაციის გამორთვა ასევე შეიძლება სისტემური რეესტრიდან იხილეთ დოკუმენტი
* იხილეთ დოკუმენტი* www.hidglobal.com/documents/ok_contactless_developer_guide_an_en.pdf
*
* @param terminal
* მისამართი, სადაც ჩატვირთული იყო გასაღები
* @param onCardEmulation
* თუ true-ა, მეთოდი გამორთავს წამკითხველზე MIFARE ემულაციას
*/
private static Card getCardConnection(CardTerminal terminal,
boolean onCardEmulation) throws CardException {
Card card = terminal.connect("T=0");
if (onCardEmulation) {
// გავუშვათ ემულაციის ბრძანება
byte[] ioctl = new byte[] { 4, 0, 0, 0, 4, 0, 0, 0 };
byte[] resp = card.transmitControlCommand(
CM_IOCTL_SET_RFID_CONTROL_FLAGS, ioctl);
System.out.println("IOCTL Sent");
System.out.println(new BigInteger(1, resp).toString(16));
// ბრძანების გაშვების შემდეგ წამკითხველი გაითიშება და თავიდან
ჩაირთვება
// ჩაირთვება. ამიტომ თავიდანსაჭიროა უნდამასთან დავუკავშირდეთთავიდან მასდაკავშირება
terminal.waitForCardPresent(0);
card = terminal.connect("T=0");
}
return card;
}
/**
* ბარათის წამკითხველის არჩევა. მეთოდი იყენებს მარტივ გზას მკითხველს შეუძლია
* მკითხველს შეუძლია გაცილებით უფრო დახვეწილი გზა გამოიყენოს. მაგალითად შეეკითხოს მომხმარებელს
* მომხმარებელს ან აიღოს კონფიგურაციის ფაილიდან
*/
private static CardTerminal selectCardTerminal() throws CardException {
CardTerminal terminal = null;
// show the list of available terminals
TerminalFactory factory = TerminalFactory.getDefault();
List<CardTerminal> terminals = factory.terminals().list();
for (int i = 0; i < terminals.size(); i++) {
String terminalFull = terminals.get(i).toString();
System.out.println("Terminal: " + terminalFull);
if (terminalFull.contains("CL")) {
terminal = terminals.get(i);
System.out.println("SELECTED Terminal: " + terminalFull);
}
}
return terminal;
}
/**
* ბრძანების გაგზავნა წამკითხველთან და პასუხის მიღება
*
* @param cmd
* ბრძანება ბაიტების მასივში
* @param channel
* კავშირის არხი ბარათთან
* @param echo
* გამოვიდეს თუ არა ბრძანება ეკრანზე?
*/
public static String send(byte[] cmd, CardChannel channel, boolean echo) {
String res = "";
for (int i = 0; i < cmd.length; i++) {
res += String.format("%02X", cmd[i]);
// The result is formatted as a hexadecimal integer
}
if (echo) {
System.out.println("Sending " + res);
}
byte[] baResp = new byte[258];
ByteBuffer bufCmd = ByteBuffer.wrap(cmd);
ByteBuffer bufResp = ByteBuffer.wrap(baResp);
// output = The length of the received response APDU
int output = 0;
try {
output = channel.transmit(bufCmd, bufResp);
} catch (CardException ex) {
ex.printStackTrace();
}
res = "";
for (int i = 0; i < output; i++) {
res += String.format("%02X", baResp[i]);
// The result is formatted as a hexadecimal integer
}
return res;
}
} |