Versions Compared

Key

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

...

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

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