-
Notifications
You must be signed in to change notification settings - Fork 2
Basic implementation #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,7 +4,5 @@ | |
|
|
||
| @Data | ||
| public class AliasCreateRequest { | ||
|
|
||
| private String secret; | ||
|
|
||
| private String secret; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package com.verygood.security.coding.aliases; | ||
|
|
||
| import lombok.Data; | ||
|
|
||
| @Data | ||
| public class AliasCreateResponse { | ||
| private String alias; | ||
|
|
||
| public AliasCreateResponse(String alias) { | ||
| this.alias = alias; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,25 +1,30 @@ | ||
| package com.verygood.security.coding.aliases; | ||
|
|
||
| import lombok.extern.slf4j.Slf4j; | ||
| 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.ResponseStatus; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
|
|
||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.web.bind.annotation.*; | ||
|
|
||
| @Slf4j | ||
| @RestController | ||
| @RequestMapping("/aliases") | ||
| public class AliasesController { | ||
|
|
||
| @PostMapping | ||
| @ResponseStatus(HttpStatus.CREATED) | ||
| public ResponseEntity<String> createAlias(@RequestBody final AliasCreateRequest createRequest){ | ||
| log.info("Creating alias for: " + createRequest.getSecret()); | ||
| return new ResponseEntity<>("Created", HttpStatus.CREATED); | ||
| } | ||
| private final AliasesService aliasesService; | ||
|
|
||
| @Autowired | ||
| public AliasesController(AliasesService aliasesService) { | ||
| this.aliasesService = aliasesService; | ||
| } | ||
|
|
||
| @PostMapping() | ||
| @ResponseStatus(HttpStatus.CREATED) | ||
| public ResponseEntity<AliasCreateResponse> createAlias(@RequestBody AliasCreateRequest createRequest) { | ||
| log.info("Creating alias for: " + createRequest.getSecret()); | ||
| String alias = aliasesService.redact(createRequest.getSecret()); | ||
| log.info("Alias created: {} => {}", createRequest.getSecret(), alias); | ||
| return new ResponseEntity(new AliasCreateResponse(alias), HttpStatus.CREATED); | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,4 +2,7 @@ | |
|
|
||
| public interface AliasesService { | ||
|
|
||
| String redact(String data); | ||
|
|
||
| String reveal(String redacted); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| package com.verygood.security.coding.aliases; | ||
|
|
||
| import com.verygood.security.coding.dao.AliasDao; | ||
| import com.verygood.security.coding.model.Alias; | ||
| import com.verygood.security.coding.util.CipherUtils; | ||
| import org.springframework.beans.factory.annotation.Autowired; | ||
| import org.springframework.stereotype.Service; | ||
|
|
||
| import java.util.Optional; | ||
| import java.util.UUID; | ||
|
|
||
| @Service | ||
| public class AliasesServiceImpl implements AliasesService { | ||
| final String secretKey = "Very__$ecure#@Key!@"; | ||
|
|
||
| private final AliasDao db; | ||
|
|
||
| @Autowired | ||
| public AliasesServiceImpl(AliasDao db) { | ||
| this.db = db; | ||
| } | ||
|
|
||
| @Override | ||
| public String redact(String data) { | ||
| String encryptedString = CipherUtils.encrypt(data, secretKey); | ||
| String id = UUID.randomUUID().toString(); | ||
|
|
||
| Alias alias = new Alias(); | ||
| alias.setAlias(id); | ||
| alias.setData(encryptedString); | ||
| db.saveAlias(alias); | ||
|
|
||
| return id; | ||
|
|
||
| } | ||
|
|
||
| @Override | ||
| public String reveal(String uuid) { | ||
| Optional<Alias> encrypted = db.getAliasByKey(uuid); | ||
| return encrypted.map(alias -> CipherUtils.decrypt(alias.getData(), secretKey)).orElse(null); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package com.verygood.security.coding.dao; | ||
|
|
||
| import com.verygood.security.coding.model.Alias; | ||
| import org.springframework.stereotype.Repository; | ||
|
|
||
| import java.util.Optional; | ||
|
|
||
| @Repository | ||
| public interface AliasDao { | ||
| boolean saveAlias(Alias alias); | ||
| Optional<Alias> getAliasByKey(String key); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| package com.verygood.security.coding.dao; | ||
|
|
||
| import com.verygood.security.coding.model.Alias; | ||
| import org.springframework.data.repository.CrudRepository; | ||
| import org.springframework.stereotype.Repository; | ||
|
|
||
| import java.util.Optional; | ||
|
|
||
| @Repository("db") | ||
| public interface AliasRepository extends AliasDao, CrudRepository<Alias, String> { | ||
|
|
||
| default boolean saveAlias(Alias alias) { | ||
| save(alias); | ||
| return true; | ||
| } | ||
|
|
||
| default Optional<Alias> getAliasByKey(String key) { | ||
| return findById(key); | ||
| } | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| package com.verygood.security.coding.dao; | ||
|
|
||
| import com.verygood.security.coding.model.Alias; | ||
| import org.springframework.stereotype.Repository; | ||
|
|
||
| import java.util.Map; | ||
| import java.util.Optional; | ||
| import java.util.concurrent.ConcurrentHashMap; | ||
|
|
||
| @Repository("mock") | ||
| public class MockAliasRepository implements AliasDao { | ||
| private final Map<String, String> db = new ConcurrentHashMap<>(); | ||
|
|
||
| @Override | ||
| public boolean saveAlias(Alias alias) { | ||
| db.put(alias.getAlias(), alias.getData()); | ||
| return false; | ||
| } | ||
|
|
||
| @Override | ||
| public Optional<Alias> getAliasByKey(String key) { | ||
| if(db.containsKey(key)) { | ||
| Alias alias = new Alias(); | ||
| alias.setAlias(key); | ||
| alias.setData(db.get(key)); | ||
| return Optional.of(alias); | ||
| } | ||
| return Optional.empty(); | ||
| } | ||
|
|
||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package com.verygood.security.coding.exception; | ||
|
|
||
| public class CipherDecryptException extends RuntimeException { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we really need 2 identical exceptions? |
||
| public CipherDecryptException(String message) { | ||
| super(message); | ||
| } | ||
|
|
||
| public CipherDecryptException(String message, Throwable cause) { | ||
| super(message, cause); | ||
| } | ||
|
|
||
| public CipherDecryptException(Throwable cause) { | ||
| super(cause); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package com.verygood.security.coding.exception; | ||
|
|
||
| public class CipherEncryptException extends RuntimeException { | ||
| public CipherEncryptException(String message) { | ||
| super(message); | ||
| } | ||
|
|
||
| public CipherEncryptException(String message, Throwable cause) { | ||
| super(message, cause); | ||
| } | ||
|
|
||
| public CipherEncryptException(Throwable cause) { | ||
| super(cause); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| package com.verygood.security.coding.model; | ||
|
|
||
| import lombok.Data; | ||
|
|
||
| import javax.persistence.Entity; | ||
| import javax.persistence.Id; | ||
|
|
||
| @Entity(name = "aliases") | ||
| @Data | ||
| public class Alias { | ||
| @Id | ||
| private String alias; | ||
| private String data; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| package com.verygood.security.coding.util; | ||
|
|
||
| import com.verygood.security.coding.exception.CipherDecryptException; | ||
| import com.verygood.security.coding.exception.CipherEncryptException; | ||
| import lombok.extern.slf4j.Slf4j; | ||
|
|
||
| import javax.crypto.Cipher; | ||
| import javax.crypto.spec.SecretKeySpec; | ||
| import java.security.MessageDigest; | ||
| import java.security.NoSuchAlgorithmException; | ||
| import java.util.Arrays; | ||
| import java.util.Base64; | ||
|
|
||
| import static java.nio.charset.StandardCharsets.UTF_8; | ||
| import static java.util.Objects.requireNonNull; | ||
|
|
||
| @Slf4j | ||
| public enum CipherUtils { | ||
| ; | ||
| private static final String DEFAULT_ALGORITHM = "AES/ECB/PKCS5Padding"; | ||
| private static SecretKeySpec secretKey; | ||
|
|
||
| public static String encrypt(String strToEncrypt, String secret) { | ||
| return encrypt(strToEncrypt, secret, DEFAULT_ALGORITHM); | ||
| } | ||
|
|
||
| public static String encrypt(String whatToEncrypt, String secret, String algorithm) throws CipherEncryptException { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| requireNonNull(whatToEncrypt); | ||
| requireNonNull(secret); | ||
| try { | ||
| Cipher cipher = Cipher.getInstance(algorithm); | ||
| cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(secret)); | ||
| return Base64.getEncoder().encodeToString(cipher.doFinal(whatToEncrypt.getBytes(UTF_8))); | ||
| } catch (Exception e) { | ||
| log.error("Error encrypting: " + e); | ||
| throw new CipherEncryptException(e); | ||
| } | ||
| } | ||
|
|
||
| public static String decrypt(String strToEncrypt, String secret) { | ||
| return decrypt(strToEncrypt, secret, DEFAULT_ALGORITHM); | ||
| } | ||
|
|
||
| public static String decrypt(String whatToDecrypt, String secret, String algorithm) throws CipherDecryptException { | ||
| requireNonNull(whatToDecrypt); | ||
| requireNonNull(secret); | ||
| try { | ||
| Cipher cipher = Cipher.getInstance(algorithm); | ||
| cipher.init(Cipher.DECRYPT_MODE, getSecretKey(secret)); | ||
| return new String(cipher.doFinal(Base64.getDecoder().decode(whatToDecrypt))); | ||
| } catch (Exception e) { | ||
| log.error("Error decrypting: " + e); | ||
| throw new CipherDecryptException(e); | ||
| } | ||
| } | ||
|
|
||
|
|
||
| private static SecretKeySpec getSecretKey(String myKey) { | ||
| try { | ||
| byte[] key = myKey.getBytes(UTF_8); | ||
| MessageDigest sha = MessageDigest.getInstance("SHA-1"); | ||
| key = sha.digest(key); | ||
| key = Arrays.copyOf(key, 16); | ||
| return new SecretKeySpec(key, "AES"); | ||
| } catch (NoSuchAlgorithmException e) { | ||
| log.error("Exception setting key", e); | ||
| throw new IllegalStateException("Failed to create SecretKeySpec", e); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,2 @@ | ||
|
|
||
| spring.h2.console.enabled=true | ||
| spring.datasource.url=jdbc:h2:mem:aliases |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| DROP TABLE IF EXISTS aliases; | ||
|
|
||
| CREATE TABLE aliases | ||
| ( | ||
| alias VARCHAR(30) PRIMARY KEY, | ||
| data VARCHAR(250) NOT NULL | ||
| ); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| package com.verygood.security.coding; | ||
|
|
||
| import com.verygood.security.coding.aliases.AliasesService; | ||
| import com.verygood.security.coding.aliases.AliasesServiceImpl; | ||
| import com.verygood.security.coding.dao.MockAliasRepository; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.junit.Test; | ||
| import org.junit.runner.RunWith; | ||
| import org.springframework.test.context.junit4.SpringRunner; | ||
|
|
||
| import static org.junit.jupiter.api.Assertions.*; | ||
|
|
||
| @RunWith(SpringRunner.class) | ||
| @Slf4j | ||
| public class AliasesServiceTest { | ||
| private AliasesService aliasesService = new AliasesServiceImpl(new MockAliasRepository()); | ||
|
|
||
| @Test | ||
| public void inputIsEncryted() { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure we need |
||
| String input = "1111 2222 3333 4567"; | ||
| String redacted = aliasesService.redact(input); | ||
| log.info("{} => {}", input, redacted); | ||
| assertNotEquals(input, redacted); | ||
| } | ||
|
|
||
| @Test(expected = NullPointerException.class) | ||
| public void inputIsValid() { | ||
| String redacted = aliasesService.redact(null); | ||
| fail(); | ||
| } | ||
|
|
||
| @Test | ||
| public void canDecryptAlias() { | ||
| String input = "1111 2222 3333 4567"; | ||
| String redacted = aliasesService.redact(input); | ||
| String revealed = aliasesService.reveal(redacted); | ||
| assertEquals(input, revealed); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be covered by
@RequiredArgsConstructorwhich is a part of@Dataannotation.Reason why It's not there is the absence of
@NotNullannotation.