추가 작업을 수행하기 전에 암호화 와 인증 의 차이점 과 단순히 암호화가 아닌 인증 된 암호화를 원하는 이유 를 이해 하십시오 .
인증 된 암호화를 구현하려면 암호화 한 다음 MAC을 사용하려고합니다. 암호화 및 인증 순서는 매우 중요합니다! 이 질문에 대한 기존의 답변 중 하나가이 실수를 범했습니다. PHP로 작성된 많은 암호화 라이브러리와 마찬가지로.
당신은해야 자신의 암호화를 구현하지 않도록 하고, 대신 보안 라이브러리에 의해 작성하고 암호 전문가 검토를 사용합니다.
업데이트 : PHP 7.2는 이제 libsodium을 제공합니다 ! 최상의 보안을 위해 PHP 7.2 이상을 사용하도록 시스템을 업데이트하고이 답변의 libsodium 조언 만 따르십시오.
사용 libsodium 당신은 PECL에 액세스 할 수있는 경우 (또는 sodium_compat 당신이 PECL없이 libsodium을 원하는 경우); 그렇지 않으면 ...
defuse / php-encryption 사용 ; 자신의 암호화를 굴리지 마십시오!
위에 링크 된 두 라이브러리 모두 인증 된 암호화를 자신의 라이브러리에 쉽고 쉽게 구현할 수 있습니다.
인터넷에있는 모든 암호화 전문가의 일반적인 지식에 반하여 자체 암호화 라이브러리를 작성하고 배포하려는 경우 다음 단계를 수행해야합니다.
암호화 :
- CTR 모드에서 AES를 사용하여 암호화합니다. GCM을 사용할 수도 있습니다 (별도의 MAC이 필요 없음). 또한 ChaCha20 및 Salsa20 (의해 제공 libsodium ) 스트림 암호이며, 특별한 모드가 필요하지 않습니다.
- 위에서 GCM을 선택하지 않은 경우 HMAC-SHA-256을 사용하여 암호문을 인증해야합니다 (또는 스트림 암호의 경우 Poly1305-대부분의 libsodium API가이를 수행함). MAC은 암호문뿐만 아니라 IV도 포함해야합니다!
복호화 :
- Poly1305 또는 GCM을 사용하지 않는 한 암호문의 MAC을 다시 계산하고이를 사용하여 보낸 MAC과 비교하십시오.
hash_equals()
. 실패하면 중단하십시오.
- 메시지를 해독하십시오.
다른 디자인 고려 사항 :
- 압축하지 마십시오. 암호문은 압축 할 수 없습니다. 암호화 전에 일반 텍스트를 압축하면 정보가 유출 될 수 있습니다 (예 : CRLS 및 BREACH on TLS).
- 당신이 사용 확인
mb_strlen()
및 mb_substr()
사용 '8bit'
방지하기 위해 문자 설정 모드 mbstring.func_overload
문제.
- IV는 CSPRNG를 사용하여 생성되어야합니다 . 당신이 사용하는 경우
mcrypt_create_iv()
, 사용을하지 마십시오MCRYPT_RAND
!
- AEAD 구문을 사용하지 않는 한 항상 MAC을 암호화 한 다음!
bin2hex()
, base64_encode()
, 등 캐시 타이밍을 통해 암호화 키에 대한 정보가 누수 될 수 있습니다. 가능하면 피하십시오.
여기에 제공된 조언을 따를지라도 암호화에는 많은 문제가 발생할 수 있습니다. 암호 전문가가 항상 구현을 검토하도록하십시오. 지역 대학에서 암호화 학생과 개인적인 친구가 될만큼 운이 좋지 않은 경우 언제든지 암호화 스택 교환을 시도 할 수 있습니다 포럼에서 조언을 구할 수 있습니다.
구현에 대한 전문적인 분석이 필요한 경우, 평판이 좋은 보안 컨설턴트 팀을 고용 하여 PHP 암호화 코드 (공개 : 내 고용주) 를 검토 할 수 있습니다 .
중요 : 암호화를 사용하지 않는 경우
비밀번호를 암호화 하지 마십시오 . 당신이 원하는 해시 이러한 암호 해싱 알고리즘 중 하나를 사용하는 대신 그들 :
비밀번호 저장에 범용 해시 기능 (MD5, SHA256)을 사용하지 마십시오.
URL 매개 변수를 암호화하지 마십시오 . 작업에 대한 잘못된 도구입니다.
Libsodium을 사용한 PHP 문자열 암호화 예제
PHP <7.2에 있거나 libsodium이 설치되어 있지 않은 경우 sodium_compat 를 사용하여 동일한 결과를 얻을 수 있습니다 (느리기는하지만).
<?php
declare(strict_types=1);
/**
* Encrypt a message
*
* @param string $message - message to encrypt
* @param string $key - encryption key
* @return string
* @throws RangeException
*/
function safeEncrypt(string $message, string $key): string
{
if (mb_strlen($key, '8bit') !== SODIUM_CRYPTO_SECRETBOX_KEYBYTES) {
throw new RangeException('Key is not the correct size (must be 32 bytes).');
}
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$cipher = base64_encode(
$nonce.
sodium_crypto_secretbox(
$message,
$nonce,
$key
)
);
sodium_memzero($message);
sodium_memzero($key);
return $cipher;
}
/**
* Decrypt a message
*
* @param string $encrypted - message encrypted with safeEncrypt()
* @param string $key - encryption key
* @return string
* @throws Exception
*/
function safeDecrypt(string $encrypted, string $key): string
{
$decoded = base64_decode($encrypted);
$nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
$ciphertext = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');
$plain = sodium_crypto_secretbox_open(
$ciphertext,
$nonce,
$key
);
if (!is_string($plain)) {
throw new Exception('Invalid MAC');
}
sodium_memzero($ciphertext);
sodium_memzero($key);
return $plain;
}
그런 다음 테스트하십시오.
<?php
// This refers to the previous code block.
require "safeCrypto.php";
// Do this once then store it somehow:
$key = random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);
$message = 'We are all living in a yellow submarine';
$ciphertext = safeEncrypt($message, $key);
$plaintext = safeDecrypt($ciphertext, $key);
var_dump($ciphertext);
var_dump($plaintext);
Halite-더 쉽게 만들어지는 Libsodium
내가 작업했던 프로젝트 중 하나라는 암호화 라이브러리 암염 보다 쉽고 직관적 libsodium 수 있도록하는 것을 목표로.
<?php
use \ParagonIE\Halite\KeyFactory;
use \ParagonIE\Halite\Symmetric\Crypto as SymmetricCrypto;
// Generate a new random symmetric-key encryption key. You're going to want to store this:
$key = new KeyFactory::generateEncryptionKey();
// To save your encryption key:
KeyFactory::save($key, '/path/to/secret.key');
// To load it again:
$loadedkey = KeyFactory::loadEncryptionKey('/path/to/secret.key');
$message = 'We are all living in a yellow submarine';
$ciphertext = SymmetricCrypto::encrypt($message, $key);
$plaintext = SymmetricCrypto::decrypt($ciphertext, $key);
var_dump($ciphertext);
var_dump($plaintext);
모든 기본 암호화는 libsodium에 의해 처리됩니다.
defuse / php-encryption을 사용한 예
<?php
/**
* This requires https://github.com/defuse/php-encryption
* php composer.phar require defuse/php-encryption
*/
use Defuse\Crypto\Crypto;
use Defuse\Crypto\Key;
require "vendor/autoload.php";
// Do this once then store it somehow:
$key = Key::createNewRandomKey();
$message = 'We are all living in a yellow submarine';
$ciphertext = Crypto::encrypt($message, $key);
$plaintext = Crypto::decrypt($ciphertext, $key);
var_dump($ciphertext);
var_dump($plaintext);
참고 : Crypto::encrypt()
16 진수로 인코딩 된 출력을 반환합니다.
암호화 키 관리
"암호"를 사용하고 싶은 유혹이 있다면 지금 중지하십시오. 사람이 기억할 수있는 암호가 아닌 임의의 128 비트 암호화 키가 필요합니다.
다음과 같이 장기간 사용하기 위해 암호화 키를 저장할 수 있습니다.
$storeMe = bin2hex($key);
요청시 다음과 같이 검색 할 수 있습니다.
$key = hex2bin($storeMe);
내가 강하게 단지 장기간 사용을 위해 임의로 생성 된 키 대신 키와 암호를 모든 종류의를 저장하는 것이 좋습니다 (또는 키를 파생합니다).
Defuse의 라이브러리를 사용하는 경우 :
"하지만 정말 암호를 사용하고 싶습니다."
좋지 않은 생각이지만, 안전하게하는 방법은 다음과 같습니다.
먼저 임의의 키를 생성하여 상수로 저장하십시오.
/**
* Replace this with your own salt!
* Use bin2hex() then add \x before every 2 hex characters, like so:
*/
define('MY_PBKDF2_SALT', "\x2d\xb7\x68\x1a\x28\x15\xbe\x06\x33\xa0\x7e\x0e\x8f\x79\xd5\xdf");
추가 작업을 추가 하고이 상수를 키로 사용하여 많은 상심을 구할 수 있습니다!
그런 다음 PBKDF2 (예 : 이와 같이)를 사용하여 비밀번호로 직접 암호화하지 않고 비밀번호에서 적절한 암호화 키를 도출하십시오.
/**
* Get an AES key from a static password and a secret salt
*
* @param string $password Your weak password here
* @param int $keysize Number of bytes in encryption key
*/
function getKeyFromPassword($password, $keysize = 16)
{
return hash_pbkdf2(
'sha256',
$password,
MY_PBKDF2_SALT,
100000, // Number of iterations
$keysize,
true
);
}
16 자 암호 만 사용하지 마십시오. 암호화 키가 엉망이됩니다.