From 4db09682f7b2fbdddf91dc16d5cbf0f49985fb58 Mon Sep 17 00:00:00 2001 From: francescods Date: Wed, 5 Nov 2025 09:06:05 +0100 Subject: [PATCH] modifica codifica stringa QR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit l'algoritmo rimane lo stesso, ma non usiamo più un JSON ma la concatenazione di IV in Base64, un carattere | (pipe) e il payload in Base64 (dopo la criptazione con AES-GCM con chiave da 256 bit) il payload attuale contiene solo il codice fiscale, un carattere | (pipe) e la data di scadenza del QR --- src/puntocassa/utils/QrCryptoService.java | 135 ++++++++-------------- 1 file changed, 49 insertions(+), 86 deletions(-) diff --git a/src/puntocassa/utils/QrCryptoService.java b/src/puntocassa/utils/QrCryptoService.java index 7da95fc..404bbd4 100644 --- a/src/puntocassa/utils/QrCryptoService.java +++ b/src/puntocassa/utils/QrCryptoService.java @@ -16,65 +16,49 @@ 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 { 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()); + private static final Duration CLOCK_SKEW = Duration.ofSeconds(60); + + public static String createEncryptedQr(String uid, OffsetDateTime expiresAt) throws GeneralSecurityException { + String payload = uid + "|" + expiresAt.toString(); byte[] iv = SecureRandom.getInstanceStrong().generateSeed(16); 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)); + byte[] encrypted = cipher.doFinal(payload.getBytes(StandardCharsets.UTF_8)); - // Concatenazione IV + encrypted - ByteBuffer buffer = ByteBuffer.allocate(iv.length + encrypted.length); - buffer.put(iv); - buffer.put(encrypted); - byte[] combined = buffer.array(); + String encryptedBase64 = Base64.getEncoder().encodeToString(encrypted); + String ivBase64 = Base64.getEncoder().encodeToString(iv); - // Codifica tutto in Base64 - return Base64.getEncoder().encodeToString(combined); + return ivBase64 + "|" + encryptedBase64; } - - - public static List decryptAndValidate(String base64Qr) { + + public static List decryptAndValidate(String qrRaw) { List resultData = new ArrayList<>(); try { - // Step 1: Decodifica base64 del QR - byte[] decoded = Base64.getDecoder().decode(base64Qr); - String jsonString = new String(decoded, StandardCharsets.UTF_8); + String[] parts = qrRaw.split("\\|", 2); + if (parts.length != 2) { + throw new IllegalArgumentException("Formato QR non valido. Atteso: IV|payload"); + } - // Step 2: Parsing JSON - JSONObject qrObject = new JSONObject(jsonString); - String ivBase64 = qrObject.getString("iv"); - String dataBase64 = qrObject.getString("data"); + byte[] iv = Base64.getDecoder().decode(parts[0]); + byte[] ciphertext = Base64.getDecoder().decode(parts[1]); - // 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)); + String payload = new String(decrypted, StandardCharsets.UTF_8); ValidationResult result = validatePayload(payload); if (result.valid()) { @@ -87,61 +71,40 @@ public class QrCryptoService { } catch (Exception e) { resultData.add("NOT VALID"); - resultData.add("Errore di decifratura o struttura non valida. QR letto: " + base64Qr); + resultData.add("Errore di decifratura o struttura non valida. QR letto: " + qrRaw); + resultData.add(e.toString()); } return resultData; } - - private static ValidationResult validatePayload(JSONObject payload) { + private static ValidationResult validatePayload(String 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")); + String[] parts = payload.split("\\|", 2); + if (parts.length != 2) { + return ValidationResult.invalid("Payload malformato: atteso UID|dataOra"); + } + + String uid = parts[0].trim(); + String dateTimeStr = parts[1].trim(); + + if (!uid.matches("[A-Z0-9]{16}")) { + return ValidationResult.invalid("Formato UID non valido"); + } + + OffsetDateTime expiresAt = OffsetDateTime.parse(dateTimeStr); Instant now = Instant.now(); - Instant iat = issuedAt.toInstant(); Instant exp = expiresAt.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)) + 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); + return ValidationResult.valid(uid, exp); } catch (Exception e) { - return ValidationResult.invalid("Errore di validazione QR Code: Payload malformato o incompleto"); + return ValidationResult.invalid("Errore di validazione QR Code: Payload malformato o data non valida"); } } @@ -151,13 +114,15 @@ public class QrCryptoService { : 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 record ValidationResult(boolean valid, String reason, String uid, Instant expiresAt) { + + public static ValidationResult valid(String uid, Instant expiresAt) { + return new ValidationResult(true, null, uid, expiresAt); } public static ValidationResult invalid(String reason) { - return new ValidationResult(false, reason, null, null, null); + return new ValidationResult(false, reason, null, null); } } @@ -166,14 +131,12 @@ public class QrCryptoService { OffsetDateTime now = OffsetDateTime.now().withNano(0); OffsetDateTime expiration = now.plusSeconds(CLOCK_SKEW.getSeconds()); - //String qr = createEncryptedQr("TSTTST91A48A271O", now, expiration); - String qr = "eyJpdiI6Ik0xTVJvR3dMbm40UGFMalFEVUh5M1E9PSIsImRhdGEiOiJSVERTTkFURi8wK0lrekFGVVRaSkMrb1Y5QWVZcFRnMFFVKzgzY25QUVgrM2ZMVkRMV3kyOFRsMnBIQTlBRWVGTFQvK0pxSWNtblBYRnBjMys5T0tVWHJpc3FQc1VHazUxRzFpSERSOFJ1eEFCSWhPaTZlamlkdHU4d090WmRzY2F2Y0FRbmNESTd3bGxJNWgvOFFmZnc9PSJ9"; + //String qr = createEncryptedQr("TSTTST91A48A271O", expiration); + //String qr = "eyJpdiI6Ik0xTVJvR3dMbm40UGFMalFEVUh5M1E9PSIsImRhdGEiOiJSVERTTkFURi8wK0lrekFGVVRaSkMrb1Y5QWVZcFRnMFFVKzgzY25QUVgrM2ZMVkRMV3kyOFRsMnBIQTlBRWVGTFQvK0pxSWNtblBYRnBjMys5T0tVWHJpc3FQc1VHazUxRzFpSERSOFJ1eEFCSWhPaTZlamlkdHU4d090WmRzY2F2Y0FRbmNESTd3bGxJNWgvOFFmZnc9PSJ9"; + String qr = "Z8j6FyrmwmSTBYs4iOWTEg==|WKTaAvEqlC+RYW3Ix4ftJcqgDriqdk7pv+U5Aclhmgb70n8y9447wRV6MXW5ikPN"; System.out.println("QR generato:\n" + qr + "\n"); - dataResult = decryptAndValidate(qr); + dataResult = decryptAndValidate(qr); System.out.println("Verifica:\n" + dataResult); } } - - -