지난 포스트에서 DES64 알고리즘을 구현해봤는데 이번에는 구현한 알고리즘을 토대로 파일 암복호화를 진행해보도록 하겠습니다.
먼저 지난시간에 만든 코드에서 추가된 부분을 설명드리겠습니다.
public byte[] padding(byte[] input, int size){
if(size%8 != 0){
byte[] x = new byte[8-size%8];
for(int i=0; i<x.length; i++){
x[i] = 0;
}
input = addArray(input, x);
}
return input;
}
위의 코드는 입력 파일의 값이 8byte(64bit)로 딱 떨어지지 않을 때 암호화해주기 위해 블럭단위로 자를 수 있게 마지막에 "0"값을 추가하여 패딩을 진행하여주는 메소드입니다.
아래는 지난포스트에 만든 모든 소스코드입니다.
import java.util.Arrays;
public class DES64 {
private final byte[] initialPermutationTable = {58, 50, 42, 34, 26, 18, 10, 2,
60, 52, 44, 36, 28, 20, 12, 4,
62, 54, 46, 38, 30, 22, 14, 6,
64, 56, 48, 40, 32, 24, 16, 8,
57, 49, 41, 33, 25, 17, 9, 1,
59, 51, 43, 35, 27, 19, 11, 3,
61, 53, 45, 37, 29, 21, 13, 5,
63, 55, 47, 39, 31, 23, 15, 7};
private final byte[][][] sBox ={
{
{14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7},
{0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8},
{4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0},
{15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13}
},
{
{15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10},
{3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5},
{0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15},
{13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9}
},
{
{10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8},
{13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1},
{13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7},
{1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12}
},
{
{7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15},
{13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9},
{10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4},
{3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14}
},
{
{2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9},
{14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6},
{4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14},
{11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3}
},
{
{12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11},
{10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8},
{9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6},
{4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13}
},
{
{4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1},
{13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6},
{1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2},
{6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12}
},
{
{13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7},
{1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2},
{7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8},
{2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11}
}
};
private final byte[] permutationTable = {15, 6, 19, 20, 28, 11, 27, 16,
0, 14, 22, 25, 4, 17, 30, 9,
1, 7, 23, 13, 31, 26, 2, 8,
18, 12, 29, 5, 21, 10, 3, 24};
public byte[] padding(byte[] input, int size){
if(size%8 != 0){
byte[] x = new byte[8-size%8];
for(int i=0; i<x.length; i++){
x[i] = 0;
}
input = addArray(input, x);
}
return input;
}
public byte[] addArray(byte[] a, byte[] b){
byte[] temp = new byte[a.length + b.length];
for(int i=0; i<temp.length; i++){
if(i<a.length)
temp[i] = a[i];
else{
temp[i] = b[i-a.length];
}
}
return temp;
}
// DES64 암호화를 진행
public byte[] encryption(byte[] data, byte[] key){
KeyGenerator keyGenerator = new KeyGenerator();
byte[][] roundKey = keyGenerator.generateKey(key);
data = this.initialPermutation(data);
for(int i=0; i<16; i++){
byte[] leftData = Arrays.copyOfRange(data, 0, 32);
byte[] rightData = Arrays.copyOfRange(data, 32, data.length);
data = round(leftData, rightData, roundKey[i]);
}
// 16라운드는 left right 교체 x
byte[] leftData = Arrays.copyOfRange(data, 0, 32);
byte[] rightData = Arrays.copyOfRange(data, 32, data.length);
data = addArray(rightData, leftData);
data = inverseInitialPermutation(data);
return data;
}
// DES64 복호화를 진행 (key를 반대로 입력)
public byte[] decryption(byte[] data, byte[] key){
KeyGenerator keyGenerator = new KeyGenerator();
int count = 15;
data = this.initialPermutation(data);
byte[][] roundKey = keyGenerator.generateKey(key);
for(int i=0; i<16; i++){
byte[] leftData = Arrays.copyOfRange(data, 0, 32);
byte[] rightData = Arrays.copyOfRange(data, 32, data.length);
data = round(leftData, rightData, roundKey[count--]);
}
// 16라운드는 left right 교체 x
byte[] leftData = Arrays.copyOfRange(data, 0, 32);
byte[] rightData = Arrays.copyOfRange(data, 32, data.length);
data = addArray(rightData, leftData);
data = inverseInitialPermutation(data);
return data;
}
// initial Permutation
public byte[] initialPermutation(byte[] data){
byte[] temp = new byte[64];
for(int i=0; i<temp.length; i++){
temp[i] = data[this.initialPermutationTable[i]-1];
}
return temp;
}
// inverse initial Permutation
public byte[] inverseInitialPermutation(byte[] data){
byte[] temp = new byte[64];
for(int i=0; i<temp.length; i++){
temp[this.initialPermutationTable[i]-1] = data[i];
}
return temp;
}
// Round를 진행
public byte[] round(byte[] left, byte[] right, byte[] key){
byte[] resultLeft = right;
byte[] expentionRight = this.expantion(right);
expentionRight = this.keyXor(expentionRight, key);
right = this.substitution(expentionRight);
right = this.permutation(right);
byte[] resultRight = this.leftXorRight(left, right);
byte[] result = this.addArray(resultLeft, resultRight);
return result;
}
// round 내부 32bit의 right값을 48bit로 expansion
public byte[] expantion(byte[] right){
byte[] expentionRight = new byte[48];
int count = 0;
for(int i=0; i<48; i++){
if(i%6 == 0){
expentionRight[i] = right[ i==0 ? 31 : count-1];
}else if(i%6 == 5){
expentionRight[i] = right[ i==47 ? 0 : count];
}else{
expentionRight[i] = right[count++];
}
}
return expentionRight;
}
// roundKey와 expansion된 right값을 XOR연산
public byte[] keyXor(byte[] right, byte[] key){
byte[] result = new byte[48];
for(int i=0; i<48; i++){
result[i] = (byte) (right[i]^key[i]);
}
return result;
}
// S-Box를 통하여 substitution 진행
public byte[] substitution(byte[] right){
byte[] result = new byte[32];
int count = 0;
for(int i = 0 ;i<right.length;i = i+6 ){
String strCol = String.valueOf(right[i]) + String.valueOf(right[i+5]);
String strRow = String.valueOf(right[i+1]) + String.valueOf(right[i+2]) + String.valueOf(right[i+3]) + String.valueOf(right[i+4]);
int col = Integer.parseInt(strCol, 2);
int row = Integer.parseInt(strRow, 2);
int s = this.sBox[i/6][col][row];
String strS = Integer.toBinaryString(s);
for(int x = strS.length(); x< 4; x++)
strS = "0" + strS;
for(int j=0; j<4; j++) {
result[count++] = Byte.valueOf(String.valueOf(strS.charAt(j)));
}
}
return result;
}
// substitution된 값을 permutation 진행
public byte[] permutation(byte[] right){
byte[] temp = new byte[32];
for(int i=0; i<temp.length; i++){
temp[i] = right[this.permutationTable[i]];
}
return temp;
}
// permutation된 값을 left값과 XOR 진행
public byte[] leftXorRight(byte[] left, byte[] right){
byte[] temp = new byte[32];
for(int i=0; i<temp.length; i++){
temp[i] = (byte) (left[i]^right[i]);
}
return temp;
}
}
import java.util.Arrays;
public class KeyGenerator {
private final byte[] permutedChoice1Table = {57, 49, 41, 33, 25, 17, 9,
1, 58, 50, 42, 34, 26, 18,
10, 2, 59, 51, 43, 35, 27,
19, 11, 3, 60, 52, 44, 36,
63, 55, 47, 39, 31, 23, 15,
7, 62, 54, 46, 38, 30, 22,
14, 6, 61, 53, 45, 37, 29,
21, 13, 5, 28, 20, 12, 4};
private final byte[] permutedChoice2Table = {14, 17, 11, 24, 1, 5,
3, 28, 15, 6, 21, 10,
23, 19, 12, 4, 26, 8,
16, 7, 27, 20, 13, 2,
41, 52, 31, 37, 47, 55,
30, 40, 51, 45, 33, 48,
44, 49, 39, 56, 34, 53,
46, 42, 50, 36, 29, 32};
// 64bit로 들어온 키값을 56bit로 permuted choice 진행
public byte[] permutedChoice1(byte[] key){
byte[] temp = new byte[56];
for(int i=0; i<this.permutedChoice1Table.length; i++){
temp[i] = key[this.permutedChoice1Table[i]-1];
}
return temp;
}
// shift된 값을 48bit로 permuted choice 진행
public byte[] permutedChoice2(byte[] key){
byte[] temp = new byte[48];
for(int i=0; i<this.permutedChoice2Table.length; i++){
temp[i] = key[this.permutedChoice2Table[i]-1];
}
return temp;
}
// 좌우로 분리시킨 key값을 shift
public byte[] shiftKey(byte[] key, int round){
byte[] temp = new byte[28];
if(round == 1 || round == 2 ||round == 9 ||round == 16) {
temp[27] = key[0];
for(int i = 0; i<temp.length-1; i++)
temp[i] = key[i+1];
}else{
temp[26] = key[0];
temp[27] = key[1];
for(int i=0; i<temp.length-2; i++)
temp[i] = key[i+2];
}
return temp;
}
// 모든 round key를 만든다.
public byte[][] generateKey(byte[] initKey){
byte[][] key = new byte[16][48];
byte[] choice = this.permutedChoice1(initKey);
for(int i=1; i<17; i++){
byte[] leftKey = Arrays.copyOfRange(choice, 0, 28);
byte[] rightKey = Arrays.copyOfRange(choice, 28, choice.length);
leftKey = shiftKey(leftKey, i);
rightKey = shiftKey(rightKey, i);
byte[] choice2 = this.permutedChoice2(addArray(leftKey, rightKey));
key[i-1] = choice2;
}
return key;
}
// a + b
public byte[] addArray(byte[] a, byte[] b){
byte[] temp = new byte[a.length + b.length];
for(int i=0; i<temp.length; i++){
if(i<a.length)
temp[i] = a[i];
else{
temp[i] = b[i-a.length];
}
}
return temp;
}
}
먼저 파일의 입력값을 FileInputStream으로 받아와줍니다.
File file = new File("input Data 경로");
FileInputStream fileInputStream = new FileInputStream(file);
int size = isFileSize(file);
byte[] input = new byte[ size ];
byte[] text = new byte[0];
// 대칭키 값 (64bit)
byte[] initKey = {1,1,1,1,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1};
fileInputStream.read(input);
System.out.println("input size : "+size);
// 64bit(8byte)단위로 떨어질 수 있게 패딩해준다.
input = des64.padding(input, size);
size = input.length;
initial Key값을 선언해주고 Inputdata가 64bit(8Byte)로 떨어질 수 있도록 Padding을 진행하여 줍니다.
byte[] plaintext = new byte[64];
// input값의 어디까지 변환했는지를 나타낸다.
int countInput = 0;
for(int x=0;x*8<size; x++){
System.out.println("x : " + x);
// 바이트 단위의 값을 64비트로 쪼개서 byte[]에 담아 암복호화 진행
for (int count = 0; count < 64; ) {
String binary = Integer.toBinaryString(input[countInput++]);
// 음수 10진수값을 바이너리 스트링값으로 변환시 2의 보수 32비트 값이 나온다. 따라서 마지막 8자리만 사용
if(binary.length() > 8){
binary = binary.substring(binary.length()-8, binary.length());
}
// 어떤 값이 들어간지를 체크
System.out.println(binary+" : "+ (input[countInput-1] ==0 ? "padding":(char)input[countInput-1]) );
for (int j = 0; j < 8; j++) {
// 8비트 이하의 바이너리 스트링일 때 8비트로 만들어주기 위해 앞에 0을 추가해준다.
if (binary.length() < 8) {
for (int z = Integer.toBinaryString(input[countInput-1]).length(); z < 8; z++)
binary = "0" + binary;
}
// 8비트 값을 plaintext에 넣는다.
plaintext[count++] = Byte.valueOf(String.valueOf(binary.charAt(j)));
}
}
if(choice == 1){
plaintext = des64.encryption(plaintext, initKey);
}else if(choice == 2){
plaintext = des64.decryption(plaintext, initKey);
}else{
plaintext = des64.encryption(plaintext, initKey);
plaintext = des64.decryption(plaintext, initKey);
}
// 블록단위로 암호화 혹은 복호화된 값을 이어붙여준다.
text = addArray(text, plaintext);
}
이렇게 들어온 데이터를 64bit단위로 암복호화를 진행합니다.
저는 byte[64]를 64bit로 사용했기 때문에 들어온 데이터를 byte[]값으로 변환해주는 작업을 진행합니다.
inputdata는 1Byte 데이터로 값이 담겨있기 때문에 해당 데이터를 Integer.toBinaryString을 통하여 Bit단위로 변환해주고
8Bit에 맞도록 값을 잘라주거나 앞에 0값을 붙여 맞춰줍니다.
plaintext[64]에 64비트 값을 넣어주고 사용자의 입력에 따라 encryption할지 decryption할지 선택 후 알고리즘을 진행해주고
text[size]값에 이어붙여줍니다.
byte[] outText = new byte[0];
// 바이너리 형태로 text byte[]에 저장되어 있는 값을 64비트씩 나누어 8비트 -> 1바이트 값으로 변환 후 담아준다.
for(int i=0; i<text.length/64; i++){
outText = addArray(outText, binaryToBytes(Arrays.copyOfRange(text, i*64, i*64+64)));
}
// 출력
FileOutputStream fos = new FileOutputStream("출력 데이터 위치", false);
fos.write(outText);
fos.close();
이렇게 만들어진 text값은 byte[size]에 bit단위로 담겨있기 때문에 8bit씩 잘라서 ASCII 값으로 변환하고 outText[]에 담아 출력값으로 반환합니다.
// 바이너리 값(8비트)를 1바이트 ASCII 값으로 변환
public static byte[] binaryToBytes(byte[] data){
byte[] value = new byte[8];
for(int i=0; i<data.length/8; i++){
String temp = "";
for(int j=0; j<8; j++) {
temp += data[i * 8 + j];
}
System.out.print(temp+" : ");
value[i] = (byte) Integer.parseInt(temp, 2);
System.out.println(value[i]);
}
return value;
}
// file의 사이즈를 리턴 (바이트 단위)
public static int isFileSize(File f) {
int retSize = 0;
try {
long fileSize = f.length();
if (fileSize >= 2147483649L) {
throw new Exception("long to int casting Range Over Error");
}
retSize = Long.valueOf(fileSize).intValue();
} catch (Exception e) {
e.printStackTrace();
}
return retSize;
}
// a + b
public static byte[] addArray(byte[] a, byte[] b){
byte[] temp = new byte[a.length + b.length];
for(int i=0; i<temp.length; i++){
if(i<a.length)
temp[i] = a[i];
else{
temp[i] = b[i-a.length];
}
}
return temp;
}
위 코드는 진행하면서 사용한 부가 메소드들입니다.
이번 알고리즘 구현 및 파일 암복호화를 진행하면서 DES에 대해서 자세하게 알게되었고 자바 파일 입출력 및 여러 연산들을 다시한번 복습해볼 수 있는 좋은 기회였던 것 같습니다.
아래는 전체코드이며 질문사항 및 잘못된 부분을 발견하시면 댓글 작성해주시면 감사하겠습니다!!.
읽어주셔서 감사합니다!!
import java.io.*;
import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static void main(String[] args) throws IOException {
DES64 des64 = new DES64();
Scanner sc = new Scanner(System.in);
File file = new File("input");
FileInputStream fileInputStream = new FileInputStream(file);
int size = isFileSize(file);
byte[] input = new byte[ size ];
byte[] text = new byte[0];
// 대칭키 값 (64bit)
byte[] initKey = {1,1,1,1,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1};
fileInputStream.read(input);
System.out.println("input size : "+size);
// 64bit(8byte)단위로 떨어질 수 있게 패딩해준다.
input = des64.padding(input, size);
size = input.length;
int choice = 0;
System.out.println("\nsize after padding : "+ size);
System.out.println("\n1. encryption");
System.out.println("2. decryption");
System.out.print("입력하시오. >");
choice = sc.nextInt();
byte[] plaintext = new byte[64];
// input값의 어디까지 변환했는지를 나타낸다.
int countInput = 0;
for(int x=0;x*8<size; x++){
System.out.println("x : " + x);
// 바이트 단위의 값을 64비트로 쪼개서 byte[]에 담아 암복호화 진행
for (int count = 0; count < 64; ) {
String binary = Integer.toBinaryString(input[countInput++]);
// 음수 10진수값을 바이너리 스트링값으로 변환시 2의 보수 32비트 값이 나온다. 따라서 마지막 8자리만 사용
if(binary.length() > 8){
binary = binary.substring(binary.length()-8, binary.length());
}
// 어떤 값이 들어간지를 체크
System.out.println(binary+" : "+ (input[countInput-1] ==0 ? "padding":(char)input[countInput-1]) );
for (int j = 0; j < 8; j++) {
// 8비트 이하의 바이너리 스트링일 때 8비트로 만들어주기 위해 앞에 0을 추가해준다.
if (binary.length() < 8) {
for (int z = Integer.toBinaryString(input[countInput-1]).length(); z < 8; z++)
binary = "0" + binary;
}
// 8비트 값을 plaintext에 넣는다.
plaintext[count++] = Byte.valueOf(String.valueOf(binary.charAt(j)));
}
}
if(choice == 1){
plaintext = des64.encryption(plaintext, initKey);
}else if(choice == 2){
plaintext = des64.decryption(plaintext, initKey);
}else{
plaintext = des64.encryption(plaintext, initKey);
plaintext = des64.decryption(plaintext, initKey);
}
// 블록단위로 암호화 혹은 복호화된 값을 이어붙여준다.
text = addArray(text, plaintext);
}
byte[] outText = new byte[0];
// 바이너리 형태로 text byte[]에 저장되어 있는 값을 64비트씩 나누어 8비트 -> 1바이트 값으로 변환 후 담아준다.
for(int i=0; i<text.length/64; i++){
outText = addArray(outText, binaryToBytes(Arrays.copyOfRange(text, i*64, i*64+64)));
}
// 출력
FileOutputStream fos = new FileOutputStream("output", false);
fos.write(outText);
fos.close();
}
// 바이너리 값(8비트)를 1바이트 ASCII 값으로 변환
public static byte[] binaryToBytes(byte[] data){
byte[] value = new byte[8];
for(int i=0; i<data.length/8; i++){
String temp = "";
for(int j=0; j<8; j++) {
temp += data[i * 8 + j];
}
System.out.print(temp+" : ");
value[i] = (byte) Integer.parseInt(temp, 2);
System.out.println(value[i]);
}
return value;
}
// file의 사이즈를 리턴 (바이트 단위)
public static int isFileSize(File f) {
int retSize = 0;
try {
long fileSize = f.length();
if (fileSize >= 2147483649L) {
throw new Exception("long to int casting Range Over Error");
}
retSize = Long.valueOf(fileSize).intValue();
} catch (Exception e) {
e.printStackTrace();
}
return retSize;
}
// a + b
public static byte[] addArray(byte[] a, byte[] b){
byte[] temp = new byte[a.length + b.length];
for(int i=0; i<temp.length; i++){
if(i<a.length)
temp[i] = a[i];
else{
temp[i] = b[i-a.length];
}
}
return temp;
}
}
'JAVA' 카테고리의 다른 글
[JAVA] 대기중인 스레드를 RUNNABLE로 깨우려면 어떻게 해야할까? (0) | 2024.09.21 |
---|---|
[JAVA] 스레드의 상태 (getState()) (3) | 2024.09.02 |
[JAVA] 자바에서 스레드를 만들고 사용하는 방법 (1) | 2024.08.30 |
[JAVA] JVM, 자바의 메모리 구조 (0) | 2024.08.30 |
DES알고리즘(+Key Generator) 구현 (0) | 2023.10.02 |