http encryption : js <-> java

This commit is contained in:
haerong22
2021-09-02 18:02:27 +09:00
parent 3762924952
commit aa514596a7
5 changed files with 459 additions and 24 deletions

View File

@@ -10,13 +10,12 @@ import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Arrays;
public class AESUtil {
private final String ALGORITHM = "AES/CBC/PKCS5PADDING";
private final String KEY = "example";
private byte[] iv;
private String iv;
public String encrypt(String data) {
@@ -24,25 +23,21 @@ public class AESUtil {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, createKeySpec(), createIvSpec());
byte[] encryptData = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
byte[] result = new byte[iv.length + encryptData.length];
System.arraycopy(iv, 0, result, 0, iv.length);
System.arraycopy(encryptData, 0, result, iv.length, encryptData.length);
return Base64Utils.encodeToString(result);
return iv + Base64Utils.encodeToString(encryptData);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
throw new RuntimeException("encrypt fail : " + e.getMessage());
}
}
public String decrypt(String data) {
byte[] dataBytes = Base64Utils.decodeFromString(data);
byte[] iv = Arrays.copyOf(dataBytes, 16);
byte[] encryptData = Arrays.copyOfRange(dataBytes, 16, dataBytes.length);
String ivStr = data.substring(0,16);
String content = data.substring(16);
byte[] dataBytes = Base64Utils.decodeFromString(content);
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, createKeySpec(), new IvParameterSpec(iv));
byte[] original = cipher.doFinal(encryptData);
cipher.init(Cipher.DECRYPT_MODE, createKeySpec(), new IvParameterSpec(ivStr.getBytes(StandardCharsets.UTF_8)));
byte[] original = cipher.doFinal(dataBytes);
return new String(original, StandardCharsets.UTF_8);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
throw new RuntimeException("decrypt fail : " + e.getMessage());
@@ -51,10 +46,10 @@ public class AESUtil {
private IvParameterSpec createIvSpec() {
try {
byte[] iv = SecureRandom.getInstanceStrong().generateSeed(16);
String iv = StringUtil.randomStr(16);
this.iv = iv;
return new IvParameterSpec(iv);
} catch (NoSuchAlgorithmException e) {
return new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
throw new RuntimeException("createIvSpec fail : " + e.getMessage());
}

View File

@@ -0,0 +1,28 @@
package com.example.httpencryption.utils;
import java.util.Random;
public class StringUtil {
public static String randomStr(int length) {
Random random = new Random();
StringBuffer str = new StringBuffer();
for (int i = 0; i < length; i++) {
int choice = random.nextInt(3);
switch(choice) {
case 0:
str.append((char)((int)random.nextInt(25)+97));
break;
case 1:
str.append((char)((int)random.nextInt(25)+65));
break;
case 2:
str.append((char)((int)random.nextInt(10)+48));
break;
default:
break;
}
}
return str.toString();
}
}

View File

@@ -5,6 +5,36 @@
<title>Title</title>
</head>
<body>
<form id="form">
<input name="username" type="text"/>
<input name="age" type="text"/>
<button type="button" onclick="btn()">버튼</button>
</form>
</body>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
<script>
const btn = () => {
let form = document.querySelector('#form');
let formData = new FormData(form);
let obj = {};
for (let key of formData.keys()) {
obj[key] = formData.get(key);
}
let key = "example";
let sha256 = CryptoJS.SHA256(key);
let iv = Math.random().toString(36).substring(2, 10) + Math.random().toString(36).substring(2, 10);
console.log(iv.length)
let string = CryptoJS.AES.encrypt(
JSON.stringify(obj),
sha256,
{
iv: CryptoJS.enc.Utf8.parse(iv),
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
},
).toString();
console.log(iv)
console.log(iv + string, (iv+string).length);
}
</script>
</html>

View File

@@ -0,0 +1,378 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.json-viewer {
color: #000;
padding-left: 20px;
}
.json-viewer ul {
list-style-type: none;
margin: 0;
margin: 0 0 0 1px;
border-left: 1px dotted #ccc;
padding-left: 2em;
}
.json-viewer .hide {
display: none;
}
.json-viewer .type-string {
color: #0B7500;
}
.json-viewer .type-date {
color: #CB7500;
}
.json-viewer .type-boolean {
color: #1A01CC;
font-weight: bold;
}
.json-viewer .type-number {
color: #1A01CC;
}
.json-viewer .type-null, .json-viewer .type-undefined {
color: #90a;
}
.json-viewer a.list-link {
color: #000;
text-decoration: none;
position: relative;
}
.json-viewer a.list-link:before {
color: #aaa;
content: "\25BC";
position: absolute;
display: inline-block;
width: 1em;
left: -1em;
}
.json-viewer a.list-link.collapsed:before {
content: "\25B6";
}
.json-viewer a.list-link.empty:before {
content: "";
}
.json-viewer .items-ph {
color: #aaa;
padding: 0 1em;
}
.json-viewer .items-ph:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<div>
<div>
<label>key</label>
</div>
<input id="key"/>
</div>
<div>
<div>
<label>iv</label>
</div>
<input id="iv"/>
</div>
<div style="display: flex; margin-top: 10px">
<div style="flex: 1; margin-right: 10px;">
<textarea id='encrypt_data' style="width: 100%; height: 400px;">
</textarea>
<div>
<input type="button" id="btn_encrypt" onclick="encrypt();" value="암호화" />
</div>
<div id='encrypt_result'></div>
</div>
<div style="flex: 1; ">
<textarea id='decrypt_data' style="width: 100%; height: 400px;">
</textarea>
<div>
<input type="button" id="btn_decrypt" onclick="decrypt();" value="복호화" />
</div>
<div id="json"></div>
<div id='decrypt_result'></div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js" integrity="sha512-nOQuvD9nKirvxDdvQ9OMqe2dgapbPB7vYAMrzJihw5m+aNcf0dX53m6YxM4LgA9u8e9eg9QX+/+mPu8kCNpV2A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script>
/**
* JSONViewer - by Roman Makudera 2016 (c) MIT licence.
*/
var JSONViewer = (function(document) {
var Object_prototype_toString = ({}).toString;
var DatePrototypeAsString = Object_prototype_toString.call(new Date);
/** @constructor */
function JSONViewer() {
this._dom_container = document.createElement("pre");
this._dom_container.classList.add("json-viewer");
};
/**
* Visualise JSON object.
*
* @param {Object|Array} json Input value
* @param {Number} [inputMaxLvl] Process only to max level, where 0..n, -1 unlimited
* @param {Number} [inputColAt] Collapse at level, where 0..n, -1 unlimited
*/
JSONViewer.prototype.showJSON = function(jsonValue, inputMaxLvl, inputColAt) {
// Process only to maxLvl, where 0..n, -1 unlimited
var maxLvl = typeof inputMaxLvl === "number" ? inputMaxLvl : -1; // max level
// Collapse at level colAt, where 0..n, -1 unlimited
var colAt = typeof inputColAt === "number" ? inputColAt : -1; // collapse at
this._dom_container.innerHTML = "";
walkJSONTree(this._dom_container, jsonValue, maxLvl, colAt, 0);
};
/**
* Get container with pre object - this container is used for visualise JSON data.
*
* @return {Element}
*/
JSONViewer.prototype.getContainer = function() {
return this._dom_container;
};
/**
* Recursive walk for input value.
*
* @param {Element} outputParent is the Element that will contain the new DOM
* @param {Object|Array} value Input value
* @param {Number} maxLvl Process only to max level, where 0..n, -1 unlimited
* @param {Number} colAt Collapse at level, where 0..n, -1 unlimited
* @param {Number} lvl Current level
*/
function walkJSONTree(outputParent, value, maxLvl, colAt, lvl) {
var isDate = Object_prototype_toString.call(value) === DatePrototypeAsString;
var realValue = !isDate && typeof value === "object" && value !== null && "toJSON" in value ? value.toJSON() : value;
if (typeof realValue === "object" && realValue !== null && !isDate) {
var isMaxLvl = maxLvl >= 0 && lvl >= maxLvl;
var isCollapse = colAt >= 0 && lvl >= colAt;
var isArray = Array.isArray(realValue);
var items = isArray ? realValue : Object.keys(realValue);
if (lvl === 0) {
// root level
var rootCount = _createItemsCount(items.length);
// hide/show
var rootLink = _createLink(isArray ? "[" : "{");
if (items.length) {
rootLink.addEventListener("click", function() {
if (isMaxLvl) return;
rootLink.classList.toggle("collapsed");
rootCount.classList.toggle("hide");
// main list
outputParent.querySelector("ul").classList.toggle("hide");
});
if (isCollapse) {
rootLink.classList.add("collapsed");
rootCount.classList.remove("hide");
}
}
else {
rootLink.classList.add("empty");
}
rootLink.appendChild(rootCount);
outputParent.appendChild(rootLink); // output the rootLink
}
if (items.length && !isMaxLvl) {
var len = items.length - 1;
var ulList = document.createElement("ul");
ulList.setAttribute("data-level", lvl);
ulList.classList.add("type-" + (isArray ? "array" : "object"));
items.forEach(function(key, ind) {
var item = isArray ? key : value[key];
var li = document.createElement("li");
if (typeof item === "object") {
// null && date
if (!item || item instanceof Date) {
li.appendChild(document.createTextNode(isArray ? "" : key + ": "));
li.appendChild(createSimpleViewOf(item ? item : null, true));
}
// array & object
else {
var itemIsArray = Array.isArray(item);
var itemLen = itemIsArray ? item.length : Object.keys(item).length;
// empty
if (!itemLen) {
li.appendChild(document.createTextNode(key + ": " + (itemIsArray ? "[]" : "{}")));
}
else {
// 1+ items
var itemTitle = (typeof key === "string" ? key + ": " : "") + (itemIsArray ? "[" : "{");
var itemLink = _createLink(itemTitle);
var itemsCount = _createItemsCount(itemLen);
// maxLvl - only text, no link
if (maxLvl >= 0 && lvl + 1 >= maxLvl) {
li.appendChild(document.createTextNode(itemTitle));
}
else {
itemLink.appendChild(itemsCount);
li.appendChild(itemLink);
}
walkJSONTree(li, item, maxLvl, colAt, lvl + 1);
li.appendChild(document.createTextNode(itemIsArray ? "]" : "}"));
var list = li.querySelector("ul");
var itemLinkCb = function() {
itemLink.classList.toggle("collapsed");
itemsCount.classList.toggle("hide");
list.classList.toggle("hide");
};
// hide/show
itemLink.addEventListener("click", itemLinkCb);
// collapse lower level
if (colAt >= 0 && lvl + 1 >= colAt) {
itemLinkCb();
}
}
}
}
// simple values
else {
// object keys with key:
if (!isArray) {
li.appendChild(document.createTextNode(key + ": "));
}
// recursive
walkJSONTree(li, item, maxLvl, colAt, lvl + 1);
}
// add comma to the end
if (ind < len) {
li.appendChild(document.createTextNode(","));
}
ulList.appendChild(li);
}, this);
outputParent.appendChild(ulList); // output ulList
}
else if (items.length && isMaxLvl) {
var itemsCount = _createItemsCount(items.length);
itemsCount.classList.remove("hide");
outputParent.appendChild(itemsCount); // output itemsCount
}
if (lvl === 0) {
// empty root
if (!items.length) {
var itemsCount = _createItemsCount(0);
itemsCount.classList.remove("hide");
outputParent.appendChild(itemsCount); // output itemsCount
}
// root cover
outputParent.appendChild(document.createTextNode(isArray ? "]" : "}"));
// collapse
if (isCollapse) {
outputParent.querySelector("ul").classList.add("hide");
}
}
} else {
// simple values
outputParent.appendChild( createSimpleViewOf(value, isDate) );
}
};
/**
* Create simple value (no object|array).
*
* @param {Number|String|null|undefined|Date} value Input value
* @return {Element}
*/
function createSimpleViewOf(value, isDate) {
var spanEl = document.createElement("span");
var type = typeof value;
var asText = "" + value;
if (type === "string") {
asText = '"' + value + '"';
} else if (value === null) {
type = "null";
//asText = "null";
} else if (isDate) {
type = "date";
asText = value.toLocaleString();
}
spanEl.className = "type-" + type;
spanEl.textContent = asText;
return spanEl;
};
/**
* Create items count element.
*
* @param {Number} count Items count
* @return {Element}
*/
function _createItemsCount(count) {
var itemsCount = document.createElement("span");
itemsCount.className = "items-ph hide";
itemsCount.innerHTML = _getItemsTitle(count);
return itemsCount;
};
/**
* Create clickable link.
*
* @param {String} title Link title
* @return {Element}
*/
function _createLink(title) {
var linkEl = document.createElement("a");
linkEl.classList.add("list-link");
linkEl.href = "javascript:void(0)";
linkEl.innerHTML = title || "";
return linkEl;
};
/**
* Get correct item|s title for count.
*
* @param {Number} count Items count
* @return {String}
*/
function _getItemsTitle(count) {
var itemsTxt = count > 1 || count === 0 ? "items" : "item";
return (count + " " + itemsTxt);
};
return JSONViewer;
})(document);
</script>
<script>
window.onload = function(){
document.getElementById("encrypt_data").value = '';
document.getElementById("decrypt_data").value = '';
}
function encrypt() {
const key = document.getElementById("key").value;
const iv = document.getElementById("iv").value;
const data = document.getElementById("encrypt_data").value;
document.getElementById('encrypt_result').innerText = CryptoJS.AES.encrypt(
JSON.stringify(data),
CryptoJS.enc.Utf8.parse(key),
{
iv: CryptoJS.enc.Utf8.parse(iv),
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.ZeroPadding,
},
).toString()
}
function decrypt() {
document.querySelector("#json").innerHTML = '';
const key = document.getElementById("key").value;
const iv = document.getElementById("iv").value;
const data = document.getElementById("decrypt_data").value;
let result = CryptoJS.AES.decrypt(data, CryptoJS.enc.Utf8.parse(key), {
iv: CryptoJS.enc.Utf8.parse(iv),
padding: CryptoJS.pad.ZeroPadding,
}).toString(CryptoJS.enc.Utf8);
let json;
try {
json = JSON.parse(result);
var jsonViewer = new JSONViewer();
document.querySelector("#json").appendChild(jsonViewer.getContainer());
jsonViewer.showJSON(json, -1, -1);
}catch(e) {
}
// const wrapper = document.getElementById('decrypt_result');
document.getElementById('decrypt_result').innerText = result;
}
</script>
</body>
</html>

View File

@@ -4,6 +4,11 @@ import com.example.httpencryption.dto.TestDto;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.util.Base64Utils;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -12,19 +17,18 @@ class AESUtilTest {
AESUtil util = new AESUtil();
@Test
void encryptTest() throws JsonProcessingException {
void encryptTest() throws JsonProcessingException, NoSuchAlgorithmException {
ObjectMapper objectMapper = new ObjectMapper();
TestDto testDto = new TestDto("kim", 20);
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hashBytes = digest.digest("example".getBytes(StandardCharsets.UTF_8));
System.out.println(Base64Utils.encodeToString(hashBytes));
TestDto testDto = new TestDto("", 20);
String s = objectMapper.writeValueAsString(testDto);
String test = util.encrypt(s);
System.out.println("hello = " + test);
String decrypt = util.decrypt(test);
System.out.println("test = " + test);
assertEquals("{\"username\":\"kim\",\"age\":20}", decrypt);
assertEquals("{\"username\":\"\",\"age\":20}", decrypt);
}
}