/*
* rsidlib: Open source library for PKI functions on Serbian eID (GNU LGPLv3)
* Copyright (C) 2011 Aleksandar Nikolic
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version
* 3.0 as published by the Free Software Foundation.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, see
* http://www.gnu.org/licenses/.
*/
package rsidlib;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.smartcardio.Card;
import javax.smartcardio.CardChannel;
import javax.smartcardio.CardException;
import javax.smartcardio.CardTerminal;
import javax.smartcardio.CommandAPDU;
import javax.smartcardio.ResponseAPDU;
import javax.smartcardio.TerminalFactory;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.DERObject;
import org.bouncycastle.asn1.x509.RSAPublicKeyStructure;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
/**
* Klasa za rukovanje elektronskom licnom kartom.
* @author Aleksandar Nikolic
*
*/
@SuppressWarnings("restriction")
public class RSIDCard {
/**
* Terminal na kom se nalazi elektronska licna karta
*/
private CardTerminal terminal;
private Card card = null;
private CardChannel channel = null;
/**
* Podaci o dokumentu
*/
private static byte[] DOCUMENT_FILE = {0x0F, 0x02};
/**
* Licni podaci
*/
private static byte[] PERSONAL_FILE = {0x0F, 0x03};
/**
* Podaci o mestu prebivalista
*/
private static byte[] RESIDENCE_FILE = {0x0F, 0x04}; // Podaci o mestu prebivalista
/**
* Licna slika u JPEG formatu
*/
private static byte[] PHOTO_FILE = {0x0F, 0x06}; // Personal photo in JPEG format
/**
* Kvalifikovani javni sertifikat
*/
private static byte[] QUALIFIED_CERTIFICATE = {0x0F, 0x10}; // Public X.509 certificate for qualified (Non Repudiation) signing
/**
* Nekvalifikovani, standardni, sertifikat
*/
private static byte[] STANDARD_CERTIFICATE = {0x0F, 0x08}; // Public X.509 certificate for standard signing
private static byte[] FIRST_UNKNOWN = {0x0F, (byte) 0xA3}; // cita se pre VERIFY
/**
* Enkriptovani pin i tajni kljuc za dekriptovanje privatnog kljuca
*/
private static byte[] ENCRYPTED_PIN = {0x0F, 0x13}; // drugi se cita ppre VERIFY
private static byte[] XOR_VALUE = {0x0F, (byte) 0xA1}; // treci se cita pre VERIFY
/**
* Podaci privatnog kljuca
*/
public byte[] PRIVATE_KEY = {0x0F, 0x09}; // unknown , selektuje ga pri potpisivanju
private static final int BLOCK_SIZE = 0xFF;
/**
* Konstruise RSIDCard objekat vezan za dati terminal
* @param terminal
*/
public RSIDCard(CardTerminal terminal){
this.terminal = terminal;
}
/**
* Povezivanje sa karticom
* @throws CardException
*/
private void connect() throws CardException
{
card = terminal.connect("*");
channel = card.getBasicChannel();
}
/**
* Kraj rada sa karticom
* @throws CardException
*/
private void disconnect() throws CardException
{
card.disconnect(false);
card = null;
}
/**
* Selektovanje elementarnog fajla po oznaci
* @param name
* @throws CardException
*/
private void selectFile(byte[] name) throws CardException
{
ResponseAPDU r = channel.transmit(new CommandAPDU(0x00, 0xA4, 0x08, 0x00, name));
if(r.getSW() != 0x9000) {
throw new CardException("Select failed: " + RSIDUtils.int2Hex(r.getSW()));
}
}
/**
* Citanje dela selektovanog elementarnog fajla
* @param offset
* @param length
* @return niz procitanih bajtova
* @throws CardException
*/
private byte[] readBinary(int offset, int length) throws CardException
{
ByteArrayOutputStream out = new ByteArrayOutputStream();
while(length > 0)
{
int block = Math.min(length, BLOCK_SIZE);
ResponseAPDU r = channel.transmit(new CommandAPDU(0x00, 0xB0, offset >> 8, offset & 0xFF, block));
if(r.getSW() != 0x9000) {
throw new CardException("Read binary failed: " + RSIDUtils.int2Hex(r.getSW()));
}
try {
byte[] data = r.getData();
int data_len = data.length;
out.write(data);
offset += data_len;
length -= data_len;
} catch (IOException e) {
throw new CardException("Read binary failed: Could not write byte stream");
}
}
return out.toByteArray();
}
/**
* Citanje celog elementarnog fajla
* @param name
* @return
* @throws CardException
*/
private byte[] readElementaryFile(byte[] name) throws CardException
{
selectFile(name);
// Read first 6 bytes from the EF
byte[] header = readBinary(0, 6);
// Missing files have header filled with 0xFF
int i = 0;
while(i < header.length && header[i] == 0xFF) i++;
if(i == header.length) {
throw new CardException("Read EF file failed: File header is missing");
}
// Total EF length: data as 16bit LE at 4B offset
int length = ((0xFF&header[5])<<8) + (0xFF&header[4]);
// Read binary into buffer
return readBinary(6, length);
}
/**
* Potpisivanje prosledjenih podataka standardnim privatnim kljucem sa
* elektronske licne karte gradjana.
* Koraci:
* 1. Dekriptovanje PINa za pristup kartici i tajnog kljuca za dekripciju privatnog kljuca
* 2. Autorizacija korisnika ekriptovanim PINom
* 3. Citanje privatnog kljuca sa kartice
* 4. Potpisivanje podataka
*
* @param data - podaci koji se potpisuju
* @param password - lozinka kartice
* @return
* @throws RSIDCardException
*/
public byte[] SignData(byte[] data, String password) throws RSIDCardException{
// prvi korak : dekriptovanje pina i tajnog kljuca
String id;
byte[] signedData = null;
HashMap<Integer, byte[]> document = null;
try {
connect();
} catch (CardException e2) {
throw new RSIDCardException("Greska pri povezivanju na karticu");
}
try {
document = RSIDUtils.parseTLV(readElementaryFile(DOCUMENT_FILE));
} catch (CardException e) {
System.out.println("Could not parse DOCUMENT_FILE" + e.getMessage());
}
if(document == null){
throw new RSIDCardException("Greska pri citanju podataka o licnoj karti");
}
id = RSIDUtils.bytes2UTFString(document.get(10));
/* SHA1 hash vrednost xorKey ucestvuje u dekripciji PINa za pristup kartici i
* dekripciji tajnog kljuca za dekriptovanje privatnog kljuca.
*/
String xorKey = "ID" + id + '\u0001' + password;
byte[] xorKeyBytes = xorKey.getBytes();
MessageDigest md = null;
try {
md = MessageDigest.getInstance("SHA-1");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
byte[] xorKeyHash;
md.update(xorKeyBytes, 0, xorKeyBytes.length);
xorKeyHash = md.digest();
byte[] encryptedPinAndSecretKey = null;
/*
* ENCRYPTED_PIN elementarni fajl sadrzi enkriptovani PIN duzine 8 fajta pocevsi od cetvrtog bajta.
* Na ofsetu 17 se nalazi tajni kljuc za dekriptovanje privatnog kljuca. Duzina tajnog kljuca je 32 bajta.
* ----------------------------------------------------------------------------------------------------
* |tlv tag(4 bajta)| enkriptovani pin (8 bajta)|tlv tag (4 bajta)| enkriptovani tajni kljuc (32 bajta)|
* ----------------------------------------------------------------------------------------------------
*/
try {
encryptedPinAndSecretKey = readElementaryFile(ENCRYPTED_PIN);
} catch (CardException e) {
throw new RSIDCardException("Greska pri citanju enkriptovanig pina i tajnog kljuca.");
}
// enkriptvotani pin
byte[] encryptedPin = new byte[8];
// enkriptovatni tajni kljuc
byte[] encryptedSecretKey = new byte[32];
System.arraycopy(encryptedPinAndSecretKey, 4, encryptedPin, 0, 8);
System.arraycopy(encryptedPinAndSecretKey, 17, encryptedSecretKey, 0, 32);
byte[] xorValueWhole = null;
try {
xorValueWhole = readElementaryFile(XOR_VALUE);
} catch (CardException e) {
throw new RSIDCardException("Greska pri citanju podataka za dekriptovanje PINa!");
}
// dekriptovani pin
byte[] pin = rsidXorDecryptPIN(encryptedPin, xorKeyHash, xorValueWhole);
// dekriptovani tajni kljuc
byte[] secretKey = rsidXorDecryptSecretKey(encryptedSecretKey, xorKeyHash, xorValueWhole);
// drugi korak: autorizacija korisnika
verifyUser(pin);
// treci korak: citanje i dekripcija privatnog kljuca
RSAPrivateCrtKeySpec privateKeySpec = getPrivateKey(secretKey);
// cetvrti korak: potpisivanje podataka
KeyFactory factory;
try {
factory = KeyFactory.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
PrivateKey privateKey = null;
try {
privateKey = factory.generatePrivate(privateKeySpec);
} catch (InvalidKeySpecException e1) {
e1.printStackTrace();
}
if(privateKey == null){
throw new RSIDCardException("Greska pri generisanju privatnog kljuca!");
}
Signature sig;
try {
sig = Signature.getInstance("SHA1withRSA");
//inicijalizacija privatnim kljucem
sig.initSign(privateKey);
//postavljamo podatke koje potpisujemo
sig.update(data);
signedData = sig.sign();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (SignatureException e) {
e.printStackTrace();
}
if(signedData == null){
throw new RSIDCardException("Greska pri potpisivanju podataka!");
}
try {
disconnect();
} catch (CardException e) {
throw new RSIDCardException("Greska pri oslobadjanju kartice.");
}
return signedData;
}
public boolean verifySignature(byte[] signature, byte[] data) throws RSIDCardException{
try {
connect();
} catch (CardException e2) {
throw new RSIDCardException("Greska pri povezivanju na karticu");
}
// citamo sertifikat zbog podataka o javnom kljucu
byte[] standardFileBytes;
try {
standardFileBytes = readElementaryFile(STANDARD_CERTIFICATE);
} catch (CardException e) {
throw new RSIDCardException("Greska pri citanju standardnog sertifikata!");
}
byte[] certificateBytes = new byte[standardFileBytes.length];
System.arraycopy(standardFileBytes, 4, certificateBytes, 0, standardFileBytes.length-4);
ByteArrayInputStream certificateInputStream = new ByteArrayInputStream(certificateBytes);
CertificateFactory cf = null;
try {
cf = CertificateFactory.getInstance("X.509");
} catch (CertificateException e) {
e.printStackTrace();
}
X509Certificate x509Cert = null;
try {
x509Cert = (X509Certificate) cf.generateCertificate(certificateInputStream);
} catch (CertificateException e) {
e.printStackTrace();
}
// javni kljuc se sastoji od modula i javnog eksponenta u odgovarajucoj ASN1 strukturi
ASN1InputStream is = new ASN1InputStream(x509Cert.getPublicKey().getEncoded());
DERObject obj = null;
try {
obj = is.readObject();
} catch (IOException e) {
e.printStackTrace();
}
ASN1Sequence seq = (ASN1Sequence)obj;
RSAPublicKeyStructure pubk = null;
SubjectPublicKeyInfo pkInfo = SubjectPublicKeyInfo.getInstance(seq);
try {
pubk = RSAPublicKeyStructure.getInstance(pkInfo.getPublicKey());
} catch (IOException e) {
e.printStackTrace();
}
RSAPublicKeySpec rsaPubKey = new RSAPublicKeySpec(pubk.getModulus(), pubk.getPublicExponent());
KeyFactory factory;
try {
factory = KeyFactory.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
PublicKey publicKey = null;
try {
publicKey = factory.generatePublic(rsaPubKey);
} catch (InvalidKeySpecException e) {
e.printStackTrace();
}
Signature sig = null;
try {
sig = Signature.getInstance("SHA1withRSA");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
try {
sig.initVerify(publicKey);
} catch (InvalidKeyException e) {
e.printStackTrace();
}
try {
sig.update(data);
} catch (SignatureException e) {
e.printStackTrace();
}
boolean res = false;
try {
res = sig.verify(signature);
} catch (SignatureException e) {
e.printStackTrace();
}
try {
disconnect();
} catch (CardException e) {
e.printStackTrace();
}
return res;
}
/**
* Dekripcija i rekonstrukcija privatnog kljuca.
* Privatni kljuc je enkriptovan AESom sa tajnim kljucem od 256 bita u CFB modu.
* Inicijalizacioni vektor za CFB mod su prvih 16 bajta SHA1 hasha tajnog kljuca
* U dekriptovanim podacima privatni kljuc se nalazi u ASN1 strukturi nakon TLV
* polja od 4 bajta. ASN1 struktura sadrzi prvi prosti faktor, drugi prosti faktor,
* eksponent prvog prostog faktora, eksponent drugog prostog faktora i koeficijent
* za teoremu o kineskom ostatku.
* @param secretKey
* @return
* @throws RSIDCardException
*/
private RSAPrivateCrtKeySpec getPrivateKey(byte[] secretKey) throws RSIDCardException{
// Enkriptovani privatni kljuc
byte[] encryptedPrivateKey = null;
try {
encryptedPrivateKey = readElementaryFile(PRIVATE_KEY);
} catch (CardException e) {
throw new RSIDCardException("Greska pri citanju enkriptovanog privatnog kljuca.");
}
// Privatni kljuc je enkriptovan AESom sa tajnim kljucem od 256 bita u CFB modu
Security.addProvider(new BouncyCastleProvider());
SecretKeySpec key;
Cipher cipher = null;
// zbog CFB moda potreban nam je IV
byte[] iv = new byte[16];
//Inicijalizacioni vektor za CFB mod su prvih 16 bajta SHA1 hasha tajnog kljuca
MessageDigest md = null;
try {
md = MessageDigest.getInstance("SHA-1");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
byte[] sha1hash = new byte[40];
md.update(secretKey, 0, secretKey.length);
sha1hash = md.digest();
System.arraycopy(sha1hash, 0, iv, 0, 16);
//postavljanje IVa
IvParameterSpec ivSpec = new IvParameterSpec(iv);
// Postavljanje tajnog kljuca za dekripciju
key = new SecretKeySpec(secretKey, "AES");
// Instanciranje AES sifrara u CFB modu
try {
cipher = Cipher.getInstance("AES/CFB/NoPadding", "BC");
} catch (NoSuchAlgorithmException e1) {
e1.printStackTrace();
} catch (NoSuchProviderException e1) {
e1.printStackTrace();
} catch (NoSuchPaddingException e1) {
e1.printStackTrace();
}
// inicijalizacija AESa tajnim kljucem i inicijalizacionim vektorom
try {
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
}
// DEKRIPCIJA
byte[] plainText = new byte[cipher.getOutputSize(encryptedPrivateKey.length)];
int ptLength = 0;
try {
ptLength = cipher.update(encryptedPrivateKey, 0, encryptedPrivateKey.length, plainText, 0);
} catch (ShortBufferException e) {
e.printStackTrace();
}
try {
ptLength += cipher.doFinal(plainText, ptLength);
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (ShortBufferException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
// u dekriptovanim podacima privatni kljuc se nalazi u ASN1 strukturi nakon TLV polja od 4 bajta
byte[] decryptedPrivateKey = new byte[plainText.length];
System.arraycopy(plainText, 4, decryptedPrivateKey, 0, plainText.length-4);
RSAPrivateCrtKeySpec privKeySpec = reconstructPrivateKey(decryptedPrivateKey);
if(privKeySpec == null){
throw new RSIDCardException("Greska pri rekonstrukciji privatnog kljuca.");
}
return privKeySpec;
}
/**
* Rekonstrukcija privatnog kljuca iz dekriptovanih podataka.
* Podaci se nalaze unutar sledece ASN1 sturkture:
* PrivateKey ::= SEQUENCE {
* primeP BITSTRING,
* primeQ BITSTRING,
* primeExponentP BITSTRING,
* primeExponentQ BITSTRING,
* crtCoeficient BITSTRING
* }
* Privatni kljuc je u obliku spremnom za potpisivanje koriscenjem teoreme
* kineskog ostatka, stoga privatni eksponent nije potreban.
* @param privateKey
* @return
*/
private RSAPrivateCrtKeySpec reconstructPrivateKey(byte[] privateKey){
RSAPrivateCrtKeySpec privKeySpec = null;
ASN1InputStream asn1InputStream = new ASN1InputStream(privateKey);
//parsiranje ASN1 strukture
ArrayList<BigInteger> privateKeyParts = new ArrayList<BigInteger>();
try {
DERObject derObject = asn1InputStream.readObject();
ASN1Sequence asn1Seq = (ASN1Sequence) derObject;
Enumeration<DERObject> en = asn1Seq.getObjects();
while(en.hasMoreElements()){
DERObject obj = en.nextElement();
DERBitString bitString = DERBitString.getInstance(obj);
byte[] prependZero = new byte[bitString.getBytes().length+1];
// zbog "cudnog" BigInteger konstruktora mora se dodati 0x0 na pocetak,
// u suprotnom u pojedinim slucajevima pogresno se protumaci znak i brojevi ispadnu negativni
prependZero[0] = 0x0;
System.arraycopy(bitString.getBytes(), 0, prependZero, 1, bitString.getBytes().length);
privateKeyParts.add(new BigInteger(prependZero));
}
// citamo sertifikat zbog podataka o javnom kljucu
byte[] standardFileBytes = readElementaryFile(STANDARD_CERTIFICATE);
byte[] certificateBytes = new byte[standardFileBytes.length];
System.arraycopy(standardFileBytes, 4, certificateBytes, 0, standardFileBytes.length-4);
ByteArrayInputStream certificateInputStream = new ByteArrayInputStream(certificateBytes);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate x509Cert = (X509Certificate) cf.generateCertificate(certificateInputStream);
// javni kljuc se sastoji od modula i javnog eksponenta u odgovarajucoj ASN1 strukturi
ASN1InputStream is = new ASN1InputStream(x509Cert.getPublicKey().getEncoded());
DERObject obj = is.readObject();
ASN1Sequence seq = (ASN1Sequence)obj;
SubjectPublicKeyInfo pkInfo = SubjectPublicKeyInfo.getInstance(seq);
RSAPublicKeyStructure pubk = RSAPublicKeyStructure.getInstance(pkInfo.getPublicKey());
BigInteger expo = pubk.getPublicExponent();
BigInteger mod = pubk.getModulus();
// sada imamo sve potrebne podatke za privatni kljuc
privKeySpec = new RSAPrivateCrtKeySpec(mod,
expo,
null,
privateKeyParts.get(0),
privateKeyParts.get(1),
privateKeyParts.get(2),
privateKeyParts.get(3),
privateKeyParts.get(4));
} catch (IOException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
} catch (CardException e) {
e.printStackTrace();
}
return privKeySpec;
}
/**
* Verifikacija korisnika PINom pomocu VERIFY APDU komande
* {@link http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4_6_basic_interindustry_commands.aspx#chap6_12}
* @param pin
* @throws RSIDCardException
*/
private void verifyUser(byte[] pin) throws RSIDCardException{
final byte CLA = 0x00;
final byte INS = 0x20;
final byte P1 = 0x00;
final byte P2 = 0x01;
final byte LC = 0x08;
byte[] cmd = new byte[14];
cmd[0] = CLA;
cmd[1] = INS;
cmd[2] = P1;
cmd[3] = P2;
cmd[4] = LC;
System.arraycopy(pin, 0, cmd, 5, 8);
cmd[13] = 0x00;
ResponseAPDU r = null;
try {
r = channel.transmit(new CommandAPDU(cmd));
} catch (CardException e) {
throw new RSIDCardException("Greska pri autorizaciji korisnika. Pogresna loznika?");
}
if(r.getSW() != 0x9000) {
throw new RSIDCardException("Greska pri autorizaciji korisnika. Pogresna loznika?");
}
}
/**
*Dekriptovanje podataka se vrsi u dva koraka:
* 1. Enkriptovani podaci (encryptedData) se XORuju sa xorValue
* 2. Rezultat se XORuje sa xorKeyHash, tj hash vrednosti xorKey-a
* u koji ulazi korisnikova lozinka.
* Rezultat drugog koraka su dekriptovani dekriptovani pocaci.
* U slucaju da su podaci duzi od kljuca za XOR, kljuc se koristi ciklicno.
* @param encryptedData - enkriptovani pin
* @param xorKeyHash - SHA1 hash stringa u koji ulazi lozinka
* @param xorValueWhole - XOR_VALUE podaci
* @return - dekriptovani podaci
*/
private byte[] rsidXorDecryptPIN(byte[] encryptedPIN, byte[] xorKeyHash, byte[] xorValueWhole){
// ucestvuje kao XOR kljuc u prvom delu dekriptovanja PINa i kljuca
byte[] xorValue = new byte[xorValueWhole.length];
System.arraycopy(xorValueWhole, 4, xorValue, 0,8);
/* dekriptovanje PINa se vrsi u dva koraka:
* 1. Enkriptovani pin (encryptedPin) se XORuje sa xorValue
* 2. Rezultat se XORuje sa prvih 8 bajta xorKeyHash, tj hash vrednosti xorKey-a
* u koji ulazi korisnikova lozinka.
* Rezultat drugog koraka je dekriptovani PIN.
*/
// prvi korak
byte[] xoredPIN = new byte[8];
for(int i = 0; i<8;i++){
xoredPIN[i] = (byte) (xorValue[i] ^ encryptedPIN[i]);
}
//drugi korak
// nakon ovoga imamo dekriptovani PIN
byte[] decryptedData = new byte[xoredPIN.length];
for(int i = 0 ; i<8; i++){
decryptedData[i] = (byte) (xoredPIN[i] ^ xorKeyHash[i]);
}
return decryptedData;
}
/**
* Slicno kao rsidXorDecryptPIN samo umesto 8 radi sa 16 bajta
* @param encryptedSecretKey
* @param xorKeyHash
* @param xorValueWhole
* @return
*/
private byte[] rsidXorDecryptSecretKey(byte[] encryptedSecretKey, byte[] xorKeyHash, byte[] xorValueWhole){
// ucestvuje kao XOR kljuc u prvom delu dekriptovanja PINa i kljuca
byte[] xorValue = new byte[xorValueWhole.length];
System.arraycopy(xorValueWhole, 4, xorValue, 0, 16);
/* dekriptovanje PINa se vrsi u dva koraka:
* 1. Enkriptovani pin (encryptedPin) se XORuje sa xorValue
* 2. Rezultat se XORuje sa prvih 8 bajta xorKeyHash, tj hash vrednosti xorKey-a
* u koji ulazi korisnikova lozinka.
* Rezultat drugog koraka je dekriptovani PIN.
*/
// priv korak
byte[] xoredPIN = new byte[32];
for(int i = 0; i<32;i++){
xoredPIN[i] = (byte) (xorValue[i%16] ^ encryptedSecretKey[i]);
}
//drugi korak
// nakon ovoga imamo dekriptovani PIN
byte[] decryptedData = new byte[32];
for(int i = 0 ; i<32; i++){
decryptedData[i] = (byte) (xoredPIN[i] ^ xorKeyHash[i%16]);
}
return decryptedData;
}
/**
* Test metoda
* @param args
*/
public static void main(String[] args){
if(args.length != 1){
System.out.println("Lozinka mora biti prvi argument!");
System.exit(0);
}
String password = args[0];
TerminalFactory factory = TerminalFactory.getDefault();
List<CardTerminal> terminals = null;
try {
terminals = factory.terminals().list();
} catch (CardException e) {
e.printStackTrace();
}
CardTerminal terminal = terminals.get(0);
RSIDCard rsidCard = new RSIDCard(terminal);
byte[] dataToSign = {0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07};
byte[] signedData = null;
try {
signedData = rsidCard.SignData(dataToSign, password);
} catch (RSIDCardException e) {
System.out.println(e.getMessage());
e.printStackTrace();
System.exit(0);
}
System.out.println("Data to sign: " + RSIDUtils.bytes2Hex(dataToSign));
System.out.println("Signed data: " + RSIDUtils.bytes2Hex(signedData));
try {
System.out.println("Verifying signature... " + rsidCard.verifySignature(signedData, dataToSign) );
} catch (RSIDCardException e) {
System.out.println(e.getMessage());
e.printStackTrace();
System.exit(0);
}
}
}
@SuppressWarnings("serial")
class RSIDCardException extends Exception{
public RSIDCardException(String msg) {
super(msg);
}
}