Repository: pagopa/p4pa-send-notification
Pull request #31: feat: P4ADEV_2148 get files from file storage
Author: login: antocalo
Created: 2025-03-21T08:42:50Z | Merged: 2025-03-21T09:31:23Z

#### Description
<!--- Please always add a PR description as if nobody knows anything about the context these changes come from. -->
<!--- Even if we are all from our internal team, we may not be on the same page. -->
<!--- Write this PR as you were contributing to a public OSS project, where nobody knows you and you have to earn their trust. -->
<!--- This will improve our projects in the long run! Thanks. -->

#### List of Changes
<!--- Describe your changes in detail -->

#### Motivation and Context
<!--- Why is this change required? What problem does it solve? -->

#### How Has This Been Tested?
<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, tests ran to see how -->
<!--- your change affects other areas of the code, etc. -->
- Pre-Deploy Test
  - [ ] Unit
  - [ ] Integration (Narrow)
- Post-Deploy Test
  - [ ] Isolated Microservice
  - [ ] Broader Integration
  - [ ] Acceptance
  - [ ] Performance & Load

#### Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

- [ ] PATCH - Bug fix (backwards compatible bug fixes)
- [ ] MINOR - New feature (add functionality in a backwards compatible manner)
- [ ] MAJOR - Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] CHORE - Minor Change (fix or feature that don't impact the functionality e.g. Documentation or lint configuration)

#### Checklist:
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.



Diff:
diff --git a/openapi/generated.openapi.json b/openapi/generated.openapi.json
index 2ff8d36f..f81228e5 100644
--- a/openapi/generated.openapi.json
+++ b/openapi/generated.openapi.json
@@ -712,9 +712,6 @@
           },
           "fileName" : {
             "type" : "string"
-          },
-          "path" : {
-            "type" : "string"
           }
         },
         "required" : [ "digest", "fileName" ]
diff --git a/openapi/p4pa-send-notification.openapi.yaml b/openapi/p4pa-send-notification.openapi.yaml
index 7c265727..de5b6d6b 100644
--- a/openapi/p4pa-send-notification.openapi.yaml
+++ b/openapi/p4pa-send-notification.openapi.yaml
@@ -380,8 +380,6 @@ components:
           type: string
         fileName:
           type: string
-        path:
-          type: string
     CreateNotificationResponse:
       type: object
       properties:
diff --git a/postman/P4PA-Send-E2E.postman_collection.json b/postman/P4PA-Send-E2E.postman_collection.json
index af75b4ee..f95ca4fb 100644
--- a/postman/P4PA-Send-E2E.postman_collection.json
+++ b/postman/P4PA-Send-E2E.postman_collection.json
@@ -137,7 +137,7 @@
 						"header": [],
 						"body": {
 							"mode": "raw",
-							"raw": "{\r\n    \"paProtocolNumber\":\"Prot_001\",\r\n    \"recipient\": {\r\n        \"recipientType\": \"PF\", \r\n        \"taxId\": \"BNRMHL75C06G702B\",\r\n        \"denomination\": \"Michelangelo Buonarroti\",\r\n        \"physicalAddress\": { \r\n        \"address\": \"Via Larga 10\", \r\n        \"zip\": \"00186\", \r\n        \"municipality\": \"Roma\", \r\n        \"province\": \"RM\" \r\n        },\r\n        \"payments\": [ \r\n        {\r\n            \"pagoPa\": { \r\n                \"noticeCode\": {{noticeCode}}, \r\n                \"creditorTaxId\": \"77777777777\", \r\n                \"applyCost\": true, \r\n                \"attachment\": { \r\n                    \"fileName\":\"Pagamento.pdf\",\r\n                    \"contentType\": \"application/pdf\",\r\n                    \"digest\": \"45Iba3tn9Dfm7TX+AbtWDR1csMuHgEbrHi/zZr6DjHU=\"\r\n                }\r\n            }\r\n        }]\r\n    },\r\n    \"documents\":[{\r\n        \"fileName\":\"Notifica.pdf\",\r\n        \"contentType\": \"application/pdf\",\r\n        \"digest\": \"YSxsCpvZHvwL8IIosWJBUDjgUwa01sBHu6Cj4laQRLA=\"\r\n    }]\r\n}",
+							"raw": "{\r\n    \"paProtocolNumber\":\"Prot_001\",\r\n    \"recipient\": {\r\n        \"recipientType\": \"PF\", \r\n        \"taxId\": \"BNRMHL75C06G702B\",\r\n        \"denomination\": \"Michelangelo Buonarroti\",\r\n        \"physicalAddress\": { \r\n        \"address\": \"Via Larga 10\", \r\n        \"zip\": \"00186\", \r\n        \"municipality\": \"Roma\", \r\n        \"province\": \"RM\" \r\n        },\r\n        \"payments\": [ \r\n        {\r\n            \"pagoPa\": { \r\n                \"noticeCode\": {{noticeCode}}, \r\n                \"creditorTaxId\": \"77777777777\", \r\n                \"applyCost\": true, \r\n                \"attachment\": { \r\n                    \"fileName\":\"Pagamento.pdf\",\r\n                    \"contentType\": \"application/pdf\",\r\n                    \"digest\": \"45Iba3tn9Dfm7TX+AbtWDR1csMuHgEbrHi/zZr6DjHU=\"\r\n                }\r\n            }\r\n        }]\r\n    },\r\n    \"documents\":[{\r\n        \"fileName\":\"Notifica.pdf\",\r\n        \"contentType\": \"application/pdf\",\r\n        \"digest\": \"YSxsCpvZHvwL8IIosWJBUDjgUwa01sBHu6Cj4laQRLA=\"\r\n    }],\r\n    \"notificationFeePolicy\": \"DELIVERY_MODE\", \r\n    \"physicalCommunicationType\": \"AR_REGISTERED_LETTER\", \r\n    \"senderDenomination\": \"Ente Intermediario 2\", \r\n    \"senderTaxId\": \"00000000018\", \r\n    \"amount\": 0, \r\n    \"paymentExpirationDate\": \"2025-12-27\", \r\n    \"taxonomyCode\": \"010101P\", \r\n    \"paFee\": 100, \r\n    \"vat\":22, \r\n    \"pagoPaIntMode\": \"NONE\" \r\n}",
 							"options": {
 								"raw": {
 									"language": "json"
diff --git a/src/main/java/it/gov/pagopa/pu/send/connector/pagopa/workflow/config/WorkflowApiClientConfig.java b/src/main/java/it/gov/pagopa/pu/send/connector/pagopa/workflow/config/WorkflowApiClientConfig.java
index ee08b66c..fa5fb479 100644
--- a/src/main/java/it/gov/pagopa/pu/send/connector/pagopa/workflow/config/WorkflowApiClientConfig.java
+++ b/src/main/java/it/gov/pagopa/pu/send/connector/pagopa/workflow/config/WorkflowApiClientConfig.java
@@ -7,7 +7,7 @@
 import org.springframework.context.annotation.Configuration;
 
 @Configuration
-@ConfigurationProperties(prefix = "rest.workflow-hub")
+@ConfigurationProperties(prefix = "rest.pagopa.workflow-hub")
 @SuperBuilder
 @NoArgsConstructor
 public class WorkflowApiClientConfig extends ApiClientConfig {
diff --git a/src/main/java/it/gov/pagopa/pu/send/service/FileRetrieverService.java b/src/main/java/it/gov/pagopa/pu/send/service/FileRetrieverService.java
new file mode 100644
index 00000000..6ddcd819
--- /dev/null
+++ b/src/main/java/it/gov/pagopa/pu/send/service/FileRetrieverService.java
@@ -0,0 +1,31 @@
+package it.gov.pagopa.pu.send.service;
+
+import it.gov.pagopa.pu.send.util.AESUtils;
+import java.io.InputStream;
+import java.nio.file.Path;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+@Service
+public class FileRetrieverService {
+  private final String fileEncryptPassword;
+  private final String sendFilePath;
+  private final String sharedFolder;
+
+  public FileRetrieverService(@Value("${app.fileEncryptPassword}") String fileEncryptPassword,
+    @Value("${folders.send-file-folder}") String sendFilePath,
+    @Value("${folders.shared}") String sharedFolder) {
+    this.fileEncryptPassword = fileEncryptPassword;
+    this.sendFilePath = sendFilePath;
+    this.sharedFolder = sharedFolder;
+  }
+
+  public InputStream retrieveFile(Long organizationId, String fileName) {
+    Path relativePath = Path.of(sharedFolder, String.valueOf(organizationId), sendFilePath);
+    return decryptFile(relativePath, fileName);
+  }
+
+  public InputStream decryptFile(Path filePath, String fileName) {
+    return AESUtils.decrypt(fileEncryptPassword, filePath, fileName);
+  }
+}
diff --git a/src/main/java/it/gov/pagopa/pu/send/service/SendFacadeServiceImpl.java b/src/main/java/it/gov/pagopa/pu/send/service/SendFacadeServiceImpl.java
index 7e727842..2efdce21 100644
--- a/src/main/java/it/gov/pagopa/pu/send/service/SendFacadeServiceImpl.java
+++ b/src/main/java/it/gov/pagopa/pu/send/service/SendFacadeServiceImpl.java
@@ -67,7 +67,7 @@ public void uploadFiles(String sendNotificationId) {
     for(DocumentDTO doc : notification.getDocuments()){
       Optional<String> versionId = Optional.empty();
       if(!doc.getStatus().equals(FileStatus.UPLOADED))
-        versionId = uploadService.uploadFile(sendNotificationId, doc);
+        versionId = uploadService.uploadFile(notification.getOrganizationId(), sendNotificationId, doc);
       if (versionId.isPresent()) {
         sendNotificationRepository.updateFileStatus(sendNotificationId, doc.getFileName(), FileStatus.UPLOADED);
         sendNotificationRepository.updateFileVersionId(sendNotificationId, doc.getFileName(), versionId.get());
diff --git a/src/main/java/it/gov/pagopa/pu/send/service/SendNotificationServiceImpl.java b/src/main/java/it/gov/pagopa/pu/send/service/SendNotificationServiceImpl.java
index c17aa4e9..1380ac43 100644
--- a/src/main/java/it/gov/pagopa/pu/send/service/SendNotificationServiceImpl.java
+++ b/src/main/java/it/gov/pagopa/pu/send/service/SendNotificationServiceImpl.java
@@ -16,8 +16,7 @@
 import it.gov.pagopa.pu.send.util.FileUtils;
 import it.gov.pagopa.pu.send.util.NotificationUtils;
 
-import java.io.File;
-
+import java.io.InputStream;
 import org.springframework.beans.factory.annotation.Value;
 import it.gov.pagopa.pu.workflowhub.dto.generated.WorkflowCreatedDTO;
 import org.springframework.stereotype.Service;
@@ -29,13 +28,16 @@ public class SendNotificationServiceImpl implements SendNotificationService {
   private final WorkflowService workflowService;
   private final CreateNotificationRequest2SendNotificationMapper mapper;
   private final String fileShareBaseUrl;
+  private final FileRetrieverService fileRetrieverService;
 
   public SendNotificationServiceImpl(@Value("${rest.pagopa.fileshare.base-url}") String fileShareBaseUrl,
-    SendNotificationRepository sendNotificationRepository, CreateNotificationRequest2SendNotificationMapper mapper, WorkflowService workflowService) {
+    SendNotificationRepository sendNotificationRepository, CreateNotificationRequest2SendNotificationMapper mapper, WorkflowService workflowService,
+    FileRetrieverService fileRetrieverService) {
     this.fileShareBaseUrl = fileShareBaseUrl;
     this.sendNotificationRepository = sendNotificationRepository;
     this.mapper = mapper;
     this.workflowService = workflowService;
+    this.fileRetrieverService = fileRetrieverService;
   }
 
   @Override
@@ -58,7 +60,7 @@ public StartNotificationResponse startSendNotification(String sendNotificationId
     notification.getDocuments().stream()
       .filter(doc -> doc.getFileName().equals(loadFileRequest.getFileName()))
       .findFirst().ifPresentOrElse(
-        doc -> updateFileStatus(sendNotificationId, doc, loadFileRequest),
+        doc -> updateFileStatus(sendNotificationId, doc, loadFileRequest, organizationId),
         () -> {
           throw new IllegalArgumentException("File not found with id: " + loadFileRequest.getFileName());
         }
@@ -93,17 +95,11 @@ private SendNotification findSendNotification(String sendNotificationId, Long or
       .orElseThrow(() -> new IllegalArgumentException("Notification not found with id: " + sendNotificationId + " for organizationId: " + organizationId));
   }
 
-  private void updateFileStatus(String sendNotificationId, DocumentDTO doc, LoadFileRequest loadFileRequest) {
+  private void updateFileStatus(String sendNotificationId, DocumentDTO doc, LoadFileRequest loadFileRequest, Long organizationId) {
     NotificationUtils.validateStatus(FileStatus.WAITING, doc.getStatus());
-    //TODO edit file retrieve with P4ADEV-2148, change static sendNotificationId with doc.getSendNotificationId
-    String filePath;
-    if (loadFileRequest.getPath() != null)
-      filePath = loadFileRequest.getPath() + "sendNotificationId_" + doc.getFileName();
-    else
-      filePath = "src/main/resources/tmp/" + "sendNotificationId_" + doc.getFileName();
-
     try {
-      File file = new File(filePath);
+      String fileName = sendNotificationId +"_" + doc.getFileName();
+      InputStream file = fileRetrieverService.retrieveFile(organizationId, fileName);
       if (!FileUtils.calculateFileHash(file).equals(loadFileRequest.getDigest()))
         throw new InvalidSignatureException("File " + doc.getFileName() + " has not a valid signature");
     } catch (Exception e) {
diff --git a/src/main/java/it/gov/pagopa/pu/send/service/SendUploadFacadeService.java b/src/main/java/it/gov/pagopa/pu/send/service/SendUploadFacadeService.java
index 11fa9bba..b2ca3d25 100644
--- a/src/main/java/it/gov/pagopa/pu/send/service/SendUploadFacadeService.java
+++ b/src/main/java/it/gov/pagopa/pu/send/service/SendUploadFacadeService.java
@@ -4,5 +4,5 @@
 import java.util.Optional;
 
 public interface SendUploadFacadeService {
-  Optional<String> uploadFile(String sendNotidicationId, DocumentDTO documentDTO);
+  Optional<String> uploadFile(Long organizationId, String sendNotidicationId, DocumentDTO documentDTO);
 }
diff --git a/src/main/java/it/gov/pagopa/pu/send/service/SendUploadFacadeServiceImpl.java b/src/main/java/it/gov/pagopa/pu/send/service/SendUploadFacadeServiceImpl.java
index 27cf5988..502c33fd 100644
--- a/src/main/java/it/gov/pagopa/pu/send/service/SendUploadFacadeServiceImpl.java
+++ b/src/main/java/it/gov/pagopa/pu/send/service/SendUploadFacadeServiceImpl.java
@@ -2,40 +2,31 @@
 
 import it.gov.pagopa.pu.send.connector.pagopa.send.client.SendUploadClient;
 import it.gov.pagopa.pu.send.dto.DocumentDTO;
-import it.gov.pagopa.pu.send.exception.InvalidSignatureException;
 import it.gov.pagopa.pu.send.exception.UploadFileException;
-import it.gov.pagopa.pu.send.util.FileUtils;
-import java.io.File;
 import java.io.FileNotFoundException;
-import java.nio.file.Files;
+import java.io.InputStream;
 import java.util.Optional;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.core.io.ClassPathResource;
 import org.springframework.stereotype.Service;
 
 @Service
-@Slf4j
 public class SendUploadFacadeServiceImpl implements SendUploadFacadeService {
 
   private final SendUploadClient sendUploadClient;
+  private final FileRetrieverService fileRetrieverService;
 
-  public SendUploadFacadeServiceImpl(SendUploadClient sendUploadClient) {
+  public SendUploadFacadeServiceImpl(SendUploadClient sendUploadClient,
+    FileRetrieverService fileRetrieverService) {
     this.sendUploadClient = sendUploadClient;
+    this.fileRetrieverService = fileRetrieverService;
   }
 
   @Override
-  public Optional<String> uploadFile(String sendNotificationId, DocumentDTO documentDTO) {
-    //TODO edit file retrieve with P4ADEV-2148
-    String fileName = "tmp/sendNotificationId" + "_" + documentDTO.getFileName();
-    try {
-      File file = new ClassPathResource(fileName).getFile();
-      if (!file.exists() || !file.isFile())
+  public Optional<String> uploadFile(Long organizationId, String sendNotificationId, DocumentDTO documentDTO) {
+    String fileName = sendNotificationId + "_" + documentDTO.getFileName();
+    try(InputStream inputStream = fileRetrieverService.retrieveFile(organizationId, fileName)) {
+      if(inputStream==null)
         throw new FileNotFoundException("File not found: " + documentDTO.getFileName());
-
-      if(!FileUtils.calculateFileHash(file).equals(documentDTO.getDigest()))
-        throw new InvalidSignatureException("File "+documentDTO.getFileName()+" has not a valid signature");
-
-      byte[] fileBytes = Files.readAllBytes(file.toPath());
+      byte[] fileBytes = inputStream.readAllBytes();
       return sendUploadClient.upload(documentDTO, fileBytes);
     } catch (Exception e) {
       throw new UploadFileException(e.getMessage());
diff --git a/src/main/java/it/gov/pagopa/pu/send/util/AESUtils.java b/src/main/java/it/gov/pagopa/pu/send/util/AESUtils.java
new file mode 100644
index 00000000..2ff5f929
--- /dev/null
+++ b/src/main/java/it/gov/pagopa/pu/send/util/AESUtils.java
@@ -0,0 +1,257 @@
+package it.gov.pagopa.pu.send.util;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.SequenceInputStream;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.security.DigestInputStream;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * Utility class for AES encryption and decryption using the GCM mode.
+ * Supports secure handling of files and data streams.
+ */
+public class AESUtils {
+  private AESUtils() {
+  }
+
+  private static final String ALGORITHM = "AES/GCM/NoPadding";
+  private static final String FACTORY_INSTANCE = "PBKDF2WithHmacSHA256";
+  private static final int TAG_LENGTH_BIT = 128;
+  private static final int IV_LENGTH_BYTE = 12;
+  private static final int SALT_LENGTH_BYTE = 16;
+  private static final String ALGORITHM_TYPE = "AES";
+  private static final int KEY_LENGTH = 256;
+  private static final int ITERATION_COUNT = 65536;
+  private static final Charset UTF_8 = StandardCharsets.UTF_8;
+
+  public static final String CIPHER_EXTENSION = ".cipher";
+
+  /** Generates a random byte array to be used as a nonce. */
+  private static byte[] getRandomNonce(int length) {
+    byte[] nonce = new byte[length];
+    new SecureRandom().nextBytes(nonce);
+    return nonce;
+  }
+
+  /**
+   * Derives an AES key from a password and a cryptographic salt using PBKDF2.
+   * @throws IllegalStateException if the key derivation fails.
+   */
+  private static SecretKey getSecretKey(String password, byte[] salt) {
+    KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, ITERATION_COUNT, KEY_LENGTH);
+
+    try {
+      SecretKeyFactory factory = SecretKeyFactory.getInstance(FACTORY_INSTANCE);
+      return new SecretKeySpec(factory.generateSecret(spec).getEncoded(), ALGORITHM_TYPE);
+    } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+      throw new IllegalStateException("Cannot initialize cryptographic data", e);
+    }
+  }
+
+  /** It will encrypt the input message using AES GCM mode configured with the provided password */
+  public static byte[] encrypt(String password, String plainMessage) {
+    byte[] salt = getRandomNonce(SALT_LENGTH_BYTE);
+    SecretKey secretKey = getSecretKey(password, salt);
+
+    // GCM recommends 12 bytes iv
+    byte[] iv = getRandomNonce(IV_LENGTH_BYTE);
+    Cipher cipher = initCipher(Cipher.ENCRYPT_MODE, secretKey, iv);
+
+    byte[] encryptedMessageByte = executeCipherOp(cipher, plainMessage.getBytes(UTF_8));
+
+    // prefix IV and Salt to cipher text
+    return ByteBuffer.allocate(iv.length + salt.length + encryptedMessageByte.length)
+      .put(iv)
+      .put(salt)
+      .put(encryptedMessageByte)
+      .array();
+  }
+
+  /** It will wrap the provided inputStream into a ciphered inputStream using AES GCM mode configured with the provided password */
+  public static InputStream encrypt(String password, InputStream plainStream) {
+    byte[] salt = getRandomNonce(SALT_LENGTH_BYTE);
+    SecretKey secretKey = getSecretKey(password, salt);
+
+    // GCM recommends 12 bytes iv
+    byte[] iv = getRandomNonce(IV_LENGTH_BYTE);
+    Cipher cipher = initCipher(Cipher.ENCRYPT_MODE, secretKey, iv);
+
+    // prefix IV and Salt to cipher text
+    byte[] prefix = ByteBuffer.allocate(iv.length + salt.length)
+      .put(iv)
+      .put(salt)
+      .array();
+
+    return new SequenceInputStream(
+      new ByteArrayInputStream(prefix),
+      new CipherInputStream(new BufferedInputStream(plainStream), cipher));
+  }
+
+  /**
+   * It will read and store the provided inputStream as a ciphered file using
+   * AES GCM mode configured with the provided password.<BR /> If the ciphered
+   * file already exists, it will throw
+   * {@link java.nio.file.FileAlreadyExistsException}
+   *
+   * @return file digest
+   */
+  public static byte[] encryptAndSave(String password, InputStream plainStream, Path targetPath, String fileName)
+    throws IOException, NoSuchAlgorithmException {
+    Path targetCipherFile = targetPath.resolve(fileName + CIPHER_EXTENSION);
+    MessageDigest digest = MessageDigest.getInstance("SHA-256");
+    try (DigestInputStream is = new DigestInputStream(plainStream, digest);
+         InputStream cipherIs = encrypt(password, is)) {
+      Files.copy(cipherIs, targetCipherFile);
+      return digest.digest();
+    }
+  }
+
+  /** It will cipher the provided file using AES GCM mode configured with the provided password.<BR />
+   * If the ciphered file already exists, it will throw {@link java.nio.file.FileAlreadyExistsException} */
+  public static File encrypt(String password, File plainFile) {
+    File cipherFile = new File(plainFile.getAbsolutePath() + CIPHER_EXTENSION);
+    try (FileInputStream fis = new FileInputStream(plainFile);
+         InputStream cipherStream = encrypt(password, fis)) {
+      Files.copy(cipherStream, cipherFile.toPath());
+    } catch (IOException e) {
+      throw new IllegalStateException("Something went wrong when ciphering input file " + plainFile.getAbsolutePath(), e);
+    }
+    return cipherFile;
+  }
+
+  /** It will decrypt the provided cipher message using AES GCM mode configured with the provided password */
+  public static String decrypt(String password, byte[] cipherMessage) {
+    ByteBuffer byteBuffer = ByteBuffer.wrap(cipherMessage);
+
+    byte[] iv = new byte[IV_LENGTH_BYTE];
+    byteBuffer.get(iv);
+
+    byte[] salt = new byte[SALT_LENGTH_BYTE];
+    byteBuffer.get(salt);
+
+    byte[] encryptedByte = new byte[byteBuffer.remaining()];
+    byteBuffer.get(encryptedByte);
+
+    SecretKey secretKey = getSecretKey(password, salt);
+    Cipher cipher = initCipher(Cipher.DECRYPT_MODE, secretKey, iv);
+
+    byte[] decryptedMessageByte = executeCipherOp(cipher, encryptedByte);
+    return new String(decryptedMessageByte, UTF_8);
+  }
+
+  /** It will wrap the provided inputStream into a decrypted inputStream using AES GCM mode configured with the provided password */
+  public static InputStream decrypt(String password, InputStream cipherStream) {
+    try {
+      byte[] iv = cipherStream.readNBytes(IV_LENGTH_BYTE);
+      byte[] salt = cipherStream.readNBytes(SALT_LENGTH_BYTE);
+
+      SecretKey secretKey = getSecretKey(password, salt);
+      Cipher cipher = initCipher(Cipher.DECRYPT_MODE, secretKey, iv);
+
+      return new CipherInputStream(new BufferedInputStream(cipherStream), cipher);
+    } catch (IOException e) {
+      throw new IllegalStateException("Cannot read AES prefix data", e);
+    }
+  }
+
+  /** @see #decrypt(String, Path, String, File)  */
+  public static void decrypt(String password, File cipherFile, File outputPlainFile){
+    AESUtils.decrypt(password, cipherFile.getParentFile().toPath(), cipherFile.getName(), outputPlainFile);
+  }
+
+  /** It will decrypt the provided file using AES GCM mode configured with the provided password.<BR />
+   * If the decrypted file already exists, it will override it */
+  public static void decrypt(String password, Path filePath, String fileName, File outputPlainFile) {
+    Path cipherFilePath = resolveCipherFilePath(filePath, fileName);
+
+    try (FileInputStream fis = new FileInputStream(cipherFilePath.toFile());
+         InputStream plainStream = decrypt(password, fis)) {
+      Files.copy(plainStream, outputPlainFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
+    } catch (IOException e) {
+      throw new IllegalStateException("Something went wrong when deciphering input file " + cipherFilePath, e);
+    }
+  }
+
+  /** @see #decrypt(String, Path, String, File)  */
+  public static InputStream decrypt(String password, File cipherFile){
+    return AESUtils.decrypt(password, cipherFile.getParentFile().toPath(), cipherFile.getName());
+  }
+
+  /** It will create an InputStream to read the provided file decrypting it using AES GCM mode configured with the given password */
+  public static InputStream decrypt(String password, Path filePath, String fileName) {
+    Path cipherFilePath = resolveCipherFilePath(filePath, fileName);
+    try {
+      return decrypt(password, new FileInputStream(cipherFilePath.toFile()));
+    } catch (FileNotFoundException e) {
+      throw new IllegalStateException("Something went wrong when deciphering input file " + cipherFilePath, e);
+    }
+  }
+
+  /** A ciphered file should have {@link #CIPHER_EXTENSION} extension  */
+  private static Path resolveCipherFilePath(Path filePath, String fileName) {
+    Path cipherFilePath;
+    if(fileName.endsWith(CIPHER_EXTENSION)) {
+      cipherFilePath = filePath.resolve(fileName);
+    } else {
+      cipherFilePath = filePath.resolve(fileName + CIPHER_EXTENSION);
+    }
+    return cipherFilePath;
+  }
+
+  private static byte[] executeCipherOp(Cipher cipher, byte[] encryptedByte) {
+    try {
+      return cipher.doFinal(encryptedByte);
+    } catch (IllegalBlockSizeException | BadPaddingException e) {
+      throw new IllegalStateException("Cannot execute cipher op", e);
+    }
+  }
+
+  /**
+   * Initializes a Cipher instance with the specified mode, secret key, and IV.
+   *
+   * @param mode the cipher mode (Cipher.ENCRYPT_MODE or Cipher.DECRYPT_MODE).
+   * @param secretKey the secret key.
+   * @param iv the initialization vector.
+   * @return an initialized Cipher instance.
+   * @throws IllegalStateException if cipher initialization fails.
+   */
+  private static Cipher initCipher(int mode, SecretKey secretKey, byte[] iv) {
+    try {
+      Cipher cipher = Cipher.getInstance(ALGORITHM);
+      cipher.init(mode, secretKey, new GCMParameterSpec(TAG_LENGTH_BIT, iv));
+      return cipher;
+    } catch (NoSuchPaddingException | NoSuchAlgorithmException |
+             InvalidKeyException
+             | InvalidAlgorithmParameterException e) {
+      throw new IllegalStateException("Cannot initialize cipher data", e);
+    }
+  }
+
+}
diff --git a/src/main/java/it/gov/pagopa/pu/send/util/FileUtils.java b/src/main/java/it/gov/pagopa/pu/send/util/FileUtils.java
index 72c3a180..1b143818 100644
--- a/src/main/java/it/gov/pagopa/pu/send/util/FileUtils.java
+++ b/src/main/java/it/gov/pagopa/pu/send/util/FileUtils.java
@@ -1,9 +1,8 @@
 package it.gov.pagopa.pu.send.util;
 
-import java.io.File;
-import java.io.FileInputStream;
 import java.io.IOException;
 
+import java.io.InputStream;
 import java.security.DigestInputStream;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
@@ -14,10 +13,10 @@ public class FileUtils {
   private FileUtils() {
   }
 
-  public static String calculateFileHash(File file)
+  public static String calculateFileHash(InputStream inputStream)
     throws NoSuchAlgorithmException, IOException {
     MessageDigest digest = MessageDigest.getInstance("SHA-256");
-    try(DigestInputStream digestInputStream = new DigestInputStream(new FileInputStream(file), digest)){
+    try(DigestInputStream digestInputStream = new DigestInputStream(inputStream, digest)){
       byte[] inputStreamBuffer = new byte[8192];
       while (digestInputStream.read(inputStreamBuffer) > -1);
     }
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 704a79c2..75ff7c48 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -89,6 +89,10 @@ jwt:
                   ewIDAQAB
                   -----END PUBLIC KEY-----}"
 
+folders:
+  shared: "\${SHARED_FOLDER_ROOT:/shared}"
+  send-file-folder: "data/send"
+
 rest:
   defaults:
     connection-pool:
@@ -120,3 +124,6 @@ rest:
       max-attempts: "\${FILESHARE_MAX_ATTEMPTS:3}" # It's counting also the first attempt
       wait-time-millis: "\${FILESHARE_WAIT_TIME_MILLIS:500}"
       print-body-when-error: "\${FILESHARE_PRINT_BODY_WHEN_ERROR:true}"
+
+app:
+  fileEncryptPassword: "\${FILE_ENCRYPT_PASSWORD:ENCR_PSW}"
diff --git a/src/main/resources/tmp/sendNotificationId_Notifica.pdf b/src/main/resources/tmp/sendNotificationId_Notifica.pdf
deleted file mode 100644
index 97a48b3b..00000000
Binary files a/src/main/resources/tmp/sendNotificationId_Notifica.pdf and /dev/null differ
diff --git a/src/main/resources/tmp/sendNotificationId_Pagamento.pdf b/src/main/resources/tmp/sendNotificationId_Pagamento.pdf
deleted file mode 100644
index ef277b1b..00000000
Binary files a/src/main/resources/tmp/sendNotificationId_Pagamento.pdf and /dev/null differ
diff --git a/src/test/java/it/gov/pagopa/pu/send/connector/pagopa/send/client/SendUploadClientTest.java b/src/test/java/it/gov/pagopa/pu/send/connector/pagopa/send/client/SendUploadClientTest.java
index e6123b4c..a9341181 100644
--- a/src/test/java/it/gov/pagopa/pu/send/connector/pagopa/send/client/SendUploadClientTest.java
+++ b/src/test/java/it/gov/pagopa/pu/send/connector/pagopa/send/client/SendUploadClientTest.java
@@ -3,6 +3,8 @@
 import it.gov.pagopa.pu.send.connector.pagopa.send.config.PagopaSendApiClientConfig;
 import it.gov.pagopa.pu.send.dto.DocumentDTO;
 import it.gov.pagopa.pu.send.exception.UploadFileException;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -16,12 +18,8 @@
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.client.RestTemplate;
 
-import java.io.File;
-import java.io.FileWriter;
 import java.io.IOException;
 import java.net.URI;
-import java.nio.file.Files;
-import java.nio.file.Paths;
 import java.util.Optional;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -53,15 +51,8 @@ public void setUp() {
   @Test
   void givenValidFileWhenUploadThenReturnsVersionId() throws IOException {
     //GIVEN
-    String sendNotificationId = "SENDNOTIFICATIONID";
     String versionId = "VERSIONID";
-    String filePath = "src/main/resources/tmp/" + sendNotificationId + "_file.pdf";
-    File file = new File(filePath);
-    file.deleteOnExit();
-
-    try (FileWriter writer = new FileWriter(file)) {
-      writer.write("TEST FILE HASH P4PA SEND");
-    }
+    InputStream file = new ByteArrayInputStream("TEST FILE HASH P4PA SEND".getBytes());
 
     DocumentDTO documentDTO = DocumentDTO.builder()
       .fileName("file.pdf")
@@ -72,7 +63,7 @@ void givenValidFileWhenUploadThenReturnsVersionId() throws IOException {
       .secret("SECRET")
       .build();
 
-    byte[] fileBytes = Files.readAllBytes(Paths.get(filePath));
+    byte[] fileBytes = file.readAllBytes();
 
     HttpHeaders headers = new HttpHeaders();
     headers.add("x-amz-version-id", versionId);
diff --git a/src/test/java/it/gov/pagopa/pu/send/service/FileRetrieverServiceTest.java b/src/test/java/it/gov/pagopa/pu/send/service/FileRetrieverServiceTest.java
new file mode 100644
index 00000000..06276deb
--- /dev/null
+++ b/src/test/java/it/gov/pagopa/pu/send/service/FileRetrieverServiceTest.java
@@ -0,0 +1,83 @@
+package it.gov.pagopa.pu.send.service;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.eq;
+
+import it.gov.pagopa.pu.send.util.AESUtils;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.file.Path;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+class FileRetrieverServiceTest {
+
+  private static final String FILE_ENCRYPT_PASSWORD = "testPassword123";
+  private static final String SEND_FILE_PATH = "send";
+  private static final String SHARED_FOLDER = "/shared";
+  private static final String FILECONTENT = "TEST FILE HASH P4PA SEND";
+
+  private FileRetrieverService fileRetrieverService;
+
+  @BeforeEach
+  void setUp() {
+    fileRetrieverService = new FileRetrieverService(
+      FILE_ENCRYPT_PASSWORD,
+      SEND_FILE_PATH,
+      SHARED_FOLDER
+    );
+  }
+
+  @Test
+  void givenCorrectPathWhenRetrieveFileThenReturnDecryptFile() {
+    // Given
+    Long organizationId = 1L;
+    String fileName = "test.pdf";
+    InputStream expectedInputStream = new ByteArrayInputStream(FILECONTENT.getBytes());
+    Path expectedPath = Path.of(SHARED_FOLDER, String.valueOf(organizationId), SEND_FILE_PATH);
+
+    try (MockedStatic<AESUtils> aesUtils = Mockito.mockStatic(AESUtils.class)) {
+      aesUtils.when(() -> AESUtils.decrypt(
+        eq(FILE_ENCRYPT_PASSWORD),
+        eq(expectedPath),
+        eq(fileName)
+      )).thenReturn(expectedInputStream);
+
+      // When
+      InputStream result = fileRetrieverService.retrieveFile(organizationId, fileName);
+
+      // Then
+      assertNotNull(result);
+      assertEquals(expectedInputStream, result);
+    }
+  }
+
+  @Test
+  void givenCorrectParametersWhenDecryptFileThenReturnInputStream() {
+    // Given
+    Path filePath = Path.of("/test/path");
+    String fileName = "test.pdf";
+    InputStream expectedInputStream = new ByteArrayInputStream(FILECONTENT.getBytes());
+
+    try (MockedStatic<AESUtils> aesUtils = Mockito.mockStatic(AESUtils.class)) {
+      aesUtils.when(() -> AESUtils.decrypt(
+        eq(FILE_ENCRYPT_PASSWORD),
+        eq(filePath),
+        eq(fileName)
+      )).thenReturn(expectedInputStream);
+
+      // When
+      InputStream result = fileRetrieverService.decryptFile(filePath, fileName);
+
+      // Then
+      assertNotNull(result);
+      assertEquals(expectedInputStream, result);
+    }
+  }
+
+}
diff --git a/src/test/java/it/gov/pagopa/pu/send/service/SendFacadeServiceImplTest.java b/src/test/java/it/gov/pagopa/pu/send/service/SendFacadeServiceImplTest.java
index 9642456f..4747c077 100644
--- a/src/test/java/it/gov/pagopa/pu/send/service/SendFacadeServiceImplTest.java
+++ b/src/test/java/it/gov/pagopa/pu/send/service/SendFacadeServiceImplTest.java
@@ -101,6 +101,7 @@ void givenValidNotificationWhenUploadFilesThenVerify() {
     String sendNotificationId = "SENDNOTIFICATIONID";
     String fileName = "FILENAME";
     String versionId = "VERSIONID";
+    Long organizationId = 1L;
 
     DocumentDTO documentDTO = DocumentDTO.builder()
       .fileName(fileName)
@@ -115,13 +116,14 @@ void givenValidNotificationWhenUploadFilesThenVerify() {
 
     SendNotification notification = SendNotification.builder()
       .sendNotificationId(sendNotificationId)
+      .organizationId(organizationId)
       .status(NotificationStatus.REGISTERED)
       .documents(List.of(documentDTO))
       .build();
 
     Mockito.when(sendNotificationRepository.findById(sendNotificationId)).thenReturn(
       Optional.of(notification));
-    Mockito.when(uploadService.uploadFile(sendNotificationId, documentDTO)).thenReturn(Optional.of(versionId));
+    Mockito.when(uploadService.uploadFile(organizationId, sendNotificationId, documentDTO)).thenReturn(Optional.of(versionId));
 
     sendService.uploadFiles(sendNotificationId);
     Mockito.verify(sendNotificationRepository, Mockito.times(1)).updateFileVersionId(sendNotificationId, fileName, versionId);
diff --git a/src/test/java/it/gov/pagopa/pu/send/service/SendNotificationServiceImplTest.java b/src/test/java/it/gov/pagopa/pu/send/service/SendNotificationServiceImplTest.java
index 6b5ce54f..f62cc21c 100644
--- a/src/test/java/it/gov/pagopa/pu/send/service/SendNotificationServiceImplTest.java
+++ b/src/test/java/it/gov/pagopa/pu/send/service/SendNotificationServiceImplTest.java
@@ -12,9 +12,8 @@
 import it.gov.pagopa.pu.send.mapper.CreateNotificationRequest2SendNotificationMapper;
 import it.gov.pagopa.pu.send.model.SendNotification;
 import it.gov.pagopa.pu.send.repository.SendNotificationRepository;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
 import java.util.List;
 import java.util.Optional;
 
@@ -39,6 +38,9 @@ class SendNotificationServiceImplTest {
   @Mock
   private WorkflowService workflowServiceMock;
 
+  @Mock
+  private FileRetrieverService fileRetrieverServiceMock;
+
   @InjectMocks
   private SendNotificationServiceImpl sendNotificationService;
 
@@ -63,9 +65,9 @@ void givenCreateNotificationRequestWhenCreateSendNotificationThenReturnCreateNot
     Assertions.assertEquals("SENDNOTIFICATIONID", response.getSendNotificationId());
   }
 
+
   @Test
-  void givenStartNotificationRequestWhenStartSendNotificationThenReturnVerifyAllFilesReady()
-    throws IOException {
+  void givenStartNotificationRequestWhenStartSendNotificationThenReturnVerifyAllFilesReady() {
     String sendNotificationId = "sendNotificationId";
     String fileName = "file.pdf";
     LoadFileRequest loadFileRequest = new LoadFileRequest("9e9LsYp4qQ4bjyGI4Mp/jmBN2jKehKTTaonMr1AJEPU=",fileName);
@@ -73,10 +75,12 @@ void givenStartNotificationRequestWhenStartSendNotificationThenReturnVerifyAllFi
     SendNotification updatedNotification = createMockNotification(sendNotificationId, fileName, FileStatus.READY);
     Long organizationId = 1L;
     WorkflowCreatedDTO workflow = WorkflowCreatedDTO.builder().workflowId("WORKFLOWID").build();
+    InputStream inputStream = new ByteArrayInputStream("TEST FILE HASH P4PA SEND".getBytes());
 
     Mockito.when(sendNotificationRepositoryMock.findByIdAndOrganizationId(sendNotificationId, organizationId))
       .thenReturn(Optional.of(notification))
       .thenReturn(Optional.of(updatedNotification));
+    Mockito.when(fileRetrieverServiceMock.retrieveFile(organizationId, sendNotificationId+"_"+fileName)).thenReturn(inputStream);
     Mockito.when(workflowServiceMock.sendNotificationProcess(sendNotificationId, null))
         .thenReturn(workflow);
 
@@ -104,15 +108,17 @@ void givenStartNotificationRequestWhenStartSendNotificationThenExceptionFileNotF
     Assertions.assertEquals("File not found with id: NOTEXISTS", exception.getMessage());
   }
 
+
   @Test
-  void givenStartNotificationRequestWhenStartSendNotificationThenExceptionInvalidSignature()
-    throws IOException {
+  void givenStartNotificationRequestWhenStartSendNotificationThenExceptionInvalidSignature() {
     String sendNotificationId = "sendNotificationId";
     String fileName = "file.pdf";
     LoadFileRequest loadFileRequest = new LoadFileRequest("DIGEST", fileName);
     SendNotification notification = createMockNotification(sendNotificationId, fileName, FileStatus.WAITING);
     Long organizationId = 1L;
+    InputStream inputStream = new ByteArrayInputStream("TEST FILE HASH P4PA SEND".getBytes());
 
+    Mockito.when(fileRetrieverServiceMock.retrieveFile(organizationId, sendNotificationId+"_"+fileName)).thenReturn(inputStream);
     Mockito.when(sendNotificationRepositoryMock.findByIdAndOrganizationId(sendNotificationId, organizationId)).thenReturn(
       Optional.of(notification));
 
@@ -121,9 +127,9 @@ void givenStartNotificationRequestWhenStartSendNotificationThenExceptionInvalidS
     Assertions.assertEquals("File "+fileName+" has not a valid signature", exception.getMessage());
   }
 
+
   @Test
-  void givenDeleteNotificationRequestWhenDeleteSendNotificationThenVerify()
-    throws IOException {
+  void givenDeleteNotificationRequestWhenDeleteSendNotificationThenVerify() {
     //Given
     String sendNotificationId = "sendNotificationId";
     String fileName = "file.pdf";
@@ -138,8 +144,7 @@ void givenDeleteNotificationRequestWhenDeleteSendNotificationThenVerify()
   }
 
   @Test
-  void givenDeleteNotificationRequestWithStatusCompleteWhenDeleteSendNotificationThenInvalidStatusException()
-    throws IOException {
+  void givenDeleteNotificationRequestWithStatusCompleteWhenDeleteSendNotificationThenInvalidStatusException() {
     //Given
     String sendNotificationId = "sendNotificationId";
     String fileName = "file.pdf";
@@ -154,20 +159,11 @@ void givenDeleteNotificationRequestWithStatusCompleteWhenDeleteSendNotificationT
     Assertions.assertEquals("Cannot delete notification with status complete", exception.getMessage());
   }
 
-  private SendNotification createMockNotification(String sendNotificationId, String fileName, FileStatus fileStatus)
-    throws IOException {
-
+  private SendNotification createMockNotification(String sendNotificationId, String fileName, FileStatus fileStatus) {
     SendNotification notification = new SendNotification();
+    notification.setSendNotificationId(sendNotificationId);
     notification.setStatus(NotificationStatus.WAITING_FILE);
 
-    String filePath = "src/main/resources/tmp/" + sendNotificationId + "_"+ fileName;
-    File file = new File(filePath);
-    file.deleteOnExit();
-
-    try (FileWriter writer = new FileWriter(file)) {
-      writer.write("TEST FILE HASH P4PA SEND");
-    }
-
     DocumentDTO documentDTO = DocumentDTO.builder()
       .fileName(fileName)
       .digest("9e9LsYp4qQ4bjyGI4Mp/jmBN2jKehKTTaonMr1AJEPU=")
diff --git a/src/test/java/it/gov/pagopa/pu/send/service/SendUploadFacadeServiceImplTest.java b/src/test/java/it/gov/pagopa/pu/send/service/SendUploadFacadeServiceImplTest.java
index 76108be6..6fef1f9f 100644
--- a/src/test/java/it/gov/pagopa/pu/send/service/SendUploadFacadeServiceImplTest.java
+++ b/src/test/java/it/gov/pagopa/pu/send/service/SendUploadFacadeServiceImplTest.java
@@ -5,10 +5,8 @@
 import it.gov.pagopa.pu.send.connector.pagopa.send.client.SendUploadClient;
 import it.gov.pagopa.pu.send.dto.DocumentDTO;
 import it.gov.pagopa.pu.send.exception.UploadFileException;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.nio.file.Files;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
 import java.util.Optional;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
@@ -23,18 +21,19 @@ class SendUploadFacadeServiceImplTest {
   @Mock
   private SendUploadClient sendUploadClient;
 
+  @Mock
+  private FileRetrieverService fileRetrieverServiceMock;
+
   @InjectMocks
   private SendUploadFacadeServiceImpl uploadService;
 
   @Test
-  void givenValidFileWhenUploadFileThenReturnsVersionId()
-    throws IOException {
+  void givenValidFileWhenUploadFileThenReturnsVersionId() {
     //GIVEN
     String sendNotificationId = "sendNotificationId";
     Optional<String> versionId = Optional.of("VERSIONID");
-
-    String fileName = "src/test/resources/tmp/sendNotificationId_file.pdf";
-    File file = new File(fileName);
+    Long organizationId = 1L;
+    InputStream inputStream = new ByteArrayInputStream("TEST FILE HASH P4PA SEND".getBytes());
 
     DocumentDTO documentDTO = DocumentDTO.builder()
       .fileName("file.pdf")
@@ -45,42 +44,25 @@ void givenValidFileWhenUploadFileThenReturnsVersionId()
       .secret("SECRET")
       .build();
 
-    byte[] fileBytes = Files.readAllBytes(file.toPath());
+    Mockito.when(fileRetrieverServiceMock.retrieveFile(organizationId, sendNotificationId+"_file.pdf")).thenReturn(inputStream);
+    Mockito.when(sendUploadClient.upload(documentDTO, "TEST FILE HASH P4PA SEND".getBytes())).thenReturn(versionId);
 
-    Mockito.when(sendUploadClient.upload(documentDTO, fileBytes)).thenReturn(versionId);
-
-    Optional<String> result = uploadService.uploadFile(sendNotificationId, documentDTO);
+    Optional<String> result = uploadService.uploadFile(organizationId, sendNotificationId, documentDTO);
     // THEN
     assertTrue(result.isPresent());
     assertEquals(versionId, result);
   }
 
-
   @Test
   void givenInvalidFileWhenUploadFileThenThrowsFileNotFoundException() {
       String sendNotificationId = "SENDNOTIFICATIONID";
+      Long organizationId = 1L;
+
       DocumentDTO documentDTO = DocumentDTO.builder()
               .fileName("non_existent_file.pdf")
               .build();
 
-      assertThrows(UploadFileException.class, () -> uploadService.uploadFile(sendNotificationId, documentDTO));
+      assertThrows(UploadFileException.class, () -> uploadService.uploadFile(organizationId, sendNotificationId, documentDTO));
   }
 
-  @Test
-  void givenInvalidSignatureWhenUploadFileThenInvalidSignatureException() throws Exception {
-    String sendNotificationId = "SENDNOTIFICATIONID";
-    String filePath = "src/main/resources/tmp/" + sendNotificationId + "_file.pdf";
-    File file = new File(filePath);
-    file.deleteOnExit();
-
-    try (FileWriter writer = new FileWriter(file)) {
-      writer.write("TEST FILE HASH P4PA SEND");
-    }
-
-    DocumentDTO documentDTO = new DocumentDTO();
-    documentDTO.setFileName("file.pdf");
-    documentDTO.setDigest("invalidDigest");
-
-    assertThrows(UploadFileException.class, () -> uploadService.uploadFile(sendNotificationId, documentDTO));
-  }
 }
diff --git a/src/test/java/it/gov/pagopa/pu/send/util/AESUtilsTest.java b/src/test/java/it/gov/pagopa/pu/send/util/AESUtilsTest.java
new file mode 100644
index 00000000..478261d2
--- /dev/null
+++ b/src/test/java/it/gov/pagopa/pu/send/util/AESUtilsTest.java
@@ -0,0 +1,148 @@
+package it.gov.pagopa.pu.send.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.List;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class AESUtilsTest {
+
+  @Test
+  void test() {
+    // Given
+    String plain = "PLAINTEXT";
+    String psw = "PSW";
+
+    // When
+    byte[] cipher = AESUtils.encrypt(psw, plain);
+    String result = AESUtils.decrypt(psw, cipher);
+
+    // Then
+    Assertions.assertEquals(plain, result);
+  }
+
+  @Test
+  void testStream() throws IOException {
+    // Given
+    String plain = "PLAINTEXT";
+    String psw = "PSW";
+
+    // When
+    InputStream cipherStream = AESUtils.encrypt(psw, new ByteArrayInputStream(plain.getBytes(StandardCharsets.UTF_8)));
+    InputStream resultStream = AESUtils.decrypt(psw, cipherStream);
+
+    // Then
+    Assertions.assertEquals(plain, new String(resultStream.readAllBytes(), StandardCharsets.UTF_8));
+  }
+
+  @Test
+  void testFile() throws IOException {
+    // Given
+    String plain = "PLAINTEXT";
+    Path plainFile = Path.of("build", "tmp", "plainFile.txt");
+    Files.writeString(plainFile, plain);
+    String psw = "PSW";
+    Path decryptedFile = plainFile.getParent().resolve("decryptedFile.txt");
+
+    Files.deleteIfExists(decryptedFile);
+    Files.deleteIfExists(plainFile.getParent().resolve(plainFile.getFileName() + ".cipher"));
+
+    // When
+    File cipherFile = null;
+    try {
+      cipherFile = AESUtils.encrypt(psw, plainFile.toFile());
+      AESUtils.decrypt(psw, cipherFile, decryptedFile.toFile());
+
+      // Then
+      Assertions.assertEquals(Files.readAllLines(decryptedFile), List.of(plain));
+    } finally {
+      if (cipherFile != null && cipherFile.exists()) {
+        cipherFile.delete();
+      }
+      Files.deleteIfExists(decryptedFile);
+    }
+  }
+
+  @Test
+  void testFileThroughInputStream() throws IOException, NoSuchAlgorithmException {
+    // Given
+    String plain = "PLAINTEXT";
+    String psw = "PSW";
+
+    Path targetPath = Path.of("build", "tmp");
+    String fileName = "cipherFile2.txt";
+    Path expectedResultedFile = targetPath.resolve(fileName + AESUtils.CIPHER_EXTENSION);
+
+    Files.deleteIfExists(expectedResultedFile);
+
+    // When
+    AESUtils.encryptAndSave(psw, new ByteArrayInputStream(plain.getBytes(StandardCharsets.UTF_8)), targetPath, fileName);
+    try (InputStream decrypted = AESUtils.decrypt(psw, targetPath, fileName)) {
+      // Then
+      Assertions.assertEquals(plain, new String(decrypted.readAllBytes(), StandardCharsets.UTF_8));
+    } finally {
+      Files.deleteIfExists(expectedResultedFile);
+    }
+  }
+
+  @Test
+  void testFileThroughInputStreamUsingAlreadyExtendedFileName() throws IOException {
+    // Given
+    String plain = "PLAINTEXT";
+    String psw = "PSW";
+
+    Path targetPath = Path.of("build", "tmp");
+    String fileName = "cipherFile2.txt";
+    Path expectedResultedFile = targetPath.resolve(fileName + AESUtils.CIPHER_EXTENSION);
+
+    Files.deleteIfExists(expectedResultedFile);
+
+    // When
+    try (ByteArrayInputStream plainStream = new ByteArrayInputStream(plain.getBytes(StandardCharsets.UTF_8))) {
+      AESUtils.encryptAndSave(psw, plainStream, targetPath, fileName);
+      try (InputStream decrypted = AESUtils.decrypt(psw, expectedResultedFile.toFile())) {
+        // Then
+        Assertions.assertEquals(plain, new String(decrypted.readAllBytes(), StandardCharsets.UTF_8));
+      }
+    } catch (NoSuchAlgorithmException e) {
+      throw new RuntimeException(e);
+    } finally {
+      Files.deleteIfExists(expectedResultedFile);
+    }
+  }
+
+  @Test
+  void testDigestThroughEncryptAndSave() throws IOException, NoSuchAlgorithmException {
+    // Act
+    String plain = "PLAINTEXT";
+    String psw = "PSW";
+    Path targetPath = Path.of("build", "tmp");
+    String fileName = "cipherFile2.txt";
+
+    Path encryptedFile = targetPath.resolve(fileName + AESUtils.CIPHER_EXTENSION);
+    Files.deleteIfExists(encryptedFile);
+
+    byte[] resultDigest = AESUtils.encryptAndSave(
+      psw,
+      new ByteArrayInputStream(plain.getBytes(StandardCharsets.UTF_8)),
+      targetPath,
+      fileName
+    );
+
+    MessageDigest expectedDigest = MessageDigest.getInstance("SHA-256");
+    byte[] expectedHash = expectedDigest.digest(plain.getBytes());
+
+    Assertions.assertTrue(Files.exists(encryptedFile));
+    Assertions.assertTrue(Files.size(encryptedFile) > 0);
+    Assertions.assertEquals(32, resultDigest.length);
+    Assertions.assertArrayEquals(expectedHash, resultDigest);
+  }
+}
diff --git a/src/test/java/it/gov/pagopa/pu/send/util/FileUtilsTest.java b/src/test/java/it/gov/pagopa/pu/send/util/FileUtilsTest.java
index 72079212..8204de4a 100644
--- a/src/test/java/it/gov/pagopa/pu/send/util/FileUtilsTest.java
+++ b/src/test/java/it/gov/pagopa/pu/send/util/FileUtilsTest.java
@@ -2,9 +2,9 @@
 
 import static org.junit.jupiter.api.Assertions.*;
 
-import java.io.File;
-import java.io.FileWriter;
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.security.NoSuchAlgorithmException;
 import org.junit.jupiter.api.Test;
 
@@ -14,27 +14,13 @@ class FileUtilsTest {
   void givenValidFileWhenCalculateFileHashThenVerifyHash()
     throws NoSuchAlgorithmException, IOException {
     // Given
-    File tempFile = File.createTempFile("testFile", ".txt");
-    tempFile.deleteOnExit();
-
-    try (FileWriter writer = new FileWriter(tempFile)) {
-      writer.write("TEST FILE HASH P4PA SEND");
-    }
+    InputStream inputStream = new ByteArrayInputStream("TEST FILE HASH P4PA SEND".getBytes());
 
     String expectedHash = "9e9LsYp4qQ4bjyGI4Mp/jmBN2jKehKTTaonMr1AJEPU=";
     // When
-    String actualHash = FileUtils.calculateFileHash(tempFile);
+    String actualHash = FileUtils.calculateFileHash(inputStream);
 
     // Then
     assertEquals(expectedHash, actualHash);
   }
-
-  @Test
-  void givenNotExistsFileWhenCalculateFileHashThenException() {
-    // Given
-    File nonExistentFile = new File("FILENOTFOUND.txt");
-
-    // Then
-    assertThrows(IOException.class, () -> FileUtils.calculateFileHash(nonExistentFile));
-  }
 }
diff --git a/src/test/resources/tmp/sendNotificationId_file.pdf b/src/test/resources/tmp/sendNotificationId_file.pdf
deleted file mode 100644
index 97a48b3b..00000000
Binary files a/src/test/resources/tmp/sendNotificationId_file.pdf and /dev/null differ


Reviews:
user: login: github-advanced-security[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-21T08:45:23Z
user: login: RiccardoGiuliani
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T09:30:01Z

Review comments:
diff_hunk: @@ -0,0 +1,148 @@
+package it.gov.pagopa.pu.send.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.List;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class AESUtilsTest {
+
+  @Test
+  void test() {
+    // Given
+    String plain = "PLAINTEXT";
+    String psw = "PSW";
path: src/test/java/it/gov/pagopa/pu/send/util/AESUtilsTest.java
user: login: github-advanced-security[bot]
body: ## Hard-coded credential in API call

Hard-coded value flows to [sensitive API call](1).

[Show more details](https://github.com/pagopa/p4pa-send-notification/security/code-scanning/16)
created_at: 2025-03-21T08:45:23Z
author_association: NONE
diff_hunk: @@ -0,0 +1,148 @@
+package it.gov.pagopa.pu.send.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.List;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class AESUtilsTest {
+
+  @Test
+  void test() {
+    // Given
+    String plain = "PLAINTEXT";
+    String psw = "PSW";
+
+    // When
+    byte[] cipher = AESUtils.encrypt(psw, plain);
+    String result = AESUtils.decrypt(psw, cipher);
+
+    // Then
+    Assertions.assertEquals(plain, result);
+  }
+
+  @Test
+  void testStream() throws IOException {
+    // Given
+    String plain = "PLAINTEXT";
+    String psw = "PSW";
path: src/test/java/it/gov/pagopa/pu/send/util/AESUtilsTest.java
user: login: github-advanced-security[bot]
body: ## Hard-coded credential in API call

Hard-coded value flows to [sensitive API call](1).

[Show more details](https://github.com/pagopa/p4pa-send-notification/security/code-scanning/17)
created_at: 2025-03-21T08:45:23Z
author_association: NONE
diff_hunk: @@ -0,0 +1,148 @@
+package it.gov.pagopa.pu.send.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.List;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class AESUtilsTest {
+
+  @Test
+  void test() {
+    // Given
+    String plain = "PLAINTEXT";
+    String psw = "PSW";
+
+    // When
+    byte[] cipher = AESUtils.encrypt(psw, plain);
+    String result = AESUtils.decrypt(psw, cipher);
+
+    // Then
+    Assertions.assertEquals(plain, result);
+  }
+
+  @Test
+  void testStream() throws IOException {
+    // Given
+    String plain = "PLAINTEXT";
+    String psw = "PSW";
+
+    // When
+    InputStream cipherStream = AESUtils.encrypt(psw, new ByteArrayInputStream(plain.getBytes(StandardCharsets.UTF_8)));
+    InputStream resultStream = AESUtils.decrypt(psw, cipherStream);
+
+    // Then
+    Assertions.assertEquals(plain, new String(resultStream.readAllBytes(), StandardCharsets.UTF_8));
+  }
+
+  @Test
+  void testFile() throws IOException {
+    // Given
+    String plain = "PLAINTEXT";
+    Path plainFile = Path.of("build", "tmp", "plainFile.txt");
+    Files.writeString(plainFile, plain);
+    String psw = "PSW";
path: src/test/java/it/gov/pagopa/pu/send/util/AESUtilsTest.java
user: login: github-advanced-security[bot]
body: ## Hard-coded credential in API call

Hard-coded value flows to [sensitive API call](1).

[Show more details](https://github.com/pagopa/p4pa-send-notification/security/code-scanning/18)
created_at: 2025-03-21T08:45:23Z
author_association: NONE
diff_hunk: @@ -0,0 +1,148 @@
+package it.gov.pagopa.pu.send.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.List;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class AESUtilsTest {
+
+  @Test
+  void test() {
+    // Given
+    String plain = "PLAINTEXT";
+    String psw = "PSW";
+
+    // When
+    byte[] cipher = AESUtils.encrypt(psw, plain);
+    String result = AESUtils.decrypt(psw, cipher);
+
+    // Then
+    Assertions.assertEquals(plain, result);
+  }
+
+  @Test
+  void testStream() throws IOException {
+    // Given
+    String plain = "PLAINTEXT";
+    String psw = "PSW";
+
+    // When
+    InputStream cipherStream = AESUtils.encrypt(psw, new ByteArrayInputStream(plain.getBytes(StandardCharsets.UTF_8)));
+    InputStream resultStream = AESUtils.decrypt(psw, cipherStream);
+
+    // Then
+    Assertions.assertEquals(plain, new String(resultStream.readAllBytes(), StandardCharsets.UTF_8));
+  }
+
+  @Test
+  void testFile() throws IOException {
+    // Given
+    String plain = "PLAINTEXT";
+    Path plainFile = Path.of("build", "tmp", "plainFile.txt");
+    Files.writeString(plainFile, plain);
+    String psw = "PSW";
+    Path decryptedFile = plainFile.getParent().resolve("decryptedFile.txt");
+
+    Files.deleteIfExists(decryptedFile);
+    Files.deleteIfExists(plainFile.getParent().resolve(plainFile.getFileName() + ".cipher"));
+
+    // When
+    File cipherFile = null;
+    try {
+      cipherFile = AESUtils.encrypt(psw, plainFile.toFile());
+      AESUtils.decrypt(psw, cipherFile, decryptedFile.toFile());
+
+      // Then
+      Assertions.assertEquals(Files.readAllLines(decryptedFile), List.of(plain));
+    } finally {
+      if (cipherFile != null && cipherFile.exists()) {
+        cipherFile.delete();
+      }
+      Files.deleteIfExists(decryptedFile);
+    }
+  }
+
+  @Test
+  void testFileThroughInputStream() throws IOException, NoSuchAlgorithmException {
+    // Given
+    String plain = "PLAINTEXT";
+    String psw = "PSW";
path: src/test/java/it/gov/pagopa/pu/send/util/AESUtilsTest.java
user: login: github-advanced-security[bot]
body: ## Hard-coded credential in API call

Hard-coded value flows to [sensitive API call](1).

[Show more details](https://github.com/pagopa/p4pa-send-notification/security/code-scanning/19)
created_at: 2025-03-21T08:45:23Z
author_association: NONE
diff_hunk: @@ -0,0 +1,148 @@
+package it.gov.pagopa.pu.send.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.List;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class AESUtilsTest {
+
+  @Test
+  void test() {
+    // Given
+    String plain = "PLAINTEXT";
+    String psw = "PSW";
+
+    // When
+    byte[] cipher = AESUtils.encrypt(psw, plain);
+    String result = AESUtils.decrypt(psw, cipher);
+
+    // Then
+    Assertions.assertEquals(plain, result);
+  }
+
+  @Test
+  void testStream() throws IOException {
+    // Given
+    String plain = "PLAINTEXT";
+    String psw = "PSW";
+
+    // When
+    InputStream cipherStream = AESUtils.encrypt(psw, new ByteArrayInputStream(plain.getBytes(StandardCharsets.UTF_8)));
+    InputStream resultStream = AESUtils.decrypt(psw, cipherStream);
+
+    // Then
+    Assertions.assertEquals(plain, new String(resultStream.readAllBytes(), StandardCharsets.UTF_8));
+  }
+
+  @Test
+  void testFile() throws IOException {
+    // Given
+    String plain = "PLAINTEXT";
+    Path plainFile = Path.of("build", "tmp", "plainFile.txt");
+    Files.writeString(plainFile, plain);
+    String psw = "PSW";
+    Path decryptedFile = plainFile.getParent().resolve("decryptedFile.txt");
+
+    Files.deleteIfExists(decryptedFile);
+    Files.deleteIfExists(plainFile.getParent().resolve(plainFile.getFileName() + ".cipher"));
+
+    // When
+    File cipherFile = null;
+    try {
+      cipherFile = AESUtils.encrypt(psw, plainFile.toFile());
+      AESUtils.decrypt(psw, cipherFile, decryptedFile.toFile());
+
+      // Then
+      Assertions.assertEquals(Files.readAllLines(decryptedFile), List.of(plain));
+    } finally {
+      if (cipherFile != null && cipherFile.exists()) {
+        cipherFile.delete();
+      }
+      Files.deleteIfExists(decryptedFile);
+    }
+  }
+
+  @Test
+  void testFileThroughInputStream() throws IOException, NoSuchAlgorithmException {
+    // Given
+    String plain = "PLAINTEXT";
+    String psw = "PSW";
+
+    Path targetPath = Path.of("build", "tmp");
+    String fileName = "cipherFile2.txt";
+    Path expectedResultedFile = targetPath.resolve(fileName + AESUtils.CIPHER_EXTENSION);
+
+    Files.deleteIfExists(expectedResultedFile);
+
+    // When
+    AESUtils.encryptAndSave(psw, new ByteArrayInputStream(plain.getBytes(StandardCharsets.UTF_8)), targetPath, fileName);
+    try (InputStream decrypted = AESUtils.decrypt(psw, targetPath, fileName)) {
+      // Then
+      Assertions.assertEquals(plain, new String(decrypted.readAllBytes(), StandardCharsets.UTF_8));
+    } finally {
+      Files.deleteIfExists(expectedResultedFile);
+    }
+  }
+
+  @Test
+  void testFileThroughInputStreamUsingAlreadyExtendedFileName() throws IOException {
+    // Given
+    String plain = "PLAINTEXT";
+    String psw = "PSW";
path: src/test/java/it/gov/pagopa/pu/send/util/AESUtilsTest.java
user: login: github-advanced-security[bot]
body: ## Hard-coded credential in API call

Hard-coded value flows to [sensitive API call](1).

[Show more details](https://github.com/pagopa/p4pa-send-notification/security/code-scanning/20)
created_at: 2025-03-21T08:45:23Z
author_association: NONE
diff_hunk: @@ -0,0 +1,148 @@
+package it.gov.pagopa.pu.send.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.List;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class AESUtilsTest {
+
+  @Test
+  void test() {
+    // Given
+    String plain = "PLAINTEXT";
+    String psw = "PSW";
+
+    // When
+    byte[] cipher = AESUtils.encrypt(psw, plain);
+    String result = AESUtils.decrypt(psw, cipher);
+
+    // Then
+    Assertions.assertEquals(plain, result);
+  }
+
+  @Test
+  void testStream() throws IOException {
+    // Given
+    String plain = "PLAINTEXT";
+    String psw = "PSW";
+
+    // When
+    InputStream cipherStream = AESUtils.encrypt(psw, new ByteArrayInputStream(plain.getBytes(StandardCharsets.UTF_8)));
+    InputStream resultStream = AESUtils.decrypt(psw, cipherStream);
+
+    // Then
+    Assertions.assertEquals(plain, new String(resultStream.readAllBytes(), StandardCharsets.UTF_8));
+  }
+
+  @Test
+  void testFile() throws IOException {
+    // Given
+    String plain = "PLAINTEXT";
+    Path plainFile = Path.of("build", "tmp", "plainFile.txt");
+    Files.writeString(plainFile, plain);
+    String psw = "PSW";
+    Path decryptedFile = plainFile.getParent().resolve("decryptedFile.txt");
+
+    Files.deleteIfExists(decryptedFile);
+    Files.deleteIfExists(plainFile.getParent().resolve(plainFile.getFileName() + ".cipher"));
+
+    // When
+    File cipherFile = null;
+    try {
+      cipherFile = AESUtils.encrypt(psw, plainFile.toFile());
+      AESUtils.decrypt(psw, cipherFile, decryptedFile.toFile());
+
+      // Then
+      Assertions.assertEquals(Files.readAllLines(decryptedFile), List.of(plain));
+    } finally {
+      if (cipherFile != null && cipherFile.exists()) {
+        cipherFile.delete();
+      }
+      Files.deleteIfExists(decryptedFile);
+    }
+  }
+
+  @Test
+  void testFileThroughInputStream() throws IOException, NoSuchAlgorithmException {
+    // Given
+    String plain = "PLAINTEXT";
+    String psw = "PSW";
+
+    Path targetPath = Path.of("build", "tmp");
+    String fileName = "cipherFile2.txt";
+    Path expectedResultedFile = targetPath.resolve(fileName + AESUtils.CIPHER_EXTENSION);
+
+    Files.deleteIfExists(expectedResultedFile);
+
+    // When
+    AESUtils.encryptAndSave(psw, new ByteArrayInputStream(plain.getBytes(StandardCharsets.UTF_8)), targetPath, fileName);
+    try (InputStream decrypted = AESUtils.decrypt(psw, targetPath, fileName)) {
+      // Then
+      Assertions.assertEquals(plain, new String(decrypted.readAllBytes(), StandardCharsets.UTF_8));
+    } finally {
+      Files.deleteIfExists(expectedResultedFile);
+    }
+  }
+
+  @Test
+  void testFileThroughInputStreamUsingAlreadyExtendedFileName() throws IOException {
+    // Given
+    String plain = "PLAINTEXT";
+    String psw = "PSW";
+
+    Path targetPath = Path.of("build", "tmp");
+    String fileName = "cipherFile2.txt";
+    Path expectedResultedFile = targetPath.resolve(fileName + AESUtils.CIPHER_EXTENSION);
+
+    Files.deleteIfExists(expectedResultedFile);
+
+    // When
+    try (ByteArrayInputStream plainStream = new ByteArrayInputStream(plain.getBytes(StandardCharsets.UTF_8))) {
+      AESUtils.encryptAndSave(psw, plainStream, targetPath, fileName);
+      try (InputStream decrypted = AESUtils.decrypt(psw, expectedResultedFile.toFile())) {
+        // Then
+        Assertions.assertEquals(plain, new String(decrypted.readAllBytes(), StandardCharsets.UTF_8));
+      }
+    } catch (NoSuchAlgorithmException e) {
+      throw new RuntimeException(e);
+    } finally {
+      Files.deleteIfExists(expectedResultedFile);
+    }
+  }
+
+  @Test
+  void testDigestThroughEncryptAndSave() throws IOException, NoSuchAlgorithmException {
+    // Act
+    String plain = "PLAINTEXT";
+    String psw = "PSW";
path: src/test/java/it/gov/pagopa/pu/send/util/AESUtilsTest.java
user: login: github-advanced-security[bot]
body: ## Hard-coded credential in API call

Hard-coded value flows to [sensitive API call](1).

[Show more details](https://github.com/pagopa/p4pa-send-notification/security/code-scanning/21)
created_at: 2025-03-21T08:45:23Z
author_association: NONE

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-03-21T09:09:42Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=pagopa_p4pa-send-notification&pullRequest=31) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=pagopa_p4pa-send-notification&pullRequest=31&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=pagopa_p4pa-send-notification&pullRequest=31&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=pagopa_p4pa-send-notification&pullRequest=31&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [87.5% Coverage on New Code](https://sonarcloud.io/component_measures?id=pagopa_p4pa-send-notification&pullRequest=31&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=pagopa_p4pa-send-notification&pullRequest=31&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=pagopa_p4pa-send-notification&pullRequest=31)


author_association: NONE