Refactor encryption system to support hybrid RSA-AES encryption per file

- Changed file upload logic to:
  - Generate random AES key per file
  - Encrypt AES key using user's RSA public key
  - Store encrypted AES key, IV, and salt in FileMetadata entity

- Changed file download logic to:
  - Decrypt AES key using user's RSA private key (encrypted with password-derived AES)
  - Use decrypted AES key and IV to decrypt file contents from HDFS

- Modified FileMetadata entity:
  - Changed `encryptedKey` to @Lob byte[] to support large encrypted AES keys

- Updated User entity:
  - Encrypted private RSA key with password-derived AES
  - Stored associated salt and IV for decryption

- Updated AuthenticationService:
  - Generate RSA keypair during sign-up
  - Encrypt and store private key with AES (salt, IV)
  - Create user folder in HDFS upon registration

- Updated FileService:
  - Rewrote upload and download logic to support hybrid encryption
  - Handled key wrapping and unwrapping securely
  - Added logging for upload/download events

- Fixed FileController upload to remove password from endpoint
  - Password now only required during download for private key decryption

- Updated EncryptionUtil and RSAKeyUtil:
  - Added RSA OAEP support and helper methods
  - Added AES key generation, encryption, decryption utilities

FILE UPLOAD AND ENCRYPTION WORKS! TESTED USING HEXDUMP.
This commit is contained in:
K
2025-07-03 16:22:41 +05:30
parent 23eda639c0
commit 4af5aabd42
7 changed files with 190 additions and 105 deletions
@@ -5,6 +5,7 @@ import com.skycrate.backend.skycrateBackend.dto.LoginUserDto;
import com.skycrate.backend.skycrateBackend.dto.RegisterUserDto;
import com.skycrate.backend.skycrateBackend.entity.User;
import com.skycrate.backend.skycrateBackend.repository.UserRepository;
import com.skycrate.backend.skycrateBackend.utils.EncryptionUtil;
import com.skycrate.backend.skycrateBackend.utils.RSAKeyUtil;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
@@ -13,6 +14,7 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import javax.crypto.SecretKey;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
@@ -32,6 +34,7 @@ public class AuthenticationService {
}
public User signUp(RegisterUserDto inputUser) {
// Generate RSA key pair
KeyPair keyPair;
try {
keyPair = RSAKeyUtil.generateKeyPair();
@@ -39,28 +42,43 @@ public class AuthenticationService {
throw new RuntimeException("Failed to generate RSA key pair", e);
}
// Encrypt private key using password-derived AES key
byte[] salt = EncryptionUtil.generateSalt();
byte[] iv = EncryptionUtil.generateIv();
byte[] encryptedPrivateKey;
try {
SecretKey aesKey = EncryptionUtil.deriveKey(inputUser.getPassword().toCharArray(), salt);
encryptedPrivateKey = EncryptionUtil.encrypt(keyPair.getPrivate().getEncoded(), aesKey, iv);
} catch (Exception e) {
throw new RuntimeException("Failed to encrypt private key", e);
}
// Create user entity with encrypted private key, salt, and iv
User user = User.builder()
.fullname(inputUser.getFirstname() + " " + inputUser.getLastname())
.username(inputUser.getUsername())
.email(inputUser.getEmail())
.password(passwordEncoder.encode(inputUser.getPassword()))
.publicKey(keyPair.getPublic().getEncoded())
.privateKey(keyPair.getPrivate().getEncoded())
.privateKey(encryptedPrivateKey)
.privateKeySalt(salt)
.privateKeyIv(iv)
.build();
// Save user
User savedUser = userRepository.save(user);
// Create HDFS directory in root with username
try {
FileSystem fs = HDFSConfig.getHDFS();
String folderName = savedUser.getUsername();
Path userDir = new Path("/" + folderName);
Path userDir = new Path("/" + savedUser.getUsername());
if (!fs.exists(userDir)) {
fs.mkdirs(userDir);
}
} catch (Exception e) {
throw new RuntimeException("Failed to create HDFS directory for user: " + savedUser.getUsername(), e);
}
return savedUser;
}
@@ -2,86 +2,113 @@ 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.entity.User;
import com.skycrate.backend.skycrateBackend.repository.FileMetadataRepository;
import com.skycrate.backend.skycrateBackend.repository.UserRepository;
import com.skycrate.backend.skycrateBackend.utils.EncryptionUtil;
import com.skycrate.backend.skycrateBackend.utils.RSAKeyUtil;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;
import java.security.PrivateKey;
import java.security.PublicKey;
@Service
public class FileService {
private final FileMetadataRepository fileMetadataRepository;
private static final Logger log = LoggerFactory.getLogger(FileService.class);
public FileService(FileMetadataRepository fileMetadataRepository) {
private final FileMetadataRepository fileMetadataRepository;
private final UserRepository userRepository;
public FileService(FileMetadataRepository fileMetadataRepository, UserRepository userRepository) {
this.fileMetadataRepository = fileMetadataRepository;
this.userRepository = userRepository;
}
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();
public void uploadEncryptedFile(String username, byte[] fileContent, String filename) throws Exception {
log.info("Starting upload for user={}, file={}", username, filename);
try {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new RuntimeException("User not found: " + username));
// Derive AES key
SecretKey key = EncryptionUtil.deriveKey(password.toCharArray(), salt);
SecretKey aesKey = EncryptionUtil.generateAESKey();
byte[] salt = EncryptionUtil.generateSalt(); // reserved for future use
byte[] iv = EncryptionUtil.generateIv();
// Encrypt file content
byte[] encryptedData = EncryptionUtil.encrypt(fileContent, key, iv);
byte[] encryptedData = EncryptionUtil.encrypt(fileContent, aesKey, iv);
// Prepare HDFS path
Path userDir = new Path("/" + username);
Path filePath = new Path(userDir, filename);
FileSystem fs = HDFSConfig.getHDFS();
PublicKey publicKey = RSAKeyUtil.decodePublicKey(user.getPublicKey());
byte[] encryptedAesKey = EncryptionUtil.encryptRSA(aesKey.getEncoded(), publicKey);
// Ensure user directory exists
if (!fs.exists(userDir)) {
fs.mkdirs(userDir);
Path userDir = new Path("/" + username);
Path filePath = new Path(userDir, filename);
FileSystem fs = HDFSConfig.getHDFS();
if (!fs.exists(userDir)) {
log.info("Creating directory in HDFS: {}", userDir);
fs.mkdirs(userDir);
}
log.info("Writing encrypted file to HDFS: {}", filePath);
try (FSDataOutputStream out = fs.create(filePath, true);
ByteArrayInputStream in = new ByteArrayInputStream(encryptedData)) {
in.transferTo(out);
}
FileMetadata metadata = FileMetadata.builder()
.username(username)
.filePath(filePath.toString())
.salt(salt)
.iv(iv)
.encryptedKey(encryptedAesKey)
.uploadedAt(System.currentTimeMillis())
.build();
fileMetadataRepository.save(metadata);
log.info("Upload complete: file={} for user={}", filename, username);
} catch (Exception e) {
log.error("Error during file upload for user={}, file={}: {}", username, filename, e.getMessage(), e);
throw e;
}
// 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();
log.info("Download request: user={}, file={}", username, filename);
try {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new RuntimeException("User not found: " + username));
Optional<FileMetadata> metadataOpt = fileMetadataRepository.findByUsernameAndFilePath(username, filePath.toString());
if (metadataOpt.isEmpty()) {
throw new RuntimeException("File metadata not found");
Path filePath = new Path("/" + username + "/" + filename);
FileMetadata metadata = fileMetadataRepository.findByUsernameAndFilePath(username, filePath.toString())
.orElseThrow(() -> new RuntimeException("File metadata not found for: " + filePath));
SecretKey derivedKey = EncryptionUtil.deriveKey(password.toCharArray(), user.getPrivateKeySalt());
byte[] decryptedPrivateKeyBytes = EncryptionUtil.decrypt(user.getPrivateKey(), derivedKey, user.getPrivateKeyIv());
PrivateKey privateKey = RSAKeyUtil.decodePrivateKey(decryptedPrivateKeyBytes);
byte[] aesKeyBytes = EncryptionUtil.decryptRSA(metadata.getEncryptedKey(), privateKey);
SecretKey aesKey = EncryptionUtil.rebuildAESKey(aesKeyBytes);
FileSystem fs = HDFSConfig.getHDFS();
byte[] encryptedData;
try (FSDataInputStream in = fs.open(filePath)) {
encryptedData = in.readAllBytes();
}
return EncryptionUtil.decrypt(encryptedData, aesKey, metadata.getIv());
} catch (Exception e) {
log.error("Download failed for user={}, file={}: {}", username, filename, e.getMessage(), e);
throw e;
}
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());
}
}