modifica timeout lettura

ridotto per velocizzare il processo di lettura di qr code.
la classe qrscanner è utilizzata solo a fini di test per la lettura di qr code con webcam.
nel frmcerca aggiunto campo nascosto per gestire la lettura del qr code senza mostrarlo a video, viene inserito il numero di tessera solo se i controlli vanno a buon fine
nel form principale (puntocassa) non è stato necessario, in quanto ci sono thread in ascolto che leggono i caratteri (emulazione tastiera), validi sia per il qr code che per rfid.
nella classe qrcryptoservice.java è inserito il timer del qr code (CLOCK_SKEW) a 60 secondi, nonchè la chiave AES per la decriptazione (AES_KEY): dovrà essere modificata quando si andrà in produzione.
L'algoritmo di decriptazione usato per il qr code è AES-256 con CBC.
This commit is contained in:
Francesco Di Sciascio 2025-08-29 16:12:47 +02:00
parent 92995d5658
commit f8fba1fec3
5 changed files with 634 additions and 212 deletions

View File

@ -73,6 +73,8 @@
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Component id="lblCF" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="61" max="-2" attributes="0"/>
<Component id="jtxtNumTesseraHidden" min="-2" max="-2" attributes="0"/>
<EmptySpace max="32767" attributes="0"/>
</Group>
<Group type="102" attributes="0">
@ -98,14 +100,22 @@
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="1" attributes="0">
<Component id="jLblTitolo" min="-2" pref="25" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="lblCognome" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="lblNome" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="lblCF" alignment="3" min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Component id="jLblTitolo" min="-2" pref="25" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="lblCognome" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="lblNome" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="lblCF" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" pref="18" max="-2" attributes="0"/>
<Component id="jtxtNumTesseraHidden" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
<EmptySpace min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" max="-2" attributes="0">
<Group type="103" groupAlignment="3" attributes="0">
<Component id="jtxtNumTessera" alignment="3" min="-2" pref="40" max="-2" attributes="0"/>
@ -263,6 +273,13 @@
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jbtnGiuActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JTextField" name="jtxtNumTesseraHidden">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 0]"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Container>
</SubComponents>

View File

@ -6,24 +6,38 @@
package puntocassa;
import com.github.sarxos.webcam.Webcam;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.NotFoundException;
import com.google.zxing.Result;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.HybridBinarizer;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import javax.swing.ImageIcon;
import javax.swing.JOptionPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.table.TableColumn;
import puntocassa.utils.QrCryptoService;
import puntocassa.utils.Utils;
/**
@ -43,6 +57,7 @@ private JTextField jtxtTessera;
private Boolean FlagMostraDataNascita=true;
private Boolean DisabilitaPerBuono=false;
/**
* Creates new form frm
*/
@ -100,7 +115,44 @@ private JTextField jtxtTessera;
jtxtCognome.setText(BuoniPasto);
Cerca();
Disabilita();
}
}
jtxtNumTesseraHidden.addActionListener(e -> {
System.out.println("QR letto: " + jtxtNumTesseraHidden.getText().trim());
String valueRead = jtxtNumTesseraHidden.getText().trim();
//decripting
List<String> dataResult = QrCryptoService.decryptAndValidate(valueRead);
//System.out.println("QR code decriptato: " + r);
if(dataResult.get(0).equals("VALID")){
//verifica che il codice fiscale corrisponda ad una tessera
String numTessera = Utils.mySelect("select numero from tessere where id_utente = (select id from utenti where codice_fiscale = '" +
dataResult.get(1) +
"')", "numero", frmPuntoCassa);
if(numTessera == null || "".equals(numTessera)){
//mostra un popup di errore UTENTE NON COLLEGATO AD UNA TESSERA
System.out.println("nessuna tessera collegata all'utente " + dataResult.get(1));
JOptionPane.showOptionDialog(this, "Nessuna tessera collegata all'utente " + dataResult.get(1) , "Attenzione",
JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null, null, null);
jtxtNumTesseraHidden.setText("");
jtxtNumTessera.setText("");
return;
}
//scrivi il numero della tessera nel campo input
jtxtNumTessera.setText(numTessera);
jtxtNumTesseraHidden.setText("");
Cerca();
}
else {
System.out.println("QR non valido: "+ dataResult.get(1));
JOptionPane.showOptionDialog(this, dataResult.get(1) , "Attenzione",
JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null, null, null);
jtxtNumTesseraHidden.setText("");
jtxtNumTessera.setText("");
}
});
}
@ -128,6 +180,7 @@ private JTextField jtxtTessera;
jBtnOK = new javax.swing.JButton();
jbtnSu = new javax.swing.JButton();
jbtnGiu = new javax.swing.JButton();
jtxtNumTesseraHidden = new javax.swing.JTextField();
setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
setTitle("Cerca Tessera Attiva");
@ -237,6 +290,8 @@ private JTextField jtxtTessera;
}
});
jtxtNumTesseraHidden.setPreferredSize(new java.awt.Dimension(0, 0));
javax.swing.GroupLayout pnl1Layout = new javax.swing.GroupLayout(pnl1);
pnl1.setLayout(pnl1Layout);
pnl1Layout.setHorizontalGroup(
@ -256,6 +311,8 @@ private JTextField jtxtTessera;
.addGroup(pnl1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(pnl1Layout.createSequentialGroup()
.addComponent(lblCF)
.addGap(61, 61, 61)
.addComponent(jtxtNumTesseraHidden, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap())
.addGroup(pnl1Layout.createSequentialGroup()
.addComponent(jtxtNumTessera, javax.swing.GroupLayout.PREFERRED_SIZE, 176, javax.swing.GroupLayout.PREFERRED_SIZE)
@ -274,12 +331,17 @@ private JTextField jtxtTessera;
pnl1Layout.setVerticalGroup(
pnl1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, pnl1Layout.createSequentialGroup()
.addComponent(jLblTitolo, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(pnl1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(lblCognome)
.addComponent(lblNome)
.addComponent(lblCF))
.addGroup(pnl1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(pnl1Layout.createSequentialGroup()
.addComponent(jLblTitolo, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(pnl1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(lblCognome)
.addComponent(lblNome)
.addComponent(lblCF)))
.addGroup(pnl1Layout.createSequentialGroup()
.addGap(18, 18, 18)
.addComponent(jtxtNumTesseraHidden, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(pnl1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
.addGroup(pnl1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
@ -330,6 +392,9 @@ private JTextField jtxtTessera;
jtxtNome.setBackground(Color.white);
jtxtCognome.setBackground(Color.white);
}
jtxtNumTesseraHidden.requestFocusInWindow();
// MyApplication MyA = new MyApplication();
// if (MyA.TastieraVideo) {
// if (MyA.UltimoTxt.equalsIgnoreCase(evt.getComponent().toString())) {
@ -478,6 +543,54 @@ private JTextField jtxtTessera;
private void jtxtNumTesseraMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_jtxtNumTesseraMouseClicked
Tastiera(evt);
// //attivazione webcam per lettura qr
// Webcam webcam = Webcam.getDefault();
// //webcam.setViewSize(new Dimension(1280, 720));
// Dimension hd = new Dimension(1280, 720);
// webcam.setCustomViewSizes(new Dimension[] { hd });
// webcam.setViewSize(hd);
//
// webcam.open();
//
// while (true) {
// BufferedImage image = webcam.getImage();
// if (image == null) continue;
//
// LuminanceSource source = new BufferedImageLuminanceSource(image);
// BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
//
// try {
// Result result = new MultiFormatReader().decode(bitmap);
// System.out.println("QR Code trovato: " + result.getText());
// //decripting
// List<String> dataResult = QrCryptoService.decryptAndValidate(result.getText());
// //System.out.println("QR code decriptato: " + r);
// if(dataResult.get(0).equals("VALID")){
// //verifica che il codice fiscale corrisponda ad una tessera
// String numTessera = Utils.mySelect("select numero from tessere where id_utente = (select id from utenti where codice_fiscale = '" +
// dataResult.get(1) +
// "')", "numero", frmPuntoCassa);
// if(numTessera == null || "".equals(numTessera)){
// //mostra un popup di errore UTENTE NON COLLEGATO AD UNA TESSERA
// System.out.println("nessuna tessera collegata all'utente " + dataResult.get(1));
// return;
// }
// //scrivi il numero della tessera nel campo input
// jtxtNumTessera.setText(numTessera);
// Cerca();
// }
// else {
// System.out.println("QR non valido: "+ dataResult.get(1));
// }
// break;
// } catch (NotFoundException e) {
// // Nessun QR code trovato nel frame
// }
// }
//
// webcam.close();
}//GEN-LAST:event_jtxtNumTesseraMouseClicked
private void Tastiera(java.awt.event.MouseEvent evt) {
MyApplication MyA = new MyApplication();
@ -753,6 +866,7 @@ private JTextField jtxtTessera;
private javax.swing.JTextField jtxtCognome;
private javax.swing.JTextField jtxtNome;
private javax.swing.JTextField jtxtNumTessera;
private javax.swing.JTextField jtxtNumTesseraHidden;
private javax.swing.JLabel lblCF;
private javax.swing.JLabel lblCognome;
private javax.swing.JLabel lblNome;

View File

@ -27,10 +27,13 @@ import java.util.Date;
import java.util.Locale;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.DefaultComboBoxModel;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
@ -49,6 +52,7 @@ import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import puntocassa.utils.QrCryptoService;
import puntocassa.utils.Utils;
//******************************************************************************
@ -223,20 +227,18 @@ public class PuntoCassa extends JFrame implements ActionListener {
//adatto panel login
pnlLogin.setSize(calcolaLarghezzaAltezza(pnlLogin.getWidth(), largControlli), calcolaLarghezzaAltezza(pnlLogin.getHeight(), altControlli));
jBtnReadCard.setVisible(false);
settingFontsInterfaccia();
jcmbSmartCard.setVisible(false);
visualizzaMessaggiDisplay = document.getElementsByTagName("Visualizza_Messaggi_Display").item(0).getFirstChild().getNodeValue()
.equalsIgnoreCase("SI");
.equalsIgnoreCase("SI");
//visualizzaMessaggiDisplay = VMD.equalsIgnoreCase("SI");
messageDisplay = document.getElementsByTagName("Messaggio_Display").item(0).getFirstChild().getNodeValue();
aggiornaSmartCardReaderCombo(myApp);
String ultimiMovimenti = Utils.mySelect("SELECT valore FROM parametri WHERE chiave = 'FlagVisualizzaUltimiPassaggi'",
"valore",
this);
@ -313,8 +315,7 @@ public class PuntoCassa extends JFrame implements ActionListener {
txtPassword.setFont(f);
jTxtTessera.setFont(f);
jChLogin.setFont(f);
Font newFontBtnAnnulla = new Font(jbtnAnnulla.getFont().getName(), jbtnAnnulla.getFont().getStyle(), hFont);
jbtnAnnulla.setFont(newFontBtnAnnulla);
Font newFontBtnDuplica = new Font(jbtnDuplica.getFont().getName(), jbtnDuplica.getFont().getStyle(), hFont);
@ -331,10 +332,11 @@ public class PuntoCassa extends JFrame implements ActionListener {
/**
* Lettura del nodo 'display' del file di configurazione
*
* @param document
* @param myApp
* @throws NumberFormatException
* @throws DOMException
* @throws DOMException
*/
private void leggiNodoDisplay(Document document, MyApplication myApp) throws NumberFormatException, DOMException {
// --------------------------------------------------------------------
@ -353,7 +355,7 @@ public class PuntoCassa extends JFrame implements ActionListener {
largControlli = Integer.parseInt(p.getElementsByTagName("larghezzaControlli").item(0).getFirstChild().getNodeValue());
String tastiera = p.getElementsByTagName("TastieraVideo").item(0).getFirstChild().getNodeValue();
myApp.tastieraVideo = !tastiera.equals("NO");
}
}
}
@ -410,7 +412,7 @@ public class PuntoCassa extends JFrame implements ActionListener {
* @throws DOMException
*/
private void leggiNodoDatabase(Document document) throws DOMException {
NodeList database = document.getElementsByTagName("database");
NodeList database = document.getElementsByTagName("database");
for (int i = 0; i < database.getLength(); i++) {
Node nodo = database.item(i);
@ -508,8 +510,10 @@ public class PuntoCassa extends JFrame implements ActionListener {
//29-10-18------------------------------------------------------
if (timerUnaVolta == false) {
timerUnaVolta = true;
final Timer timer = new Timer();
final TimerTask task = new TimerTask() {
@Override
public void run() {
@ -526,12 +530,11 @@ public class PuntoCassa extends JFrame implements ActionListener {
}
};
timer.schedule(task, 5000);
timer.schedule(task, 4000);
}
//fine 29-10-18------------------------------------------------------
//fine 29-10-18------------------------------------------------------
}
return true;
}
@ -553,73 +556,241 @@ public class PuntoCassa extends JFrame implements ActionListener {
@Override
public void run() {
String tessera = "";
boolean isQrCode = false;
//se abbiamo uno \n e la lunghezza è > 200 caratteri, è un qr code
if (testoSwipCard.toString().contains("\n") && testoSwipCard.length() > 200) {
recuperaNumeroTesseraQrCode();
isQrCode = true;
}
//per tessere tipo 8 e 9
if (testoSwipCard.toString().contains("ì")) {
String[] txt = testoSwipCard.toString().split("ì");
if (!isQrCode) {
if (testoSwipCard.toString().contains("ì")) {
String[] txt = testoSwipCard.toString().split("ì");
// per tessera MASTERCARD CARDHOLDER
if (testoSwipCard.toString().length() > 30) {
// per tessera MASTERCARD CARDHOLDER
if (testoSwipCard.toString().length() > 30) {
tessera = txt[0].trim().replace("_", "");
} else {
tessera = txt[1].trim().replace("_", "");
}
} else if (testoSwipCard.toString().contains("&")) {
// per tessera MASTERCARD CARDHOLDER
String[] txt = testoSwipCard.toString().split("&");
tessera = txt[0].trim().replace("%B", "");
} else {
tessera = testoSwipCard.toString().trim().replace("_", "");
}
//per tessera tipo 3
if (tessera.length() >= 17 && tessera.length() <= 19) {
tessera = tessera.substring(0, 7);
}
//per tessere tipo 6 e 7
if (tessera.length() == 21) {
tessera = tessera.substring(0, 10);
int posI = 0;
for (int i = 0; i <= tessera.length(); i++) {
if (tessera.substring(i, i + 1).equalsIgnoreCase("0") == false) {
posI = i;
break;
tessera = txt[0].trim().replace("_", "");
} else {
tessera = txt[1].trim().replace("_", "");
}
} else if (testoSwipCard.toString().contains("&")) {
// per tessera MASTERCARD CARDHOLDER
String[] txt = testoSwipCard.toString().split("&");
tessera = txt[0].trim().replace("%B", "");
} else {
tessera = testoSwipCard.toString().trim().replace("_", "");
}
tessera = tessera.substring(posI);
}
//per tessera tipo 3
if (tessera.length() >= 17 && tessera.length() <= 19) {
tessera = tessera.substring(0, 7);
}
spiaAltreCarte = false;
swip = false;
if (pnlLogin.isVisible()) {
controllaLogInCarta(tessera);
} else {
//05/11/2018 le nuove tessere con CF a volte leggono solo la matricola
jTxtTessera.setText(tessera);
passaggioTessera = true;
logNumeroTessera = tessera;
cercaTessera();
}
//per tessere tipo 6 e 7
if (tessera.length() == 21) {
tessera = tessera.substring(0, 10);
int posI = 0;
for (int i = 0; i <= tessera.length(); i++) {
if (tessera.substring(i, i + 1).equalsIgnoreCase("0") == false) {
posI = i;
break;
}
}
testoSwipCard = new StringBuilder();
tessera = tessera.substring(posI);
}
spiaAltreCarte = false;
swip = false;
if (pnlLogin.isVisible()) {
controllaLogInCarta(tessera);
} else {
//05/11/2018 le nuove tessere con CF a volte leggono solo la matricola
jTxtTessera.setText(tessera);
passaggioTessera = true;
logNumeroTessera = tessera;
cercaTessera();
}
testoSwipCard = new StringBuilder();
}
timer.cancel();
timer.purge();
statoCardLettore = false;
}
};
timer.schedule(task, delaySwipCard);
}
}
};
}
// public KeyEventDispatcher creaKeyEventDispatcher() {
// return new KeyEventDispatcher() {
// Boolean timerUnaVolta = false;
// MyApplication myApp = new MyApplication();
//
// public boolean dispatchKeyEvent(KeyEvent e) {
// int id = e.getID();
//
// if (jbtnChiudi.isEnabled() && !pnlLogin.isVisible() && jTxtTessera.getText().length() > 0) {
// e.consume();
// if (!myApp.unaVolta) {
// myApp.unaVolta = true;
// }
// return false;
// }
// myApp.unaVolta = false;
//
// int keyCode = e.getKeyCode();
// if (id == KeyEvent.KEY_RELEASED) {
// String tasto = "" + e.getKeyChar();
// System.out.println(keyCode + " - " + tasto);
//
// logTestoTessera.append(e.getKeyChar());
//
// // RFID input
// if (keyCode != KeyEvent.VK_SHIFT) {
// testoRFIDCard.append(tasto);
// }
//
// // Invio: fine lettura RFID
// if (keyCode == KeyEvent.VK_ENTER) {
// System.out.println(testoRFIDCard.toString());
// warn();
// }
//
// // Timer per letture incomplete
// if (!timerUnaVolta) {
// timerUnaVolta = true;
//
// final Timer timer = new Timer();
// final TimerTask task = new TimerTask() {
// @Override
// public void run() {
// if (testoRFIDCard.toString().length() > 2) {
// System.out.println("Timer: " + testoRFIDCard.toString());
// warn();
// testoRFIDCard = new StringBuilder();
// }
//
// timer.cancel();
// timer.purge();
// timerUnaVolta = false;
// }
// };
//
// timer.schedule(task, 4000);
// }
// }
//
// return true;
// }
//
// public void warn() {
// if (statoCardLettore) {
// return;
// }
//
// //testoRFIDCard = new StringBuilder();
//
// if (testoRFIDCard.toString().length() > 0) {
// statoCardLettore = true;
//
// final Timer timer = new Timer();
// final TimerTask task = new TimerTask() {
// @Override
// public void run() {
// String tessera = "";
// boolean isQrCode = false;
//
// if (testoRFIDCard.toString().contains("\n") && testoRFIDCard.length() > 200) {
// recuperaNumeroTesseraQrCode();
// isQrCode = true;
// }
//
// if (!isQrCode) {
// tessera = testoRFIDCard.toString().trim().replace("_", "");
//
// if (tessera.length() >= 17 && tessera.length() <= 19) {
// tessera = tessera.substring(0, 7);
// }
//
// if (tessera.length() == 21) {
// tessera = tessera.substring(0, 10);
// int posI = 0;
// for (int i = 0; i <= tessera.length(); i++) {
// if (!tessera.substring(i, i + 1).equalsIgnoreCase("0")) {
// posI = i;
// break;
// }
// }
// tessera = tessera.substring(posI);
// }
//
// if (pnlLogin.isVisible()) {
// controllaLogInCarta(tessera);
// } else {
// jTxtTessera.setText(tessera);
// passaggioTessera = true;
// logNumeroTessera = tessera;
// cercaTessera();
// }
//
// testoRFIDCard = new StringBuilder();
// }
//
// timer.cancel();
// timer.purge();
// statoCardLettore = false;
// }
// };
//
// timer.schedule(task, delaySwipCard);
// }
// }
// };
// }
public void recuperaNumeroTesseraQrCode() {
//controllo sul qr code prima di lanciare la ricerca della tessera
String valueRead = testoSwipCard.toString().trim();
//String valueRead = testoRFIDCard.toString().trim();
//decripting
java.util.List<String> dataResult = QrCryptoService.decryptAndValidate(valueRead);
if (dataResult.get(0).equals("VALID")) {
//verifica che il codice fiscale corrisponda ad una tessera
String numTessera = Utils.mySelect("select seleziona_tessera ('" + dataResult.get(1) + "') as numero from dual", "numero", this);
System.out.println("Dopo select numero from tessere");
if (numTessera == null || "".equals(numTessera)) {
//mostra un popup di errore UTENTE NON COLLEGATO AD UNA TESSERA
System.out.println("nessuna tessera collegata all'utente " + dataResult.get(1));
JOptionPane.showOptionDialog(this, "Nessuna tessera collegata all'utente " + dataResult.get(1), "Attenzione",
JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null, null, null);
jTxtTessera.setText("");
}
//scrivi il numero della tessera nel campo input
jTxtTessera.setText(numTessera);
cercaTessera();
} else {
System.out.println("Errore validazione QR code: " + dataResult.get(1));
JOptionPane.showOptionDialog(this, dataResult.get(1), "Attenzione",
JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null, null, null);
//jtxtNumTesseraHidden.setText("");
jTxtTessera.setText("");
//reset del logTestoTessera in quanto non viene richiamata la cercaTessera() che contiene logTessera() che effettua la stessa istruzione
logTestoTessera = new StringBuilder();
}
}
/**
@ -701,7 +872,8 @@ public class PuntoCassa extends JFrame implements ActionListener {
/**
* Aggiorna la lista di lettori smartcard disponibili
* @param myApp
*
* @param myApp
*/
private void aggiornaSmartCardReaderCombo(MyApplication myApp) {
try {
@ -1796,7 +1968,7 @@ public class PuntoCassa extends JFrame implements ActionListener {
// ==========================================================================
// *** Metodo per gestire gli eventi sui button dinamici
// ==========================================================================
public void actionPerformed(ActionEvent e) {
public void actionPerformed(ActionEvent e) {
String cmd = e.getActionCommand();
String tipo;
String id;
@ -1818,7 +1990,7 @@ public class PuntoCassa extends JFrame implements ActionListener {
case "PRODOTTO" -> {
//boolean prenotazionePagata = false;
String idPrenDettaglio = "null";
Object src = e.getSource();
if (src instanceof EventParameters evtParams) {
flagPrenotazionePagata = evtParams.flagPrenotazionePagata;
@ -1827,8 +1999,8 @@ public class PuntoCassa extends JFrame implements ActionListener {
//inserisci i prodotti se la cassa è aperta e
//non c'è una prenotazione da gestire oppure
//c'è una prenotazione e si stanno inserendo i prodotti in lista (idPrenDettaglio viene valorizzato in inserisciPrenotazioni())
if (isCassaAperta &&
(idPrenotazione == 0 || !idPrenDettaglio.equals("null"))) {
if (isCassaAperta
&& (idPrenotazione == 0 || !idPrenDettaglio.equals("null"))) {
sql = "SELECT "
+ "prodotti.id as idProdotto,"
+ "prodotti.nome, "
@ -1841,8 +2013,6 @@ public class PuntoCassa extends JFrame implements ActionListener {
+ "left join tariffe on prodotti.id=tariffe.id_prodotto "
+ "where prodotti.id = " + id + " and Tariffe.id_fascia=" + idProfiloTariffario;
try {
//spengo altri togglebutton
for (Integer y = 0; y < this.pnlProdotti.getComponentCount(); y++) {
@ -1896,13 +2066,14 @@ public class PuntoCassa extends JFrame implements ActionListener {
}
tblListaProdotti.setModel(model);
if(idPrenotazione == 0)
if (idPrenotazione == 0) {
controllaCompleti(model, "null");
else
} else {
controllaCompleti(model, idPrenotazione.toString());
}
controllaExtra(model);
sommaColonne(model, flagPrenotazionePagata);
//inizio 21-12-18<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
if (jbtnChiudi.isEnabled()) {
if (saldoAcquisti(0.0) == false
@ -1911,7 +2082,7 @@ public class PuntoCassa extends JFrame implements ActionListener {
&& (bonusResidui <= 0 || flagBonus == 0)
&& consentiCredito == false) {
JOptionPane.showOptionDialog(this, "Saldo tessera insufficiente!", "Attenzione",
JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null, null, null);
JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null, null, null);
return;
}
} else {
@ -1928,8 +2099,7 @@ public class PuntoCassa extends JFrame implements ActionListener {
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
}
else if(idPrenotazione > 0 && idPrenDettaglio.equals("null")){
} else if (idPrenotazione > 0 && idPrenDettaglio.equals("null")) {
JOptionPane.showMessageDialog(this, "Non è possibile modificare prodotti e composizioni se è presente una prenotazione collegata alla tessera");
}
}
@ -1995,10 +2165,10 @@ public class PuntoCassa extends JFrame implements ActionListener {
/**
* funzione che calcola i prezzi dei prodotti e il totale in base alla
* fascia dello studente oppure utilizza la fascia di default.
* Se flagPrenotazionePagata = true vuol dire che non va modificato il saldo
* residuo della tessera perchè la somma delle colonne viene richiamata
* a seguito della verifica di una prenotazione pagata
* fascia dello studente oppure utilizza la fascia di default. Se
* flagPrenotazionePagata = true vuol dire che non va modificato il saldo
* residuo della tessera perchè la somma delle colonne viene richiamata a
* seguito della verifica di una prenotazione pagata
*
* @param model
* @param flagPrenotazionePagata
@ -2011,8 +2181,9 @@ public class PuntoCassa extends JFrame implements ActionListener {
}
DecimalFormat df2 = new DecimalFormat("#,###,###,##0.00");
if(!flagPrenotazionePagata)
if (!flagPrenotazionePagata) {
txtTotaleCassa.setText("" + df2.format(t));
}
int p = model.sommaInt(2);
txtTotalePunti.setText("Punti " + p);
@ -2413,7 +2584,7 @@ public class PuntoCassa extends JFrame implements ActionListener {
// ** Quando viene chiusa la form esce dall'applicazione
// ==========================================================================
private void exitForm(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_exitForm
Object selectedValue = JOptionPane.showOptionDialog(this, "Confermi chiusura programma?", "Attenzione",
JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null, siNoOptions, siNoOptions[1]);
@ -2704,7 +2875,7 @@ public class PuntoCassa extends JFrame implements ActionListener {
this);
progressivoDifferito = Double.valueOf(progDI);
aggiornaLabelInfo();
if (cercaTestoModoPagamento().equalsIgnoreCase("A scalare") && jTxtTessera.getText().length() > 0) {
verificaSaldo(jTxtTessera.getText());
@ -2834,10 +3005,9 @@ public class PuntoCassa extends JFrame implements ActionListener {
}
}
private void jbtnEliminaActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jbtnEliminaActionPerformed
if(idPrenotazione > 0){
if (idPrenotazione > 0) {
JOptionPane.showMessageDialog(this, "Non è possibile modificare prodotti e composizioni se è presente una prenotazione collegata alla tessera");
}
else {
} else {
cancellaComposizioni();
MyTableModel model = (MyTableModel) tblListaProdotti.getModel();
Integer riga = tblListaProdotti.getSelectedRow();
@ -2875,7 +3045,7 @@ public class PuntoCassa extends JFrame implements ActionListener {
}
doLayout();
}
}
}
}//GEN-LAST:event_jbtnEliminaActionPerformed
private void jbtnChiudiActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jbtnChiudiActionPerformed
@ -2972,7 +3142,7 @@ public class PuntoCassa extends JFrame implements ActionListener {
private void jbtnEsciActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jbtnEsciActionPerformed
Object selectedValue = JOptionPane.showOptionDialog(this, "Confermi chiusura sessione?", "Attenzione",
JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null, siNoOptions, siNoOptions[0]);
if (Integer.parseInt(selectedValue.toString()) == 0) {
@ -3065,7 +3235,7 @@ public class PuntoCassa extends JFrame implements ActionListener {
private void aggiornaDB(Boolean messaggio) {
try {
Boolean aggiorna = false;
if (messaggio) {
if (messaggio) {
Object selectedValue = JOptionPane.showOptionDialog(this, "Confermi aggiornamento del database locale?", "Attenzione",
JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null, siNoOptions, siNoOptions[0]);
if (Integer.parseInt(selectedValue.toString()) != 0) {
@ -3164,8 +3334,7 @@ public class PuntoCassa extends JFrame implements ActionListener {
}//GEN-LAST:event_btnImpostaClienteActionPerformed
private void jbtnAnnullaActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jbtnAnnullaActionPerformed
if(idPrenotazione > 0)
{
if (idPrenotazione > 0) {
Object selectedValue = JOptionPane.showOptionDialog(this, "Desideri bypassare la prenotazione?", "Attenzione",
JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null, siNoOptions, siNoOptions[0]);
if (Integer.parseInt(selectedValue.toString()) == 0) {
@ -3175,11 +3344,9 @@ public class PuntoCassa extends JFrame implements ActionListener {
flagPrenotazionePagata = false;
}
//JOptionPane.showMessageDialog(this, "Non è possibile modificare prodotti e composizioni se è presente una prenotazione collegata alla tessera");
}
else
{
} else {
MyTableModel model = (MyTableModel) tblListaProdotti.getModel();
if (model.getRowCount() > 0) {
if (model.getRowCount() > 0) {
Object selectedValue = JOptionPane.showOptionDialog(this, "Desideri annullare tutte le righe?", "Attenzione",
JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null, siNoOptions, siNoOptions[0]);
if (Integer.parseInt(selectedValue.toString()) == 0) {
@ -3193,7 +3360,7 @@ public class PuntoCassa extends JFrame implements ActionListener {
try {
MyTableModel model = (MyTableModel) tblListaProdotti.getModel();
MyTableModel model1 = (MyTableModel) tblListaComposizioni.getModel();
model.clearTable();
model1.clearTable();
jbtnSu.setEnabled(false);
@ -3484,7 +3651,7 @@ public class PuntoCassa extends JFrame implements ActionListener {
//l'aggiornamento del residuo va fatto solo se non c'è una prenotazione in corso
//oppure se la prenotazione in corso non risulta pagata, altrimenti il residuo
//resta pari al saldo della tessera
if(idPrenotazione == 0 || !flagPrenotazionePagata){
if (idPrenotazione == 0 || !flagPrenotazionePagata) {
MyTableModel model = (MyTableModel) tblListaProdotti.getModel();
Double t = model.somma(colImporto);
MyTableModel model2 = (MyTableModel) tblListaComposizioni.getModel();
@ -3493,11 +3660,10 @@ public class PuntoCassa extends JFrame implements ActionListener {
}
//---------------------------
lblResiduo.setText("" + df2.format(saldo - t));
}
else {
} else {
lblResiduo.setText("" + df2.format(saldo));
}
lblPunti.setText(punti);
String tipo = cercaTestoModoPagamento();
lblTipoPagamento.setText(tipo);
@ -3623,7 +3789,6 @@ public class PuntoCassa extends JFrame implements ActionListener {
}
}
private void chiudi() {
//04-02-2019---------------------------------------------
@ -3664,8 +3829,7 @@ public class PuntoCassa extends JFrame implements ActionListener {
return;
}
}
}
else {
} else {
//mostra messaggio ma non cancella i piatti
JOptionPane.showMessageDialog(this, "Saldo insufficiente!", "Saldo", JOptionPane.OK_OPTION);
return;
@ -3794,8 +3958,8 @@ public class PuntoCassa extends JFrame implements ActionListener {
+ "id_Turno,flag_gratuita,flag_bonus,flag_asporto,flag_passaggio_tessera, id_pren_dettaglio)"
+ "values(-1," + tessera + "," + model2.getValueAt(i, 2) + "," + idPuntoCassa + ","
+ //formattaDataOra(adesso) + "," + model2.getValueAt(i, 1) + ",0," + progressivo + "," +
formatLocalDateTime(adesso) + ","
+ model2.getValueAt(i, 1)
formatLocalDateTime(adesso) + ","
+ model2.getValueAt(i, 1)
+ ",0," + progressivo + ","
+ idModoPagamento + "," + idTurno + "," + flagGratuita + "," + flagBonus + ","
+ flagAsporto + "," + flagPassaggioTessera + "," + model2.getValueAt(i, colIdPrenotazione) + ")",
@ -3805,7 +3969,8 @@ public class PuntoCassa extends JFrame implements ActionListener {
spiaProgressivo = true;
}
} /*else {
}
/*else {
StringBuilder elencoIdProdotti = new StringBuilder();
for (int i = model.getRowCount() - 1; i >= 0; i--) {
Long idCateg = Long.valueOf(model.getValueAt(i, colCategoria).toString());
@ -3906,11 +4071,12 @@ public class PuntoCassa extends JFrame implements ActionListener {
}
/**
* Verifica se i prodotti selezionati differiscono da quelli presenti
* nella prenotazione collegata alla tessera. L'utente può scegliere di
* modificare i prodotti, oppure di azzerare la lista
* Verifica se i prodotti selezionati differiscono da quelli presenti nella
* prenotazione collegata alla tessera. L'utente può scegliere di modificare
* i prodotti, oppure di azzerare la lista
*
* @param elencoIdProdotto
* @return
* @return
*/
private boolean controllaDiscrepanzePrenotazioni(String elencoIdProdotto) {
boolean ok = true;
@ -3938,7 +4104,7 @@ public class PuntoCassa extends JFrame implements ActionListener {
return true;
}
}
Object selectedValue = JOptionPane.showOptionDialog(this,
"I prodotti selezionati sono diversi dai prenotati!\nVuoi proseguire?", "Prenotazione",
JOptionPane.DEFAULT_OPTION, JOptionPane.INFORMATION_MESSAGE, null, siNoOptions, null);
@ -3946,7 +4112,7 @@ public class PuntoCassa extends JFrame implements ActionListener {
azzeraDopoStorno();
return false;
}
}
}
return ok;
@ -4229,7 +4395,7 @@ public class PuntoCassa extends JFrame implements ActionListener {
}
lblScadTessera.setText("" + bonusResidui);
if (flagPrecaricaLista) {
forzaPrezzi(model);
}
@ -4264,8 +4430,6 @@ public class PuntoCassa extends JFrame implements ActionListener {
jBtnAsportoActionPerformed(null);
}
if (abilitatoPassaggi != 0L) {
controllaPrenotazioni();
}
@ -4301,7 +4465,7 @@ public class PuntoCassa extends JFrame implements ActionListener {
String queryPassaggi = "select count(*) as NRec from st_acquisti a inner join prodotti p on a.id_prodotto=p.id inner join Tipi_Pagamenti tp on a.id_tipo_pagamento=tp.id where TRUNC(data) = TRUNC(SYSDATE) and id_tessera=" + idTessera + " and flag_in_vassoio=0 and id_punto_cassa=" + idPuntoCassa;
Long numeroPassaggi = Utils.mySelectInteger(queryPassaggi, "NRec", this);
//fine----
if (flagEsegueStorni && numeroPassaggi > 0) {
if (flagEsegueStorni && numeroPassaggi > 0) {
Object selectedValue = JOptionPane.showOptionDialog(this, "N° passaggi giornalieri esauriti. Non è possibile usufruire del pasto!\nSi desidera effettuare uno storno per un pasto erroneamente addebitato?", "Attenzione",
JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null, siNoOptions, siNoOptions[1]);
if (Integer.parseInt(selectedValue.toString()) == 0) {
@ -4323,7 +4487,7 @@ public class PuntoCassa extends JFrame implements ActionListener {
// controllo abilitazione tessera
String controlloPunto = "Select controlla_abilitazione('" + numeroTessera + "'," + idPuntoCassa + ") as T from dual";
Long abilitato = Utils.mySelectInteger(controlloPunto, "T", this);
if (abilitato == 0) {
if (abilitato == 0) {
Object selectedValue = JOptionPane.showOptionDialog(this, "Tessera non abilitata per questo punto di distribuzione!\nConsenti passaggio?", "Attenzione",
JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null, siNoOptions, siNoOptions[0]);
if (Integer.parseInt(selectedValue.toString()) != 0) {
@ -4348,7 +4512,7 @@ public class PuntoCassa extends JFrame implements ActionListener {
+ "order by id_prenotazione) "
+ "where rownum = 1";
String idP = Utils.mySelect(query, "nr", this);
if (idP.length() > 0) {
if (idP.length() > 0) {
idPrenotazione = Long.valueOf(idP);
if (idPrenotazione > 0L) {
query = "SELECT Distinct Nome FROM VIEW_PRENOTAZIONI_TURNO v join prodotti p on v.ID_PRODOTTO=p.ID "
@ -4407,14 +4571,14 @@ public class PuntoCassa extends JFrame implements ActionListener {
while (rs.next()) {
//l'ID nella VIEW_PRENOTAZIONI_TURNO corrisponde all'ID della tabella SIR.PRENOTAZIONI_PASTI_DETTAGLIO
//idPrenDettaglio = Long.valueOf(rs.getString("ID"));
boolean flagPagato = Double.parseDouble(rs.getString("IMPORTO_PAGATO")) > 0.0;
EventParameters evtParams = new EventParameters(rs.getString("ID_PRODOTTO"),
null,
flagPagato,
rs.getString("ID"));
java.awt.event.ActionEvent evt;
boolean flagPagato = Double.parseDouble(rs.getString("IMPORTO_PAGATO")) > 0.0;
EventParameters evtParams = new EventParameters(rs.getString("ID_PRODOTTO"),
null,
flagPagato,
rs.getString("ID"));
java.awt.event.ActionEvent evt;
evt = new java.awt.event.ActionEvent(evtParams, 0, "PRODOTTO[" + rs.getString("ID_PRODOTTO") + "]");
actionPerformed(evt);
@ -4457,8 +4621,10 @@ public class PuntoCassa extends JFrame implements ActionListener {
}
/**
* Calcolo dei prezzi dei prodotti sulla base del profilo tariffario corrente
* @param model
* Calcolo dei prezzi dei prodotti sulla base del profilo tariffario
* corrente
*
* @param model
*/
private void forzaPrezzi(MyTableModel model) {
for (int y = 0; y < model.getRowCount(); y++) {
@ -4840,13 +5006,14 @@ public class PuntoCassa extends JFrame implements ActionListener {
}
}
}
/**
* Metodo che stampa un messaggio sul POS
* Se il POS è chiuso, stampa a prescindere, altrimenti stampa solo se non è stata letta una tessera
* @param flagAperto
* Metodo che stampa un messaggio sul POS Se il POS è chiuso, stampa a
* prescindere, altrimenti stampa solo se non è stata letta una tessera
*
* @param flagAperto
*/
private void stampaSuPos(boolean flagAperto){
private void stampaSuPos(boolean flagAperto) {
String idPuntoCassaString = idPuntoCassa.toString();
if (idPuntoCassaString.length() >= 5) {
idPuntoCassaString = idPuntoCassaString.substring(idPuntoCassaString.length() - 5, idPuntoCassaString.length());
@ -4854,11 +5021,10 @@ public class PuntoCassa extends JFrame implements ActionListener {
idPuntoCassaString = Utils.spaziBianchi(idPuntoCassaString, 5, false);
}
if(flagAperto && jTxtTessera.getText().trim().length() <= 0){
if (flagAperto && jTxtTessera.getText().trim().length() <= 0) {
String riga = Utils.spaziBianchi(messageDisplay, 20, true) + "POS " + idPuntoCassaString + " APERTO";
display(riga, null);
}
else if (!flagAperto){
} else if (!flagAperto) {
String riga = Utils.spaziBianchi(messageDisplay, 20, true) + "POS " + idPuntoCassaString + " CHIUSO";
display(riga, null);
}

View File

@ -0,0 +1,51 @@
/*
* Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
* Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
*/
package puntocassa.utils;
/**
*
* @author assis
*/
import com.github.sarxos.webcam.Webcam;
import com.google.zxing.*;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.HybridBinarizer;
import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
public class QRScanner {
public static void main(String[] args) {
Webcam webcam = Webcam.getDefault();
webcam.setViewSize(new Dimension(1280, 720));
webcam.open();
while (true) {
BufferedImage image = webcam.getImage();
if (image == null) continue;
LuminanceSource source = new BufferedImageLuminanceSource(image);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
try {
List<String> dataResult = new ArrayList<>();
Result result = new MultiFormatReader().decode(bitmap);
System.out.println("QR Code trovato: " + result.getText());
//decripting
dataResult = QrCryptoService.decryptAndValidate(result.getText());
System.out.println("QR code decriptato: " + dataResult);
break;
} catch (NotFoundException e) {
// Nessun QR code trovato nel frame
}
}
webcam.close();
}
}

View File

@ -9,97 +9,171 @@ package puntocassa.utils;
* @author assis
*/
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.time.Duration;
import java.time.OffsetDateTime;
import java.util.Base64;
import javax.crypto.spec.IvParameterSpec;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.time.*;
import java.util.*;
import org.json.JSONException;
import org.json.JSONObject;
public class QrCryptoService {
// Chiave AES 256-bit (32 byte) - hardcoded per test
private static final byte[] AES_KEY = "0123456789ABCDEF0123456789ABCDEF".getBytes(StandardCharsets.UTF_8);
// Crea il JSON cifrato da inserire nel QR
public static String createEncryptedQr(String uid, OffsetDateTime issuedAt, OffsetDateTime expiresAt) throws Exception {
// Crea il payload JSON con i dati utente
String payloadJson = new JSONObject()
private static final byte[] AES_KEY = hexToBytes("3031323334353637383961626364656630313233343536373839616263646566");
private static final Duration CLOCK_SKEW = Duration.ofSeconds(60);
public static String createEncryptedQr(String uid, OffsetDateTime issuedAt, OffsetDateTime expiresAt) throws GeneralSecurityException, JSONException {
JSONObject payload = new JSONObject()
.put("uid", uid)
.put("issued_at", issuedAt.toString())
.put("expires_at", expiresAt.toString())
.toString();
.put("expires_at", expiresAt.toString());
// Genera un Initialization Vector (IV) da 96 bit
byte[] iv = new byte[12];
new SecureRandom().nextBytes(iv);
byte[] iv = SecureRandom.getInstanceStrong().generateSeed(16);
// Cifra il payload con AES-GCM
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec keySpec = new SecretKeySpec(AES_KEY, "AES");
GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmSpec);
byte[] encrypted = cipher.doFinal(payloadJson.getBytes(StandardCharsets.UTF_8));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(AES_KEY, "AES"), new IvParameterSpec(iv));
byte[] encrypted = cipher.doFinal(payload.toString().getBytes(StandardCharsets.UTF_8));
// Codifica IV e dati cifrati in Base64
String ivBase64 = Base64.getEncoder().encodeToString(iv);
String encryptedBase64 = Base64.getEncoder().encodeToString(encrypted);
// Concatenazione IV + encrypted
ByteBuffer buffer = ByteBuffer.allocate(iv.length + encrypted.length);
buffer.put(iv);
buffer.put(encrypted);
byte[] combined = buffer.array();
// Costruisce il JSON finale con iv e data
JSONObject qrJson = new JSONObject()
.put("iv", ivBase64)
.put("data", encryptedBase64);
// Codifica tutto in Base64
return Base64.getEncoder().encodeToString(combined);
}
public static List<String> decryptAndValidate(String base64Qr) {
List<String> resultData = new ArrayList<>();
try {
// Step 1: Decodifica base64 del QR
byte[] decoded = Base64.getDecoder().decode(base64Qr);
String jsonString = new String(decoded, StandardCharsets.UTF_8);
return qrJson.toString(2); // con indentazione
// Step 2: Parsing JSON
JSONObject qrObject = new JSONObject(jsonString);
String ivBase64 = qrObject.getString("iv");
String dataBase64 = qrObject.getString("data");
// Step 3: Decodifica separata di IV e data
byte[] iv = Base64.getDecoder().decode(ivBase64);
byte[] ciphertext = Base64.getDecoder().decode(dataBase64);
// Step 4: Decrittazione AES
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(AES_KEY, "AES"), new IvParameterSpec(iv));
byte[] decrypted = cipher.doFinal(ciphertext);
// Step 5: Parsing del payload decifrato
JSONObject payload = new JSONObject(new String(decrypted, StandardCharsets.UTF_8));
ValidationResult result = validatePayload(payload);
if (result.valid()) {
resultData.add("VALID");
resultData.add(result.uid());
} else {
resultData.add("NOT VALID");
resultData.add("Errore validazione QR code: " + result.reason());
}
} catch (Exception e) {
resultData.add("NOT VALID");
resultData.add("Errore di decifratura o struttura non valida. QR letto: " + base64Qr);
}
return resultData;
}
// Decifra il JSON e verifica se è ancora valido
public static String decryptAndValidate(String qrJson) throws Exception {
JSONObject obj = new JSONObject(qrJson);
byte[] iv = Base64.getDecoder().decode(obj.getString("iv"));
byte[] encryptedData = Base64.getDecoder().decode(obj.getString("data"));
// Decrittazione con AES-GCM
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec keySpec = new SecretKeySpec(AES_KEY, "AES");
GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmSpec);
byte[] decryptedBytes = cipher.doFinal(encryptedData);
// Parsing del payload decifrato
JSONObject payload = new JSONObject(new String(decryptedBytes, StandardCharsets.UTF_8));
String uid = payload.getString("uid");
OffsetDateTime issuedAt = OffsetDateTime.parse(payload.getString("issued_at"));
OffsetDateTime expiresAt = OffsetDateTime.parse(payload.getString("expires_at"));
boolean isExpired = OffsetDateTime.now(expiresAt.getOffset()).isAfter(expiresAt);
private static ValidationResult validatePayload(JSONObject payload) {
try {
String uid = payload.getString("uid");
//con timezone esplicito
OffsetDateTime issuedAt = OffsetDateTime.parse(payload.getString("issued_at"));
OffsetDateTime expiresAt = OffsetDateTime.parse(payload.getString("expires_at"));
Instant now = Instant.now();
Instant iat = issuedAt.toInstant();
Instant exp = expiresAt.toInstant();
return String.format(
"Dati decifrati:\n" +
"- UID: %s\n" +
"- Emesso: %s\n" +
"- Scadenza: %s\n" +
"- Stato: %s\n",
uid,
issuedAt,
expiresAt,
isExpired ? "SCADUTO" : "VALIDO"
);
if (!uid.matches("[A-Z0-9]{16}"))
return ValidationResult.invalid("Formato UID non valido");
if (exp.isBefore(iat))
return ValidationResult.invalid("Scadenza antecedente all'emissione");
if (now.plus(CLOCK_SKEW).isBefore(iat))
return ValidationResult.invalid("QR non ancora valido");
if (now.minus(CLOCK_SKEW).isAfter(exp))
return ValidationResult.invalid("QR scaduto");
//senza timezone esplicito
/*LocalDateTime issuedAt = LocalDateTime.parse(payload.getString("issued_at"));
LocalDateTime expiresAt = LocalDateTime.parse(payload.getString("expires_at"));
ZoneId zone = ZoneId.systemDefault(); // oppure un valore fisso come ZoneId.of("Europe/Rome")
Instant now = Instant.now();
Instant iat = issuedAt.atZone(zone).toInstant();
Instant exp = expiresAt.atZone(zone).toInstant();
if (!uid.matches("[A-Z0-9]{16}"))
return ValidationResult.invalid("Formato UID non valido");
if (exp.isBefore(iat))
return ValidationResult.invalid("Scadenza antecedente all'emissione");
if (now.plus(CLOCK_SKEW).isBefore(iat))
return ValidationResult.invalid("QR non ancora valido");
if (now.minus(CLOCK_SKEW).isAfter(exp))
return ValidationResult.invalid("QR scaduto");*/
return ValidationResult.valid(uid, iat, exp);
} catch (Exception e) {
return ValidationResult.invalid("Errore di validazione QR Code: Payload malformato o incompleto");
}
}
public static byte[] hexToBytes(String hex) {
return new BigInteger(hex, 16).toByteArray().length == 33
? Arrays.copyOfRange(new BigInteger(hex, 16).toByteArray(), 1, 33)
: new BigInteger(hex, 16).toByteArray();
}
public record ValidationResult(boolean valid, String reason, String uid, Instant issuedAt, Instant expiresAt) {
public static ValidationResult valid(String uid, Instant issuedAt, Instant expiresAt) {
return new ValidationResult(true, null, uid, issuedAt, expiresAt);
}
public static ValidationResult invalid(String reason) {
return new ValidationResult(false, reason, null, null, null);
}
}
// Test completo della generazione e verifica
public static void main(String[] args) throws Exception {
//data valida
OffsetDateTime issuedAt = OffsetDateTime.now().withNano(0);
//data scaduta
//OffsetDateTime issuedAt = OffsetDateTime.now().minusMinutes(5).withNano(0);
OffsetDateTime expiresAt = issuedAt.plusSeconds(60);
String qrJson = createEncryptedQr("TSTTST91A48A271O", issuedAt, expiresAt);
List<String> dataResult = new ArrayList<>();
OffsetDateTime now = OffsetDateTime.now().withNano(0);
OffsetDateTime expiration = now.plusSeconds(CLOCK_SKEW.getSeconds());
System.out.println("QR JSON simulato:");
System.out.println(qrJson);
//String qr = createEncryptedQr("TSTTST91A48A271O", now, expiration);
String qr = "eyJpdiI6Ik0xTVJvR3dMbm40UGFMalFEVUh5M1E9PSIsImRhdGEiOiJSVERTTkFURi8wK0lrekFGVVRaSkMrb1Y5QWVZcFRnMFFVKzgzY25QUVgrM2ZMVkRMV3kyOFRsMnBIQTlBRWVGTFQvK0pxSWNtblBYRnBjMys5T0tVWHJpc3FQc1VHazUxRzFpSERSOFJ1eEFCSWhPaTZlamlkdHU4d090WmRzY2F2Y0FRbmNESTd3bGxJNWgvOFFmZnc9PSJ9";
System.out.println("QR generato:\n" + qr + "\n");
System.out.println("\nVerifica decifratura:");
System.out.println(decryptAndValidate(qrJson));
dataResult = decryptAndValidate(qr);
System.out.println("Verifica:\n" + dataResult);
}
}