암호화 – SEED / ARIA / AES
몇가지 암호화 기법을 정리해 보려합니다.
SEED/ARIA 는 국산 암호화 알고리즘이고,
AES 는 미국에서 개발된 암호화 알고리즘이다.
기초지식 : 블록 암호화에서의 운영 모드 및 패딩
암호화 기법은 데이타를 일정 크기(8byte 또는 16byte) 로 쪼갠 후,
각각의 조각을 암호화하는 방법에 대해서만 적용된다.
따라서, 한 조각(8byte 또는 16byte)의 복호화만 성공하게 되면,
나머지 전부의 복호화도 성공한다는 문제가 생긴다.
여기서 운영 모드 라는 기법이 생긴다.
예를 들어 CBC(Cipher Block Chaining) Mode 같은 경우,
각각의 블록을 XOR 연산을 해서 2중으로 암호화를 진행한다.
또 하나의 문제가 발생하는데 일정 크기(8byte 또는 16byte) 로 데이타를 쪼갤 때,
해당 바이트로 나누어 떨어지지 않는 데이타는 어떻게 암호화할 것인가의 문제인다.
여기서 부족한 부분을 특정 문자로 채워서 일정크기로 맞춰주는 작업을 하게되는데,
이것을 패딩이라고 한다.
조심할 부분은 16바이트와 같이 사이즈가 딱 떨어지는 경우,
패딩을 하지 않는 것이 아니라,
추가로 16바이트를 패딩한다는 것이다.
ARIA / SEED
공공기관 납품이 필요한 경우 써야 하는 경우가 생긴다.
JAVA
dependencies {
implementation group: 'org.bouncycastle', name: 'bcprov-jdk18on', version: '1.78.1'
}
위 라이브러리에서 ARIA/SEED 를 지원해 주고 있다.
ARIA/CBC/PKCS7Padding
부분만 변경해 주면 암호화방식/운영모드/패딩을 변경해 줄 수 있다.
public class AriaUtil {
private static final String privateKey_256 = MyConstants.PRIVATE_KEY;
public static String ariaCBCEncode(String plainText) throws Exception {
byte[] rawKeyBytes = privateKey_256.getBytes(StandardCharsets.UTF_8);
byte[] keyBytes = new byte[16];
System.arraycopy(rawKeyBytes, 0, keyBytes, 0, Math.min(rawKeyBytes.length, keyBytes.length));
SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "ARIA");
IvParameterSpec IV = new IvParameterSpec(keyBytes);
Cipher c = Cipher.getInstance("ARIA/CBC/PKCS7Padding");
c.init(Cipher.ENCRYPT_MODE, secretKey, IV);
byte[] encryptionByte = c.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encryptionByte);
}
public static String ariaCBCDecode(String encodeText) throws Exception {
byte[] rawKeyBytes = privateKey_256.getBytes(StandardCharsets.UTF_8);
byte[] keyBytes = new byte[16];
System.arraycopy(rawKeyBytes, 0, keyBytes, 0, Math.min(rawKeyBytes.length, keyBytes.length));
SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "ARIA");
IvParameterSpec IV = new IvParameterSpec(keyBytes);
byte[] decryptionByte = Base64.getDecoder().decode(encodeText);
Cipher c = Cipher.getInstance("ARIA/CBC/PKCS7Padding");
c.init(Cipher.DECRYPT_MODE, secretKey, IV);
return new String(c.doFinal(decryptionByte), StandardCharsets.UTF_8);
}
}
public class SeedUtil {
private static final String privateKey_256 = MyConstants.PRIVATE_KEY;
public static String seedCBCEncode(String plainText) throws Exception {
byte[] rawKeyBytes = privateKey_256.getBytes(StandardCharsets.UTF_8);
byte[] keyBytes = new byte[16];
System.arraycopy(rawKeyBytes, 0, keyBytes, 0, Math.min(rawKeyBytes.length, keyBytes.length));
SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "SEED");
IvParameterSpec IV = new IvParameterSpec(keyBytes);
Cipher c = Cipher.getInstance("SEED/CBC/PKCS7Padding");
c.init(Cipher.ENCRYPT_MODE, secretKey, IV);
byte[] encryptionByte = c.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encryptionByte);
}
public static String seedCBCDecode(String encodeText) throws Exception {
byte[] rawKeyBytes = privateKey_256.getBytes(StandardCharsets.UTF_8);
byte[] keyBytes = new byte[16];
System.arraycopy(rawKeyBytes, 0, keyBytes, 0, Math.min(rawKeyBytes.length, keyBytes.length));
SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "SEED");
IvParameterSpec IV = new IvParameterSpec(keyBytes);
byte[] decryptionByte = Base64.getDecoder().decode(encodeText);
Cipher c = Cipher.getInstance("SEED/CBC/PKCS7Padding");
c.init(Cipher.DECRYPT_MODE, secretKey, IV);
return new String(c.doFinal(decryptionByte), StandardCharsets.UTF_8);
}
}
public class Main {
public static void main(String[] args) throws Exception {
String plainText = "Hello world! 안녕하세요.";
String encData;
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
Security.addProvider(new BouncyCastleProvider());
}
encData = AriaUtil.ariaCBCEncode(plainText);
System.out.println("# AriaUtil 평문 데이터 : " + plainText);
System.out.println("# AriaUtil 암호화 데이터 : " + encData);
System.out.println("# AriaUtil 복호화 데이터 : " + AriaUtil.ariaCBCDecode(encData));
encData = SeedUtil.seedCBCEncode(plainText);
System.out.println("# SeedUtil 평문 데이터 : " + plainText);
System.out.println("# SeedUtil 암호화 데이터 : " + encData);
System.out.println("# SeedUtil 복호화 데이터 : " + SeedUtil.seedCBCDecode(encData));
}
}
C# – 문자열
NuGet\Install-Package BouncyCastle.Cryptography -Version 2.4.0
public class SeedUtil
{
// SEED128(128 bit == 16 byte)
public static string PRIVATE_KEY = "1234567890123456"; // 16byte
public static string Encrypt(string plaintxt, string keys)
{
if (string.IsNullOrEmpty(plaintxt)) return plaintxt;
var skeyBytes = Encoding.UTF8.GetBytes(keys);
var iv = new byte[16];
Array.Copy(skeyBytes, iv, Math.Min(iv.Length, skeyBytes.Length));
IBufferedCipher cipher = CipherUtilities.GetCipher("SEED/CBC/PKCS7Padding");
KeyParameter key = ParameterUtilities.CreateKeyParameter("SEED", skeyBytes);
cipher.Init(true, new ParametersWithIV(key, iv));
byte[] encrypted = cipher.DoFinal(Encoding.UTF8.GetBytes(plaintxt));
return Convert.ToBase64String(encrypted);
}
public static string Decrypt(string encrypted, string keys)
{
if (string.IsNullOrEmpty(encrypted)) return encrypted;
var skeyBytes = Encoding.UTF8.GetBytes(keys);
var iv = new byte[16];
Array.Copy(skeyBytes, iv, Math.Min(iv.Length, skeyBytes.Length));
IBufferedCipher cipher = CipherUtilities.GetCipher("SEED/CBC/PKCS7Padding");
KeyParameter key = ParameterUtilities.CreateKeyParameter("SEED", skeyBytes);
cipher.Init(false, new ParametersWithIV(key, iv));
byte[] dec = cipher.DoFinal(Convert.FromBase64String(encrypted));
return Encoding.UTF8.GetString(dec);
}
}
public class AriaUtil
{
// ARIA128(128 bit == 16 byte)
// ARIA192(192 bit == 24 byte)
// ARIA256(256 bit == 32 byte)
public static string PRIVATE_KEY = "12345678901234567890123456789012"; // 32byte
public static string Encrypt(string plaintxt, string keys)
{
if (string.IsNullOrEmpty(plaintxt)) return plaintxt;
var skeyBytes = Encoding.UTF8.GetBytes(keys);
var iv = new byte[16];
Array.Copy(skeyBytes, iv, Math.Min(iv.Length, skeyBytes.Length));
IBufferedCipher cipher = CipherUtilities.GetCipher("ARIA/CBC/PKCS7Padding");
KeyParameter key = ParameterUtilities.CreateKeyParameter("ARIA", skeyBytes);
cipher.Init(true, new ParametersWithIV(key, iv));
byte[] encrypted = cipher.DoFinal(Encoding.UTF8.GetBytes(plaintxt));
return Convert.ToBase64String(encrypted);
}
public static string Decrypt(string encrypted, string keys)
{
if (string.IsNullOrEmpty(encrypted)) return encrypted;
var skeyBytes = Encoding.UTF8.GetBytes(keys);
var iv = new byte[16];
Array.Copy(skeyBytes, iv, Math.Min(iv.Length, skeyBytes.Length));
IBufferedCipher cipher = CipherUtilities.GetCipher("ARIA/CBC/PKCS7Padding");
KeyParameter key = ParameterUtilities.CreateKeyParameter("ARIA", skeyBytes);
cipher.Init(false, new ParametersWithIV(key, iv));
byte[] dec = cipher.DoFinal(Convert.FromBase64String(encrypted));
return Encoding.UTF8.GetString(dec);
}
}
string encText = AriaUtil.Encrypt(plainText, AriaUtil.PRIVATE_KEY);
string decText = AriaUtil.Decrypt(encText, AriaUtil.PRIVATE_KEY);
Console.WriteLine("AriaUtil 평문 : " + plainText);
Console.WriteLine("AriaUtil 암호화 : " + encText);
Console.WriteLine("AriaUtil 복호화 : " + decText);
encText = SeedUtil.Encrypt(plainText, SeedUtil.PRIVATE_KEY);
decText = SeedUtil.Decrypt(encText, SeedUtil.PRIVATE_KEY);
Console.WriteLine("SeedUtil 평문 : " + plainText);
Console.WriteLine("SeedUtil 암호화 : " + encText);
Console.WriteLine("SeedUtil 복호화 : " + decText);
C# – 파일
파일인 경우 사이즈가 크므로 조금 다르게 작성해야 한다.
public class AriaUtil
{
// ARIA128(128 bit == 16 byte)
// ARIA192(192 bit == 24 byte)
// ARIA256(256 bit == 32 byte)
public static string PRIVATE_KEY = "12345678901234567890123456789012"; // 32byte
public static IBufferedCipher GetCipher(bool isEncMode, string keys)
{
var skeyBytes = Encoding.UTF8.GetBytes(keys);
var iv = new byte[16];
Array.Copy(skeyBytes, iv, Math.Min(iv.Length, skeyBytes.Length));
IBufferedCipher cipher = CipherUtilities.GetCipher("ARIA/CBC/PKCS7Padding");
KeyParameter key = ParameterUtilities.CreateKeyParameter("ARIA", skeyBytes);
cipher.Init(isEncMode, new ParametersWithIV(key, iv));
return cipher;
}
}
string filename_src = "plain.txt";
string filename_enc = "enc.txt";
string filename_dec = "dec.txt";
IBufferedCipher cipherEnc = AriaUtil.GetCipher(true, AriaUtil.PRIVATE_KEY);
IBufferedCipher cipherDec = AriaUtil.GetCipher(false, AriaUtil.PRIVATE_KEY);
FileStream file_enc = new FileStream(filename_enc, FileMode.Create);
using (FileStream fs = new FileStream(filename_src, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: BUFFERSIZE * 4, useAsync: true))
{
long fileSize = fs.Length;
long fileRead = 0;
int blockSize = cipherEnc.GetBlockSize();
byte[] buffer = new byte[blockSize * 128]; // 16 * 128 = 2048
do
{
int bytesRead = fs.Read(buffer, 0, buffer.Length);
if (bytesRead == 0) { break; }
fileRead = fileRead + bytesRead;
byte[] outputBytes = new byte[cipherEnc.GetOutputSize(bytesRead)];
if (outputBytes.Length <= buffer.Length || fileSize == fileRead)
{
// last read
int processSize = cipherEnc.ProcessBytes(buffer, 0, bytesRead, outputBytes, 0);
file_enc.Write(outputBytes, 0, processSize);
int finalSize = cipherEnc.DoFinal(outputBytes, processSize);
file_enc.Write(outputBytes, processSize, finalSize);
break;
}
else
{
int processSize = cipherEnc.ProcessBytes(buffer, 0, bytesRead, outputBytes, 0);
file_enc.Write(outputBytes, 0, processSize);
}
} while (true);
}
file_enc.Flush();
file_enc.Close();
FileStream file_dec = new FileStream(filename_dec, FileMode.Create);
using (FileStream fs = new FileStream(filename_enc, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: BUFFERSIZE * 4, useAsync: true))
{
long fileSize = fs.Length;
long fileRead = 0;
int blockSize = cipherDec.GetBlockSize();
byte[] buffer = new byte[blockSize * 128]; // 16 * 128 = 2048
do
{
int bytesRead = fs.Read(buffer, 0, buffer.Length);
if (bytesRead == 0) { break; }
fileRead = fileRead + bytesRead;
byte[] outputBytes = new byte[cipherDec.GetOutputSize(bytesRead)];
if (outputBytes.Length < buffer.Length || fileSize == fileRead)
{
// last read
int processSize = cipherDec.ProcessBytes(buffer, 0, bytesRead, outputBytes, 0);
file_dec.Write(outputBytes, 0, processSize);
int finalSize = cipherDec.DoFinal(outputBytes, processSize);
file_dec.Write(outputBytes, processSize, finalSize);
break;
}
else
{
int processSize = cipherDec.ProcessBytes(buffer, 0, bytesRead, outputBytes, 0);
file_dec.Write(outputBytes, 0, processSize);
}
} while (true);
}
file_dec.Flush();
file_dec.Close();
AES
전 세계적으로 많이 쓰이며,
JAVA / C# 등 기본 라이브러리에 포함되어 있는 경우가 많다.
JAVA – 문자열
구 버전의 JDK 에서는 AES256 사용을 위해 별도 설정이 필요하다.
public class MyConstants {
// AES128(128 bit == 16 byte)
// AES192(192 bit == 24 byte)
// AES256(256 bit == 32 byte)
public static final String PRIVATE_KEY = "12345678901234567890123456789012"; // 32byte
}
AES 암호화를 사용하는 것 이외에 운영모드(CBC) 및 패딩(PKCS5Padding) 을 지정해 주어야 한다.
CBC 가 보안이 가장 강하다고 알려져 있다.
JDK 에서는 PKCS5Padding 라고 명칭이 되어 있지만 실제로는 PKCS7Padding 로 작동한다.
Base64 로 인코딩/디코딩하는 것은 AES 의 범위에서 벗어난다.
즉, AES 암호화를 사용한다는 것과 KEY 를 아는 것으로 부족하고,
어떤 방식으로 암호화되어 있는지를 알아야 복호화를 진행할 수 있다.
public class AesUtil {
private static final String privateKey_256 = MyConstants.PRIVATE_KEY;
public static String aesCBCEncode(String plainText) throws Exception {
byte[] rawKeyBytes = privateKey_256.getBytes(StandardCharsets.UTF_8);
byte[] keyBytes = new byte[16];
System.arraycopy(rawKeyBytes, 0, keyBytes, 0, Math.min(rawKeyBytes.length, keyBytes.length));
SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES");
IvParameterSpec IV = new IvParameterSpec(keyBytes);
// PKCS5가 PKCS7로 수행되지만, PKCS5란 이름으로 JCA(Java Cryptography Architecture)에 정의되어 있다
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
c.init(Cipher.ENCRYPT_MODE, secretKey, IV);
byte[] encryptionByte = c.doFinal(plainText.getBytes("UTF-8"));
return Base64.getEncoder().encodeToString(encryptionByte);
}
public static String aesCBCDecode(String encodeText) throws Exception {
byte[] rawKeyBytes = privateKey_256.getBytes(StandardCharsets.UTF_8);
byte[] keyBytes = new byte[16];
System.arraycopy(rawKeyBytes, 0, keyBytes, 0, Math.min(rawKeyBytes.length, keyBytes.length));
SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES");
IvParameterSpec IV = new IvParameterSpec(keyBytes);
byte[] decryptionByte = Base64.getDecoder().decode(encodeText);
// PKCS5가 PKCS7로 수행되지만, PKCS5란 이름으로 JCA(Java Cryptography Architecture)에 정의되어 있다
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
c.init(Cipher.DECRYPT_MODE, secretKey, IV);
return new String(c.doFinal(decryptionByte), "UTF-8");
}
}
public class Main {
public static void main(String[] args) throws Exception {
String plainText = "Hello world! 안녕하세요.";
String encData = AesUtil.aesCBCEncode(plainText);
System.out.println("# 평문 데이터 : " + plainText);
System.out.println("# 암호화 데이터 : " + encData);
System.out.println("# 복호화 데이터 : " + AesUtil.aesCBCDecode(encData));
}
}
JAVA – 파일
Bouncy Castle 라이브러리에서 AES 암호화도 지원하므로,
Bouncy Castle 라이브러리를 이용해 작성합니다.
public class AesUtil {
public static final String PRIVATE_KEY = "12345678901234567890123456789012"; // 32byte
public static Cipher GetCipher(int isEncMode) throws Exception
{
byte[] skeyBytes = privateKey_128.getBytes(StandardCharsets.UTF_8);
byte[] iv = new byte[16];
System.arraycopy(skeyBytes, 0, iv, 0, Math.min(skeyBytes.length, iv.length));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
SecretKeySpec secretKey = new SecretKeySpec(skeyBytes, "AES");
IvParameterSpec IV = new IvParameterSpec(iv);
cipher.init(isEncMode, secretKey, IV);
return cipher;
}
public static boolean encrypt(Cipher cipherEnc, String plainFileName, String encFileName) {
return doEncryptDecrypt(cipherEnc, plainFileName, encFileName);
}
public static boolean decrypt(Cipher cipherDec, String encFileName, String plainFileName) {
return doEncryptDecrypt(cipherDec, encFileName, plainFileName);
}
public static boolean doEncryptDecrypt(Cipher cipher, String srcFileName, String destFileName) {
byte[] buffer = new byte[2048];
try {
FileInputStream srcFile = new FileInputStream(srcFileName);
FileOutputStream destFile = new FileOutputStream(destFileName);
int bytesRead;
byte[] outputBytes;
while ((bytesRead = srcFile.read(buffer)) != -1) {
outputBytes = cipher.update(buffer, 0, bytesRead);
if (outputBytes != null) {
destFile.write(outputBytes);
}
}
outputBytes = cipher.doFinal();
if (outputBytes != null) {
destFile.write(outputBytes);
}
destFile.flush();
destFile.close();
srcFile.close();
} catch (SecurityException | IOException | IllegalBlockSizeException | BadPaddingException e) {
return false;
}
return true;
}
}
Security.addProvider(new BouncyCastleProvider());
Cipher cipherEnc = AesUtil.GetCipher(Cipher.ENCRYPT_MODE);
AesUtil.encrypt(cipherEnc, "D:\\work\\plain.gz", "D:\\work\\enc.gz");
Cipher cipherDec = AesUtil.GetCipher(Cipher.DECRYPT_MODE);
AesUtil.decrypt(cipherDec, "D:\\work\\enc.gz", "D:\\work\\dec.gz");
C#
public class AesUtil
{
public AesUtil()
{
}
// AES128(128 bit == 16 byte)
// AES192(192 bit == 24 byte)
// AES256(256 bit == 32 byte)
public static string PRIVATE_KEY = "12345678901234567890123456789012"; // 32byte
public static string Encrypt(string plaintxt, string keys)
{
if (string.IsNullOrEmpty(plaintxt)) return plaintxt;
var b = Encoding.UTF8.GetBytes(plaintxt);
var encrypted = getAes(keys).CreateEncryptor().TransformFinalBlock(b, 0, b.Length);
return Convert.ToBase64String(encrypted);
}
public static string Decrypt(string encrypted, string keys)
{
if (string.IsNullOrEmpty(encrypted)) return encrypted;
var b = Convert.FromBase64String(encrypted);
var decrypted = getAes(keys).CreateDecryptor().TransformFinalBlock(b, 0, b.Length);
return Encoding.UTF8.GetString(decrypted);
}
public static Aes getAes(string keys)
{
var keyBytes = new byte[16];
var skeyBytes = Encoding.UTF8.GetBytes(keys);
Array.Copy(skeyBytes, keyBytes, Math.Min(keyBytes.Length, skeyBytes.Length));
Aes aes = Aes.Create();
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
aes.KeySize = 256;
aes.Key = keyBytes;
aes.IV = keyBytes;
return aes;
}
}
string plainText = "Hello, World! 안녕하세요.";
AesUtil aes = new AesUtil();
string encText = AesUtil.Encrypt(plainText, AesUtil.PRIVATE_KEY);
string decText = AesUtil.Decrypt(encText, AesUtil.PRIVATE_KEY);
Console.WriteLine("평문 : " + plainText);
Console.WriteLine("암호화 : " + encText);
Console.WriteLine("복호화 : " + decText);
https://seed.kisa.or.kr/kisa/Board/19/detailView.do
aria java from kisa
https://seed.kisa.or.kr/kisa/Board/17/detailView.do
seed java from kisa