Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
}
Expand Down
16 changes: 16 additions & 0 deletions MiniKms/src/main/java/ftn/security/minikms/dto/CryptoDTO.java
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
53 changes: 51 additions & 2 deletions MiniKms/src/main/java/ftn/security/minikms/service/AESService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@
import java.util.UUID;

@Service
public class KeyService {
public class KeyManagementService {
private final KeyMetadataRepository metadataRepository;
private final WrappedKeyRepository keyRepository;
private final UserRepository userRepository;
private final RootKeyManager rootKeyManager;
private final Map<KeyType, ICryptoService> 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,
Expand Down
45 changes: 41 additions & 4 deletions MiniKms/src/main/java/ftn/security/minikms/service/RSAService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Loading