나는 최근에 참석했다 라라 콘 유럽 2018 여기서 마커스 보 인턴 에 대한 좋은 강연을했다. 나는 훨씬 복잡한 암호화가 얼마나 대한 훨씬 더 큰 감사를 갖는 이야기를 왼쪽,뿐만 아니라 나트륨의 도입에 암호화를 더 접근 할 수 있도록하는 방법에 대한. 나는 그것이 내가 몇 가지 통찰력을 공유 할 때라고 생각 그래서 나는 스핀 업에 내 작업의 일환으로 작업 한 무언가이다. 이 울퉁불퉁 타고 될 수 있기 때문에,버클!
암호화 유형
오늘날 사용되는 다양한 암호화 방법이 있으며,가장 일반적인 방법은 해싱,비밀 키 암호화 및 공개 키 암호화입니다. 또한 각 암호화 방법에는 선택할 수있는 여러 알고리즘 또는 암호가 있습니다(각각 고유 한 강점과 약점이 있음). 이 기사에서는 해싱 및 비밀 키 암호화를 구현하는 데 중점을 둘 것입니다.
해싱
해싱 알고리즘은 입력 값을 가져와 메시지 다이제스트로 변환합니다. 간단히 말해서 일반 텍스트 값은 고정 길이 해시로 변환되며 원래 값을 해시 알고리즘에 전달해야만 유효성을 검사할 수 있습니다. 이 사용자 암호를 저장하기위한 완벽한 해싱한다.
해싱이 방탄 솔루션이 아니며 모든 해시 알고리즘이 동일한 것은 아니라는 점은 주목할 가치가 있습니다. 빠르고 효율적이므로 체크섬 및 파일 확인에 이상적입니다. 그러나 속도는 사용자의 암호를 해시하는 데 적합하지 않습니다. 암호는 몇 분 만에 무차별 대입에 의해 해독되어 원래의 일반 텍스트 암호를 공개 할 수 있습니다. 대신 다음과 같은 의도적으로 느린 해싱 알고리즘을 사용해야합니다.
어떤 알고리즘에 의해 생성 된 해시 암호는 확실히 원래의 데이터를 모호하게하고 공격자가 될 것입니다 속도가 느려집니다 동안,우리는 개발자로 사용할 수있는 가장 강력한 알고리즘을 사용하기 위해 노력해야한다. 다행히password_hash()
덕분에 쉽게 만들 수 있습니다.
$hash = password_hash($password, PASSWORD_DEFAULT);
password_hash()
함수는 안전한 단방향 해싱 알고리즘을 사용할 뿐만 아니라 자동으로 소금을 처리하고 시간 기반 사이드 채널 공격을 방지합니다. 그러나 이것은 미래에 더 새롭고 더 안전한 해싱 알고리즘이 추가됨에 따라 바뀔 것이다. 2633>대신PASSWORD_ARGON2I
플래그를 전달하여 사용할 수 있습니다.
사용자의 암호를 확인하는 것도password_verify()
기능 덕분에 간단한 프로세스입니다. 단순히 사용자가 제공 한 일반 텍스트 암호를 전달하고 다음과 같이 저장된 해시와 비교하십시오:
if (password_verify($password, $hash)) { echo "Let me in, I'm genuine!";}
암호 확인이 어떻게 수행되는지 확인하십시오. 데이터베이스에 사용자의 자격 증명을 저장하는 경우 로그인시 입력 한 암호를 해시 한 다음 다음과 같이 데이터베이스 쿼리를 수행 할 수 있습니다:
SELECT * FROM usersWHERE username = 'Ashley'AND password = 'password_hash'LIMIT 1;
이 접근 방식은 측면 채널 공격에 취약하며 피해야합니다. 대신 사용자를 반환 한 다음 암호 해시를 확인하십시오.
SELECT username, password FROM usersWHERE username = 'Ashley'LIMIT 1;
해싱은 사용자 암호를 저장하는 데 유용하지만 응용 프로그램이 사용자 개입없이 액세스해야하는 임의의 데이터에는 작동하지 않습니다. 사용자의 신용 카드 정보를 암호화하는 청구 응용 프로그램을 고려해 보겠습니다. 매달 우리의 응용 프로그램은 이전 달의 사용에 대한 사용자를 청구 할 필요가있다. 그것은 우리의 응용 프로그램이 일반 텍스트로 검색에 대한 원래의 데이터를 알고 있어야하기 때문에 신용 카드 데이터를 해싱이 작동하지 않습니다.
구조에 비밀 키 암호화!
비밀 키 암호화
비밀 키 암호화(또는 대칭 암호화라고도 함)는 단일 키를 사용하여 데이터를 암호화하고 해독합니다. 우리가 나트륨을 사용하여 이러한 메커니즘을 구현하는 방법을 보자. 이전 버전의
먼저random_bytes()
기능을 사용하여 생성 할 수있는 암호화 키가 필요합니다. 일반적으로 이 작업을 한 번만 수행하여 환경 변수로 저장합니다. 이 키는 모든 비용을 비밀로 유지해야한다는 것을 기억하십시오. 키가 손상되면,그래서 모든 암호화 된 데이터입니다.
$key = random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);
값을 암호화하기 위해$key
와$nonce
로sodium_crypto_secretbox()
에 전달합니다. 넌스는random_bytes()
을 사용하여 생성됩니다.
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);$ciphertext = sodium_crypto_secretbox('This is a secret!', $nonce, $key);
나중에 값을 해독하기 위해 논스가 필요하기 때문에 문제가 발생합니다. 운 좋게도 빙 스는 비밀로 유지 될 필요가 없으므로 데이터베이스에 저장하기 전에 값 앞에$ciphertext
를 추가 한 다음base64_encode()
를 추가 할 수 있습니다.
$encoded = base64_encode($nonce . $ciphertext);var_dump($encoded);// string 'v6KhzRACVfUCyJKCGQF4VNoPXYfeFY+/pyRZcixz4x/0jLJOo+RbeGBTiZudMLEO7aRvg44HRecC' (length=76)
이 값을 해독에 올 때,우리는 반대 않습니다.
$decoded = base64_decode($encoded);
우리는 넌스의 길이를 알고 있기 때문에 값을 해독하기 전에mb_substr()
를 사용하여 추출 할 수 있습니다.
$nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');$ciphertext = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');$plaintext = sodium_crypto_secretbox_open($ciphertext, $nonce, $key);var_dump($plaintext);// string 'This is a secret!' (length=17)
나트륨 덕분에 비밀 키 암호화가 전부입니다!
봉투 암호화
위에 설명 된 접근 방식은 확실히 올바른 방향으로 나아가는 단계이지만 비밀 키가 손상되면 여전히 데이터가 취약 해집니다. 의는 우리의 응용 프로그램을 호스팅하는 서버에 대한 액세스를 얻는 악의적 인 사용자를 생각해 보자. 이 시나리오에서는 공격자가 데이터를 암호화하는 데 사용한 비밀 키를 발견 할 수 있습니다. 이것은 우리의 데이터를 완전히 노출시킵니다.
간단한 해결책은 암호화된 데이터와 같은 위치에 비밀 키를 저장하지 않는 것이지만,이는 문제를 야기한다. 우리는 어떻게 암호화하고 필요에 따라 암호를 해독합니까? 구글 클라우드 키 관리 서비스(클라우드 킬로미터)를 입력합니다.
클라우드 킬로미터는 안전하게 암호화 키를 호스팅 구글에서 제공하는 서비스입니다. 자동 키 회전 및 지연된 키 파괴를 포함하여 키 저장과 관련된 다양한 유용한 기능을 제공합니다. 그러나,이 예에서 우리는 멀리 우리의 데이터에서 우리의 비밀 키를 저장하는 주로 우려.
일을 더 안전하게 만들기 위해 우리는 봉투 암호화로 알려진 기술을 사용하는 것입니다. 기본적으로 봉투 암호화는 다른 키로 키를 암호화하는 것을 포함합니다. 우리는 두 가지 이유로이 작업을 수행:
- 클라우드 킬로미터는 암호화 및 해독 할 수있는 데이터에 64 킬로바이트의 크기 제한이 있습니다. 따라서,하나의 급습에 모든 데이터를 전송하지 못할 수 있습니다.
- 더 중요한 것은 우리가 상관없이 그들이 보일 수 있습니다 얼마나 신뢰할 수의,제 3 자에게 우리의 민감한 일반 텍스트 데이터를 전송하고 싶지 않아.
대신 클라우드 킬로미터에 우리의 일반 텍스트 데이터를 보내는,우리는 고유 한 암호화 키를 우리가 데이터베이스에 중요한 데이터를 쓸 때마다 생성하는 것입니다. 이 키는 데이터 암호화 키(덱)로 알려져 있으며 데이터를 암호화하는 데 사용됩니다. 덱은 다음(켁으로 알려진)키 암호화 키를 반환 암호화 할 클라우드 킬로미터로 전송됩니다. 마지막으로,켁은 암호화된 데이터 옆의 데이터베이스에 나란히 저장되고 덱은 파괴된다. 이 과정은 다음과 같습니다:
- 암호화된 데이터 및 암호화된 키(켁)를 나란히 저장
- 생성된 키(덱)를 파괴한다.)
데이터를 해독 할 때 프로세스가 반전됩니다:1994>
- 데이터베이스에서 암호화 된 데이터와 암호화 된 키(켁)검색
- 암호 해독을 위해 클라우드 킬로미터에 켁 보내기,이는 덱
- 우리의 암호화 된 데이터의 암호를 해독하기 위해 덱을 사용하여
- 파괴 덱
이를 염두에두고 나는 매우 간단한 도우미를 만들었습니다.봉투 암호화를 수행하는 클래스입니다. 나는 빠른 시작 및 인증 가이드는 당신이 시작하는 데 필요한 모든 개요로,구글 클라우드 콘솔에 필요한 단계를 통해 갈거야. 간결함을 위해 오류 처리 등이 없습니다. 이 예에서.
<?phpuse Google_Service_CloudKMS as Kms;use Google_Service_CloudKMS_DecryptRequest as DecryptRequest;use Google_Service_CloudKMS_EncryptRequest as EncryptRequest;class KeyManager{ private $kms; private $encryptRequest; private $decryptRequest; private $projectId; private $locationId; private $keyRingId; private $cryptoKeyId; public function __construct(Kms $kms, EncryptRequest $encryptRequest, DecryptRequest $decryptRequest, $projectId, $locationId, $keyRingId, $cryptoKeyId) { $this->kms = $kms; $this->encryptRequest = $encryptRequest; $this->decryptRequest = $decryptRequest; $this->projectId = $projectId; $this->locationId = $locationId; $this->keyRingId = $keyRingId; $this->cryptoKeyId = $cryptoKeyId; } public function encrypt($data) { $key = random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES); $nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES); $ciphertext = sodium_crypto_secretbox($data, $nonce, $key); return ; } public function decrypt($secret, $data) { $decoded = base64_decode($data); $key = $this->decryptSecret($secret); $nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit'); $ciphertext = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit'); return sodium_crypto_secretbox_open($ciphertext, $nonce, $key); } private function encryptKey($key) { $this->encryptRequest->setPlaintext(base64_encode($key)); $response = $this->kms->projects_locations_keyRings_cryptoKeys->encrypt( $this->getResourceName(), $this->encryptRequest ); return $response; } private function decryptSecret($secret) { $this->decryptRequest->setCiphertext($secret); $response = $this->kms->projects_locations_keyRings_cryptoKeys->decrypt( $this->getResourceName(), $this->decryptRequest ); return base64_decode($response); } private function getResourceName() { return sprintf( 'projects/%s/locations/%s/keyRings/%s/cryptoKeys/%s', $this->projectId, $this->locationId, $this->keyRingId, $this->cryptoKeyId ); }}
실제 암호화 및 암호 해독 방법은 위에 소개 된 비밀 키 구현과 거의 동일하다는 것을 알 수 있습니다. 그러나 차이점은 우리가 지금 여러 암호화 키를 사용하고 있다는 것입니다. 의 행동에 도우미 클래스를 보자. 당신은 당신을 제공할 필요가 있을 것입니다$projectId
, $locationId
, $keyRingId
그리고$cryptoKeyId
구글 클라우드 콘솔에서 사용할 수있는.
<?phpuse Google_Service_CloudKMS as Kms;use Google_Service_CloudKMS_DecryptRequest as DecryptRequest;use Google_Service_CloudKMS_EncryptRequest as EncryptRequest;$client = new Google_Client();$client->setAuthConfig(getenv('GOOGLE_CREDENTIALS_FILE'));$client->addScope('https://www.googleapis.com/auth/cloud-platform');$keyManager = new KeyManager( new Kms($client), new EncryptRequest(), new DecryptRequest(), $projectId, $locationId, $keyRingId, $cryptoKeyId);$encrypted = $keyManager->encrypt('This is a secret!');var_dump($encrypted);// array (size=2)// 'data' => string 'uKjmEU7e1JEU+2vL3hBK2wBk6afCSgb+Y4GQtu/mmLuffgHlnqxnqOMPOI6WGkM18vAGGvFVDTvd' (length=76)// 'secret' => string 'CiQAdA0emUW2nhlU3RijX/5GnUsTnPPrQdLZNxdHWXWYugx49a4SSQBHyYr0T/PEbKwyFhIkaZl28oKkJRkXqNcqOL4Z+OTQFLpGvS6zCDt2mFn/nUQ/bi4znD4DORk9ZDTqiIBK3UNFUZcrXvoExds=' (length=152)$decrypted = $keyManager->decrypt($encrypted, $encrypted);var_dump($decrypted);// string 'This is a secret!' (length=17)
공격자가 우리 시스템을 손상시킨 경우,그들은 또한 클라우드 킬로미터에 대한 우리의 자격 증명을 얻을 수있을 것인가? 사용 된 인증 방법에 따라 다음 네 그것은 가능성이있을 수 있습니다. 이 경우 봉투 암호화가 일반 비밀 키 암호화보다 더 안전한지 궁금 할 것입니다. 따라서 공격자가 민감한 데이터를 사용하는 것을 방해할 수 있습니다. 그것은 누군가가 당신의 집 열쇠를 훔치는 경우 잠금을 변경하는 것과 동일합니다. 하나의 로컬 키가 손상 일반 비밀 키 암호화를 사용하면 고급 스러움이 없습니다. 공격자는 민감한 데이터의 암호를 해독 할 수있는 세계의 모든 시간을 가지고있다.
마무리
데이터 보안 및 암호화는 방대한 주제이며 민감한 데이터를 보호하는 몇 가지 방법 만 다루었습니다. (우리는 이전에 로컬 환경에서 이러한 종류의 데이터를 보호하는 것에 대해 썼습니다)