/* * 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 javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; 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.charset.StandardCharsets; import java.security.*; import java.time.*; import java.util.*; 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 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.getBytes(StandardCharsets.UTF_8)); String encryptedBase64 = Base64.getEncoder().encodeToString(encrypted); String ivBase64 = Base64.getEncoder().encodeToString(iv); return ivBase64 + "|" + encryptedBase64; } public static List decryptAndValidate(String qrRaw) { List resultData = new ArrayList<>(); try { String[] parts = qrRaw.split("\\|", 2); if (parts.length != 2) { throw new IllegalArgumentException("Formato QR non valido. Atteso: IV|payload"); } byte[] iv = Base64.getDecoder().decode(parts[0]); byte[] ciphertext = Base64.getDecoder().decode(parts[1]); 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); String payload = 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: " + qrRaw); resultData.add(e.toString()); } return resultData; } private static ValidationResult validatePayload(String payload) { try { 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 exp = expiresAt.toInstant(); if (now.minus(CLOCK_SKEW).isAfter(exp)) { return ValidationResult.invalid("QR scaduto"); } return ValidationResult.valid(uid, exp); } catch (Exception e) { return ValidationResult.invalid("Errore di validazione QR Code: Payload malformato o data non valida"); } } 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 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); } } public static void main(String[] args) throws Exception { List dataResult = new ArrayList<>(); OffsetDateTime now = OffsetDateTime.now().withNano(0); OffsetDateTime expiration = now.plusSeconds(CLOCK_SKEW.getSeconds()); //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); System.out.println("Verifica:\n" + dataResult); } }