PHP를 사용한 가장 간단한 양방향 암호화


230

일반적인 PHP 설치에서 양방향 암호화를 수행하는 가장 간단한 방법은 무엇입니까?

문자열 키로 데이터를 암호화 할 수 있어야하고 같은 키를 사용하여 다른 쪽 끝을 해독해야합니다.

보안은 코드의 이식성만큼 큰 문제가 아니므로 가능한 한 간단하게 유지할 수 있기를 바랍니다. 현재 RC4 구현을 사용하고 있지만 기본적으로 지원되는 것을 찾을 수 있으면 불필요한 코드를 많이 저장할 수 있습니다.



3
범용 암호화의 경우 자체 롤링 대신 defuse / php-encryption /을 사용하십시오.
Scott Arciszewski

2
github.com/defuse/php-encryption 에서 멀리 떨어진 곳 -mcrypt 보다 몇 배나 느립니다.
Eugen Rieck

1
@Scott "이것은 아마도 병목 현상이 없을 것"이라는 선을 따라 생각하는 것은 우리에게 많은 나쁜 소프트웨어를 가져 왔습니다.
Eugen Rieck

3
실제로 많은 데이터를 암호화 / 복호화하는 데 소요되는 시간 (밀리 초)이 응용 프로그램을 정체 시키려면 총알을 물고 libsodium으로 전환하십시오. Sodium::crypto_secretbox()그리고 Sodium::crypto_secretbox_open()안전하고 성능이 좋은 있습니다.
Scott Arciszewski

답변:


196

편집 :

실제로 openssl_encrypt () & openssl_decrypt ()를 사용해야합니다

으로 스콧은 말한다, Mcrypt 라이브러리는 2007 년부터 업데이트되지 않은으로 좋은 생각이 아니다.

PHP에서 Mcrypt 라이브러리를 제거하는 RFC도있다 - https://wiki.php.net/rfc/mcrypt-viking-funeral


6
@EugenRieck 네, 그게 요점입니다. Mcrypt는 패치를받지 않습니다. OpenSSL은 크거나 작은 취약점이 발견되는 즉시 패치를받습니다.
그렉

5
그렇게 투표가 쉬운 답변이 가장 간단한 예를 제공하는 것이 좋습니다. 어쨌든 고마워
T.Todua

여러분, 단지 FYI => MCRYPT가 더 이상 사용되지 않습니다. 우리에게 무수한 문제가 생겼기 때문에 모든 사람들이 그것을 사용하지 않아야한다는 것을 알고 있어야합니다. 내가 실수하지 않으면 PHP 7.1부터 더 이상 사용되지 않습니다.
clusterBuddy

PHP 7부터 mcrypt 함수는 PHP 코드베이스에서 제거됩니다. 따라서 최신 버전의 PHP (표준이어야 함)를 사용하면 더 이상 사용되지 않는 기능을 사용할 수 없습니다.
Alexander Behling

234

중요 사항 : 매우 특별한 사용 사례 가없는 경우 비밀번호를 암호화하지 말고 비밀번호 해시 알고리즘을 대신 사용하십시오. 누군가 서버 측 애플리케이션에서 비밀번호를 암호화 한다고 말하면 정보가 없거나 위험한 시스템 설계를 설명하고 있습니다. 암호를 안전하게 저장하는 것은 암호화와 완전히 별개의 문제입니다.

정보를 받으십시오. 안전한 시스템을 설계하십시오.

PHP의 휴대용 데이터 암호화

PHP 5.4 이상을 사용 하고 있고 암호화 모듈을 직접 작성하지 않으려면 인증 된 암호화를 제공하는 기존 라이브러리를 사용 하는 것이 좋습니다 . 필자가 링크 한 라이브러리는 PHP가 제공하는 것에 만 의존하며 소수의 보안 연구원들이 주기적으로 검토하고 있습니다. (자신도 포함되어 있습니다.)

이식성 목표가 PECL 확장을 요구하지 않으면 libsodium 은 PHP로 작성할 수있는 것보다 강력히 권장됩니다.

업데이트 (2016-06-12) : 이제 PECL 확장을 설치하지 않고도 sodium_compat 를 사용하고 동일한 암호화 라이브러리를 사용할 수 있습니다 .

암호화 공학을 경험하고 싶다면 계속 읽으십시오.


먼저 인증되지 않은 암호화암호화 운명 원칙 의 위험 을 배우는 시간을 가져야합니다 .

  • 악의적 인 사용자가 암호화 된 데이터를 조작 할 수 있습니다.
  • 암호화 된 데이터를 인증하면 변조가 방지됩니다.
  • 암호화되지 않은 데이터를 인증해도 변조가 방지되지는 않습니다.

암호화 및 암호 해독

PHP에서의 암호화는 실제로 간단합니다 (우리는 사용 openssl_encrypt()하고 openssl_decrypt()정보를 암호화하는 방법에 대한 결정을 한 후에 결정 openssl_get_cipher_methods()합니다. 시스템에서 지원되는 방법 목록을 참조하십시오 . 가장 좋은 방법 은 CTR 모드의 AES입니다 .

  • aes-128-ctr
  • aes-192-ctr
  • aes-256-ctr

현재 AES 키 크기 를 믿을만한 이유가 없습니다. 에 대한 걱정에 상당한 문제가 (더 큰 아마이다는 하지 때문에 256 비트 모드에서 나쁜 키 스케줄링, 더 나은).

참고 : 우리 mcrypt포기 하고 있기 때문에 사용하지 않습니다 패치되지 않은 버그 보안에 영향을 줄 수 있습니다. 이러한 이유로 인해 다른 PHP 개발자들도이를 피하는 것이 좋습니다.

OpenSSL을 사용하는 간단한 암호화 / 복호화 래퍼

class UnsafeCrypto
{
    const METHOD = 'aes-256-ctr';

    /**
     * Encrypts (but does not authenticate) a message
     * 
     * @param string $message - plaintext message
     * @param string $key - encryption key (raw binary expected)
     * @param boolean $encode - set to TRUE to return a base64-encoded 
     * @return string (raw binary)
     */
    public static function encrypt($message, $key, $encode = false)
    {
        $nonceSize = openssl_cipher_iv_length(self::METHOD);
        $nonce = openssl_random_pseudo_bytes($nonceSize);

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

        // Now let's pack the IV and the ciphertext together
        // Naively, we can just concatenate
        if ($encode) {
            return base64_encode($nonce.$ciphertext);
        }
        return $nonce.$ciphertext;
    }

    /**
     * Decrypts (but does not verify) a message
     * 
     * @param string $message - ciphertext message
     * @param string $key - encryption key (raw binary expected)
     * @param boolean $encoded - are we expecting an encoded string?
     * @return string
     */
    public static function decrypt($message, $key, $encoded = false)
    {
        if ($encoded) {
            $message = base64_decode($message, true);
            if ($message === false) {
                throw new Exception('Encryption failure');
            }
        }

        $nonceSize = openssl_cipher_iv_length(self::METHOD);
        $nonce = mb_substr($message, 0, $nonceSize, '8bit');
        $ciphertext = mb_substr($message, $nonceSize, null, '8bit');

        $plaintext = openssl_decrypt(
            $ciphertext,
            self::METHOD,
            $key,
            OPENSSL_RAW_DATA,
            $nonce
        );

        return $plaintext;
    }
}

사용 예

$message = 'Ready your ammunition; we attack at dawn.';
$key = hex2bin('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f');

$encrypted = UnsafeCrypto::encrypt($message, $key);
$decrypted = UnsafeCrypto::decrypt($encrypted, $key);

var_dump($encrypted, $decrypted);

데모 : https://3v4l.org/jl7qR


위의 간단한 암호화 라이브러리는 여전히 안전하지 않습니다. 암호문인증하고 해독하기 전에 확인해야합니다 .

참고 : 기본적 UnsafeCrypto::encrypt()으로 원시 이진 문자열을 반환합니다. 바이너리 안전 형식 (base64 인코딩)으로 저장해야하는 경우 다음과 같이 호출하십시오.

$message = 'Ready your ammunition; we attack at dawn.';
$key = hex2bin('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f');

$encrypted = UnsafeCrypto::encrypt($message, $key, true);
$decrypted = UnsafeCrypto::decrypt($encrypted, $key, true);

var_dump($encrypted, $decrypted);

데모 : http://3v4l.org/f5K93

간단한 인증 래퍼

class SaferCrypto extends UnsafeCrypto
{
    const HASH_ALGO = 'sha256';

    /**
     * Encrypts then MACs a message
     * 
     * @param string $message - plaintext message
     * @param string $key - encryption key (raw binary expected)
     * @param boolean $encode - set to TRUE to return a base64-encoded string
     * @return string (raw binary)
     */
    public static function encrypt($message, $key, $encode = false)
    {
        list($encKey, $authKey) = self::splitKeys($key);

        // Pass to UnsafeCrypto::encrypt
        $ciphertext = parent::encrypt($message, $encKey);

        // Calculate a MAC of the IV and ciphertext
        $mac = hash_hmac(self::HASH_ALGO, $ciphertext, $authKey, true);

        if ($encode) {
            return base64_encode($mac.$ciphertext);
        }
        // Prepend MAC to the ciphertext and return to caller
        return $mac.$ciphertext;
    }

    /**
     * Decrypts a message (after verifying integrity)
     * 
     * @param string $message - ciphertext message
     * @param string $key - encryption key (raw binary expected)
     * @param boolean $encoded - are we expecting an encoded string?
     * @return string (raw binary)
     */
    public static function decrypt($message, $key, $encoded = false)
    {
        list($encKey, $authKey) = self::splitKeys($key);
        if ($encoded) {
            $message = base64_decode($message, true);
            if ($message === false) {
                throw new Exception('Encryption failure');
            }
        }

        // Hash Size -- in case HASH_ALGO is changed
        $hs = mb_strlen(hash(self::HASH_ALGO, '', true), '8bit');
        $mac = mb_substr($message, 0, $hs, '8bit');

        $ciphertext = mb_substr($message, $hs, null, '8bit');

        $calculated = hash_hmac(
            self::HASH_ALGO,
            $ciphertext,
            $authKey,
            true
        );

        if (!self::hashEquals($mac, $calculated)) {
            throw new Exception('Encryption failure');
        }

        // Pass to UnsafeCrypto::decrypt
        $plaintext = parent::decrypt($ciphertext, $encKey);

        return $plaintext;
    }

    /**
     * Splits a key into two separate keys; one for encryption
     * and the other for authenticaiton
     * 
     * @param string $masterKey (raw binary)
     * @return array (two raw binary strings)
     */
    protected static function splitKeys($masterKey)
    {
        // You really want to implement HKDF here instead!
        return [
            hash_hmac(self::HASH_ALGO, 'ENCRYPTION', $masterKey, true),
            hash_hmac(self::HASH_ALGO, 'AUTHENTICATION', $masterKey, true)
        ];
    }

    /**
     * Compare two strings without leaking timing information
     * 
     * @param string $a
     * @param string $b
     * @ref https://paragonie.com/b/WS1DLx6BnpsdaVQW
     * @return boolean
     */
    protected static function hashEquals($a, $b)
    {
        if (function_exists('hash_equals')) {
            return hash_equals($a, $b);
        }
        $nonce = openssl_random_pseudo_bytes(32);
        return hash_hmac(self::HASH_ALGO, $a, $nonce) === hash_hmac(self::HASH_ALGO, $b, $nonce);
    }
}

사용 예

$message = 'Ready your ammunition; we attack at dawn.';
$key = hex2bin('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f');

$encrypted = SaferCrypto::encrypt($message, $key);
$decrypted = SaferCrypto::decrypt($encrypted, $key);

var_dump($encrypted, $decrypted);

데모 : 원시 바이너리 , base64 인코딩


SaferCrypto프로덕션 환경 에서이 라이브러리 를 사용 하거나 동일한 개념을 직접 구현 하려는 경우 누구에게나 문의하십시오. 경우, 다른 의견을 얻기 위해 상주 암호 해독기문의 . 내가 알지도 못하는 실수에 대해 알려줄 것입니다.

평판이 좋은 암호화 라이브러리를 사용 하는 것이 훨씬 나을 것 입니다.


3
따라서 UnsafeCrypto를 먼저 작동 시키려고합니다. 암호화는 잘 수행되지만 암호 해독을 실행할 때마다 응답으로 '거짓'이 표시됩니다. 동일한 키를 사용하여 해독하고 인코딩뿐만 아니라 디코딩에서도 true를 전달합니다. 예에서 나는 typeo라고 가정하고 있는데, 그것이 내 문제가 어디에서 왔는지 궁금합니다. $ mac 변수가 어디에서 왔는지 설명 할 수 있습니까? 간단하게 $ iv 여야합니까?
David C

1
@EugenRieck OpenSSL 암호 구현은 아마도 빠지지 않는 유일한 부분 일 것이며, 바닐라 PHP에서 AES-NI를 활용할 수있는 유일한 방법입니다. OpenBSD에 설치하면 PHP 코드에 차이가없는 PHP를 LibreSSL에 맞게 컴파일합니다. Libsodium> OpenSSL 언제든지. 또한 libmcrypt를 사용하지 마십시오 . OpenSSL 대신 PHP 개발자가 권장하는 것은 무엇입니까?
Scott Arciszewski



1
방금 키에 대해 사람이 읽을 수있는 문자열이 아닌 이진 문자열을 원한다는 것을 보여주었습니다 .
Scott Arciszewski

22

해당 매개 변수 mcrypt_encrypt()mcrypt_decrypt()함께 사용하십시오 . 정말 쉽고 간단하며 전투 테스트를 거친 암호화 패키지를 사용합니다.

편집하다

이 답변 후 5 년 4 개월 후에 mcrypt확장 프로그램은 현재 사용 중단되어 최종적으로 PHP에서 제거되고 있습니다.


34
8 년 이상 전투 테스트를 거치지 않았습니까?
Maarten Bodewes

2
글쎄, mcrypt는 PHP7에 있으며 더 이상 사용되지 않습니다-그것은 충분합니다. 모든 코드가 OpenSSL의 끔찍한 품질 인 것은 아니며 며칠마다 패치가 필요합니다.
Eugen Rieck

3
mcrypt는 지원 측면에서 끔찍한 것이 아닙니다. 또한 PKCS # 7 호환 패딩, 인증 된 암호화와 같은 모범 사례를 구현하지 않습니다. 아무도 그것을 유지하고 있지 않기 때문에 SHA-3 또는 다른 새로운 알고리즘을 지원하지 않으므로 업그레이드 경로를 강탈합니다. 또한 부분 키, 제로 패딩 등을 수행하는 데 사용되었습니다. PHP에서 점진적으로 제거되는 이유가 있습니다.
Maarten Bodewes 2016 년

2
PHP 7.1에서 모든 mcrypt_ * 함수는 E_DEPRECATED 알림을 발생시킵니다. PHP 7.1 + 1 (7.2 또는 8.0)에서 mcrypt 확장은 코어에서 PECL로 이동하며, 실제로 설치하려는 사람들 은 PECL에서 PHP 확장을 설치할 수있는 경우에도 그렇게 할 수 있습니다.
Mladen Janjetovic

4

PHP 7.2 는 완전히 사라졌으며 Mcrypt이제 암호화는 유지 관리 가능한 Libsodium라이브러리를 기반으로합니다 .

모든 암호화 요구는 기본적으로 Libsodium라이브러리를 통해 해결할 수 있습니다 .

// On Alice's computer:
$msg = 'This comes from Alice.';
$signed_msg = sodium_crypto_sign($msg, $secret_sign_key);


// On Bob's computer:
$original_msg = sodium_crypto_sign_open($signed_msg, $alice_sign_publickey);
if ($original_msg === false) {
    throw new Exception('Invalid signature');
} else {
    echo $original_msg; // Displays "This comes from Alice."
}

Libsodium 설명서 : https://github.com/paragonie/pecl-libsodium-doc


2
일부 코드를 붙여 넣으면 모든 변수가 포함되는지 확인하십시오. 귀하의 예에서 $ secret_sign_key와 $ alice_sign_publickey는 NULL입니다
undefinedman

1
crypto_signAPI는 수행 하지 암호화 메시지 - 중 하나가 필요합니다 그 crypto_aead_*_encrypt기능을합니다.
Roger Dueck

1

중요이 답변은 PHP 5에서만 유효합니다. PHP 7에서는 내장 암호화 기능을 사용합니다.

다음은 간단하지만 충분한 구현입니다.

  • CBC 모드에서 AES-256 암호화
  • 일반 텍스트 비밀번호로 암호화 키를 작성하는 PBKDF2
  • 암호화 된 메시지를 인증하기위한 HMAC

코드 및 예제는 다음과 같습니다. https://stackoverflow.com/a/19445173/1387163


1
나는 암호 전문가가 아니지만 암호에서 직접 파생 된 키를 갖는 것은 끔찍한 생각처럼 보입니다. 레인보우 테이블 + 취약한 암호는 사라졌습니다. 또한 PHP 7.1부터 더 이상 사용되지 않는 mcrypt 함수에 대한 링크 포인트
Alph.Dev

@ Alph.Dev 당신은 정확합니다 위의 답변은 PHP 5에만 유효합니다
Eugene Fidelin
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.