PHP로 구현한 SEED128 + CBC + PKCS5 암호화

SEED 암호알고리즘
SEED를 제공하는 인터넷진흥원 사이트 스크린샷

 

Prologue

회사에서 미션이 하나 떨어졌습니다. 한국인터넷진흥원에서 순수 국내기술로 개발된 SEED128을 PHP로 구현하는 것 입니다.
참고로 한국인터넷진흥원에서 JAVA나 C언어로는 SEED소스를 제공하고 있습니다. 심지어 안드로이드와 Objective-C도 제공하는데 PHP만 없습니다.
SEED에 관련된 내용은 해당 사이트에서 참고하시기 바라오며, 현재 표준으로 채택되어져 각종 금융기관에서 널리 사용되고 있다는 것만 알고계시면 될 것 같습니다.

SEED 암호화 기술은 블록 암호 알고리즘을 채택하고 있어 통신하기 위한 곳 모두 동일한 Key를 가지고 있어야 암호화와 복호화가 가능합니다.
저 역시 금융기관쪽과 중요한 데이터를 통신을 하기 위해서 사용하는 것 이며 해당 금융기관쪽은 자바로 이미 구현되어 있어 동일한 Key를 가지고 저희쪽 PHP로 암/복호화가 가능해야 합니다.
사실 말이 PHP로 구현하는 것이지 Native 언어는 명확한 기준으로 변수타입들이 정해져 있기라도 하지만 웹 전용으로 유연하게 만들어진 PHP에서 지원할리 없기때문에 무수한 삽질이 예상되었습니다.

우선 검색하기 시작했습니다. 다행히도 SEED자체에 대한 프로세스는 PHP로 구현해놓으신 분이 계셨습니다. 아래에서 class.seed.php를 다운로드 받으시면 됩니다.
다시한번 이자리를 빌어 정말 감사드립니다.

출처: http://docs.cena.co.kr/textyle/15770

하지만 위의 소스는 순수하게 SEED만 구현해 놔서 제가 원하는 것을 구현하기에는 부족했으며, 기존 class.seed.php 클래스는 운영체제가 64bit인 경우 Integer의 Max값이 커지다보니 라운드키의 정수값을 오버플로우 시키지 못하는 증상으로 인해 값을 제대로 출력하지 못하는 문제가 있어 이 부분은 별도로 해결했습니다.

[box type=”info”]자바는 플랫폼에 관계없이 32비트 고정이지만, PHP는 플랫폼에 따라 32비트면 정수의 최대값이 2147483647이고, 64비트면 9223372036854775807로 자동으로 할당됩니다.[/box]

암호화를 운용하기 위해서 저의 경우에는 CBC와 PKCS5 방식으로 구현을 해야하며 통상적으로 검색하면 나오는 IV의 값도 PHP에서 사용하는 랜덤 스트링 형태가 아닌 자바와 동일하게 바이트 배열로 정해진 값을 만들어서 써야 하기 때문에 본젹적인 개발과 검색에 들어가기 시작했습니다. 말 자체를 이해 못하시는 분들을 위해 용어에 대한 정의들 부터 살펴보겠습니다.

 

암호 블록 체인 방식 (CBC)

그림을 보면 더 이해가 잘 되야 하는데~ 저는 이런 그림을 별로 좋아하지 않아서 이해가 잘 되지 않았습니다.
간단하게 설명하면 이전 블록의 암호문을 현재 평문 블록을 암호화할 때 사용하면서 운용하는 모드입니다. 따라서 같은 평문들도 서로 다른 암호문 블록으로 순차적으로 암호화가 이루어지게 됩니다. 하지만 처음 나오는 평문을 암호화할 암호문이 없기 때문에 보통 랜덤으로 만들게 되며 이를 IV(초기화 벡터)라고 합니다.
평문의 첫번째 블록이 IV와 XOR을 수행하고 나서 암호화를 수행하게 되며 다음은 그 암호화된 블록이 평문의 다음블록을 XOR하는 식으로 평문 블록을 마칠때까지 반복하는 구조입니다. 이게 저도 수도 없이 봤지만 쉽게 이해가 되지 않더군요! 그래도 이해하고 넘어가셔야 합니다. 아래는 위키피디아에서 발췌한 이미지를 첨부하였으며 이해가 안되시는 내용은 구글링을 통해 확인하시기 바랍니다.

Cbc encryption.png
CBC 운영모드에서의 암호화
Cbc decryption.png
CBC 운영모드에서의 복호화

 

Padding (PKCS#5)

데이터를 블럭으로 암호화 할때 평문이 항상 블럭 크기(일반적으로 64비트 또는 128비트)의 배수가 되지 않습니다. 패딩은 어떻게 평문의 마지막 블록이 암호화 되기 전에 데이터로 채워지는가를 확실히 지정하는 방법으로 복호화 과정에서는 패딩을 제거하고 평문의 실제 길이를 지정하게 됩니다. 다음 링크를 보시면 이해하기가 더 쉽습니다.

PKCS5는 공개키 암호화 표준이며 패딩기법의 핵심은 채워지지 않은 블럭의 나머지 부분들을 바이트 수로 채우는 것 입니다. 블럭이 3개 남아있으면 3바이트로 전부 채우고 6개 남았으면 6바이트로 나머지 부분들을 채우는 방식이라 이해하시면 됩니다.

 

JAVA 함수전환 및 추가 함수

JAVA문서를 참고해 CBC운용모드에 필요한 arraycopy와 xor16 함수를 PHP로 변환했습니다.
예외처리가 없어서 어디서 문제가 발생할지 모릅니다. 아시는 분들께서는 보완해주시면 좋겠으며,
64bit에서 플랫폼에서 강제로 32bit의 정수값이 Max가 되었을때 오버플로우 처리해주는 함수를 추가했습니다.

 

 

CBC 운용모드에서의 SEED 암호화 작업

제가 사실 잘 알지도 못하니 왠만하면 JAVA로 구현된 방식과 동일한 형태로 작업하려고 했습니다. JAVA에서는 다 배열로 받아서 처리하는데 인터넷에 나와 있는 정보에서 PHP의 경우는 전부 String을 이용해서 사용하더군요! 아무튼 JAVA와 PHP로 구현된 결과값을 하나하나 대조하기 위해 Eclipse 를 설치해서 JAVA도 처음 써보게 되었습니다. ^^;

시작부터 걸림돌이 자바의 경우 일반 문자열을 getBytes()라는 메서드를 통해 바이트 배열로 단번에 변경시켜주는데 PHP는 어떻게 해야 동일하게 변경할 수 있는지 알 수가 없었습니다.
검색을 하다보니 uppack이라는 함수를 이용해 동일하게 변경할 수 있다는 것을 알게 되었습니다. 하단의 표시된 14줄과 같은 방식으로 구현하시면 됩니다.
array_slice를 사용한 이유는 uppack으로 반환된 배열의 키가 0부터가 아닌 1부터 반환해서 시작이 0이 될 수 있도록 Shift 시키는 용도로 사용되었습니다.

반드시 유의하셔야 할 한글 Encoding 문제가 있습니다. UTF-8에서는 한글이 3byte이고 EUC-KR에서는 2byte이기 때문에 인코딩에 따라 결과값이 완전히 다르게 나타납니다. 특히나 웹페이지에서 출력해야하는 부분도 있는지라 항상 Encoding에 신경쓰시기 바랍니다.
저는 통신해야할 곳이 EUC-KR을 사용하기 때문에 내부적으로는 ANSI(EUC-KR)로 사용해서 작성했습니다.

 

CBC 운용모드에서의 SEED 복호화 작업

복호화 작업은 암호화 작업의 역순으로 진행하며 동일하게 CBC모드로 운용되어집니다. 암호화 작업에서 설명했던 것처럼 자바에서는 바이트가 음수로 나오는데 PHP에서는 양수로 나와서 좀 헤맸습니다만, “c*” 모드로 pack()을 돌리니 제대로 복호화가 되더군요! 그런데 이상한 점은 특별히 패딩을 제거하려고 하지 않았는데 제가 원하는 결과값을 출력했습니다. 정확한 원인은 더 파악해봐야 알겠지만 초기에 암호된 문장을 가져와 바이트로 만들때 패딩된 부분들이 자동으로 제거되어 처리가 되는 듯 합니다. ^^;

 

 테스트 및 사용방법

제 작업실에 있는 테스트 사이트는 조만간 제대로 만들어서 값들을 비교체크할 수 있도록 하겠습니다.
우선 사용에 앞서 초기화하셔야 할 변수가 있습니다. 클래스 파일 내부에 주석처리 해놨으니 참고하시면 됩니다.

  • $serverEncoding : 서버의 인코딩을 설정합니다. (자신의 환경에 따라 ‘UTF-8’ 혹은 ‘EUC-KR’로 변경하셔서 사용하세요)
  • $innerEncoding : 내부적으로 처리할 인코딩을 설정합니다만 주로 EUC-KR로 운영되어지리라 생각합니다.
  • $block : 블록 사이즈를 설정합니다. 기본적으로 16바이트를 기본으로 운용됩니다.
  • $pbUserKey : 통신할 곳과 공유할 임의로 설정한 키입니다.
  • $IV : CBC모드 첫 시작시에 필요한 암호블록입니다. 이부분도 공유하셔서 사용하셔야 합니다.

해당값들을 자신의 환경에 맞게 설정하시고, 클래스를 사용하고자 하는 페이지에서 다음과 같이 사용하시면 됩니다.
(단, $pbUserKey와 $IV 키값으로 반드시 1바이트의 범위인  -128~127 사이의 숫자를 임의로 넣어주셔야 합니다.)

 

class.seed.php 파일의 원저작자의 동의를 얻어야 하는 부분으로 인해 Github의 소스코드에는 class.seed.php를 포함시키지 않았습니다.
불편하시더라도 Github의 Readme 설명서대로 사용해주시면 됩니다.

[button type=”big” link=”https://github.com/qnibus/seed128/” newwindow=”yes”] 다운로드받기[/button]

 

 

 

Published by

안반장

Web과 App 개발을 하고 있으며 최근 워드프레스에 관심이 많아져 네이버 카페 워드프레스 홈페이지의 TF팀으로 활동 중 입니다.개인적으로는 안반장의 개발 노트라는 블로그를 운영하면서 개발의 즐거움과 고충들을 차곡차곡 담아가고 있습니다.

  • 해당 기관에서 잘 된다고 하네요! 월요일날 마무리 지어야 겠습니다. 즐건 주말들 되시길… ^^

  • Guest

    dk

  • 안녕하십니까?
    안반장님의 암호화 모듈을 이용해 작업을 진행하고 있습니다.
    데이터를 암호화 한 후 복호화 했을 경우 이상한 문자가 붙어 있어서 문의드립니다.
    해당 복호화 데이터를 캡쳐한 이미지를 첨부합니다.
    살펴봐 주셨으면 합니다.

    ※ 개발환경
    – PHP Version 5.2.14
    – Linux linux3-32 2.6.18-348.3.1.el5 #1 SMP Mon Mar 11 19:39:25 EDT 2013 x86_64
    – utf-8 소스 인코딩


    – 숫자 “2” 를 암호화 한 후 복호화 한 데이터 입니다.
    – 이미지에서 #(샵)은 블록지정 대용으로 넣은것이니 신경쓰시지 않으셔도 됩니다.

    – 첫번째 이미지 : 브라우저 소스보기를 이용해서 복호화한 데이터 캡처
    – 두번째 이미지 : 복호화한 데이터 copy 해서 메모장에 붙여넣었을 경우 캡쳐

    • 저는 들어가는 문장이 너무 길어서 16자 이하에서는 테스트 해보지 못 했네요! 그 부분은 참고해서 테스트보겠습니다! 아마도 언패딩이 안되고 있는걸 수도 있겠네요! 지적 감사드립니다. ^^

      • 빠른 업데이트 희망해 봅니다. ^-^

        • 네 요즘 회사에서 정신이 없어서 손볼 시간이 여의치가 않네요! 최대한 시간을 맞춰보도록 하겠습니다. 관심가져주셔서 감사합니다.

        • class.crypto.php 에서 131번째줄에 return 영역이 있습니다.
          이부분을 다음과 같이 변경해주시면 정상작동 합니다.
          Github에 업데이트 해놓았으니 참고해주세요!! 감사합니다.

          $rst = iconv($this->innerEncoding, $this->serverEncoding, call_user_func_array(“pack”, array_merge(array(“c*”), $planBytes))); // 평문블록 바이트 배열을 문자열로 변환

          return $this->pkcs5Unpad($rst); // 패딩처리부 추가

  • jaesun

    안녕하세요? Codeigniter에서 안반장님의 암호화 모듈을 이용하여 작업을 하고있는데 135번째 줄에서 iconv(): Detected an illegal character in input string 에러가 나네요.. 초보라 왜 그런지도 모르겠습니다 혹시 도움좀 부탁드립니다.

    서버인코딩 UTF-8
    내부처리 인코딩 UTF-8로 지정하였습니다.

    • 서버인코딩이 UTF-8이면 iconv 제거하셔도 상관없습니다. 아예 제거 하시고

      $rst = call_user_func_array(“pack”, array_merge(array(“c*”), $planBytes)));

      이와 같이 변경해서 사용해보세요!

  • jung

    회원 비밀번호 암호화에 사용할려고 이래저랙 알아보던중 우연히 알게되어 테스트중인데요.

    32비트 환경의 서버와 64비트의 환경의 서버의 인코딩 결과값이 다르게 출력되네요.

    32비트
    Encrypt -a4a8494c755e10373a3c920ae1173c6d9ca8bc842adf6683847d9aad6a4b95b8
    Decrypt – 여기는 안반장의 개발 노트입니다

    64비트
    Encrypt – 4af61b7f11948a99e73ebaa9126fca440df78799b59a1f52c35d87b794a207fd
    Decrypt – 여기는 안반장의 개발 노트입니다

    아.. 어디서 뭐가 문젠지 모르겠네요.ㅠㅠ

  • 오승민

    우와 엄청난걸 하셨네요. 소스코드를 사용하고싶은데 라이센스는 어떻게 되는지 알수있을까요 ㅎㅎ? 아무리 찾아봐도 라이센스 명시가 안되있네요

    • 한국인터넷진흥원에서 제공하는 로직을 그대로 사용해서 구현한 것이기 때문에 그 규칙에 맞게 사용하시면 될 것 같습니다.
      해당 사이트의 SEED128부분을 아래와 같이 명기하고 있네요!!

      “한국인터넷진흥원은 SEED 128/256를 이용한 제품 생산 및 판매와 관련된 지적재산권에 대하여 사용료를 요구하지 않습니다.”

      맘대로 쓰셔도 될 것 같습니다. ^^;

  • Kyomin Choi

    새해 복 많이 받으십시오..

    저번에 수정해 주신거 적용해 보았습니다.
    짧은 글에서도 잘 되는거 확인하였습니다.

    그런데 암호화할 문자에 특수문자가 들어갈 경우 암호화는 되는데 복호화가 되지 않습니다.

    한번 살펴봐 주시면 감사하겠습니다.


    $serverEncoding, $innerEncoding : EUC-KR
    사용자키, 초기화 벡터 : github.com 에 올려놓은신 파일과 동일

    • 그렇군요~! 제가 한동안 너무 바빠서 몰 한 틈이 없었습니다.

      지금도 그렇구요!! ㅜㅜ
      이렇게 관심있게 지켜봐주신 것만으로도 감사드려야 하는데 기능개선할 사항까지 만들어주시니 조만간 시간내서 보겠습니다. 혹 답변이 늦어 해결하셨다면 내용 공유 좀 부탁드리겠습니다. 새해복 많이 받으세요 ^^

  • Guest

    아래 이미지 참조는 이걸로 해 주십시오..

  • Guest

    새해 복 많이 받으십시오..

    저번에 수정해 주신거 적용해 보았습니다.

    짧은 글에서도 잘 되는거 확인하였습니다.

    그런데 암호화할 문자에 특수문자가 들어갈 경우 암호화는 되는데 복호화가 되지 않습니다.

    한번 살펴봐 주시면 감사하겠습니다.

    $serverEncoding, $innerEncoding : EUC-KR

    사용자키, 초기화 벡터 : github.com 에 올려놓은신 파일과 동일

    • 특수문자 부분은 조만간 검토해보겠습니다. 자세하게 작성해주셔서 보기 편하네요~!! ^^ 해결되면 피드백 드릴께요!!

    • 확인해봤습니다.

      원인을 찾았구요!! 단문에서 작동안하던 오류 잡기 위해 넣었던 pkcs5Unpad 함수의 리턴값을 false로 해놔서… 해당 구문이 아무값도 출력을 하지 않았네요!!
      class.crypto.php 내 다음함수를 찾아서 대체해주세요!!

      public function pkcs5Unpad($text)

      {

      $pad = ord ( $text {strlen ( $text ) – 1} );

      if ($pad > strlen ( $text ))

      return $text;

      if (strspn ( $text, chr ( $pad ), strlen ( $text ) – $pad ) != $pad)

      return $text;

      return substr ( $text, 0, – 1 * $pad );

      }

  • 홍경숙

    안녕하세요?

    귀한소스 유용하게 쓰려고합니다.

    그런데 업체쪽에서 공유한 $pbUserKey와 $IV 키값이 모두 문자로 되어있습니다 ㅠㅠ
    —————————————————————————————————–

    (단, $pbUserKey와 $IV 키값으로 반드시 1바이트의 범위인 -128~127 사이의 숫자를 임의로 넣어주셔야 합니다.)
    —————————————————————————————————–

    라고 하셨는데, 문자로 할 수 있는 방법은 없을까요?

    예를 들면,

    private $pbUserKey = array(G,b,l,w,s,i,u,1,2,3,4,5,a,b,c,d); // 사용자키
    private $IV = array(E,A,X,5,2,Y,1,H,K,C,X,3,9,4,T,Q); // 초기화 벡터

    부탁드립니다.

    감사합니다.

    • 글쎄요 해당 배열의 값 각각을 바이트로 변환만 시키시면 될 것 같은데~!
      동일하게 나올지는 모르겠네요!!

      $pbUserKey = array(E,A,X,5,2,Y,1,H,K,C,X,3,9,4,T,Q);
      foreach($pbUserKey as $val) {
      $byte_array = unpack(‘C*’, $val);
      echo $byte_array[1] . ‘,’;
      }

      이렇게 돌려서 해보니
      $pbUserKey = array(71,98,108,119,115,105,117,49,50,51,52,53,97,98,99,100)
      $IV = array(69,65,88,53,50,89,49,72,75,67,88,51,57,52,84,81)

      값들이 이렇게 나오긴 하는데 정상작동을 할런지는 모르겠습니다.
      다만, 해당 업체에서 사용하는 소스를 보면 답이 확실하게 나오겠지요!! ^^;

  • 푸른곰

    php 64bit에서 어떻게 해결하셨는지 알수있을까요 ^^

    • java 함수전환 및 추가함수 섹션을 읽어보시면 되구요!
      64bit 처리가 안되기때문에 64bit로 받았지만 처리하는 부분은 32bit로 처리될 수 있게 해당함수가 그 역할을 수행합니다.
      참고하세요!!

  • phpdev

    32bit 서버에서 암호화된 데이타를 64bit 서버에서 복구가 안되는데, 방법이 없을까요.

    • 너무 바빠서 글을 이제 봤네요~ ㅜㅜ
      혹 해결하셨나요? 해결하셨으면 염치없지만 공유 좀 부탁드립니다.

  • 초보개발자

    너무 좋은정보 감사드립니다.

    (단, $pbUserKey와 $IV 키값으로 반드시 1바이트의 범위인 -128~127 사이의 숫자를 임의로 넣어주셔야 합니다.) 라고 하셨는데요.

    kisa에서 샘플로 제공하는 페이지에있는 사용자키값과 초기 백터값인

    $g_bszUser_key = “88,E3,4F,8F,08,17,79,F1,E9,F3,94,37,0A,D4,05,89”;
    $g_bszIV = “26,8D,66,A7,35,A8,1A,81,6F,BA,D9,FA,36,16,25,01”;

    를 사용할 수 있는 방법은 없을까요?

    • 지금 주신 숫자는 16진수인데요!1
      각각의 바이트 배열은 바이트가 아닌 16진수(0xFE) 형태로 사용하셔도 동일한 결과를 출력합니다.

      예를 들면 “0x88,0xE3,0x4F…” 이런식으로요!!
      주석을 제가 저렇게 적어놨네요~
      기억은 좀 가물가물한데 아마 이렇게 하시면 될거에요!

      혹 작동되신다면 꼭 피드백 좀 남겨주세요!
      포스트 내용에 첨부해야겠네요!! ^^