Dev Note/Tomcat2008. 3. 14. 15:41

WAS로 Tomcat을 사용하면 DB Connection Pool로 DBCP를 많이들 사용할 것이다.
여기서 제가 한가지 짚고 넘어갈 것은 DBCP 설정을 server.xml에 하든 Context Path XML에 설정을 하든 문제가 되는 부분은 DataBase의 url, username, password 등 DB에 대한 정보가 그대로 파일에 명시된다. 그럼 만약 Web Server가 해킹이 된다면, DB의 연결 정보는 모두 노출되는 보안의 취약점으 발생하게 된다.
WebLogic과 같은 상용 WAS들은 대부분이 DB User의 Password를 암호화하여 저장되어 있다.
그럼 Tomcat에서 DBCP를 사용할 때는 상용 WAS와 같이 설정 정보를 암호화 하여 사용할 수는 없는것인가? 당연히 가능하다. DBCP에 있는 DataSourceFactory를 조금만 변경하면 됩니다.

-- Default DBCP Setting
   <Context path="test" docBase="d:/www-root/test/webapp" debug="5" reloadable="true" crossContext="true">
     
<Resource name="jdbc/testDB" auth="Container" type="javax.sql.DataSource"
            factory="org.apache.commons.dbcp.BasicDataSourceFactory"
            initialSize="5" maxActive="10" maxIdle="5" maxWait="15000"
            username="test" password="1234" driverClassName="oracle.jdbc.driver.OracleDriver"
            url="jdbc:oracle:thin:@xxx.xxx.xxx.xxx:1521:ora" validationQuery="SELECT 1 FROM DUAL"
            removeAbandoned="false" removeAbandonedTimeout="120" logAbandoned="false" />
   </Context>
위 설정은 기본으로 설정 된 DBCP이다. 여기서 factory, username, password, url 부분을 수정하여 DB 접속 정보를 암호화 해볼것이다. 물론 다른 정보들도 암호화가 가능하다.

-- DBCP 설정 정보 암호화하기
일단 먼저 factory를 수정해야 한다. org.apache.commons.dbcp.BasicDataSourceFacroty의 소스를 조금 변경하여 암호화 된 정보를 복호화하여 적용하는 부분이 추가된 새로운 DataSourceFactory를 만든다.

   package com.junducki.blog.dbcp;
   
   import java.io.ByteArrayInputStream;
   import java.io.UnsupportedEncodingException;
   import java.sql.Connection;
   import java.util.Enumeration;
   import java.util.Hashtable;
   import java.util.Properties;
   import javax.naming.Context;
   import javax.naming.Name;
   import javax.naming.RefAddr;
   import javax.naming.Reference;
   import javax.naming.spi.ObjectFactory;
   import javax.sql.DataSource;
   import org.apache.commons.dbcp.BasicDataSource;
   import org.apache.commons.logging.Log;
   import org.apache.commons.logging.LogFactory;
   import com.goorm.common.security.SeedCipher;
   import com.goorm.common.util.StringUtil;
   import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;

   /**
      * Tomcat의 Resource(DBCP)를 Context Path XML에 설정하여 사용할때, DataBase
      * url/username/password 부분을 암호화하여 사용하기 위한 DataSourceFactory
      *
      * @author jinuk jung, junducki@naver.com
      * @version 1.0, 2008. 03. 13
      */

   public class EncryptDataSourceFacroty implements ObjectFactory {
      protected static final int UNKNOWN_TRANSACTIONISOLATION = -1;

      private final static String PROP_DEFAULTAUTOCOMMIT = "defaultAutoCommit";
      private final static String PROP_DEFAULTREADONLY = "defaultReadOnly";
      private final static String PROP_DEFAULTTRANSACTIONISOLATION = "defaultTransactionIsolation";
      private final static String PROP_DEFAULTCATALOG = "defaultCatalog";
      private final static String PROP_DRIVERCLASSNAME = "driverClassName";
      private final static String PROP_MAXACTIVE = "maxActive";
      private final static String PROP_MAXIDLE = "maxIdle";
      private final static String PROP_MINIDLE = "minIdle";
      private final static String PROP_INITIALSIZE = "initialSize";
      private final static String PROP_MAXWAIT = "maxWait";
      private final static String PROP_TESTONBORROW = "testOnBorrow";
      private final static String PROP_TESTONRETURN = "testOnReturn";
      private final static String PROP_TIMEBETWEENEVICTIONRUNSMILLIS = "timeBetweenEvictionRunsMillis";
      private final static String PROP_NUMTESTSPEREVICTIONRUN = "numTestsPerEvictionRun";
      private final static String PROP_MINEVICTABLEIDLETIMEMILLIS = "minEvictableIdleTimeMillis";
      private final static String PROP_TESTWHILEIDLE = "testWhileIdle";
      private final static String PROP_PASSWORD = "password";
      private final static String PROP_URL = "url";
      private final static String PROP_USERNAME = "username";
      private final static String PROP_VALIDATIONQUERY = "validationQuery";
      private final static String PROP_ACCESSTOUNDERLYINGCONNECTIONALLOWED
            = "accessToUnderlyingConnectionAllowed";
      private final static String PROP_REMOVEABANDONED = "removeAbandoned";
      private final static String PROP_REMOVEABANDONEDTIMEOUT = "removeAbandonedTimeout";
      private final static String PROP_LOGABANDONED = "logAbandoned";
      private final static String PROP_POOLPREPAREDSTATEMENTS = "poolPreparedStatements";
      private final static String PROP_MAXOPENPREPAREDSTATEMENTS = "maxOpenPreparedStatements";
      private final static String PROP_CONNECTIONPROPERTIES = "connectionProperties";

      private final static String[] ALL_PROPERTIES = { PROP_DEFAULTAUTOCOMMIT,
            PROP_DEFAULTREADONLY, PROP_DEFAULTTRANSACTIONISOLATION, PROP_DEFAULTCATALOG,
            PROP_DRIVERCLASSNAME, PROP_MAXACTIVE, PROP_MAXIDLE, PROP_MINIDLE,
            PROP_INITIALSIZE, PROP_MAXWAIT, PROP_TESTONBORROW, PROP_TESTONRETURN,
            PROP_TIMEBETWEENEVICTIONRUNSMILLIS, PROP_NUMTESTSPEREVICTIONRUN,
            PROP_MINEVICTABLEIDLETIMEMILLIS, PROP_TESTWHILEIDLE, PROP_PASSWORD,
            PROP_URL, PROP_USERNAME, PROP_VALIDATIONQUERY,
            PROP_ACCESSTOUNDERLYINGCONNECTIONALLOWED, PROP_REMOVEABANDONED,
            PROP_REMOVEABANDONEDTIMEOUT, PROP_LOGABANDONED,
            PROP_POOLPREPAREDSTATEMENTS, PROP_MAXOPENPREPAREDSTATEMENTS,
            PROP_CONNECTIONPROPERTIES };

      /** logger instance */
      protected static Log logger = LogFactory.getLog(GoormDataSourceFacroty.class);

      /** DataSource Factory Name */
      private static String dsFactoryName = null;

      public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable environment)
            throws Exception {
         dsFactoryName = name.toString();

         // We only know how to deal with <code>javax.naming.Reference</code>s
         // that specify a class name of "javax.sql.DataSource"
         if ((obj == null) || !(obj instanceof Reference)) {
            return null;
         }
         Reference ref = (Reference) obj;
         if (!"javax.sql.DataSource".equals(ref.getClassName())) {
            return null;
         }

         Properties properties = new Properties();
         for (int i = 0; i < ALL_PROPERTIES.length; i++) {
            String propertyName = ALL_PROPERTIES[i];
            RefAddr ra = ref.get(propertyName);
            if (ra != null) {
               String propertyValue = ra.getContent().toString();
               properties.setProperty(propertyName, propertyValue);
            }
         }

         return createDataSource(properties);
      }

      public static DataSource createDataSource(Properties properties)
            throws Exception {
         BasicDataSource dataSource = new BasicDataSource();
         String value = null;
         StringBuilder trace = null;

         value = properties.getProperty(PROP_DRIVERCLASSNAME);
         if (value != null) {
               dataSource.setDriverClassName(value);
         }

        
value = properties.getProperty(PROP_URL);
         if (value != null) {
            dataSource.setUrl(decryptDBCPProperty(value));
         }

         value = properties.getProperty(PROP_USERNAME);
         if (value != null) {
            dataSource.setUsername(decryptDBCPProperty(value));
         }

         value = properties.getProperty(PROP_PASSWORD);
         if (value != null) {
            dataSource.setPassword(decryptDBCPProperty(value));
         }

         value = properties.getProperty(PROP_DEFAULTAUTOCOMMIT);
         if (value != null) {
            dataSource.setDefaultAutoCommit(Boolean.valueOf(value).booleanValue());
         }

         value = properties.getProperty(PROP_DEFAULTREADONLY);
         if (value != null) {
               dataSource.setDefaultReadOnly(Boolean.valueOf(value).booleanValue());
         }
     
         value = properties.getProperty(PROP_DEFAULTTRANSACTIONISOLATION);
         if (value != null) {
            int level = UNKNOWN_TRANSACTIONISOLATION;
            if ("NONE".equalsIgnoreCase(value)) {
               level = Connection.TRANSACTION_NONE;
            } else if ("READ_COMMITTED".equalsIgnoreCase(value)) {
               level = Connection.TRANSACTION_READ_COMMITTED;
            } else if ("READ_UNCOMMITTED".equalsIgnoreCase(value)) {
               level = Connection.TRANSACTION_READ_UNCOMMITTED;
            } else if ("REPEATABLE_READ".equalsIgnoreCase(value)) {
               level = Connection.TRANSACTION_REPEATABLE_READ;
            } else if ("SERIALIZABLE".equalsIgnoreCase(value)) {
               level = Connection.TRANSACTION_SERIALIZABLE;
            } else {
               try {
                  level = Integer.parseInt(value);
               } catch (NumberFormatException e) {
                  System.err.println("Could not parse defaultTransactionIsolation: " + value);
                  System.err.println("WARNING: defaultTransactionIsolation not set");
                  System.err.println("using default value of database driver");

                  level = UNKNOWN_TRANSACTIONISOLATION;
               }
            }
            dataSource.setDefaultTransactionIsolation(level);
         }

          value = properties.getProperty(PROP_DEFAULTCATALOG);
         if (value != null) {
            dataSource.setDefaultCatalog(value);
         }

         value = properties.getProperty(PROP_MAXACTIVE);
         if (value != null) {
               dataSource.setMaxActive(Integer.parseInt(value));
         }

         value = properties.getProperty(PROP_MAXIDLE);
         if (value != null) {
            dataSource.setMaxIdle(Integer.parseInt(value));
         }

         value = properties.getProperty(PROP_MINIDLE);
         if (value != null) {
            dataSource.setMinIdle(Integer.parseInt(value));
         }

         value = properties.getProperty(PROP_INITIALSIZE);
         if (value != null) {
            dataSource.setInitialSize(Integer.parseInt(value));
         }

         value = properties.getProperty(PROP_MAXWAIT);
         if (value != null) {
            dataSource.setMaxWait(Long.parseLong(value));
         }
     
          value = properties.getProperty(PROP_TESTONBORROW);
         if (value != null) {
            dataSource.setTestOnBorrow(Boolean.valueOf(value).booleanValue());
         }

         value = properties.getProperty(PROP_TESTONRETURN);
         if (value != null) {
            dataSource.setTestOnReturn(Boolean.valueOf(value).booleanValue());
         }

         value = properties.getProperty(PROP_TIMEBETWEENEVICTIONRUNSMILLIS);
         if (value != null) {
            dataSource.setTimeBetweenEvictionRunsMillis(Long.parseLong(value));
         }

         value = properties.getProperty(PROP_NUMTESTSPEREVICTIONRUN);
         if (value != null) {
            dataSource.setNumTestsPerEvictionRun(Integer.parseInt(value));
         }

         value = properties.getProperty(PROP_MINEVICTABLEIDLETIMEMILLIS);
         if (value != null) {
            dataSource.setMinEvictableIdleTimeMillis(Long.parseLong(value));
         }

         value = properties.getProperty(PROP_TESTWHILEIDLE);
         if (value != null) {
            dataSource.setTestWhileIdle(Boolean.valueOf(value).booleanValue());
         }

         value = properties.getProperty(PROP_VALIDATIONQUERY);
         if (value != null) {
            dataSource.setValidationQuery(value);
         }

         value = properties.getProperty(PROP_ACCESSTOUNDERLYINGCONNECTIONALLOWED);
         if (value != null) {
            dataSource.setAccessToUnderlyingConnectionAllowed(Boolean.valueOf(value).booleanValue());
         }

         value = properties.getProperty(PROP_REMOVEABANDONED);
         if (value != null) {
            dataSource.setRemoveAbandoned(Boolean.valueOf(value).booleanValue());
         }

         value = properties.getProperty(PROP_REMOVEABANDONEDTIMEOUT);
         if (value != null) {
            dataSource.setRemoveAbandonedTimeout(Integer.parseInt(value));
         }

         value = properties.getProperty(PROP_LOGABANDONED);
         if (value != null) {
            dataSource.setLogAbandoned(Boolean.valueOf(value).booleanValue());
         }

         value = properties.getProperty(PROP_POOLPREPAREDSTATEMENTS);
         if (value != null) {
            dataSource.setPoolPreparedStatements(Boolean.valueOf(value).booleanValue());
         }

         value = properties.getProperty(PROP_MAXOPENPREPAREDSTATEMENTS);
         if (value != null) {
            dataSource.setMaxOpenPreparedStatements(Integer.parseInt(value));
         }

         value = properties.getProperty(PROP_CONNECTIONPROPERTIES);
         if (value != null) {
            Properties p = getProperties(value);
            Enumeration e = p.propertyNames();
            while (e.hasMoreElements()) {
               String propertyName = (String) e.nextElement();
               dataSource.addConnectionProperty(propertyName, p.getProperty(propertyName));
            }
         }

         return dataSource;
      }

      static private Properties getProperties(String propText) throws Exception {
         Properties p = new Properties();
         if (propText != null) {
            p.load(new ByteArrayInputStream(propText.replace(';', '\n').getBytes()));
         }
     
         return p;
      }

     
/**
         * 암호화 된 DBCP Property를 복호화해 준다.
         *
         * @param encryptStr String 암호화 된 Config 값
         * @return String 복호화 된 Config 값
         */
      private static String decryptDBCPProperty(String encryptStr) {
         SeedCipher seed = new SeedCipher();

         try {
               return seed.decryptAsString(Base64.decode(encryptStr), key.getBytes(), "UTF-8");
         } catch (UnsupportedEncodingException e) {
            return seed.decryptAsString(Base64.decode(encryptStr), key.getBytes());
         }
      }

   }

위의 소스를 보시면 BasicDataSourceFactory가 어떻게 수정되었는지를 금방 알수 있을 것이다. 변경되거나 추가된 부분은 소스 색을 다르게 해 놨다.
decryptDBCPProperty(String encryptStr)은 설정 된 암호화 된 properties를 복호화하기 위해 추가 된 것으로 여기에 암호화는 SEED API를 적용해 놨다. key는 변경하거나 룰을 가져가면 된다. 물론 다른 암호화 알고리즘을 적용해도 된다. 대신 설정 된 암호화 properties와 복호화 하는 부분에는 동일한 키와 알고리즘을 사용해야 한다.
getObjectInstance() 함수의 달라진 부분은 읽어들인 properties를 decryptDBCPProperty()를 이용하여 복호화하여 적용하게끔 변경되었다. url, username, password 부분만 현재는 적용 됨.

그럼 위의 EncryptDataSourceFacroty가 적용 된 DBCP 설정은 어떻게 달라지는가? 위의 DBCP 설정을 변경해 보면
   <Context path="test" docBase="d:/www-root/test/webapp" debug="5" reloadable="true" crossContext="true">
      <Resource name="jdbc/testDB" auth="Container" type="javax.sql.DataSource"
            factory="com.junducki.blog.dbcpEncryptDataSourceFacroty"
            initialSize="5" maxActive="10" maxIdle="5" maxWait="15000"
            username="Owm8SZ89IPJLdu2pR84DQg==" password="3N+gGSCi3+9N2v43K18e5A==" 
            driverClassName="oracle.jdbc.driver.OracleDriver"
            url="8l1Z5t8i0tpJJaMA0hE5NjAfbfQL1UXxNEKWxyPOYXkuB1bMqmOGir5QP9G9S/wh" 
            validationQuery="SELECT 1 FROM DUAL"
            removeAbandoned="false" removeAbandonedTimeout="120" logAbandoned="false" />
   </Context>
위와 같이 factory 부분은 변경 된 DataSourceFactory가 적용되었고, url, username, password는 암호화 된 정보로 설정되어 있다.

위 변경 전후의 설정은 맞지가 않다. 이 Factory는 제가 운영하는 Tomcat에 적용되어 있기 때문에 암호화 방식과 키관리 룰은 공개를 할 수 없기 때문이다. 단, 위의 내용으로 조금만 수정하면 쉽게 적용할 수 있을 것이다.


Posted by as.wind.914
Dev Note/Java2008. 3. 11. 20:29


SEED 알고리즘에 대해 간략하게 알아봤으니 이제 SEED 알고리즘 Java API를 알아본다.
밑의 소스는 KISA에서 제공하는 Java API를 조금 변경 및 추가하여 사용할 수 있게 만든 소스이다. 상단의 첨부 파일은 API와 Padding, 사용 예제가 압축되어 있다.

-- SEED API (KISA에서 제공한 API를 조금 변경 및 추가 한것)
해당 소스는 좀 많은 관계로 모든 소스를 다루지 않고, 추가 된 부분만을 다룬다. 여기서 다루지 않은 부분은 KISA에서 제공한 API 그대로라고 생각해도 무방하다.

   /**
      * 한국정보보호진흥원 소스에 없는 추가 된 함수로 입력 받은 바이트 데이터를 암호화 하여 리턴한다.
      *
      * @param inData String 평문
      * @param szKey byte[] key
      * @return byte[] 암호화 된 데이터
      */
   public byte[] encrypt(byte[] sbuffer, byte szKey[]) {
      int sRoundKey[] = new int[32];
      SeedEncRoundKey(sRoundKey, szKey);
     
      byte[] inDataBuffer = padding.addPadding(sbuffer, SeedBlockSize);
      byte[] encryptBytes = new byte[inDataBuffer.length];

      int rt = inDataBuffer.length / SeedBlockSize;
      for (int j = 0; j < rt; j++) {
         byte sSource[] = new byte[SeedBlockSize];
         byte sTarget[] = new byte[SeedBlockSize];
         System.arraycopy(inDataBuffer, (j * SeedBlockSize), sSource, 0, SeedBlockSize);
         
         SeedEncrypt(sSource, sRoundKey, sTarget);
         
System.arraycopy(sTarget, 0, encryptBytes, (j * SeedBlockSize), sTarget.length);
      }

      return encryptBytes;
   }

   /**
      * 한국정보보호진흥원 소스에 없는 추가 된 함수로 입력 받은 문자열을 암호화 하여 리턴한다.
      *
      * @param inData String 평문
      * @param szKey byte[] key
      * @return byte[] 암호화 된 데이터
      */
   public byte[] encrypt(String inData, byte szKey[]) {
      return encrypt(inData.getBytes(), szKey);
   }

   /**
      * 한국정보보호진흥원 소스에 없는 추가 된 함수로 입력 받은 문자열을 특정 Charset으로 변환하여
      * 암호화 하여 리턴한다.
      *
      * @param inData String 평문
      * @param szKey byte[] key
      * @param charset String String을 byte 데이터로 변환할때 사용할 charset
      * @return byte[] 암호화 된 데이터
      */
   public byte[] encrypt(String inData, byte szKey[], String charset) throws UnsupportedEncodingException {
      return encrypt(inData.getBytes(charset), szKey);
   }

   /**
      * 한국정보보호진흥원 소스에 없는 추가 된 함수로 암호화 된 바이트 데이터를 받아서 복호화한다.
      *
      * @param encryptBytesbyte[] 암호화 된 바이트 데이터
      * @param szKey byte[] key
      * @return byte[] 복호화 된 바이트 데이터
      */
   public byte[] decrypt(byte[] encryptBytes, byte[] szKey) {
      int sRoundKey[] = new int[32];
      byte[] decryptBytes = new byte[encryptBytes.length];
      SeedEncRoundKey(sRoundKey, szKey);
      int rt = encryptBytes.length / SeedBlockSize;

      byte sSource[] = new byte[SeedBlockSize];
      byte sTarget[] = new byte[SeedBlockSize];
      for (int j = 0; j < rt; j++) {
         System.arraycopy(encryptBytes, (j * SeedBlockSize), sSource, 0, SeedBlockSize);
         SeedDecrypt(sSource, sRoundKey, sTarget);
         
         System.arraycopy(sTarget, 0, decryptBytes, (j * SeedBlockSize), SeedBlockSize);
      }
     
      return padding.removePadding(decryptBytes, SeedBlockSize);
   }

   /**
      * 한국정보보호진흥원 소스에 없는 추가 된 함수로 암호화 된 바이트 데이터를 받아서 복호화하여
      * 문자열로 반환한다.
      * @param encryptBytes byte[] 암호화 된 바이트 데이터
      * @param szKey byte[] key
      * @return byte[] 복호화 된 바이트 데이터
      */
   public String decryptAsString(byte[] encryptBytes, byte[] szKey) {
      return new String(decrypt(encryptBytes, szKey));
   }

   /**
      * 한국정보보호진흥원 소스에 없는 추가 된 함수로 암호화 된 바이트 데이터를 받아서
      * 복호화하여 지정한 charset으로 문자열로 반환한다.
      *
      * @param encryptBytes byte[] 암호화 된 바이트 데이터
      * @param szKey byte[] key
      * @param String charset 복호화 된 바이트 데이터를 문자열로 변환할 때 사용할 Charset
      * @return byte[] 복호화 된 바이트 데이터
      */
   public String decryptAsString(byte[] encryptBytes, byte[] szKey, String charset)
         throws UnsupportedEncodingException {
      return new String(decrypt(encryptBytes, szKey), charset);
   }

여기서 Padding Object는 블록 암호화의 특징은 정해진 블록 사이즈를 맞추기 위해 모자란 부분에 대한 Padding을 의미하는데, KISA에서 제공하는 API에는 Padding이 구현되어 있지 않아 ANSI X.923 Padding을 구현하여 사용하였다.

-- ANSI X.923 Padding
모자란 부분에 대해 일정한 규칙으로 채우는 것을 Padding이라 하는데, 많은 규칙 중의 하나이다. 규칙은 간단하다 모자란 byte 수에 대해 패딩되는 가장 마지막 byte는 패딩 된 byte 수를 의미하고 나머지는 "0x00"으로 채운다.
16 byte 블록에서 10 byte에 대한 값을 Padding 한다면
   0xDD 0xDD 0xDD 0xDD 0xDD 0xDD 0xDD 0xDD 0xDD 0xDD 0x00 0x00 0x00 0x00 0x00 0x06
이 된다.

-- 사용 예제 소스
   public static void main(String[] args) throws Exception {
      String text = "Seed 암호화 Test \t 잘되겠지. HaHa~~";
      String key = "junducki_goormaa";
      StringBuilder trace = new StringBuilder();
 
      trace.append("Plain Text :: [").append(text).append("]");
      System.out.println(trace.toString());
 
      SeedCipher seed = new SeedCipher();
      String encryptText = Base64.encode(seed.encrypt(text, key.getBytes(), "UTF-8"));

      trace = new StringBuilder();
      trace.append("Encrypt Text (Base64 Encoding) :: [").append(encryptText).append("]");
      System.out.println(trace.toString());
 
      byte[] encryptbytes = Base64.decode(encryptText);
      String decryptText = seed.decryptAsString(encryptbytes, key.getBytes(), "UTF-8");
 
      trace = new StringBuilder();
      trace.append("Decrypt Text :: [").append(decryptText).append("]");
      System.out.println(trace.toString());
   }

위 예제는 첨부파일에 들어있으며, SeedCipher를 이용하여 문자열을 암호화, 복호화 하여 원문이 다시 나오는지 여부를 확인 할 수 있는 소스이다. 여기서 암호화 후 생성 된 bytes는 랜덤한 값이 나오기 때문에 Base64를 이용하여 Encoding 하였고, 복호화 할때는 Decoding 하여 처리한다.

이것으로 SEED에 대해 (1), (2)에 걸쳐 알아보왔다. 여기서 소개한 SEED에 대한 부분은 극히 일부이므로, 좀더 자세히 알고자 한다면 한국정보보호진흥원(KISA) 또는 다른 곳에서 자료를 더 찾아보기 바란다.

Posted by as.wind.914
Dev Note/Java2008. 3. 11. 17:23


JCE에는 포함되어 있지 않은 SEED 알고리즘에 대해 (1), (2) 부로 하여 간단하게 알아보고 API를 사용하여 테스트 예제도 한번 만들어 본다.

-- SEED 암호화 알고리즘 이란?
SEED 암호화 알고리즘은 민간 부분인 인터넷, 전자상거래, 무선 통신 등에서 공개 시 민감한 영향을 미칠 수 있는 정보의 보호황 개인 프라이버시 등을 보호하기 위하여 1999년 2월 한국정보보호센터(KISA)에 의해 개발 된 블럭암호알고리즘이다.
대칭키 블록 암호알고리즘으로 비밀성을 제공하는 암호시스템의 중요 요소이다. n 비트 블록 암호알고리즘이란 고정 된 n 비트 평문을 같은 길이의 n 비트 암호문으로 바꾸는 함수를 말한다. 이러한 변형 과정에 암/복호키가 작용하여 암호화와 복호화를 수행한다.
Feistel 구조란 각각 n/2 비트인 L0, R0 블록으로 이루어진 n 비트 평문 블록 (L0, R0)이 r 라운드(r ≥ 1)를 거쳐 암호문(Lr, Rr)으로 반환되는 반복 구조이다.

-- SEED 알고리즘 구조
SEED 알고리즘의 전체 구조는 Feistel 구조로 이루어져 있으며, 128비트의 평문 블록과 128비트 키를 입력으로 사용하여 총 16라운드를 거쳐 128비트 암호문 블록을 출력한다.
사용자 삽입 이미지

[ SEED 알고리즘 전체 구조도 ]

F 함수
Feistel 구조를 갖는 블록 암호알고리즘은 F 함수의 특성에 따라 구분될 수 있다. SEED의 F 함수는 수정된 64비트 Feistel 형태로 구성된다. F 함수는 각 32비트 블록 2개 (C, D)를 입력으로 받아, 32 비트 블록 2개(C', D')를 출력한다. 즉, 암호화 과정에서 64비트 블록(C, D)와 64비트 라운드 키 Ki = (Ki,0 : Ki,1)를 F 함수의 입력으로 처리하여 64비트 블록(C', D')을 출력한다. (i : 라운드 수)
사용자 삽입 이미지

[ F-함수 ]

사용자 삽입 이미지

[ F-함수 구조도 ]

G 함수
G 함수는 다음과 같다.
사용자 삽입 이미지
사용자 삽입 이미지

[ G - 함수 ]

S-Box
G 함수의 내부에 사용되는 비선형 S-Box S1, S2는 다음의 식을 이용하여 생성된다. (n1 = 247, n2 = 251, b1 = 159, b2 = 56)
사용자 삽입 이미지

[ S - Box ]

라운드 키 생성과정
SEED의 라운드 키 생성과정은 128비트 암호키를 64비트씩 좌우로 나누어 이들을 교대로 8비트씩 좌/우로 회전이동한 후, 결과의 4워드들에 대한 간단한 산술연산과 G 함수를 적용하여 라운드 키를 생성한다. 라운드 키 생성과정은 기본적으로 하드웨어나(모든 라운드 키를 저장할 수 없는) 제한 된 자원을 갖는 스마트카드와 같은 응용에서의 효율성을 위하여, 암호화나 복호화시 암호화키로부터 필요한 라둔드 키를 간단히 계산할 수 있도록 설계되었다.
주어진 128비트 암호키 K = A || B || C || D를 32비트 레지스터 A, B, C, D로 나눈다. 각 라운드 i에 사용되는 라운드 키 Ki = (Ki,0 : Ki,1)는 다음과 같은 방식으로 생성한다.
사용자 삽입 이미지

[ 라운드 키 생성과정에 사용된 상수 ]

사용자 삽입 이미지

[ 라운드 키 생성과정 구조


지금까지 SEED 암호알고리즘에 대해 알아봤다. 위의 내용은 한국정보보호진흥원(KISA)에서 제공한 정보가 거의 대부분이다. 상단에 KISA에서 제공하는 SEED에 관련 된 문서를 올리니 참조하기 바란다.

KISA의 SEED HomePage : http://www.kisa.or.kr/kisa/seed/jsp/seed.jsp

다음은 KISA에서 제공하는 SEED 알고리즘 Java API를 조금 변형한 것과 사용예를 다룬다.
Posted by as.wind.914