modifica codifica stringa QR
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
This commit is contained in:
parent
42ce8d5fd6
commit
4db09682f7
@ -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());
|
||||
|
||||
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<String> decryptAndValidate(String base64Qr) {
|
||||
public static List<String> decryptAndValidate(String qrRaw) {
|
||||
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);
|
||||
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");
|
||||
String[] parts = payload.split("\\|", 2);
|
||||
if (parts.length != 2) {
|
||||
return ValidationResult.invalid("Payload malformato: atteso UID|dataOra");
|
||||
}
|
||||
|
||||
//con timezone esplicito
|
||||
OffsetDateTime issuedAt = OffsetDateTime.parse(payload.getString("issued_at"));
|
||||
OffsetDateTime expiresAt = OffsetDateTime.parse(payload.getString("expires_at"));
|
||||
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);
|
||||
System.out.println("Verifica:\n" + dataResult);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user