I'm working on a simple Spring MVC web app that allows users to encrypt and decrypt messages with classical ciphers such as the shift or Caesar cipher. It also allows users to auto-decrypt a message using crypt-analysis and to press an example button that randomly pulls an example, for any given cipher, from a database. The 4 ciphers are Shift, Affine, Substitution, and Vigenere. Each cipher has plain text, cipher text, and a key(s). The key(s) can be integers or strings.
I have the following concerns about my app:
- My controller has 3 routes for each cipher. One is a GET for when the user navigates to the page. Another is POST with a direction for encrypting, decrypting, or auto-decrypting. The last is a GET for an example of a cipher. I've tried handling things in a better polymorphic way, but haven't had success, mostly due to the fact that ciphers can have one or multiple keys.
- My shift classes that contain the business logic are stateless and it seems unnecessary to create an object when all of the methods could just be static.
- Similar to #2, I've considered getting rid of the Value Objects and instead just have a POJO with the cipher business logic in it. However, my understanding is that a bean should not have business logic. Therefore, I separated the VO and the classes that contain the business logic for the ciphers.
- For the Shift and Affine Cipher, the keys (the shift amounts) have a specific boundary. The key for Shift has to be numbers 1-26 while Affine has to be a number relatively prime to 26. This is business logic and should not be defined in the view. Therefore, I made an array for the valid shift amounts that a user can enter. This array is a static final variable on my Domain Model. It is passed to the view and the view renders a drop down for each element in the array. In the case of an Affine Cipher, this would be the numbers relatively prime to 26. I'd appreciate insight on how I could handle this business logic.
I'll show relative code for a Shift Cipher.
ShiftVO
public class ShiftVO {
private String plainText, cipherText;
private int shiftAmount;
private int [] validShifts; //The possible shifts that a user can perform with a Shift Cipher (1-26)
public String getPlainText() {
return plainText;
}
public void setPlainText(String plainText) {
this.plainText = plainText;
}
public String getCipherText() {
return cipherText;
}
public void setCipherText(String cipherText) {
this.cipherText = cipherText;
}
public int getShiftAmount() {
return shiftAmount;
}
public void setShiftAmount(int shiftAmount) {
this.shiftAmount = shiftAmount;
}
public int[] getValidShifts() {
return validShifts;
}
public void setValidShifts(int[] validShifts) {
this.validShifts = validShifts;
}
}
ShiftCipher - Business logic for a shift cipher. As noted in #3 I'm considering getting rid of this class or make the methods static.
public class ShiftCipher {
public final static int [] VALID_SHIFTS = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26};
public String encrypt(String plainText, int shiftAmount) {
//performs encryption
}
public String decrypt(String cipherText, int shiftAmount) {
//performs decryption
}
public int autoDecrypt(String cipherText) {
//performs auto decryption
}
}
CipherDAO - used to get a random cipher from the database as an example for the user
@Component("cipherDao")
public class CipherDao {
private SimpleJdbcCall procReadCipher;
@Autowired
public void setDataSource(DataSource jdbc) {
this.procReadCipher= new SimpleJdbcCall(jdbc);
}
public ShiftVO getRandShift(){
SqlParameterSource in = new MapSqlParameterSource();
procReadCipher.withProcedureName("usp_S_rnd_ex_shift_cipher");
Map<String, Object> out = procReadCipher.execute(in);
System.out.println(out.values());
System.out.println(out.values().size());
ShiftVO shiftVO = new ShiftVO();
shiftVO.setCipherText((String) out.get("cipher_text"));
shiftVO.setPlainText((String) out.get("plain_text"));
shiftVO.setShiftAmount((Integer) out.get("shift_amount"));
return shiftVO;
}
}
RandomCipherService - Service for communication to the DAO to retrieve random ciphers for examples
@Service("randomCipherService")
public class RandomCipherService {
private CipherDao cipherDao;
@Autowired
public void setOffersDao(CipherDao cipherDao) {
this.cipherDao = cipherDao;
}
public ShiftVO getRandomShift(){
return cipherDao.getRandShift();
}
}
CipherService
@Service("cipherService")
public class CipherService {
public enum CipherDirection {
ENCRYPT, DECRYPT, AUTODECRYPT
}
public void shift(ShiftVO shiftVO, CipherDirection cipherDirection){
ShiftCipher shiftCipher = new ShiftCipher();
try{
if(cipherDirection == CipherDirection.ENCRYPT){
String cipherText = shiftCipher.encrypt(shiftVO.getPlainText(), shiftVO.getShiftAmount());
shiftVO.setCipherText(cipherText);
} else if(cipherDirection == CipherDirection.DECRYPT){
String plainText = shiftCipher.decrypt(shiftVO.getCipherText(), shiftVO.getShiftAmount());
shiftVO.setPlainText(plainText);
} else if(cipherDirection == CipherDirection.AUTODECRYPT){
int shiftAmount = shiftCipher.autoDecrypt(shiftVO.getCipherText());
String plainText = shiftCipher.decrypt(shiftVO.getCipherText(), shiftAmount);
shiftVO.setPlainText(plainText);
shiftVO.setShiftAmount(shiftAmount);
} //Still need to add IllegalArgumentException
} catch(Exception e){
System.out.println("Error: " + e);
}
}
}
CipherController
@Controller
public class CipherController {
private CipherService cipherService;
private RandomCipherService randomCipherService;
@Autowired
public void setCipherService(CipherService cipherService) {
this.cipherService = cipherService;
}
@Autowired
public void setRandomCipherService(RandomCipherService randomCipherService) {
this.randomCipherService = randomCipherService;
}
@RequestMapping(value={"","/","/shift"})
public String showShift(Model model){
ShiftVO shiftVO = new ShiftVO();
shiftVO.setValidShifts(cipherService.getShiftValidShifts());
model.addAttribute("shift",shiftVO);
return "shift";
}
@RequestMapping(value="/shift", method=RequestMethod.POST)
public String shiftCipher(ShiftVO shiftVO
, @RequestParam("cipherDirection") String cipherDirection
, Model model) {
cipherService.shift(shiftVO, CipherDirection.valueOf(cipherDirection));
shiftVO.setValidShifts(cipherService.getShiftValidShifts());
model.addAttribute("shift",shiftVO);
return "shift";
}
@RequestMapping(value="/randomshift")
public String randomShiftCipher(Model model){
System.out.println("random shift");
ShiftVO shiftVO = randomCipherService.getRandomShift();
shiftVO.setValidShifts(cipherService.getShiftValidShifts());
model.addAttribute("shift",shiftVO);
System.out.println(shiftVO.getPlainText());
return "shift";
}
}
shift.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="sf" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Classical Cipher</title>
<link href="${pageContext.request.contextPath}/assets/css/cipher.css" rel="stylesheet" type="text/css">
</head>
<body>
<nav id = "navbar-ciphers">
<h2>Ciphers</h2>
<hr>
<ul class = "menu">
<li><a href="${pageContext.request.contextPath}/shift">Shift</a></li>
<li><a href="${pageContext.request.contextPath}/affine">Affine</a></li>
<li><a href="${pageContext.request.contextPath}/substitution">Substitution</a></li>
<li><a href="${pageContext.request.contextPath}/vigenere">Vigenere</a></li>
</ul>
</nav>
<div id="main-content">
<div id="cipher-input">
<sf:form method="post" action="${pageContext.request.contextPath}/shift" commandName="shift">
<div id ="shift-cipher-input" class="selected-cipher">
<label> Shift Amount: </label>
<select name="shiftAmount" id="shift-amount">
<c:forEach var="validShift" items="${shift.validShifts}">
<option value="${validShift}" ${validShift == shift.shiftAmount ? 'selected' : ''}>${validShift}</option>
</c:forEach>
</select>
</div>
<br>
<label>Plain Text</label>
<br>
<textarea name="plainText" id="plain-text" placeholder="Plain text"><c:out value="${shift.plainText}"></c:out></textarea>
<br>
<label>Cipher Text</label>
<br>
<textarea name="cipherText" id="cipher-text" placeholder="Cipher text"><c:out value="${shift.cipherText}"></c:out></textarea>
<br>
<button type="submit" id="encrypt" name="cipherDirection" value="ENCRYPT">Encrypt</button>
<button type="submit" id="decrypt" name="cipherDirection" value="DECRYPT">Decrypt</button>
<button type="submit" id="auto-decrypt" name="cipherDirection" value="AUTODECRYPT">Auto Decrypt</button>
<input type="button" id="clear-ciphers" value = "Clear">
</sf:form>
<input type="button" onclick="${pageContext.request.contextPath}/randomshift" value="Randomshift">
<form action="<c:url value="/randomshift" />" method="GET">
<input type="submit" name="action" value="Random" />
</form>
</div>
</div>
<script type="text/javascript" src="https://code.jquery.com/jquery-2.2.1.min.js"></script>
<script type="text/javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath}/assets/js/cipher.js"></script>
</body>
</html>