간만에 네이버페이 주문형 결제연동하다가 서버에 mcrypt 함수가 지원이 안되어 모듈 실행시 오류가 발생하는 문제가 생겼다. 해당 서버의 PHP 버전은 7.4 였는데 당장 서버에서 지원이 안되는 관계로 조금 난감하게 되었다. PHP 공식 포럼에는 해당 함수가 7.1 부터 배제되었고 7.2부터는 제거 되었으니 해당 함수에 의존하지 말라고 명시가 되어있었다. 

 

아무래도 보안상 문제인듯 보이나 mcrypt를 사용하는 구간이 다른 곳에서도 많은데 당장 사용을 못하니 정말 난감하였다. 그래서 openssl 로 대체를 하게 되었다.  아래는 예제를 위해 네이버페이 nhnapi-simplecryptlib.php 모듈중 일부를 가져와 실행되도록 함수화 시킨 소스이니 참고 바란다. 

 

nhnapi-simplecryptlib.php - 기존소스 일부
<?php
  define('NHNAPISCL_BLOCK_SIZE', 16); // AES128 암호알고리즘의 블록사이즈
  define('NHNAPISCL_IV', "c437b4792a856233c183994afe10a6b2"); // AES128 암호알고리즘에 사용할 고정 Initial Vector

  /**
   * PKCS7 패딩생성
   * @param data 패딩할 데이터
   * @param block 암호생성기의 블록사이즈
   * @return pkcs7패딩을 추가한 데이터
   */
  function p7padding($data, $block){

    $len = strlen($data);

    $padding = $block - ($len % $block);

    return $data . str_repeat(chr($padding),$padding);

  }

  /**
   * PKCS7 패딩제거
   * @param text 패딩제거할 데이터
   * @return pkcs7패딩을 제거한 데이터 또는 PEAR_Error
   */
  function p7unpadding($text) {

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

    if ($pad > strlen($text)){
      // return new PEAR_Error('invalid padding');
      return 'invalid padding';
    }

    if (strspn($text, chr($pad), strlen($text) - $pad) != $pad){
      // return new PEAR_Error('invalid padding');
      return 'invalid padding';
    }

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

  }

  function encrypt($secret, $text){

     if(strlen($secret)==0){
     // return new PEAR_Error('invalid secret');
     return 'invalid secret';
     }
    
     if(strlen($text)==0){
     // return new PEAR_Error('invalid text');
     return 'invalid text';
     }


     $padded = p7padding($text, NHNAPISCL_BLOCK_SIZE);

     $iv=pack('H*',NHNAPISCL_IV);

     $key=pack('H*',$secret);

     $ctext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $padded, MCRYPT_MODE_CBC, $iv);

     return base64_encode($ctext);
  }

  /**
   * AES128 복호화
   * @param secret hex인코딩한 암호키
   * @param text base64인코딩한 암호값
   * @return 복호화된 평문(UTF-8) 또는 PEAR_Error
   */
  function decrypt($secret, $text){

     if(strlen($secret)==0){
     // return new PEAR_Error('invalid secret');
     return 'invalid secret';
     }

     if(strlen($text)==0){
     // return new PEAR_Error('invalid text');
     return 'invalid text';
     }

     $ctext = base64_decode($text);

     $iv=pack('H*',NHNAPISCL_IV);

     $key=pack('H*',$secret);

     $dtext = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $ctext, MCRYPT_MODE_CBC, $iv);

     return p7unpadding($dtext);
  }

  $secret = 'bcb04b7e103a0cd8b54763051cef08bc55abe029fdebae5e1d417e2ffb2a00a3';
  $encrypt = encrypt($secret,'텍스트');
  $decrypt = decrypt($secret,$encrypt);


  echo '암호화(AES128): '.$encrypt;
  echo '<br />';
  echo '복호화(AES128): '.$decrypt;
  /* 
  결과)
    암호화(AES128): EKAtBZSJNHyYVJTePM9MTw==
    복호화(AES128): 텍스트
  */

 

위의 소스를 openssl 함수로 대체한 소스는 아래와 같다. 

 

nhnapi-simplecryptlib.php - openssl 함수로 대체한 소스

참고로 변경된 구간은 주석상 `변경: 주석` 부분과 `변경: 추가` 부분이니 참고 하길 바란다. 

<?php
  define('NHNAPISCL_BLOCK_SIZE', 16); // AES128 암호알고리즘의 블록사이즈
  define('NHNAPISCL_IV', "c437b4792a856233c183994afe10a6b2"); // AES128 암호알고리즘에 사용할 고정 Initial Vector

  /**
   * PKCS7 패딩생성
   * @param data 패딩할 데이터
   * @param block 암호생성기의 블록사이즈
   * @return pkcs7패딩을 추가한 데이터
   */
  function p7padding($data, $block){

    $len = strlen($data);

    $padding = $block - ($len % $block);

    return $data . str_repeat(chr($padding),$padding);

  }

  /**
   * PKCS7 패딩제거
   * @param text 패딩제거할 데이터
   * @return pkcs7패딩을 제거한 데이터 또는 PEAR_Error
   */
  function p7unpadding($text) {

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

    if ($pad > strlen($text)){
      // return new PEAR_Error('invalid padding');
      return 'invalid padding';
    }

    if (strspn($text, chr($pad), strlen($text) - $pad) != $pad){
      // return new PEAR_Error('invalid padding');
      return 'invalid padding';
    }

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

  }

  function encrypt($secret, $text){

     if(strlen($secret)==0){
     // return new PEAR_Error('invalid secret');
     return 'invalid secret';
     }
    
     if(strlen($text)==0){
     // return new PEAR_Error('invalid text');
     return 'invalid text';
     }

     $padded = p7padding($text, NHNAPISCL_BLOCK_SIZE);

     $iv=pack('H*',NHNAPISCL_IV);

     $key=pack('H*',$secret);

     // $ctext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $padded, MCRYPT_MODE_CBC, $iv); // 변경: 주석
     $ctext = openssl_encrypt($padded, 'aes-256-cbc', $key, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING, $iv); // 변경: 추가


     return base64_encode($ctext);
  }

  /**
   * AES128 복호화
   * @param secret hex인코딩한 암호키
   * @param text base64인코딩한 암호값
   * @return 복호화된 평문(UTF-8) 또는 PEAR_Error
   */
  function decrypt($secret, $text){

     if(strlen($secret)==0){
     // return new PEAR_Error('invalid secret');
     return 'invalid secret';
     }
    
     if(strlen($text)==0){
     // return new PEAR_Error('invalid text');
     return 'invalid text';
     }

     $ctext = base64_decode($text);

     $iv=pack('H*',NHNAPISCL_IV);

     $key=pack('H*',$secret);

     // $dtext = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $ctext, MCRYPT_MODE_CBC, $iv); // 변경: 주석
     $dtext = openssl_decrypt($ctext, 'aes-256-cbc', $key, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING, $iv); // 변경: 추가

     return p7unpadding($dtext);
  }

  $secret = 'bcb04b7e103a0cd8b54763051cef08bc55abe029fdebae5e1d417e2ffb2a00a3';
  $encrypt = encrypt($secret,'텍스트');
  $decrypt = decrypt($secret,$encrypt);


  echo '암호화(AES128): '.$encrypt;
  echo '<br />';
  echo '복호화(AES128): '.$decrypt;
  /* 
  결과)
    암호화(AES128): EKAtBZSJNHyYVJTePM9MTw==
    복호화(AES128): 텍스트
  */

 

openssl로 대체한 함수구간에서 중요 포인트는 두가지가 있다. 첫번째는 암호화방식 aes-256-cbc 이고 두번째는 OPENSSL_RAW_DATA | OPENSSL_NO_PADDING 옵션이다. 

 

우선 첫번째 암호화 방식의 경우 mcrypt 를 대체한다고해서 무조건 쓰면 되는게 아닌 기존 mcrypt 에서 사용된 암호화방식을 확인하고 그에 맞는 암호화 방식을 써야한다.  두번째 옵션 항목은 통상 OPENSSL_RAW_DATA 만 쓰면되나 기존 암호화 함수에서 p7padding 함수를 통해 패딩을 주었기때문에 OPENSSL_NO_PADDING 옵션까지 주어 암호화 할때 패딩된 문자가 포함되지 않도록 해야한다. 

 

만약 위의 예제에서 OPENSSL_NO_PADDING 을 주지 않을 경우에는 복호화가 되지 않으며 암호화 시에도 mcrypt 로 암호화 했던 데이터와 달라지게 된다. 그 결과는 아래와 같다. 

 

암호화(AES128): EKAtBZSJNHyYVJTePM9MT04T3Zd8OCMERo4zFIc8mzI=
복호화(AES128): 텍스트

 

위와 같이 OPENSSL_NO_PADDING 옵션이 없더라도 정상적으로 암/복호화가 되었지만 실제로 암호화된 데이터는 mcrypt로 암호화 했을때와는 달라지게 되서 문제가 발생되니 확인이 필요하다.