머리말
테이블 정의부터 시작 :
- UserID
- Fname
- Lname
- Email
- Password
- IV
변경 사항은 다음과 같습니다.
- 필드는
Fname
, Lname
및 Email
에 의해 제공되는 대칭 암호화, 암호화를 사용한다 OpenSSL 부호를 ,
- 이
IV
필드는 암호화에 사용되는 초기화 벡터 를 저장합니다 . 저장소 요구 사항은 사용되는 암호 및 모드에 따라 다릅니다. 나중에 이것에 대해 더 자세히 설명합니다.
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와 같이 암호화 된 출력은 바이너리입니다. 데이터베이스에서이 값을 저장하기는 같은 지정된 열 유형을 사용하여 수행 할 수있는 BINARY
나 VARBINARY
.
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과 같은 해싱 알고리즘은 알려진 해시 값에 대해 파일 내용을 확인하기 위해 만들어졌습니다. 그들은이 검증을 가능한 한 빨리하고 여전히 정확하도록 크게 최적화되어 있습니다. 상대적으로 제한된 출력 공간을 감안할 때 알려진 암호와 각각의 해시 출력 인 레인보우 테이블을 사용하여 데이터베이스를 쉽게 구축 할 수있었습니다.
해싱하기 전에 암호에 솔트를 추가하면 레인보우 테이블이 쓸모 없게되지만 최근의 하드웨어 발전으로 인해 무차별 대입 조회가 실행 가능한 접근 방식이되었습니다. 그렇기 때문에 의도적으로 느리고 최적화가 불가능한 해싱 알고리즘이 필요합니다. 또한 기존 암호 해시를 확인하는 기능에 영향을주지 않고 더 빠른 하드웨어에 대한 부하를 늘려 미래에 대비할 수 있어야합니다.
현재 사용 가능한 두 가지 인기있는 선택이 있습니다.
- PBKDF2 (비밀번호 기반 키 유도 기능 v2)
- 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()