[php] mcrypt는 더 이상 사용되지 않습니다. 대안은 무엇입니까?

mcrypt-extension은 더 이상 사용되지 않습니다 . 여기에 게시 된 주석에 따라 PHP 7.2에서 제거 됩니다 . 그래서 암호를 암호화하는 다른 방법을 찾고 있습니다.

지금 나는 다음과 같은 것을 사용하고 있습니다.

mcrypt_encrypt(MCRYPT_RIJNDAEL_128, md5($key, true), $string, MCRYPT_MODE_CBC, $iv)

암호를 암호화하는 최선의 / 가장 강력한 방법에 대한 귀하의 의견이 필요합니다. 암호화 된 암호는 물론 PHP 7.xx에서 지원해야하며, 고객이 새 암호를 생성하지 않고 암호를 ‘복구’하는 옵션을 원하기 때문에 암호 해독이 가능해야합니다. 하나.



답변

암호를 해독 할 수 없도록 해시하는 것이 가장 좋습니다. 이로 인해 데이터베이스 또는 파일에 액세스 할 수있는 공격자가 작업을 약간 더 어렵게 만듭니다.

데이터를 암호화하고 복호화 할 수 있어야하는 경우 안전한 암호화 / 복호화 가이드는 https://paragonie.com/white-paper/2015-secure-php-data-encryption 에서 제공됩니다 . 그 링크를 요약하면 :

  • Libsodium 사용 -PHP 확장
  • Libsodium을 사용할 수 없다면 defuse / php-encryption을 사용하세요-Straight PHP code
  • Libsodium 또는 defuse / php-encryption을 사용할 수없는 경우 OpenSSL을 사용 하세요. 많은 서버에 이미이 기능이 설치되어 있습니다. 그렇지 않은 경우 –with-openssl [= DIR]로 컴파일 할 수 있습니다.

답변

@rqLizard가 제안한 대로 Rijndael 암호화라고도 하는 AES (고급 암호화 표준) 를 구현하는 훨씬 더 나은 대안을 제공하는 openssl_encrypt/ openssl_decryptPHP 함수를 대신 사용할 수 있습니다 .

php.net에서 다음 Scott의 의견에 따라 :

2015 년에 데이터를 암호화 / 암호화하는 코드를 작성하는 경우 openssl_encrypt()openssl_decrypt(). 기본 라이브러리 ( libmcrypt)는 2007 년부터 폐기되었으며 OpenSSL ( AES-NI최신 프로세서 를 활용 하고 캐시 타이밍에 안전함) 보다 성능이 훨씬 떨어졌습니다 .

또한 MCRYPT_RIJNDAEL_256아니다 AES-256그것은 Rijndael을 암호화 블록의 다른 변형 예이다. AES-256에서 원하는 경우 32 바이트 키와 함께 mcrypt사용해야 MCRYPT_RIJNDAEL_128합니다. OpenSSL은 사용중인 모드 (예 : aes-128-cbcvs aes-256-ctr) 를보다 명확하게 보여줍니다 .

OpenSSL은 또한 mcrypt의 NULL 바이트 패딩 대신 CBC 모드로 PKCS7 패딩을 사용합니다. 따라서 mcrypt는 OpenSSL보다 패딩 오라클 공격에 코드를 취약하게 만들 가능성이 높습니다.

마지막으로 암호문을 인증하지 않으면 (Encrypt Then MAC) 잘못된 것입니다.

추가 읽기 :

코드 예

예 1

PHP 7.1+ 용 GCM 모드의 AES 인증 암호화 예제

<?php
//$key should have been previously generated in a cryptographically safe way, like openssl_random_pseudo_bytes
$plaintext = "message to be encrypted";
$cipher = "aes-128-gcm";
if (in_array($cipher, openssl_get_cipher_methods()))
{
    $ivlen = openssl_cipher_iv_length($cipher);
    $iv = openssl_random_pseudo_bytes($ivlen);
    $ciphertext = openssl_encrypt($plaintext, $cipher, $key, $options=0, $iv, $tag);
    //store $cipher, $iv, and $tag for decryption later
    $original_plaintext = openssl_decrypt($ciphertext, $cipher, $key, $options=0, $iv, $tag);
    echo $original_plaintext."\n";
}
?>

예제 # 2

PHP 5.6+ 용 AES 인증 암호화 예제

<?php
//$key previously generated safely, ie: openssl_random_pseudo_bytes
$plaintext = "message to be encrypted";
$ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC");
$iv = openssl_random_pseudo_bytes($ivlen);
$ciphertext_raw = openssl_encrypt($plaintext, $cipher, $key, $options=OPENSSL_RAW_DATA, $iv);
$hmac = hash_hmac('sha256', $ciphertext_raw, $key, $as_binary=true);
$ciphertext = base64_encode( $iv.$hmac.$ciphertext_raw );

//decrypt later....
$c = base64_decode($ciphertext);
$ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC");
$iv = substr($c, 0, $ivlen);
$hmac = substr($c, $ivlen, $sha2len=32);
$ciphertext_raw = substr($c, $ivlen+$sha2len);
$original_plaintext = openssl_decrypt($ciphertext_raw, $cipher, $key, $options=OPENSSL_RAW_DATA, $iv);
$calcmac = hash_hmac('sha256', $ciphertext_raw, $key, $as_binary=true);
if (hash_equals($hmac, $calcmac))//PHP 5.6+ timing attack safe comparison
{
    echo $original_plaintext."\n";
}
?>

예 # 3

위의 예를 기반으로 사용자의 세션 ID를 암호화하는 것을 목표로하는 다음 코드를 변경했습니다.

class Session {

  /**
   * Encrypts the session ID and returns it as a base 64 encoded string.
   *
   * @param $session_id
   * @return string
   */
  public function encrypt($session_id) {
    // Get the MD5 hash salt as a key.
    $key = $this->_getSalt();
    // For an easy iv, MD5 the salt again.
    $iv = $this->_getIv();
    // Encrypt the session ID.
    $encrypt = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $session_id, MCRYPT_MODE_CBC, $iv);
    // Base 64 encode the encrypted session ID.
    $encryptedSessionId = base64_encode($encrypt);
    // Return it.
    return $encryptedSessionId;
  }

  /**
   * Decrypts a base 64 encoded encrypted session ID back to its original form.
   *
   * @param $encryptedSessionId
   * @return string
   */
  public function decrypt($encryptedSessionId) {
    // Get the MD5 hash salt as a key.
    $key = $this->_getSalt();
    // For an easy iv, MD5 the salt again.
    $iv = $this->_getIv();
    // Decode the encrypted session ID from base 64.
    $decoded = base64_decode($encryptedSessionId);
    // Decrypt the string.
    $decryptedSessionId = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $decoded, MCRYPT_MODE_CBC, $iv);
    // Trim the whitespace from the end.
    $session_id = rtrim($decryptedSessionId, "\0");
    // Return it.
    return $session_id;
  }

  public function _getIv() {
    return md5($this->_getSalt());
  }

  public function _getSalt() {
    return md5($this->drupal->drupalGetHashSalt());
  }

}

으로:

class Session {

  const SESS_CIPHER = 'aes-128-cbc';

  /**
   * Encrypts the session ID and returns it as a base 64 encoded string.
   *
   * @param $session_id
   * @return string
   */
  public function encrypt($session_id) {
    // Get the MD5 hash salt as a key.
    $key = $this->_getSalt();
    // For an easy iv, MD5 the salt again.
    $iv = $this->_getIv();
    // Encrypt the session ID.
    $ciphertext = openssl_encrypt($session_id, self::SESS_CIPHER, $key, $options=OPENSSL_RAW_DATA, $iv);
    // Base 64 encode the encrypted session ID.
    $encryptedSessionId = base64_encode($ciphertext);
    // Return it.
    return $encryptedSessionId;
  }

  /**
   * Decrypts a base 64 encoded encrypted session ID back to its original form.
   *
   * @param $encryptedSessionId
   * @return string
   */
  public function decrypt($encryptedSessionId) {
    // Get the Drupal hash salt as a key.
    $key = $this->_getSalt();
    // Get the iv.
    $iv = $this->_getIv();
    // Decode the encrypted session ID from base 64.
    $decoded = base64_decode($encryptedSessionId, TRUE);
    // Decrypt the string.
    $decryptedSessionId = openssl_decrypt($decoded, self::SESS_CIPHER, $key, $options=OPENSSL_RAW_DATA, $iv);
    // Trim the whitespace from the end.
    $session_id = rtrim($decryptedSessionId, '\0');
    // Return it.
    return $session_id;
  }

  public function _getIv() {
    $ivlen = openssl_cipher_iv_length(self::SESS_CIPHER);
    return substr(md5($this->_getSalt()), 0, $ivlen);
  }

  public function _getSalt() {
    return $this->drupal->drupalGetHashSalt();
  }

}

명확히하기 위해 두 암호화는 다른 블록 크기와 다른 암호화 된 데이터를 사용하므로 위의 변경은 진정한 변환이 아닙니다. 또한 기본 패딩은 다르며 MCRYPT_RIJNDAEL비표준 널 패딩 만 지원합니다. @zaph


추가 참고 사항 (@zaph의 의견) :

  • Rijndael 128 ( MCRYPT_RIJNDAEL_128) AES 와 동일 하지만 Rijndael 256 ( MCRYPT_RIJNDAEL_256) AES-256 이 아닙니다 . 256은 256 비트의 블록 크기를 지정하는 반면 AES 에는 128 비트의 블록 크기 만 있습니다. 따라서 기본적으로 블록 크기가 256 비트 ( MCRYPT_RIJNDAEL_256) 인 Rijndael 은 mcrypt 개발자 의 선택으로 인해 잘못 명명되었습니다 . @zaph
  • 블록 크기가 256 인 Rijndael은 128 비트의 블록 크기보다 안전하지 않을 수 있습니다. 후자는 훨씬 더 많은 리뷰와 사용을 가지고 있기 때문입니다. 둘째, AES는 일반적으로 사용 가능하지만 블록 크기가 256 비트 인 Rijndael은 사용할 수 없다는 점에서 상호 운용성이 저해됩니다.
  • Rijndael에 대해 서로 다른 블록 크기로 암호화하면 서로 다른 암호화 된 데이터가 생성됩니다.

    예를 들어, MCRYPT_RIJNDAEL_256(와 동일하지 않음 AES-256)은 256 비트 크기와 전달 된 키를 기반으로하는 키 크기를 가진 Rijndael 블록 암호의 다른 변형을 정의합니다. 여기서 aes-256-cbcRijndael은 키 크기가 128 비트이고 블록 크기가 다음과 같습니다. 256 비트. 따라서 그들은 mcrypt가 블록 크기를 지정하기 위해 숫자를 사용하기 때문에 완전히 다른 암호화 된 데이터를 생성하는 다른 블록 크기를 사용하고 있습니다. 여기서 OpenSSL은 키 크기를 지정하기 위해 숫자를 사용했습니다 (AES는 128 비트의 한 블록 크기 만 있습니다). 따라서 기본적으로 AES는 블록 크기가 128 비트이고 키 크기가 128, 192 및 256 비트 인 Rijndael입니다. 따라서 OpenSSL에서 Rijndael 128이라고하는 AES를 사용하는 것이 좋습니다.


답변

Rijndael의 Pure-PHP 구현은 composer 패키지로 사용 가능한 phpseclib 와 함께 존재하며 PHP 7.3에서 작동합니다 (내가 테스트 함).

phpseclib 문서 에는 기본 변수 (암호, 모드, 키 크기, 비트 크기)를 입력 한 후 샘플 코드생성 하는 페이지가 있습니다 . Rijndael, ECB, 256, 256에 대해 다음을 출력합니다.

mycrypt가있는 코드

$decoded = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, ENCRYPT_KEY, $term, MCRYPT_MODE_ECB);

라이브러리에서 이와 같이 작동합니다.

$rijndael = new \phpseclib\Crypt\Rijndael(\phpseclib\Crypt\Rijndael::MODE_ECB);
$rijndael->setKey(ENCRYPT_KEY);
$rijndael->setKeyLength(256);
$rijndael->disablePadding();
$rijndael->setBlockLength(256);

$decoded = $rijndael->decrypt($term);

* $termbase64_decoded


답변

여기에 다른 답변에서 자세히 설명했듯이 내가 찾은 최고의 솔루션은 OpenSSL을 사용하는 것입니다. PHP에 내장되어 있으며 외부 라이브러리가 필요하지 않습니다. 다음은 간단한 예입니다.

암호화하려면 :

function encrypt($key, $payload) {
  $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
  $encrypted = openssl_encrypt($payload, 'aes-256-cbc', $key, 0, $iv);
  return base64_encode($encrypted . '::' . $iv);
}

암호를 해독하려면 :

function decrypt($key, $garble) {
    list($encrypted_data, $iv) = explode('::', base64_decode($garble), 2);
    return openssl_decrypt($encrypted_data, 'aes-256-cbc', $key, 0, $iv);
}

참조 링크 : https://www.shift8web.ca/2017/04/how-to-encrypt-and-execute-your-php-code-with-mcrypt/


답변

phpseclib pollyfill 패키지를 사용할 수 있습니다 . rijndael 256으로 암호화 / 복호화를 위해 open ssl 또는 libsodium을 사용할 수 없습니다. 또 다른 문제는 코드를 교체 할 필요가 없다는 것입니다.


답변

OpenSSL mcrypt은 활발하게 개발되고 유지되므로 사용해야 합니다. 더 나은 보안, 유지 관리 및 이식성을 제공합니다. 둘째, AES 암호화 / 복호화를 훨씬 빠르게 수행합니다. 기본적으로 PKCS7 패딩을 사용하지만 OPENSSL_ZERO_PADDING필요한 경우 지정할 수 있습니다. 32 바이트 이진 키를 사용하려면 지정할 수 aes-256-cbc보다 훨씬 명백하다 MCRYPT_RIJNDAEL_128.

다음은 Mcrypt를 사용하는 코드 예제입니다.

PKCS7 패딩을 사용하여 Mcrypt로 작성된 인증되지 않은 AES-256-CBC 암호화 라이브러리입니다.

/**
 * This library is unsafe because it does not MAC after encrypting
 */
class UnsafeMcryptAES
{
    const CIPHER = MCRYPT_RIJNDAEL_128;

    public static function encrypt($message, $key)
    {
        if (mb_strlen($key, '8bit') !== 32) {
            throw new Exception("Needs a 256-bit key!");
        }
        $ivsize = mcrypt_get_iv_size(self::CIPHER);
        $iv = mcrypt_create_iv($ivsize, MCRYPT_DEV_URANDOM);

        // Add PKCS7 Padding
        $block = mcrypt_get_block_size(self::CIPHER);
        $pad = $block - (mb_strlen($message, '8bit') % $block, '8bit');
        $message .= str_repeat(chr($pad), $pad);

        $ciphertext = mcrypt_encrypt(
            MCRYPT_RIJNDAEL_128,
            $key,
            $message,
            MCRYPT_MODE_CBC,
            $iv
        );

        return $iv . $ciphertext;
    }

    public static function decrypt($message, $key)
    {
        if (mb_strlen($key, '8bit') !== 32) {
            throw new Exception("Needs a 256-bit key!");
        }
        $ivsize = mcrypt_get_iv_size(self::CIPHER);
        $iv = mb_substr($message, 0, $ivsize, '8bit');
        $ciphertext = mb_substr($message, $ivsize, null, '8bit');

        $plaintext = mcrypt_decrypt(
            MCRYPT_RIJNDAEL_128,
            $key,
            $ciphertext,
            MCRYPT_MODE_CBC,
            $iv
        );

        $len = mb_strlen($plaintext, '8bit');
        $pad = ord($plaintext[$len - 1]);
        if ($pad <= 0 || $pad > $block) {
            // Padding error!
            return false;
        }
        return mb_substr($plaintext, 0, $len - $pad, '8bit');
    }
}

다음은 OpenSSL을 사용하여 작성된 버전입니다.

/**
 * This library is unsafe because it does not MAC after encrypting
 */
class UnsafeOpensslAES
{
    const METHOD = 'aes-256-cbc';

    public static function encrypt($message, $key)
    {
        if (mb_strlen($key, '8bit') !== 32) {
            throw new Exception("Needs a 256-bit key!");
        }
        $ivsize = openssl_cipher_iv_length(self::METHOD);
        $iv = openssl_random_pseudo_bytes($ivsize);

        $ciphertext = openssl_encrypt(
            $message,
            self::METHOD,
            $key,
            OPENSSL_RAW_DATA,
            $iv
        );

        return $iv . $ciphertext;
    }

    public static function decrypt($message, $key)
    {
        if (mb_strlen($key, '8bit') !== 32) {
            throw new Exception("Needs a 256-bit key!");
        }
        $ivsize = openssl_cipher_iv_length(self::METHOD);
        $iv = mb_substr($message, 0, $ivsize, '8bit');
        $ciphertext = mb_substr($message, $ivsize, null, '8bit');

        return openssl_decrypt(
            $ciphertext,
            self::METHOD,
            $key,
            OPENSSL_RAW_DATA,
            $iv
        );
    }
}

출처 : PHP 코드에 MCRYPT라는 단어를 입력하는 경우 잘못하고있는 것 입니다.


답변

PHP 7.2.x에서 이것을 사용하고 있으며 잘 작동합니다.

public function make_hash($userStr){
        try{
            /**
             * Used and tested on PHP 7.2x, Salt has been removed manually, it is now added by PHP
             */
             return password_hash($userStr, PASSWORD_BCRYPT);
            }catch(Exception $exc){
                $this->tempVar = $exc->getMessage();
                return false;
            }
        }

다음 함수를 사용하여 해시를 인증합니다.

public function varify_user($userStr,$hash){
        try{
            if (password_verify($userStr, $hash)) {
                 return true;
                }
            else {
                return false;
                }
            }catch(Exception $exc){
                $this->tempVar = $exc->getMessage();
                return false;
            }
        }

예:

  //create hash from user string

 $user_password = $obj->make_hash2($user_key);

이 해시를 인증하려면 다음 코드를 사용하십시오.

if($obj->varify_user($key, $user_key)){
      //this is correct, you can proceed with  
    }

그게 다야.