diff --git a/MiniKms/src/main/java/ftn/security/minikms/controller/KeyComputeController.java b/MiniKms/src/main/java/ftn/security/minikms/controller/KeyComputeController.java new file mode 100644 index 0000000..6e9fd62 --- /dev/null +++ b/MiniKms/src/main/java/ftn/security/minikms/controller/KeyComputeController.java @@ -0,0 +1,96 @@ +package ftn.security.minikms.controller; + +import ftn.security.minikms.dto.CryptoDTO; +import ftn.security.minikms.service.KeyComputeService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import java.security.*; +import java.security.spec.InvalidKeySpecException; + +@RestController +@RequestMapping(value = "/api/v1/crypto") +public class KeyComputeController { + private final KeyComputeService service; + @Autowired + public KeyComputeController(KeyComputeService service) { + this.service = service; + } + + @PostMapping("/encrypt/symmetric") + public ResponseEntity encryptSymmetric(@RequestBody CryptoDTO dto) throws InvalidAlgorithmParameterException, + NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, + BadPaddingException, InvalidKeyException { + try { + String encrypted = service.encryptAes(dto.getMessage(), dto.getKeyId(), + dto.getUsername(), dto.getVersion()); + return ResponseEntity.status(HttpStatus.CREATED).body(encrypted); + } catch (InvalidParameterException e) { + return ResponseEntity.badRequest().body(e.getMessage()); + } + } + @PostMapping("/decrypt/symmetric") + public ResponseEntity decryptSymmetric(@RequestBody CryptoDTO dto) throws InvalidAlgorithmParameterException, + NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, + BadPaddingException, InvalidKeyException { + try { + String decrypted = service.decryptAes(dto.getMessage(), dto.getKeyId(), + dto.getUsername(), dto.getVersion()); + return ResponseEntity.status(HttpStatus.CREATED).body(decrypted); + } catch (InvalidParameterException e) { + return ResponseEntity.badRequest().body(e.getMessage()); + } + } + @PostMapping("/encrypt/asymmetric") + public ResponseEntity encryptAsymmetric(@RequestBody CryptoDTO dto) throws + NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, + BadPaddingException, InvalidKeyException, InvalidKeySpecException { + try { + String encrypted = service.encryptRsa(dto.getMessage(), dto.getKeyId(), + dto.getUsername(), dto.getVersion()); + return ResponseEntity.status(HttpStatus.CREATED).body(encrypted); + } catch (InvalidParameterException e) { + return ResponseEntity.badRequest().body(e.getMessage()); + } + } + @PostMapping("/decrypt/asymmetric") + public ResponseEntity decryptAsymmetric(@RequestBody CryptoDTO dto) throws + NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, + BadPaddingException, InvalidKeyException, InvalidKeySpecException { + try { + String decrypted = service.decryptRsa(dto.getMessage(), dto.getKeyId(), + dto.getUsername(), dto.getVersion()); + return ResponseEntity.status(HttpStatus.CREATED).body(decrypted); + } catch (InvalidParameterException e) { + return ResponseEntity.badRequest().body(e.getMessage()); + } + } + @PostMapping("/compute/hmac") + public ResponseEntity computeHmac(@RequestBody CryptoDTO dto) throws Exception { + try { + String computed = service.computeHmac(dto.getMessage(), dto.getKeyId(), + dto.getUsername(), dto.getVersion()); + return ResponseEntity.status(HttpStatus.CREATED).body(computed); + } catch (InvalidParameterException e) { + return ResponseEntity.badRequest().body(e.getMessage()); + } + } + @PostMapping("/verify/hmac") + public ResponseEntity verifyHmac(@RequestBody CryptoDTO dto) throws Exception { + try { + Boolean verified = service.verifyHmac(dto.getMessage(), dto.getHmacBase64(), dto.getKeyId(), + dto.getUsername(), dto.getVersion()); + return ResponseEntity.status(HttpStatus.CREATED).body(verified); + } catch (InvalidParameterException e) { + return ResponseEntity.badRequest().body(e.getMessage()); + } + } +} diff --git a/MiniKms/src/main/java/ftn/security/minikms/controller/KeyManagementController.java b/MiniKms/src/main/java/ftn/security/minikms/controller/KeyManagementController.java index e821ae7..16ea510 100644 --- a/MiniKms/src/main/java/ftn/security/minikms/controller/KeyManagementController.java +++ b/MiniKms/src/main/java/ftn/security/minikms/controller/KeyManagementController.java @@ -2,7 +2,7 @@ import ftn.security.minikms.dto.KeyDTO; import ftn.security.minikms.dto.KeyMapper; -import ftn.security.minikms.service.KeyService; +import ftn.security.minikms.service.KeyManagementService; import org.mapstruct.factory.Mappers; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; @@ -17,11 +17,11 @@ @RestController @RequestMapping(value = "/api/v1/keys") public class KeyManagementController { - private final KeyService keyService; + private final KeyManagementService keyService; private final KeyMapper mapper; @Autowired - public KeyManagementController(KeyService keyService) { + public KeyManagementController(KeyManagementService keyService) { this.keyService = keyService; this.mapper = Mappers.getMapper(KeyMapper.class); } diff --git a/MiniKms/src/main/java/ftn/security/minikms/dto/CryptoDTO.java b/MiniKms/src/main/java/ftn/security/minikms/dto/CryptoDTO.java new file mode 100644 index 0000000..800d361 --- /dev/null +++ b/MiniKms/src/main/java/ftn/security/minikms/dto/CryptoDTO.java @@ -0,0 +1,16 @@ +package ftn.security.minikms.dto; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.UUID; + +@Data +@NoArgsConstructor +public class CryptoDTO { + private String message; + private UUID keyId; + private String username; + private Integer version; + private String hmacBase64; +} diff --git a/MiniKms/src/main/java/ftn/security/minikms/entity/KeyMetadata.java b/MiniKms/src/main/java/ftn/security/minikms/entity/KeyMetadata.java index 95b5109..07cf9cd 100644 --- a/MiniKms/src/main/java/ftn/security/minikms/entity/KeyMetadata.java +++ b/MiniKms/src/main/java/ftn/security/minikms/entity/KeyMetadata.java @@ -62,4 +62,10 @@ public void updatePrimaryVersion(Integer version) { rotatedAt = Instant.now(); } } + public WrappedKey getVersion(int version) { + return versions.stream() + .filter(wk -> wk.getVersion() == version) + .findFirst() + .orElse(null); + } } diff --git a/MiniKms/src/main/java/ftn/security/minikms/service/AESService.java b/MiniKms/src/main/java/ftn/security/minikms/service/AESService.java index 0f42948..834ea78 100644 --- a/MiniKms/src/main/java/ftn/security/minikms/service/AESService.java +++ b/MiniKms/src/main/java/ftn/security/minikms/service/AESService.java @@ -2,15 +2,64 @@ import ftn.security.minikms.entity.KeyMaterial; -import javax.crypto.KeyGenerator; +import javax.crypto.*; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.util.Base64; -class AESService implements ICryptoService { +public class AESService implements ICryptoService { public KeyMaterial generateKey() throws NoSuchAlgorithmException { KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); keyGenerator.init(256, SecureRandom.getInstanceStrong()); var key = keyGenerator.generateKey(); return KeyMaterial.of(key); } + public String encrypt(String input, KeyMaterial key) throws NoSuchAlgorithmException, + NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, + InvalidAlgorithmParameterException, InvalidKeyException { + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + + byte[] iv = new byte[12]; + SecureRandom random = new SecureRandom(); + random.nextBytes(iv); + GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv); + + SecretKey secretKey = new SecretKeySpec(key.getKey(),"AES"); + cipher.init(Cipher.ENCRYPT_MODE, secretKey, gcmSpec); + + byte[] cipherText = cipher.doFinal(input.getBytes(StandardCharsets.UTF_8)); + + ByteBuffer byteBuffer = ByteBuffer.allocate(iv.length + cipherText.length); + byteBuffer.put(iv); + byteBuffer.put(cipherText); + + return Base64.getEncoder().encodeToString(byteBuffer.array()); + } + public String decrypt(String encrypted, KeyMaterial key) + throws NoSuchPaddingException, NoSuchAlgorithmException, + InvalidAlgorithmParameterException, InvalidKeyException, + BadPaddingException, IllegalBlockSizeException { + byte[] decoded = Base64.getDecoder().decode(encrypted); + ByteBuffer byteBuffer = ByteBuffer.wrap(decoded); + + byte[] iv = new byte[12]; + byteBuffer.get(iv); + + byte[] cipherText = new byte[byteBuffer.remaining()]; + byteBuffer.get(cipherText); + + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + SecretKey secretKey = new SecretKeySpec(key.getKey(),"AES"); + GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv); + + cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmSpec); + byte[] plainText = cipher.doFinal(cipherText); + return new String(plainText, StandardCharsets.UTF_8); + } } diff --git a/MiniKms/src/main/java/ftn/security/minikms/service/HMACService.java b/MiniKms/src/main/java/ftn/security/minikms/service/HMACService.java index 516b2d7..61a3379 100644 --- a/MiniKms/src/main/java/ftn/security/minikms/service/HMACService.java +++ b/MiniKms/src/main/java/ftn/security/minikms/service/HMACService.java @@ -3,14 +3,30 @@ import ftn.security.minikms.entity.KeyMaterial; import javax.crypto.KeyGenerator; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.util.Base64; -class HMACService implements ICryptoService { +public class HMACService implements ICryptoService { public KeyMaterial generateKey() throws NoSuchAlgorithmException { KeyGenerator keyGenerator = KeyGenerator.getInstance("HmacSHA512"); keyGenerator.init(256, SecureRandom.getInstanceStrong()); var key = keyGenerator.generateKey(); return KeyMaterial.of(key); } + public String computeHmac(String message, KeyMaterial key) throws Exception { + Mac mac = Mac.getInstance("HmacSHA512"); + mac.init(new SecretKeySpec(key.getKey(),"HmacSHA512")); + byte[] hmacBytes = mac.doFinal(message.getBytes(StandardCharsets.UTF_8)); + return Base64.getEncoder().encodeToString(hmacBytes); + } + public boolean verifyHmac(String message, String hmacBase64, KeyMaterial key) throws Exception { + String computedHmac = computeHmac(message, key); + return MessageDigest.isEqual(computedHmac.getBytes(StandardCharsets.UTF_8), + hmacBase64.getBytes(StandardCharsets.UTF_8)); + } } diff --git a/MiniKms/src/main/java/ftn/security/minikms/service/KeyComputeService.java b/MiniKms/src/main/java/ftn/security/minikms/service/KeyComputeService.java new file mode 100644 index 0000000..18badfb --- /dev/null +++ b/MiniKms/src/main/java/ftn/security/minikms/service/KeyComputeService.java @@ -0,0 +1,64 @@ +package ftn.security.minikms.service; + +import ftn.security.minikms.entity.KeyMaterial; +import ftn.security.minikms.repository.KeyMetadataRepository; +import org.springframework.stereotype.Service; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.InvalidParameterException; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.util.UUID; + +@Service +public class KeyComputeService { + private final KeyMetadataRepository metadataRepository; + private final AESService aesService; + private final RSAService rsaService; + private final HMACService hmacService; + private static final String NOT_AUTHORIZED_MSG = "You do not own a key with given id"; + + public KeyComputeService(KeyMetadataRepository metadataRepository) { + this.metadataRepository = metadataRepository; + this.aesService = new AESService(); + this.rsaService = new RSAService(); + this.hmacService = new HMACService(); + } + public String encryptAes(String message, UUID keyId, String username, Integer version) + throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, + NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { + return aesService.encrypt(message, getKey(keyId, username, version)); + } + public String decryptAes(String message, UUID keyId, String username, Integer version) + throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, + NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { + return aesService.decrypt(message, getKey(keyId, username, version)); + } + public String encryptRsa(String message, UUID keyId, String username, Integer version) + throws IllegalBlockSizeException, NoSuchPaddingException, BadPaddingException, + NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + return rsaService.encrypt(message, getKey(keyId, username, version)); + } + public String decryptRsa(String message, UUID keyId, String username, Integer version) + throws IllegalBlockSizeException, NoSuchPaddingException, BadPaddingException, + NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + return rsaService.decrypt(message, getKey(keyId, username, version)); + } + public String computeHmac(String message, UUID keyId, String username, Integer version) throws Exception { + return hmacService.computeHmac(message, getKey(keyId, username, version)); + } + public boolean verifyHmac(String message, String hmacBase64, UUID keyId, String username, Integer version) + throws Exception { + return hmacService.verifyHmac(message,hmacBase64, getKey(keyId, username, version)); + } + private KeyMaterial getKey(UUID keyId, String username, Integer version){ + var metadata = metadataRepository.findByIdAndUserUsername(keyId, username) + .orElseThrow(() -> new InvalidParameterException(NOT_AUTHORIZED_MSG)); + var wrappedKey = version != null? metadata.getVersion(version) : metadata.getVersion(metadata.getPrimaryVersion()); + return wrappedKey.getWrappedMaterial(); + } +} diff --git a/MiniKms/src/main/java/ftn/security/minikms/service/KeyService.java b/MiniKms/src/main/java/ftn/security/minikms/service/KeyManagementService.java similarity index 98% rename from MiniKms/src/main/java/ftn/security/minikms/service/KeyService.java rename to MiniKms/src/main/java/ftn/security/minikms/service/KeyManagementService.java index 4c56e64..af1c37e 100644 --- a/MiniKms/src/main/java/ftn/security/minikms/service/KeyService.java +++ b/MiniKms/src/main/java/ftn/security/minikms/service/KeyManagementService.java @@ -17,7 +17,7 @@ import java.util.UUID; @Service -public class KeyService { +public class KeyManagementService { private final KeyMetadataRepository metadataRepository; private final WrappedKeyRepository keyRepository; private final UserRepository userRepository; @@ -25,7 +25,7 @@ public class KeyService { private final Map cryptoServices; private static final String NOT_AUTHORIZED_MSG = "You do not own a key with given id"; - public KeyService( + public KeyManagementService( KeyMetadataRepository metadataRepository, WrappedKeyRepository keyRepository, UserRepository userRepository, diff --git a/MiniKms/src/main/java/ftn/security/minikms/service/RSAService.java b/MiniKms/src/main/java/ftn/security/minikms/service/RSAService.java index 83a54ec..0628098 100644 --- a/MiniKms/src/main/java/ftn/security/minikms/service/RSAService.java +++ b/MiniKms/src/main/java/ftn/security/minikms/service/RSAService.java @@ -2,14 +2,51 @@ import ftn.security.minikms.entity.KeyMaterial; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import java.nio.charset.StandardCharsets; +import java.security.*; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; -class RSAService implements ICryptoService { +public class RSAService implements ICryptoService { public KeyMaterial generateKey() throws NoSuchAlgorithmException { KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA"); - generator.initialize(2048); + generator.initialize(3072); var pair = generator.generateKeyPair(); return KeyMaterial.of(pair); } + public String encrypt(String input, KeyMaterial key) throws InvalidKeyException, + IllegalBlockSizeException, BadPaddingException, + NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException { + Cipher encryptCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); + + KeyFactory factory = KeyFactory.getInstance("RSA"); + PublicKey publicKey = factory.generatePublic(new X509EncodedKeySpec(key.getPublicKey())); + + encryptCipher.init(Cipher.ENCRYPT_MODE, publicKey); + + byte[] secretMessageBytes = input.getBytes(StandardCharsets.UTF_8); + byte[] encryptedMessageBytes = encryptCipher.doFinal(secretMessageBytes); + return Base64.getEncoder().encodeToString(encryptedMessageBytes); + } + + public String decrypt(String encrypted, KeyMaterial key) throws NoSuchPaddingException, NoSuchAlgorithmException, + InvalidKeyException, IllegalBlockSizeException, BadPaddingException, InvalidKeySpecException { + Cipher decryptCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); + + KeyFactory factory = KeyFactory.getInstance("RSA"); + PrivateKey privateKey = factory.generatePrivate(new PKCS8EncodedKeySpec(key.getKey())); + + decryptCipher.init(Cipher.DECRYPT_MODE, privateKey); + + byte[] encryptedBytes = Base64.getDecoder().decode(encrypted); + byte[] decryptedMessageBytes = decryptCipher.doFinal(encryptedBytes); + + return new String(decryptedMessageBytes, StandardCharsets.UTF_8); + } }