PHP에서 데이터를 암호화 / 복호화하는 방법은 무엇입니까?


110

저는 현재 학생이고 PHP를 공부하고 있습니다. PHP에서 간단한 데이터 암호화 / 복호화를 시도하고 있습니다. 나는 온라인 조사를했고 그들 중 일부는 (적어도 나를 위해) 꽤 혼란 스러웠습니다.

내가하려는 것은 다음과 같습니다.

이러한 필드 (UserID, Fname, Lname, Email, Password) 로 구성된 테이블이 있습니다.

내가 원하는 것은 모든 필드를 암호화 한 다음 해독하는 것입니다 ( sha256암호화 알고리즘이 아닌 경우 암호화 / 복호화 에 사용할 수 있습니까? )

제가 배우고 싶은 또 다른 것은 hash(sha256)좋은 "소금"과 결합 된 일방 통행을 만드는 방법 입니다. (기본적으로 암호화 / 복호화의 간단한 구현을 원합니다. hash(sha256)+salt) 선생님 / 부인 님, 귀하의 답변은 큰 도움이 될 것이며 대단히 감사하겠습니다. 감사합니다 ++




9
SHA는 암호화가 아니라 해시입니다. 요점은 해시를 원래 데이터로 되돌릴 수 없다는 것입니다 (어쨌든 쉽지 않음). 당신은 아마 원하는 Mcrypt 라이브러리를 하거나 사용할 수없는 경우 내가 추천 할 것입니다 phpseclib를 - 그것은 낮은 수준의 수학을 많이 포함 아무것도의 순수 PHP 구현 sloooooowww이 될 것이라고 유의해야하지만 ... 그의 이유는 phpseclib 같은 때문에 가능한 경우 먼저 mcrypt를 사용하고 마지막 수단으로 PHP 구현으로 만 폴백합니다.
DaveRandom

7
일반적으로 암호를 해독하는 것을 원하지 않습니다!
Ja͢ck 2012-06-06

1
기본적으로이 수준에서 암호화를 생각해서는 안되며 액세스 제어, 기밀성, 무결성 및 인증에 대해 생각해야합니다. 체크인 후에는,이를 달성 할 수있는 방법 가능한 암호화 또는 보안 해싱을 사용. 암호 등의 보안 해싱을 이해하기 위해 PBKDF2 및 bcrypt / scrypt를 읽을 수 있습니다.
Maarten Bodewes 2012-06-07

답변:


289

머리말

테이블 정의부터 시작 :

- UserID
- Fname
- Lname
- Email
- Password
- IV

변경 사항은 다음과 같습니다.

  1. 필드는 Fname, LnameEmail에 의해 제공되는 대칭 암호화, 암호화를 사용한다 OpenSSL 부호를 ,
  2. IV필드는 암호화에 사용되는 초기화 벡터 를 저장합니다 . 저장소 요구 사항은 사용되는 암호 및 모드에 따라 다릅니다. 나중에 이것에 대해 더 자세히 설명합니다.
  3. Password필드는 사용하여 해시됩니다 단방향 암호 해시를

암호화

암호 및 모드

최상의 암호화 암호 및 모드를 선택하는 것은이 답변의 범위를 벗어나지 만 최종 선택은 암호화 키와 초기화 벡터의 크기에 영향을줍니다. 이 게시물에서는 고정 블록 크기가 16 바이트이고 키 크기가 16, 24 또는 32 바이트 인 AES-256-CBC를 사용합니다.

암호화 키

좋은 암호화 키는 신뢰할 수있는 난수 생성기에서 생성 된 이진 Blob입니다. 다음 예제가 권장됩니다 (> = 5.3).

$key_size = 32; // 256 bits
$encryption_key = openssl_random_pseudo_bytes($key_size, $strong);
// $strong will be true if the key is crypto safe

이 작업은 한 번 또는 여러 번 수행 할 수 있습니다 (암호화 키 체인을 생성하려는 경우). 가능한 한 비공개로 유지하십시오.

IV

초기화 벡터는 암호화에 임의성을 추가하고 CBC 모드에 필요합니다. 이러한 값은 이상적으로는 한 번만 (기술적으로 암호화 키당 한 번) 사용되어야하므로 행의 모든 ​​부분을 업데이트하면 값이 다시 생성되어야합니다.

IV를 생성하는 데 도움이되는 기능이 제공됩니다.

$iv_size = 16; // 128 bits
$iv = openssl_random_pseudo_bytes($iv_size, $strong);

이전 $encryption_key$iv;를 사용하여 이름 필드를 암호화합시다 . 이렇게하려면 데이터를 블록 크기로 채워야합니다.

function pkcs7_pad($data, $size)
{
    $length = $size - strlen($data) % $size;
    return $data . str_repeat(chr($length), $length);
}

$name = 'Jack';
$enc_name = openssl_encrypt(
    pkcs7_pad($name, 16), // padded data
    'AES-256-CBC',        // cipher and mode
    $encryption_key,      // secret key
    0,                    // options (not used)
    $iv                   // initialisation vector
);

저장 요구 사항

IV와 같이 암호화 된 출력은 바이너리입니다. 데이터베이스에서이 값을 저장하기는 같은 지정된 열 유형을 사용하여 수행 할 수있는 BINARYVARBINARY.

IV와 같은 출력 값은 이진입니다. 이러한 값을 MySQL에 저장하려면 BINARY또는VARBINARY 열을 사용하는 것이 좋습니다. 이 옵션을 선택하지 않으면, 당신은 또한 사용하여 텍스트 표현으로 바이너리 데이터를 변환 할 수 있습니다 base64_encode()또는 bin2hex()일을, 그래서 100 % 더 많은 저장 공간을 33 % 사이에 있어야합니다.

복호화

저장된 값의 암호 해독은 유사합니다.

function pkcs7_unpad($data)
{
    return substr($data, 0, -ord($data[strlen($data) - 1]));
}

$row = $result->fetch(PDO::FETCH_ASSOC); // read from database result
// $enc_name = base64_decode($row['Name']);
// $enc_name = hex2bin($row['Name']);
$enc_name = $row['Name'];
// $iv = base64_decode($row['IV']);
// $iv = hex2bin($row['IV']);
$iv = $row['IV'];

$name = pkcs7_unpad(openssl_decrypt(
    $enc_name,
    'AES-256-CBC',
    $encryption_key,
    0,
    $iv
));

인증 된 암호화

비밀 키 (암호화 키와 다름) 및 암호 텍스트에서 생성 된 서명을 추가하여 생성 된 암호 텍스트의 무결성을 더욱 향상시킬 수 있습니다. 암호 텍스트가 해독되기 전에 서명이 먼저 확인됩니다 (가급적 일정 시간 비교 방법 사용).

// generate once, keep safe
$auth_key = openssl_random_pseudo_bytes(32, $strong);

// authentication
$auth = hash_hmac('sha256', $enc_name, $auth_key, true);
$auth_enc_name = $auth . $enc_name;

// verification
$auth = substr($auth_enc_name, 0, 32);
$enc_name = substr($auth_enc_name, 32);
$actual_auth = hash_hmac('sha256', $enc_name, $auth_key, true);

if (hash_equals($auth, $actual_auth)) {
    // perform decryption
}

또한보십시오: hash_equals()

해싱

가역적 암호를 데이터베이스에 저장하는 것은 가능한 한 피해야합니다. 내용을 알기보다는 암호 만 확인하고 싶을 것입니다. 사용자가 비밀번호를 분실 한 경우 원래 비밀번호를 보내는 것보다 비밀번호를 재설정하도록 허용하는 것이 좋습니다 (비밀번호 재설정은 제한된 시간 동안 만 수행 할 수 있는지 확인).

해시 함수를 적용하는 것은 단방향 작업입니다. 나중에 원본 데이터를 공개하지 않고 검증에 안전하게 사용할 수 있습니다. 암호의 경우, 무차별 대입 방법은 비교적 짧은 길이와 많은 사람들의 잘못된 암호 선택으로 인해이를 발견 할 수있는 가능한 접근 방식입니다.

MD5 또는 SHA1과 같은 해싱 알고리즘은 알려진 해시 값에 대해 파일 내용을 확인하기 위해 만들어졌습니다. 그들은이 검증을 가능한 한 빨리하고 여전히 정확하도록 크게 최적화되어 있습니다. 상대적으로 제한된 출력 공간을 감안할 때 알려진 암호와 각각의 해시 출력 인 레인보우 테이블을 사용하여 데이터베이스를 쉽게 구축 할 수있었습니다.

해싱하기 전에 암호에 솔트를 추가하면 레인보우 테이블이 쓸모 없게되지만 최근의 하드웨어 발전으로 인해 무차별 대입 조회가 실행 가능한 접근 방식이되었습니다. 그렇기 때문에 의도적으로 느리고 최적화가 불가능한 해싱 알고리즘이 필요합니다. 또한 기존 암호 해시를 확인하는 기능에 영향을주지 않고 더 빠른 하드웨어에 대한 부하를 늘려 미래에 대비할 수 있어야합니다.

현재 사용 가능한 두 가지 인기있는 선택이 있습니다.

  1. PBKDF2 (비밀번호 기반 키 유도 기능 v2)
  2. bcrypt (일명 Blowfish)

이 답변은 bcrypt와 함께 예제를 사용합니다.

세대

암호 해시는 다음과 같이 생성 될 수 있습니다.

$password = 'my password';
$random = openssl_random_pseudo_bytes(18);
$salt = sprintf('$2y$%02d$%s',
    13, // 2^n cost factor
    substr(strtr(base64_encode($random), '+', '.'), 0, 22)
);

$hash = crypt($password, $salt);

소금이 생성됩니다 openssl_random_pseudo_bytes()다음을 통해 실행되는 데이터의 임의의 덩어리를 형성 base64_encode()하고 strtr()필요한 알파벳을 일치 [A-Za-z0-9/.].

crypt()함수는 알고리즘 ( $2y$Blowfish의 경우), 비용 요소 (3GHz 시스템에서 요소 13은 약 0.40s 소요) 및 22 자의 솔트를 기반으로 해싱을 수행합니다 .

확인

사용자 정보가 포함 된 행을 가져온 후에는 다음과 같은 방식으로 비밀번호를 검증합니다.

$given_password = $_POST['password']; // the submitted password
$db_hash = $row['Password']; // field with the password hash

$given_hash = crypt($given_password, $db_hash);

if (isEqual($given_hash, $db_hash)) {
    // user password verified
}

// constant time string compare
function isEqual($str1, $str2)
{
    $n1 = strlen($str1);
    if (strlen($str2) != $n1) {
        return false;
    }
    for ($i = 0, $diff = 0; $i != $n1; ++$i) {
        $diff |= ord($str1[$i]) ^ ord($str2[$i]);
    }
    return !$diff;
}

암호를 확인하려면 crypt()다시 호출 하지만 이전에 계산 된 해시를 솔트 값으로 전달합니다. 반환 값은 주어진 암호가 해시와 일치하면 동일한 해시를 생성합니다. 해시를 확인하려면 타이밍 공격을 피하기 위해 일정 시간 비교 기능을 사용하는 것이 좋습니다.

PHP 5.5를 사용한 암호 해싱

PHP 5.5 는 위의 해싱 방법을 단순화하는 데 사용할 수 있는 암호 해싱 기능 을 도입했습니다 .

$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 13]);

그리고 확인 :

if (password_verify($given_password, $db_hash)) {
    // password valid
}

참조 : password_hash(),password_verify()


가장 안전한 베팅을 위해 이름, 성, 이메일 등을 저장하려면 얼마의 길이를 사용해야합니까? varbinary (???)
BentCoder 2013 년

2
물론입니다. 사용 방법에 따라 다릅니다. 암호화 라이브러리를 게시하면 개발자가이를 구현하는 방법을 알 수 없습니다. 그렇기 때문에 github.com/defuse/php-encryption 은 인증 된 대칭 키 암호화를 제공하고 개발자가 코드를 편집하지 않고이를 약화시킬 수 없도록합니다.
Scott Arciszewski

2
@Scott 아주 잘, 인증 된 암호화의 예를 추가했습니다. 밀어 주셔서 감사합니다 :)
Ja͢ck

1
인증 된 암호화의 경우 +1. AE가 여기에 필요하지 않다는 질문에 충분한 정보가 없습니다. 확실히 SQL 트래픽은 데이터베이스에서 스토리지로의 트래픽과 마찬가지로 보안 속성을 알 수없는 네트워크를 통해 이동하는 경우가 많습니다. 백업 및 복제도 가능합니다. 위협 모델은 무엇입니까? 질문은 말하지 않으며 가정하는 것은 위험 할 수 있습니다.
Jason Orendorff

1
하드 코딩 대신 $iv_size = 16;사용 : $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length("AES-256-CBC"))사용 된 암호와 함께 사용할 iv의 크기 사이의 링크를 표시하기 위해 사용합니다. pkcs7_pad()/ 의 필요 여부를 약간 확장 pkcs7_unpad()하거나 단순히 게시물을 제거하고 "aes-256-ctr"을 사용하여 게시물을 단순화 할 수도 있습니다. 위대한 포스트 @ 잭
패트릭 Allaert

24

나는 이것이 이전에 대답했다고 생각하지만 어쨌든 데이터를 암호화 / 복호화하려면 SHA256을 사용할 수 없습니다.

//Key
$key = 'SuperSecretKey';

//To Encrypt:
$encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, 'I want to encrypt this', MCRYPT_MODE_ECB);

//To Decrypt:
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $encrypted, MCRYPT_MODE_ECB);

7
그 문제 때문에 ECB도 사용해서는 안됩니다.
Maarten Bodewes 2012-06-07

7
키는 임의의 바이트 여야합니다. 그렇지 않으면 보안 키 파생 함수를 사용해야합니다.
Maarten Bodewes 2012-06-07

4
MCRYPT_RIJNDAEL_256는 표준화 된 함수가 아닙니다, 당신은 사용해야 AES (MCRYPT_RIJNDAEL_128)
마틴 Bodewes

14

답변 배경 및 설명

이 질문을 이해하려면 먼저 SHA256이 무엇인지 이해해야합니다. SHA256은 암호화 해시 함수 입니다. 암호화 해시 함수는 출력이 암호화 적으로 안전한 단방향 함수입니다. 즉, 해시를 계산하는 것은 쉽지만 (데이터 암호화와 동일) 해시를 사용하여 원래 입력을 얻는 것은 어렵습니다 (데이터를 해독하는 것과 동일). 암호화 해시 함수를 사용한다는 것은 복호화가 계산적으로 불가능하다는 것을 의미하므로 SHA256으로 복호화를 수행 할 수 없습니다.

사용하려는 것은 양방향 기능이지만 더 구체적으로 Block Cipher 입니다. 데이터의 암호화 및 복호화를 모두 허용하는 기능입니다. 함수 mcrypt_encryptmcrypt_decrypt기본적으로 Blowfish 알고리즘을 사용합니다. PHP의 mcrypt 사용은이 매뉴얼 에서 찾을 수 있습니다 . mcrypt가 사용하는 암호를 선택하기위한 암호 정의 목록 도 있습니다. Blowfish에 대한 위키는 Wikipedia 에서 찾을 수 있습니다 . 블록 암호는 알려진 키를 사용하여 알려진 크기와 위치의 블록으로 입력을 암호화하므로 나중에 키를 사용하여 데이터를 해독 할 수 있습니다. 이것이 SHA256이 제공 할 수없는 것입니다.

암호

$key = 'ThisIsTheCipherKey';

$ciphertext = mcrypt_encrypt(MCRYPT_BLOWFISH, $key, 'This is plaintext.', MCRYPT_MODE_CFB);

$plaintext = mcrypt_decrypt(MCRYPT_BLOWFISH, $key, $encrypted, MCRYPT_MODE_CFB);

그 문제 때문에 ECB도 사용해서는 안됩니다.
Maarten Bodewes 2012-06-07

키는 임의의 바이트 여야합니다. 그렇지 않으면 보안 키 파생 함수를 사용해야합니다.
Maarten Bodewes 2012-06-07

4
절대 ECB 모드를 사용 하지 마십시오 . 안전하지 않으며 대부분의 경우 실제로 데이터를 암호화하는 데 도움이되지 않습니다 (단순히 인코딩하는 것이 아니라). 자세한 내용은 주제에 대한 훌륭한 Wikipedia 기사 를 참조하십시오.
Holger Just

1
mcrypt를 사용하지 않는 것이 가장 좋습니다. 포기하고 수년 동안 업데이트되지 않았으며 표준 PKCS # 7 (née PKCS # 5) 패딩을 지원하지 않으며 바이너리 데이터와 함께 사용할 수도없는 비표준 널 패딩 만 지원합니다. . mcrypt는 2003 년으로 거슬러 올라가는 많은 미해결 버그를 가지고있었습니다 . 대신 defuse 사용을 고려 하십시오. 유지되고 있으며 정확합니다.
zaph

9

다음은 openssl_encrypt를 사용하는 예입니다.

//Encryption:
$textToEncrypt = "My Text to Encrypt";
$encryptionMethod = "AES-256-CBC";
$secretHash = "encryptionhash";
$iv = mcrypt_create_iv(16, MCRYPT_RAND);
$encryptedText = openssl_encrypt($textToEncrypt,$encryptionMethod,$secretHash, 0, $iv);

//Decryption:
$decryptedText = openssl_decrypt($encryptedText, $encryptionMethod, $secretHash, 0, $iv);
print "My Decrypted Text: ". $decryptedText;

2
대신 mcrypt_create_iv()사용합니다 : openssl_random_pseudo_bytes(openssl_cipher_iv_length($encryptionMethod)),이 방법은 $ encryptionMethod의 모든 값에 대해 작동하며 openssl 확장 만 사용합니다.
Patrick Allaert

반환 위의 코드 false에 대한 openssl_decrypt(). stackoverflow.com/q/41952509/1066234를 참조하십시오. AES와 같은 블록 암호는 입력 데이터가 블록 크기 (AES의 경우 16 바이트) 패딩의 정확한 배수 여야하기 때문에 필요합니다.
Kai Noack 2017 년

6
     function my_simple_crypt( $string, $action = 'e' ) {
        // you may change these values to your own
        $secret_key = 'my_simple_secret_key';
        $secret_iv = 'my_simple_secret_iv';

        $output = false;
        $encrypt_method = "AES-256-CBC";
        $key = hash( 'sha256', $secret_key );
        $iv = substr( hash( 'sha256', $secret_iv ), 0, 16 );

        if( $action == 'e' ) {
            $output = base64_encode( openssl_encrypt( $string, $encrypt_method, $key, 0, $iv ) );
        }
        else if( $action == 'd' ){
            $output = openssl_decrypt( base64_decode( $string ), $encrypt_method, $key, 0, $iv );
        }

        return $output;
    }

매우 간단합니다! URL 세그먼트 암호화-복호화에 사용합니다. 감사합니다
Mahbub Tito

0

false사용할 때 a 를 openssl_decrypt()얻지 못하고 암호화 및 암호 해독이 작동 하는 방법을 알아내는 데 꽤 오랜 시간이 걸렸습니다 .

    // cryptographic key of a binary string 16 bytes long (because AES-128 has a key size of 16 bytes)
    $encryption_key = '58adf8c78efef9570c447295008e2e6e'; // example
    $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
    $encrypted = openssl_encrypt($plaintext, 'aes-256-cbc', $encryption_key, OPENSSL_RAW_DATA, $iv);
    $encrypted = $encrypted . ':' . base64_encode($iv);

    // decrypt to get again $plaintext
    $parts = explode(':', $encrypted);
    $decrypted = openssl_decrypt($parts[0], 'aes-256-cbc', $encryption_key, OPENSSL_RAW_DATA, base64_decode($parts[1])); 

URL을 통해 암호화 된 문자열을 전달하려면 문자열을 urlencode해야합니다.

    $encrypted = urlencode($encrypted);

무슨 일이 일어나고 있는지 더 잘 이해하려면 다음을 읽으십시오.

16 바이트 길이의 키를 생성하려면 다음을 사용할 수 있습니다.

    $bytes = openssl_random_pseudo_bytes(16);
    $hex = bin2hex($bytes);

openssl의 오류 메시지를 보려면 다음을 사용할 수 있습니다. echo openssl_error_string();

도움이 되었기를 바랍니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.