Çok basit şifreleme API'si
Süper basit bir API'ye sahip bir şifreleme sistemine ihtiyacım var. Aşağıdakiler kadar basit bir şeyi tercih ederim. Çıktı aynı zamanda tarayıcı dostu ve ideal olarak url dostu olmalıdır.
$encrypted=encrypt(data); $decrypted=decrypt(encrypted);
Ben bir kriptograf değilim, ancak bunu başarmak için aşağıdakileri buldum.
<?php
declare(strict_types = 1);
class EasyCrypt
{
// format: easycryptstart_version_(bytes_of_b64bin)_b64bin_easycryptend
// b64bin: base64urlencode(bin)
// bin: versionspecific
// V1 versionspecific: IV+aes128ctr(encryption_key=hkey,csum+inner_length+data+padding)
// V1 hkey: substr(sha256(key),16); // sha256 is used as a key compressor/expander
// V1 inner_length: little_endian_uint64(strlen(data))
// V1 csum: substr(sha256(inner_length+data+padding),14)
// V1 padding: null-bytes until strlen(csum+inner_length+data+padding) is divisible by 16 bytes (128 bits), (16-(size%16))%16
// generate secure key: cat /dev/urandom | head --bytes=15 | base64
private const EASY_ENCRYPTION_KEY = "CHANGEME";
private const V1_IV_LENGTH = 16;
private const V1_ENCRYPT_ALGO = 'aes-128-ctr';
private const V1_HASH_ALGO = 'sha256';
private const V1_HASH_TRUNCATE_LENGTH = 14;
public static function encryptEasy(string $data): string { return self::encrypt($data, self::EASY_ENCRYPTION_KEY);
}
public static function decryptEasy(string $data, string &$decryptionError = null): ?string
{
return self::decrypt($data, self::EASY_ENCRYPTION_KEY, $decryptionError);
}
public static function encrypt(string $data, string $encryption_key): string
{
$version = 1; $prefix = "easycryptstart_{$version}_"; $postfix = "_easycryptend";
$ret = self::encryptV1($data, $encryption_key); $ret = self::base64url_encode($ret); $ret = $prefix . strlen($ret) . "_" . $ret . $postfix;
return $ret; } public static function decrypt(string $data, string $encryption_key, string &$decryptionError = null): ?string
{
// only 1 "version" exist thus far
$version = 1; $data = str_replace(array(
" ",
"\r",
"\n",
"\t"
), "", $data); $prefix = "easycryptstart_{$version}_"; $postfix = "_easycryptend";
$prefixpos = strpos($data, $prefix); if (false === $prefixpos) {
$decryptionError = "prefix not found"; return null; } $postfixpos = strpos($data, $postfix, $prefixpos); if (false === $postfixpos) {
$decryptionError = "postfix not found (even tho prefix was found!)"; return null; } $data = substr($data, $prefixpos + strlen($prefix), $postfixpos - ($prefixpos + strlen($prefix)));
$outer_length_end = strpos($data, "_");
if (false === $outer_length_end) { $decryptionError = "corrupted input, outer length end missing!";
return null;
}
$outer_length = substr($data, 0, $outer_length_end); $outer_length = filter_var($outer_length, FILTER_VALIDATE_INT); if (false === $outer_length) {
$decryptionError = "corrupt input, outer_length non-int!"; return null; } $data = substr($data, $outer_length_end + strlen("_"));
$dlen = strlen($data);
if ($dlen < $outer_length) {
$decryptionError = "corrupt input, outer length header said {$outer_length} bytes, but only {$dlen} bytes available!"; return null; } $data = substr($data, 0, $outer_length);
$data = self::base64url_decode($data);
return self::decryptV1($data, $encryption_key, $decryptionError); } private static function decryptV1(string $data, string $encryption_key, string &$decryptionError = null): ?string
{
if (strlen($data) < self::V1_IV_LENGTH) { $decryptionError = "corrupt input, IV is missing!";
return null;
}
$IV = substr($data, 0, self::V1_IV_LENGTH);
$data = substr($data, self::V1_IV_LENGTH);
// now we have the aes128 data..
if (strlen($data) < 16 || (strlen($data) % 16) !== 0) {
$decryptionError = "corrupted input, after removing IV, data is not a multiple of 16 bytes!"; return null; } $hkey = hash(self::V1_HASH_ALGO, $encryption_key, true); $hkey = substr($hkey, 0, 16); $data = openssl_decrypt($data, self::V1_ENCRYPT_ALGO, $hkey, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING, $IV); if (! is_string($data)) {
// should never happen
throw new \RuntimeException("openssl_decrypt failed! wtf!?");
}
if (strlen($data) < self::V1_HASH_TRUNCATE_LENGTH) { $decryptionError = "corrupt input, after decryption, checksum hash is missing!";
return null;
}
$checksum_supplied_hash = substr($data, 0, self::V1_HASH_TRUNCATE_LENGTH);
$data = substr($data, self::V1_HASH_TRUNCATE_LENGTH);
$checksum_calculated_hash = hash(self::V1_HASH_ALGO, $data, true);
$checksum_calculated_hash = substr($checksum_calculated_hash, 0, self::V1_HASH_TRUNCATE_LENGTH);
if (! hash_equals($checksum_calculated_hash, $checksum_supplied_hash)) {
$decryptionError = "checksum mismatch, possibly wrong decryption key?"; return null; } $little_endian_uint64_length = 8;
if (strlen($data) < $little_endian_uint64_length) {
$decryptionError = "after decryption, inner_length header is missing!"; return null; } $little_endian_uint64 = substr($data, 0, $little_endian_uint64_length);
$little_endian_uint64 = self::from_little_uint64_t($little_endian_uint64);
$data = substr($data, $little_endian_uint64_length); $dlen = strlen($data); if ($dlen < $little_endian_uint64) { $decryptionError = "inner_length header said {$little_endian_uint64} bytes, but only {$dlen} bytes remaining, and that includes any padding bytes!";
return null;
}
$data = substr($data, 0, $little_endian_uint64); return $data;
}
private static function encryptV1(string $data, string $encryption_key): string
{
// compress/expand the key so we can accept any encryption key length (instead of the 16 bytes key required by aes-128)
$hkey = hash(self::V1_HASH_ALGO, $encryption_key, true);
$hkey = substr($hkey, 0, 16);
$iv = random_bytes(self::V1_IV_LENGTH); $inner_length_bytes = self::to_little_uint64_t(strlen($data)); $ret = $inner_length_bytes; $ret .= $data; $padding_length = self::V1_HASH_TRUNCATE_LENGTH + strlen($ret); $padding_length = (16 - ($padding_length % 16)) % 16; $ret .= str_repeat("\x00", $padding_length); $csum = hash(self::V1_HASH_ALGO, $ret, true); $csum = substr($csum, 0, self::V1_HASH_TRUNCATE_LENGTH); $ret = $csum . $ret;
$str = openssl_encrypt($ret, self::V1_ENCRYPT_ALGO, $hkey, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING, $iv);
if (! is_string($str)) { // should never happen throw new \RuntimeException("openssl_encrypt failed! wtf!?"); } $str = $iv . $str;
return $str; } private static function to_uint8_t(int $i): string
{
return pack('C', $i); } private static function from_uint8_t(string $i): int
{
// ord($i) , i know. $arr = unpack("Cuint8_t", $i); return $arr['uint8_t'];
}
private static function to_little_uint64_t(int $i): string { return pack('P', $i);
}
private static function from_little_uint64_t(string $i): int { $arr = unpack('Puint64_t', $i); return $arr['uint64_t'];
}
private static function base64url_encode($data) { return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
private static function base64url_decode($data) { return base64_decode(strtr($data, '-_', '+/'));
}
}
Örnek kullanım:
$data = "Hello World!"; // . random_bytes(10*1024*1024); $decryptionError = "";
$encrypted = EasyCrypt::encryptEasy($data);
$decrypted = EasyCrypt::decryptEasy($encrypted, $decryptionError); $pretty = [
"data to encrypt" => $data, "encrypted" => $encrypted,
"decrypted successfully" => $decrypted === $data,
"decryption error" => $decryptionError ]; var_export($pretty);
$ php EasyCrypt.php | more
array (
'data to encrypt' => 'Hello World!',
'encrypted' => 'easycryptstart_1_86_LvBV6n3yLY-sH3vdhjzIZmbAm56s7VEZ9ah0wh5z4p9-rhJBaIDmOQYaWOTuRSei7yfmXJ6HTbqgvBaQJsQdMg_easycryptend',
'decrypted successfully' => true,
'decryption error' => '',
)
Yanıtlar
Kaldır encryptEasyve decryptEasy; her iki işlev de güvensizdir ve yalnızca olası kullanıcıların kafasını karıştırmaya hizmet eder.
Mevcut şemanız bir MAC kullanmıyor, sağlama toplamı değil, bu da şifreli metnin kimlik doğrulamasının olmadığı anlamına geliyor. Bu kötü bir fikir . Çıktı aralığını geniş tutarken karma uzantı saldırıları riskinden de kaçınmak için kimlik doğrulama için HMAC işlevinin kullanılması önerilir.
Şu anki yaklaşımınız, teorik olarak CTR bitflip saldırılarına karşı savunmasız olan, karma ve sonra şifreleme yaklaşımına sahipken, MAC'ı şifrele önerilir .
Veriler zaten boş bayt içeriyorsa doldurma uygulamanız yanlıştır (PKCS # 7 kullanın).
İstisnalar için bir referans parametresi kullanmak yerine, istisnaları doğrudan atmanız yeterlidir.