Refactor file upload/download with service layer and secure encryption
- Replaced direct encryption logic in FileController with FileService delegation - Added JWT-based username extraction in file operations - Updated FileMetadata entity to include `uploadedAt` field and removed redundant getters/setters - Refactored EncryptionUtil: - Switched to AES-CBC with PBKDF2 key derivation - Removed RSA-based encryption logic - Added salt and IV generation helpers - Changed JwtAuthenticationFilter to fetch user by username (not email) - Renamed method in FileMetadataRepository to match new parameter order FILE UPLOAD NOW WORKS! TESTED USING CURL.
This commit is contained in:
@@ -1,79 +1,70 @@
|
|||||||
package com.skycrate.backend.skycrateBackend.controller;
|
package com.skycrate.backend.skycrateBackend.controller;
|
||||||
|
|
||||||
import com.skycrate.backend.skycrateBackend.entity.FileMetadata;
|
import com.skycrate.backend.skycrateBackend.services.FileService;
|
||||||
import com.skycrate.backend.skycrateBackend.repository.FileMetadataRepository;
|
import com.skycrate.backend.skycrateBackend.services.JwtService;
|
||||||
import com.skycrate.backend.skycrateBackend.security.EncryptionService;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import org.apache.hadoop.fs.FSDataInputStream;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.apache.hadoop.fs.FSDataOutputStream;
|
import org.springframework.http.MediaType;
|
||||||
import org.apache.hadoop.fs.FileSystem;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.apache.hadoop.fs.Path;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import javax.crypto.SecretKey;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/files")
|
@RequestMapping("/api/files")
|
||||||
public class FileController {
|
public class FileController {
|
||||||
|
|
||||||
@Autowired
|
private final FileService fileService;
|
||||||
private FileSystem hdfs;
|
private final JwtService jwtService;
|
||||||
|
|
||||||
@Autowired
|
public FileController(FileService fileService, JwtService jwtService) {
|
||||||
private FileMetadataRepository metadataRepo;
|
this.fileService = fileService;
|
||||||
|
this.jwtService = jwtService;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/upload")
|
@PostMapping("/upload")
|
||||||
public String upload(@RequestParam("file") MultipartFile file,
|
public ResponseEntity<String> uploadFile(
|
||||||
@RequestParam("password") String password,
|
@RequestParam("file") MultipartFile file,
|
||||||
Authentication auth) throws Exception {
|
@RequestParam("password") String password,
|
||||||
|
HttpServletRequest request
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
String token = extractToken(request);
|
||||||
|
String username = jwtService.extractUsername(token);
|
||||||
|
|
||||||
byte[] fileBytes = file.getBytes();
|
fileService.uploadEncryptedFile(username, password, file.getBytes(), file.getOriginalFilename());
|
||||||
byte[] salt = EncryptionService.generateSalt();
|
|
||||||
byte[] iv = new byte[12];
|
|
||||||
new SecureRandom().nextBytes(iv);
|
|
||||||
SecretKey key = EncryptionService.deriveKey(password, salt);
|
|
||||||
|
|
||||||
byte[] encrypted = EncryptionService.encrypt(fileBytes, key, iv);
|
return ResponseEntity.ok("File uploaded and encrypted successfully.");
|
||||||
String pathStr = "/user/" + auth.getName() + "/" + file.getOriginalFilename();
|
} catch (Exception e) {
|
||||||
Path hdfsPath = new Path(pathStr);
|
return ResponseEntity.status(500).body("File upload failed: " + e.getMessage());
|
||||||
|
|
||||||
try (FSDataOutputStream out = hdfs.create(hdfsPath, true)) {
|
|
||||||
out.write(encrypted);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FileMetadata metadata = new FileMetadata();
|
|
||||||
metadata.setUsername(auth.getName());
|
|
||||||
metadata.setFilePath(pathStr);
|
|
||||||
metadata.setSalt(salt);
|
|
||||||
metadata.setIv(iv);
|
|
||||||
metadataRepo.save(metadata);
|
|
||||||
|
|
||||||
return "File uploaded and encrypted successfully!";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/download")
|
@GetMapping("/download/{filename}")
|
||||||
public void download(@RequestParam("path") String path,
|
public ResponseEntity<?> downloadFile(
|
||||||
@RequestParam("password") String password,
|
@PathVariable String filename,
|
||||||
Authentication auth,
|
@RequestParam("password") String password,
|
||||||
OutputStream responseStream) throws Exception {
|
HttpServletRequest request
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
String token = extractToken(request);
|
||||||
|
String username = jwtService.extractUsername(token);
|
||||||
|
|
||||||
Optional<FileMetadata> optional = metadataRepo.findByFilePathAndUsername(path, auth.getName());
|
byte[] decryptedData = fileService.downloadDecryptedFile(username, password, filename);
|
||||||
if (optional.isEmpty()) {
|
|
||||||
throw new SecurityException("You are not authorized to access this file.");
|
return ResponseEntity.ok()
|
||||||
|
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"")
|
||||||
|
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||||
|
.body(decryptedData);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ResponseEntity.status(500).body("File download failed: " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FileMetadata metadata = optional.get();
|
private String extractToken(HttpServletRequest request) {
|
||||||
SecretKey key = EncryptionService.deriveKey(password, metadata.getSalt());
|
String authHeader = request.getHeader("Authorization");
|
||||||
|
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
|
||||||
try (FSDataInputStream input = hdfs.open(new Path(path))) {
|
throw new RuntimeException("Missing or invalid Authorization header");
|
||||||
byte[] encrypted = input.readAllBytes();
|
|
||||||
byte[] decrypted = EncryptionService.decrypt(encrypted, key, metadata.getIv());
|
|
||||||
responseStream.write(decrypted);
|
|
||||||
}
|
}
|
||||||
|
return authHeader.substring(7);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -30,14 +30,7 @@ public class FileMetadata {
|
|||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private byte[] iv;
|
private byte[] iv;
|
||||||
|
|
||||||
public void setUsername(String username) { this.username = username; }
|
@Column(nullable = false)
|
||||||
public void setFilePath(String filePath) { this.filePath = filePath; }
|
private long uploadedAt;
|
||||||
public void setSalt(byte[] salt) { this.salt = salt; }
|
|
||||||
public void setIv(byte[] iv) { this.iv = iv; }
|
|
||||||
|
|
||||||
public String getUsername() { return this.username; }
|
|
||||||
public String getFilePath() { return this.filePath; }
|
|
||||||
public byte[] getSalt() { return this.salt; }
|
|
||||||
public byte[] getIv() { return this.iv; }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
+1
-1
@@ -6,5 +6,5 @@ import org.springframework.data.jpa.repository.JpaRepository;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public interface FileMetadataRepository extends JpaRepository<FileMetadata, Long> {
|
public interface FileMetadataRepository extends JpaRepository<FileMetadata, Long> {
|
||||||
Optional<FileMetadata> findByFilePathAndUsername(String filePath, String username);
|
Optional<FileMetadata> findByUsernameAndFilePath(String username, String filePath);
|
||||||
}
|
}
|
||||||
+5
-4
@@ -45,7 +45,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
|
|
||||||
final String authHeader = request.getHeader("Authorization");
|
final String authHeader = request.getHeader("Authorization");
|
||||||
final String jwt;
|
final String jwt;
|
||||||
final String userEmail;
|
final String username;
|
||||||
|
|
||||||
if (!StringUtils.hasText(authHeader) || !authHeader.startsWith("Bearer ")) {
|
if (!StringUtils.hasText(authHeader) || !authHeader.startsWith("Bearer ")) {
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
@@ -62,15 +62,16 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
userEmail = jwtService.extractUsername(jwt);
|
username = jwtService.extractUsername(jwt); // This is actually the `username`, not email
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||||
response.getWriter().write("Invalid JWT token");
|
response.getWriter().write("Invalid JWT token");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) {
|
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||||
User user = userRepository.findByEmail(userEmail).orElse(null);
|
// ❗ Use username to find the user
|
||||||
|
User user = userRepository.findByUsername(username).orElse(null);
|
||||||
|
|
||||||
if (user != null && jwtService.isTokenValid(jwt, user)) {
|
if (user != null && jwtService.isTokenValid(jwt, user)) {
|
||||||
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
|
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
|
||||||
|
|||||||
@@ -1,102 +1,79 @@
|
|||||||
package com.skycrate.backend.skycrateBackend.services;
|
package com.skycrate.backend.skycrateBackend.services;
|
||||||
|
|
||||||
import com.skycrate.backend.skycrateBackend.utils.RSAKeyUtil;
|
|
||||||
|
|
||||||
import javax.crypto.*;
|
import javax.crypto.*;
|
||||||
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
|
import javax.crypto.spec.PBEKeySpec;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.*;
|
import java.security.*;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
import java.security.spec.KeySpec;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
public class EncryptionUtil {
|
public class EncryptionUtil {
|
||||||
private static final String RSA_ALGORITHM = "RSA";
|
|
||||||
private static final String AES_ALGORITHM = "AES";
|
|
||||||
private static final int RSA_KEY_SIZE = 2048;
|
|
||||||
private static final int AES_KEY_SIZE = 256;
|
|
||||||
|
|
||||||
// Generate RSA Key Pair (Public & Private)
|
private static final int SALT_LENGTH = 16; // in bytes
|
||||||
public static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
|
private static final int IV_LENGTH = 16; // for AES CBC
|
||||||
KeyPairGenerator keyGen = KeyPairGenerator.getInstance(RSA_ALGORITHM);
|
private static final int ITERATIONS = 65536;
|
||||||
keyGen.initialize(RSA_KEY_SIZE);
|
private static final int KEY_LENGTH = 256; // bits
|
||||||
return keyGen.generateKeyPair();
|
|
||||||
|
// --- AES key derivation using PBKDF2 ---
|
||||||
|
public static SecretKey deriveAESKey(char[] password, byte[] salt)
|
||||||
|
throws NoSuchAlgorithmException, InvalidKeySpecException {
|
||||||
|
|
||||||
|
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
|
||||||
|
|
||||||
|
KeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, KEY_LENGTH);
|
||||||
|
byte[] keyBytes = factory.generateSecret(spec).getEncoded();
|
||||||
|
|
||||||
|
return new SecretKeySpec(keyBytes, "AES");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encrypt data using AES (AES Key is encrypted using RSA)
|
// --- Encrypt data using AES-CBC ---
|
||||||
// public static byte[] encrypt(byte[] data, PublicKey publicKey) throws Exception {
|
public static byte[] encrypt(byte[] data, SecretKey key, byte[] iv)
|
||||||
// // Step 1: Generate AES Key
|
throws GeneralSecurityException {
|
||||||
// SecretKey aesKey = generateAESKey();
|
|
||||||
//
|
|
||||||
// // Encrypt data using AES
|
|
||||||
// Cipher aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
|
|
||||||
// aesCipher.init(Cipher.ENCRYPT_MODE, aesKey);
|
|
||||||
// byte[] encryptedData = aesCipher.doFinal(data);
|
|
||||||
//
|
|
||||||
// // Encrypt the AES key with RSA
|
|
||||||
// Cipher rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
|
|
||||||
// rsaCipher.init(Cipher.ENCRYPT_MODE, publicKey);
|
|
||||||
// byte[] encryptedAesKey = rsaCipher.doFinal(aesKey.getEncoded());
|
|
||||||
//
|
|
||||||
// // Step 4: Combine encrypted AES key and encrypted data into one array
|
|
||||||
// byte[] combined = new byte[4 + encryptedAesKey.length + encryptedData.length];
|
|
||||||
//
|
|
||||||
// // First 4 bytes indicate the length of the AES encrypted key
|
|
||||||
// combined[0] = (byte) (encryptedAesKey.length >> 24);
|
|
||||||
// combined[1] = (byte) (encryptedAesKey.length >> 16);
|
|
||||||
// combined[2] = (byte) (encryptedAesKey.length >> 8);
|
|
||||||
// combined[3] = (byte) encryptedAesKey.length;
|
|
||||||
//
|
|
||||||
// // Copy AES Key and Encrypted Data into the combined array
|
|
||||||
// System.arraycopy(encryptedAesKey, 0, combined, 4, encryptedAesKey.length);
|
|
||||||
// System.arraycopy(encryptedData, 0, combined, 4 + encryptedAesKey.length, encryptedData.length);
|
|
||||||
//
|
|
||||||
// return combined;
|
|
||||||
// }
|
|
||||||
|
|
||||||
public static byte[] encrypt(byte[] data, PublicKey publicKey) throws Exception {
|
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||||
SecretKey aesKey = RSAKeyUtil.generateAESKey(256); // Ensure 256 bits
|
|
||||||
byte[] encryptedData = encryptDataWithAES(data, aesKey);
|
IvParameterSpec ivSpec = new IvParameterSpec(iv);
|
||||||
byte[] encryptedAesKey = RSAKeyUtil.encryptAESKey(aesKey, publicKey);
|
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
|
||||||
return combineEncryptedData(encryptedAesKey, encryptedData);
|
|
||||||
|
return cipher.doFinal(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] encryptDataWithAES(byte[] data, SecretKey aesKey) throws Exception {
|
// --- Decrypt data using AES-CBC ---
|
||||||
Cipher aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
|
public static byte[] decrypt(byte[] encryptedData, SecretKey key, byte[] iv)
|
||||||
aesCipher.init(Cipher.ENCRYPT_MODE, aesKey);
|
throws GeneralSecurityException {
|
||||||
return aesCipher.doFinal(data);
|
|
||||||
|
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||||
|
|
||||||
|
IvParameterSpec ivSpec = new IvParameterSpec(iv);
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
|
||||||
|
|
||||||
|
return cipher.doFinal(encryptedData);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] combineEncryptedData(byte[] encryptedAesKey, byte[] encryptedData) {
|
// --- Generate random salt ---
|
||||||
byte[] combined = new byte[4 + encryptedAesKey.length + encryptedData.length];
|
public static byte[] generateSalt() {
|
||||||
System.arraycopy(encryptedAesKey, 0, combined, 4, encryptedAesKey.length);
|
byte[] salt = new byte[SALT_LENGTH];
|
||||||
System.arraycopy(encryptedData, 0, combined, 4 + encryptedAesKey.length, encryptedData.length);
|
new SecureRandom().nextBytes(salt);
|
||||||
return combined;
|
return salt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Generate random IV ---
|
||||||
|
public static byte[] generateIV() {
|
||||||
|
byte[] iv = new byte[IV_LENGTH];
|
||||||
|
new SecureRandom().nextBytes(iv);
|
||||||
|
return iv;
|
||||||
|
}
|
||||||
|
|
||||||
// Decrypt data using RSA (AES Key is decrypted using RSA, then used for AES decryption)
|
// --- Optional: Utility to base64 encode data ---
|
||||||
public static byte[] decrypt(byte[] encryptedCombined, PrivateKey privateKey) throws Exception {
|
public static String encodeBase64(byte[] data) {
|
||||||
// Step 1: Extract AES Key length from the combined data
|
return Base64.getEncoder().encodeToString(data);
|
||||||
int aesKeyLength = ((encryptedCombined[0] & 0xFF) << 24) |
|
}
|
||||||
((encryptedCombined[1] & 0xFF) << 16) |
|
|
||||||
((encryptedCombined[2] & 0xFF) << 8) |
|
|
||||||
(encryptedCombined[3] & 0xFF);
|
|
||||||
|
|
||||||
// Step 2: Extract the encrypted AES key and encrypted data
|
public static byte[] decodeBase64(String base64) {
|
||||||
byte[] encryptedAesKey = new byte[aesKeyLength];
|
return Base64.getDecoder().decode(base64);
|
||||||
byte[] encryptedData = new byte[encryptedCombined.length - 4 - aesKeyLength];
|
|
||||||
|
|
||||||
System.arraycopy(encryptedCombined, 4, encryptedAesKey, 0, aesKeyLength);
|
|
||||||
System.arraycopy(encryptedCombined, 4 + aesKeyLength, encryptedData, 0, encryptedData.length);
|
|
||||||
|
|
||||||
// Step 3: Decrypt the AES key using RSA
|
|
||||||
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
|
|
||||||
rsaCipher.init(Cipher.DECRYPT_MODE, privateKey);
|
|
||||||
byte[] aesKeyBytes = rsaCipher.doFinal(encryptedAesKey);
|
|
||||||
|
|
||||||
// Create AES key
|
|
||||||
SecretKey aesKey = new SecretKeySpec(aesKeyBytes, "AES");
|
|
||||||
|
|
||||||
// Decrypt the data using AES
|
|
||||||
Cipher aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
|
|
||||||
aesCipher.init(Cipher.DECRYPT_MODE, aesKey);
|
|
||||||
return aesCipher.doFinal(encryptedData);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package com.skycrate.backend.skycrateBackend.services;
|
||||||
|
|
||||||
|
import com.skycrate.backend.skycrateBackend.config.HDFSConfig;
|
||||||
|
import com.skycrate.backend.skycrateBackend.entity.FileMetadata;
|
||||||
|
import com.skycrate.backend.skycrateBackend.repository.FileMetadataRepository;
|
||||||
|
import com.skycrate.backend.skycrateBackend.utils.EncryptionUtil;
|
||||||
|
import org.apache.hadoop.fs.FSDataOutputStream;
|
||||||
|
import org.apache.hadoop.fs.FileSystem;
|
||||||
|
import org.apache.hadoop.fs.Path;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class FileService {
|
||||||
|
|
||||||
|
private final FileMetadataRepository fileMetadataRepository;
|
||||||
|
|
||||||
|
public FileService(FileMetadataRepository fileMetadataRepository) {
|
||||||
|
this.fileMetadataRepository = fileMetadataRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void uploadEncryptedFile(String username, String password, byte[] fileContent, String filename) throws Exception {
|
||||||
|
// Generate salt and IV
|
||||||
|
byte[] salt = EncryptionUtil.generateSalt();
|
||||||
|
byte[] iv = EncryptionUtil.generateIv();
|
||||||
|
|
||||||
|
// Derive AES key
|
||||||
|
SecretKey key = EncryptionUtil.deriveKey(password.toCharArray(), salt);
|
||||||
|
|
||||||
|
// Encrypt file content
|
||||||
|
byte[] encryptedData = EncryptionUtil.encrypt(fileContent, key, iv);
|
||||||
|
|
||||||
|
// Prepare HDFS path
|
||||||
|
Path userDir = new Path("/" + username);
|
||||||
|
Path filePath = new Path(userDir, filename);
|
||||||
|
FileSystem fs = HDFSConfig.getHDFS();
|
||||||
|
|
||||||
|
// Ensure user directory exists
|
||||||
|
if (!fs.exists(userDir)) {
|
||||||
|
fs.mkdirs(userDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write encrypted file to HDFS
|
||||||
|
try (FSDataOutputStream outputStream = fs.create(filePath, true);
|
||||||
|
InputStream in = new ByteArrayInputStream(encryptedData)) {
|
||||||
|
in.transferTo(outputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save metadata
|
||||||
|
FileMetadata metadata = FileMetadata.builder()
|
||||||
|
.username(username)
|
||||||
|
.filePath(filePath.toString())
|
||||||
|
.salt(salt)
|
||||||
|
.iv(iv)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
fileMetadataRepository.save(metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] downloadDecryptedFile(String username, String password, String filename) throws Exception {
|
||||||
|
Path filePath = new Path("/" + username + "/" + filename);
|
||||||
|
FileSystem fs = HDFSConfig.getHDFS();
|
||||||
|
|
||||||
|
Optional<FileMetadata> metadataOpt = fileMetadataRepository.findByUsernameAndFilePath(username, filePath.toString());
|
||||||
|
if (metadataOpt.isEmpty()) {
|
||||||
|
throw new RuntimeException("File metadata not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
FileMetadata metadata = metadataOpt.get();
|
||||||
|
|
||||||
|
// Derive key
|
||||||
|
SecretKey key = EncryptionUtil.deriveKey(password.toCharArray(), metadata.getSalt());
|
||||||
|
|
||||||
|
// Read file from HDFS
|
||||||
|
byte[] encryptedData = Files.readAllBytes(
|
||||||
|
new java.io.File(filePath.toString()).toPath()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Decrypt
|
||||||
|
return EncryptionUtil.decrypt(encryptedData, key, metadata.getIv());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -63,4 +63,4 @@ public class JwtService {
|
|||||||
public String generateToken(User user) {
|
public String generateToken(User user) {
|
||||||
return generateToken((UserDetails) user);
|
return generateToken((UserDetails) user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package com.skycrate.backend.skycrateBackend.utils;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.SecretKeyFactory;
|
||||||
|
import javax.crypto.spec.*;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class EncryptionUtil {
|
||||||
|
|
||||||
|
private static final int SALT_LENGTH = 16; // 128 bits
|
||||||
|
private static final int IV_LENGTH = 16; // 128 bits for AES
|
||||||
|
private static final int ITERATIONS = 65536;
|
||||||
|
private static final int KEY_LENGTH = 256; // AES-256
|
||||||
|
|
||||||
|
private static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA256";
|
||||||
|
private static final String AES_CIPHER = "AES/CBC/PKCS5Padding";
|
||||||
|
|
||||||
|
private static final SecureRandom secureRandom = new SecureRandom();
|
||||||
|
|
||||||
|
public static byte[] generateSalt() {
|
||||||
|
byte[] salt = new byte[SALT_LENGTH];
|
||||||
|
secureRandom.nextBytes(salt);
|
||||||
|
return salt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] generateIv() {
|
||||||
|
byte[] iv = new byte[IV_LENGTH];
|
||||||
|
secureRandom.nextBytes(iv);
|
||||||
|
return iv;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SecretKey deriveKey(char[] password, byte[] salt) throws Exception {
|
||||||
|
SecretKeyFactory factory = SecretKeyFactory.getInstance(PBKDF2_ALGORITHM);
|
||||||
|
PBEKeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, KEY_LENGTH);
|
||||||
|
SecretKey tmp = factory.generateSecret(spec);
|
||||||
|
return new SecretKeySpec(tmp.getEncoded(), "AES");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] encrypt(byte[] plaintext, SecretKey key, byte[] iv) throws Exception {
|
||||||
|
Cipher cipher = Cipher.getInstance(AES_CIPHER);
|
||||||
|
IvParameterSpec ivSpec = new IvParameterSpec(iv);
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
|
||||||
|
return cipher.doFinal(plaintext);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] decrypt(byte[] ciphertext, SecretKey key, byte[] iv) throws Exception {
|
||||||
|
Cipher cipher = Cipher.getInstance(AES_CIPHER);
|
||||||
|
IvParameterSpec ivSpec = new IvParameterSpec(iv);
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
|
||||||
|
return cipher.doFinal(ciphertext);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user