Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

კოდი განკუთვნილია სამაგალითოდ და არ წარმოადგენს რაიმე რეალური ამოცანის გადაწყვეტას. თუმცა ის წარმოდგენას შეგიქმნით, რა ტიპის ბრძანებები უნდა გაუგზავნოთ თქვენს წამკითხველს რათა მან შესძლოს ID ბარათიდან ან სხვა MIFARE Classic 1k თავსებადი ბარათიდან ინფორმაციის ამოკითხვა.

Warning

პროგრამა დაწერილი და შემოწმებულია HID OMNIKEY 5321 USB მოდელის წამკითხველებზე (როგორც სტანდარტულ, 2–მაგ ინტერფეისიან, ისე მხოლოდ უკონტაქტო ინტერფეისიან, -CL ვარიანტზე). სხვა მოდელის წამკითხველებზე მის გამოყენებას შეიძლება დასჭირდეს დამატებითი ადაპტაცია. პირველ რიგში სცადეთ გამოიყენოთ EMULATED = false;

პროგრამის დაწერისას გამოყენებულია შემდეგი ინსტრუქცია: www.hidglobal.com/documents/ok_contactless_developer_guide_an_en.pdf და მისი წაკითხვა რეკომენდებულია ყველასთვის ვისაც ამ რიდერის გამოყენება უნდა.

ასევე რეკომენდებულია გადახედოთ სტატიას MIFARE Classic 1k ემულატორი ID ბარათზე

პროგრამა იყენებს PC/SC ინტერფეისს. აქედან გამომდინარე, მისი პორტირება შესაძლებელია ნებისმიერ სხვა პროგრამირების ენაზე, რომლის საშუალებითაც PC/SC ინტერფეისთან მუშაობაა შესაძლებელი.

Code Block
titleპროგრამის ტექსტი
themeEclipse
linenumberstrue
languagejava
collapsetrue
package ge.eid.card.mifare.demos;

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ოჯახის მოდელის წამკითხველებზე
 * როგორცდა ორმაგი (კონტაქტური და უკონტაქტო) ისე მხოლოდ უკონტაქტო ინტერფეისით.
 * მუშაობს მხოლოდ უკონტაქტო ინტერფეისით (თუმცა ზოგიერთ მოდელს შეიძლება 2
 * ინტერფეისი ჰქონდეს. კონტაქტურიც და უკონტაქტოც)
 * 
 * პროგრამა იყენებს PC/SC ინტერფეისს, რომელიც სტანდარტულია როგორც Windows-ში,
 * ისე სხვა ოპერაციულ  * სისტემებში. ამ უკანასკნელებში pcsc lite-ს გამოყენებით
 * 
 * @author mikheil
 */
public class MainDumpCard {

	    // 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;
    }
}