[JAVA] Bouncy Castle로 LEA/ARIA 블록 암호화 하기
C/C++ 예제는 많은데...
예전부터 국산 암호화 기술에 관심은 많았는데, 이상할 정도로 Java
나 Kotlin
으로 작성된 예제는 그렇게 많지 않았다. 심지어 KISA
공식 홈페이지에서도 LEA
블록 암호화 알고리즘 정도나 암호화 모드별로 샘플 소스코드가 제공되고 있고, ARIA
는 엔진 코드만 제공되어 있을 정도로 Java
진영에서는 찬밥 취급이다. 아마도 국내에서는 해당 알고리즘들이 DRM
위주로만 사용되다 보니까 빠른 암복호화가 필요해서 그런 것 같다. (사실 그냥 AES 쓰면 되서 그렇다.) 그러던 와중에, Java
및 C#
에서 지원하는 Bouncy Castle
이라는 라이브러리를 발견하게 되었다. 이 라이브러리는 놀랍게도 전세계에서 개발된 대부분의 암호화 알고리즘을 지원하고 있으며, 당연하게도 ARIA
와 LEA
블록 암호화 알고리즘도 지원하고 있다.
📣
Bouncy Castle
라이브러리는 2024년 2월을 기준으로, Java 1.8 이상을 요구하고 있다.암호화 모드별로 암호화해보기
📣
밑의 예제 코드들은
LEA
알고리즘을 기반으로 작성되었다. ARIA
알고리즘을 사용하고 싶다면, new LEAEngine()
대신에 new ARIAEngine()
을 사용하면 된다.ECB
import org.bouncycastle.crypto.BufferedBlockCipher;
import org.bouncycastle.crypto.engines.LEAEngine;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import java.util.Arrays;
public void leaecb() {
byte[] messageBytes = "The lazy dog jumps over the brown fox!".getBytes();
// 16, 24, 32bytes 길이의 key를 사용할 수 있다
byte[] key = "0123456789012345".getBytes();
try {
byte[] encryptedData = encrypt(key, messageBytes);
byte[] originalMessage = decrypt(key, encryptedData);
System.out.println(new String(originalMessage));
} catch (Exception e) {
e.printStackTrace();
}
}
private static byte[] encrypt(byte[] key, byte[] plainText) throws Exception {
// block size가 16의 배수가 아닐경우, 암호화가 안될수 있으므로 항상 데이터를 패딩한다
BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new LEAEngine());
cipher.init(true, new KeyParameter(key));
byte[] outputData = new byte[cipher.getOutputSize(plainText.length)];
int tam = cipher.processBytes(plainText, 0, plainText.length, outputData, 0);
cipher.doFinal(outputData, tam);
return outputData;
}
private static byte[] decrypt(byte[] key, byte[] cipherText) throws Exception {
BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new LEAEngine());
cipher.init(false, new KeyParameter(key));
byte[] outputData = new byte[cipher.getOutputSize(cipherText.length)];
int tam = cipher.processBytes(cipherText, 0, cipherText.length, outputData, 0);
int finalLen = cipher.doFinal(outputData, tam);
return Arrays.copyOfRange(outputData, 0, finalLen + tam);
}
CBC
import org.bouncycastle.crypto.BufferedBlockCipher;
import org.bouncycastle.crypto.engines.LEAEngine;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import java.util.Arrays;
public void leacbc() {
byte[] messageBytes = "The lazy dog jumps over the brown fox!".getBytes();
byte[] key = "0123456789012345".getBytes();
byte[] iv = "0123456789012345".getBytes();
try {
byte[] encryptedData = encrypt(key, iv, messageBytes);
byte[] originalMessage = decrypt(key, iv, encryptedData);
System.out.println(new String(originalMessage));
} catch (Exception e) {
e.printStackTrace();
}
}
private static byte[] encrypt(byte[] key, byte[] iv, byte[] plainText) throws Exception {
BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(CBCBlockCipher.newInstance(new LEAEngine()));
cipher.init(true, new ParametersWithIV(new KeyParameter(key), iv));
byte[] outputData = new byte[cipher.getOutputSize(plainText.length)];
int tam = cipher.processBytes(plainText, 0, plainText.length, outputData, 0);
cipher.doFinal(outputData, tam);
return outputData;
}
private static byte[] decrypt(byte[] key, byte[] iv, byte[] cipherText) throws Exception {
BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(CBCBlockCipher.newInstance(new LEAEngine()));
cipher.init(false, new ParametersWithIV(new KeyParameter(key), iv));
byte[] outputData = new byte[cipher.getOutputSize(cipherText.length)];
int tam = cipher.processBytes(cipherText, 0, cipherText.length, outputData, 0);
int finalLen = cipher.doFinal(outputData, tam);
return Arrays.copyOfRange(outputData, 0, finalLen + tam);
}
CFB
import org.bouncycastle.crypto.engines.LEAEngine;
import org.bouncycastle.crypto.modes.CFBBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
public void leacfb() {
byte[] messageBytes = "The lazy dog jumps over the brown fox!".getBytes();
byte[] key = "0123456789012345".getBytes();
byte[] iv = "0123456789012345".getBytes();
byte[] encryptedData = encrypt(key, iv, messageBytes);
byte[] originalMessage = decrypt(key, iv, encryptedData);
System.out.println(new String(originalMessage));
}
public static byte[] encrypt(byte[] key, byte[] iv, byte[] plainText) {
// blockSize는 64 혹은 128만 입력 가능 (128 권장)
CFBModeCipher cipher = CFBBlockCipher.newInstance(new LEAEngine(), 128);
cipher.init(true, new ParametersWithIV(new KeyParameter(key), iv));
byte[] outputData = new byte[plainText.length];
cipher.processBytes(plainText, 0, plainText.length, outputData, 0);
return outputData;
}
public static byte[] decrypt(byte[] key, byte[] iv, byte[] cipherText) {
CFBModeCipher cipher = CFBBlockCipher.newInstance(new LEAEngine(), 128);
cipher.init(false, new ParametersWithIV(new KeyParameter(key), iv));
byte[] result = new byte[cipherText.length];
cipher.processBytes(cipherText, 0, cipherText.length, result, 0);
return result;
}
OFB
import org.bouncycastle.crypto.engines.LEAEngine;
import org.bouncycastle.crypto.modes.OFBBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
public void leaofb() {
byte[] messageBytes = "The lazy dog jumps over the brown fox!".getBytes();
byte[] key = "0123456789012345".getBytes();
byte[] iv = "0123456789012345".getBytes();
byte[] encryptedData = encrypt(key, iv, messageBytes);
byte[] originalMessage = decrypt(key, iv, encryptedData);
System.out.println(new String(originalMessage));
}
public static byte[] encrypt(byte[] key, byte[] iv, byte[] plainText) {
// blockSize는 8 혹은 16만 입력 가능 (16 권장)
// OFBBlockCipher는 newInstance() 메소드가 없다
OFBBlockCipher cipher = new OFBBlockCipher(new LEAEngine(), 16);
cipher.init(true, new ParametersWithIV(new KeyParameter(key), iv));
byte[] outputData = new byte[plainText.length];
cipher.processBytes(plainText, 0, plainText.length, outputData, 0);
return outputData;
}
public static byte[] decrypt(byte[] key, byte[] iv, byte[] cipherText) {
OFBBlockCipher cipher = new OFBBlockCipher(new LEAEngine(), 16);
cipher.init(false, new ParametersWithIV(new KeyParameter(key), iv));
byte[] result = new byte[cipherText.length];
cipher.processBytes(cipherText, 0, cipherText.length, result, 0);
return result;
}
CTS
import org.bouncycastle.crypto.engines.LEAEngine;
import org.bouncycastle.crypto.modes.CTSBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
public void leacts() {
byte[] messageBytes = "The lazy dog jumps over the brown fox!".getBytes();
byte[] key = "0123456789012345".getBytes();
try {
byte[] encryptedData = encrypt(key, messageBytes);
byte[] originalMessage = decrypt(key, encryptedData);
System.out.println(new String(originalMessage));
} catch (Exception e) {
e.printStackTrace();
}
}
public static byte[] encrypt(byte[] key, byte[] plainText) throws Exception {
CTSBlockCipher cipher = new CTSBlockCipher(new LEAEngine());
cipher.init(true, new KeyParameter(key));
byte[] outputData = new byte[plainText.length];
int tam = cipher.processBytes(plainText, 0, plainText.length, outputData, 0);
cipher.doFinal(outputData, tam);
return outputData;
}
public static byte[] decrypt(byte[] key, byte[] cipherText) throws Exception {
CTSBlockCipher cipher = new CTSBlockCipher(new LEAEngine());
cipher.init(false, new KeyParameter(key));
byte[] result = new byte[cipherText.length];
int finalLen = cipher.processBytes(cipherText, 0, cipherText.length, result, 0);
cipher.doFinal(result, finalLen);
return result;
}
CTR
import org.bouncycastle.crypto.engines.LEAEngine;
import org.bouncycastle.crypto.modes.CTRModeCipher;
import org.bouncycastle.crypto.modes.SICBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
public void leactr() {
byte[] messageBytes = "The lazy dog jumps over the brown fox!".getBytes();
byte[] key = "0123456789012345".getBytes();
byte[] iv = "0123456789012345".getBytes();
byte[] encryptedData = encrypt(key, iv, messageBytes);
byte[] originalMessage = decrypt(key, iv, encryptedData);
System.out.println(new String(originalMessage));
}
public static byte[] encrypt(byte[] key, byte[] iv, byte[] plainText) {
CTRModeCipher cipher = SICBlockCipher.newInstance(new LEAEngine());
cipher.init(true, new ParametersWithIV(new KeyParameter(key), iv));
byte[] outputData = new byte[plainText.length];
cipher.processBytes(plainText, 0, plainText.length, outputData, 0);
return outputData;
}
public static byte[] decrypt(byte[] key, byte[] iv, byte[] cipherText) {
CTRModeCipher cipher = SICBlockCipher.newInstance(new LEAEngine());
cipher.init(false, new ParametersWithIV(new KeyParameter(key), iv));
byte[] result = new byte[cipherText.length];
cipher.processBytes(cipherText, 0, cipherText.length, result, 0);
return result;
}
CCM
GCM
정리하며
꽤 오랜 기간동안 LEA
와 ARIA
알고리즘으로 암호화 하는 방법에 대해 조사해본 결과, Bouncy Castle
로 암호화 하는 것이 가장 좋다는 판단하에 해당 라이브러리를 활용하는 방법에 대해 모드 별로 다루어 보았다. 또한 위의 알고리즘 뿐만 아니라 대부분의 블록 암호화 알고리즘은 위의 코드에서 엔진만 변경하면 그대로 사용할 수 있으니 참고 바란다.