JAVA와 .NET 간의 AES 암호화 연동
전에도 누군가 C언어와의 연동을 질문한 적이 있는데,
c# aes 128 암복호화 관련 문의드립니다.
; https://www.sysnet.pe.kr/3/0/4767
일단 저는 이제서야 Java와의 연동 테스트를 해보게 되는군요. ^^
우선, Java에서 다음과 같이 암호화를 했다고 가정해 보겠습니다.
// http://happinessoncode.com/2019/04/06/java-cipher-algorithm-mode-padding/
// java -classpath .\target\mytest-1.0-SNAPSHOT.jar org.example.mytest.App
package org.example.mytest;
import java.nio.charset.*;
import java.util.*;
import java.security.*;
import org.apache.commons.codec.binary.*;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
/**
* Hello world!
*/
public final class App {
private App() {
}
/**
* Says hello to the world.
*
* @param args The arguments of the program.
* @throws NoSuchPaddingException
* @throws NoSuchAlgorithmException
* @throws InvalidKeyException
* @throws BadPaddingException
* @throws IllegalBlockSizeException
*/
public static void main(String[] args) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
IllegalBlockSizeException, BadPaddingException {
Cipher cipher = Cipher.getInstance("AES");
byte[] keyBytes = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
Key key = new SecretKeySpec(keyBytes, "AES");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] src = "0123456789abcdef".getBytes(StandardCharsets.UTF_8);
byte[] enc = cipher.doFinal(src);
System.out.println(Hex.encodeHexString(enc));
}
}
그럼, 위의 코드로부터 다음과 같은 정보를 얻을 수 있습니다.
알고리즘: AES
키: 길이 128bit { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }
패딩: PKCS5 (Java Cipher의 AES 기본 패딩)
모드: ECB (Java Cipher의 AES 기본 모드)
초기화 벡터: ECB에서는 중요하지 않음
이에 기반을 둬 닷넷 측에서는 다음과 같이 AES 클래스를 사용하면 됩니다.
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
class Program
{
static void Main(string[] args)
{
byte[] keyBuf = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
byte[] ivBuf = new byte[keyBuf.Length];
using (Aes aesAlg = Aes.Create())
{
aesAlg.Key = keyBuf;
// aesAlg.IV = ivBuf; // 명시적으로 설정하지 않으면 임의 값들로 IV 값이 채워져 있음.
// 하지만, ECB 모드이므로 IV 값이 사용되진 않음.
aesAlg.Padding = PaddingMode.PKCS7;
aesAlg.Mode = CipherMode.ECB;
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
byte[] src = Encoding.UTF8.GetBytes("0123456789abcdefghijklmnopqrstuvwxyz");
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
csEncrypt.Write(src, 0, src.Length);
}
byte [] enc = msEncrypt.ToArray();
Console.WriteLine(BitConverter.ToString(enc).Replace("-", ""));
}
}
}
}
저걸로 그럼 설명이 대충 끝난 것 같군요. 실행해 봐서 출력 결과가 동일한 것을 꼭 확인해 주는 것이 좋습니다.
[Java]
281567ab2f4cf0d73d3198225b8b83936d8f88023b081a5e7bf4b718594b8a8e13a411b6a559dd4dc3bef545ac82c21c
[C#]
281567AB2F4CF0D73D3198225B8B83936D8F88023B081A5E7BF4B718594B8A8E13A411B6A559DD4DC3BEF545AC82C21C
물론, 암호화 파라미터를 바꿀 수도 있습니다. 일례로 자바에서 다음과 같이 바꿨다면,
// https://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#impl
/*
AES/CBC/NoPadding (128)
AES/CBC/PKCS5Padding (128)
AES/ECB/NoPadding (128)
AES/ECB/PKCS5Padding (128)
...
*/
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
그럼, 그에 맞게 C# 측도 바꿔주면 됩니다.
aesAlg.Padding = PaddingMode.PKCS7;
aesAlg.Mode = CipherMode.CBC;
그런데 실제로 위와 같이 해보면 Java의 경우 암호문이 실행할 때마다 바뀌는 것을 볼 수 있습니다. 왜냐하면 CBC 모드에서는 (랜덤 생성된) 초기화 벡터가 사용되기 때문입니다. 따라서 이런 경우에는 반드시 IV 값을 구해 C# 측에 전달해 주거나,
System.out.println(Hex.encodeHexString(cipher.getIV()));
아니면 IV를 애당초 서로 합의된 값으로 설정해서 초기화를 해야 합니다.
byte[] iv = new byte [16] { /* ...[합의된 값]... */ };
Key key = new SecretKeySpec(keyBytes, "AES");
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
참고로, .NET 2.0의 경우 Aes 클래스가 없고 예전 이름인 RijndaelManaged로 동일하게 구현할 수 있습니다.
using (RijndaelManaged rijAlg = new RijndaelManaged())
{
rijAlg.Key = keyBuf;
rijAlg.IV = ivBuf;
rijAlg.Padding = PaddingMode.PKCS7;
rijAlg.Mode = CipherMode.CBC;
ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);
byte[] src = Encoding.UTF8.GetBytes("0123456789abcdefghijklmnopqrstuvwxyz");
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
csEncrypt.Write(src, 0, src.Length);
}
byte[] enc = msEncrypt.ToArray();
Console.WriteLine(BitConverter.ToString(enc).Replace("-", ""));
}
rijAlg.Clear();
}
(
첨부 파일은 이 글의 예제 프로젝트를 포함합니다.)
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]