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 HIDოჯახის OMNIKEYწამკითხველებზე
* 5321და USBმუშაობს მოდელისმხოლოდ წამკითხველებზეუკონტაქტო როგორცინტერფეისით ორმაგი (კონტაქტურითუმცა დაზოგიერთ უკონტაქტო)მოდელს ისეშეიძლება 2
* მხოლოდინტერფეისი უკონტაქტო ინტერფეისითჰქონდეს. კონტაქტურიც *და უკონტაქტოც)
*
* პროგრამა იყენებს 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[] readUIDCommand = new byte[] { (byte) 0xFF, (byte) 0xCA, (byte) 0x00,
(byte) 0x00, (byte) 0x00 };
System.out.println("UID: " + send(readUIDCommand, channel, true));
// წარმატებული წაკითხვის შემთხვევაში UID-ს შემდეგ ეწერება 9000
// ჩავტვირთოთ A გასაღები წამკითხველის მეხსიერებაში
// მაგალითად, 0x07 ზონაში
byte[] loadKeyCommand = createKeyLoadingCommand((byte) 0x07);
System.out.println("LOAD KEY: " + send(loadKeyCommand, channel, true));
// წარმატების შემთხვევაში გამოვა 9000
byte[] readCommand;
// გადავუაროთ ყველა ბლოკს (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[] authCommand = createBlockAuthenticationCommand(
authBlockId, keyAddress);
System.out.println("AUTHENTICATE: "
+ send(authCommand, channel, true));
// წარმატებული აუთენტიფიკაციის შემთხვევაში ეწერება 9000
}
// თუ იდენტიფიკაცია გავლილია, შეგვიძლია წავიკითხოთ ბლოკი
readCommand = new byte[] { (byte) 0xFF, (byte) 0xB0, (byte) 0x00,
(byte) (blockId), (byte) 0x10 };
System.out.println("READ: " + send(readCommand, 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[] loadKeyCommand;
if (FACTORY_KEYS) {
// მწარმოებლის გასაღებები. მოჰყვება ცარიელ ბარათებს
loadKeyCommand = 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) {
// ნულოვანი გასაღებები
loadKeyCommand = 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
loadKeyCommand = new byte[] { (byte) 0xFF, (byte) 0x82, (byte) 0x20,
loadKeyTo, (byte) 0x06, (byte) 0xA0, (byte) 0xA1,
(byte) 0xA2, (byte) 0xA3, (byte) 0xA4, (byte) 0xA5 };
}
}
return loadKeyCommand;
}
/**
* ამ მეთოდით ხდება წამკითხველთან დაკავშირება. 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;
}
} |