在网页上,最终用户可以看到一个带有链接的列表。这些链接需要通过URL向表单发送一个ID。然后,这个表单从URL中收集参数,并使用这个ID从数据库中检索信息。
为了禁止最终用户简单地更改URL中的ID,我使用PHP中的openssl_encrypt
函数对ID进行加密。这个函数还需要一个初始化向量(IV),它是16个字符长,因为我使用AES-128-CTR
作为密码方法。因此,对于每个链接,我也生成一个唯一的IV,然后我将IV与加密的ID结合起来。因为这可以有URL中不允许的字符,所以我使用urlencode
使链接安全。为了测试解密,我还解密了页面上带有所有链接的每个加密消息。这工作得很好。请参阅此页面的代码。$submissionId is a number and all echos are for testing purposes
.
// Display the original string
echo "Original String: " . $submissionId;
// Store the cipher method
$ciphering = "AES-128-CTR";
// Use OpenSSl Encryption method
$iv_length = openssl_cipher_iv_length($ciphering);
$options = 0;
// Create a Initialization Vector
$iv = random_bytes(8);
echo "<br><br>" . bin2hex($iv) . "<br><br>";
// Non-NULL Initialization Vector for encryption
$encryption_iv = $iv;
// Store the encryption key
$encryption_key = "test";
// Use openssl_encrypt() function to encrypt the data
$encryption = openssl_encrypt($submissionId, $ciphering,
$encryption_key, $options, $encryption_iv);
// Display the encrypted string
echo "Encrypted String: " . $encryption . "<br>";
// Encrypted submissionId
$encryptedSubmissionIdAndIv = bin2hex($iv).$encryption;
$encryptedSubmissionIdAndIvUrlEncoded = urlencode( $encryptedSubmissionIdAndIv );
echo "Encrypted Sub ID: " . $encryptedSubmissionIdAndIv . "<br>";
echo "Encrypted Sub ID URL Encoded: " . $encryptedSubmissionIdAndIvUrlEncoded . "<br>";
// BELOW IS DECRYPTING FOR TESTING!!!
// Non-NULL Initialization Vector for decryption
$decryption_iv = $iv;
// Store the decryption key
$decryption_key = "test";
// Use openssl_decrypt() function to decrypt the data
$decryption=openssl_decrypt ($encryption, $ciphering,
$decryption_key, $options, $decryption_iv);
// Display the decrypted string
echo "Decrypted String: " . $decryption;
在表单中,我从URL中获取参数/组件。名为sId
的组件对IV和加密ID都进行了解码。然后我继续设置密码,然后从sId
字符串收集IV,并收集ID($encryption
)。然后,我使用与链接页面上相同的代码来解密ID。但是在这个页面上,它返回的是垃圾,而不是正确的ID。在链接页面上,它可以工作。我检查了服务器上的错误日志,没有给出错误。对于垃圾,我的意思是,它返回类似这样的东西:�b�
.
// Retrieving the encrypted submission id and iv that is url encoded and automatically decode via get.
$encryptedSubmissionIdAndIvUrlDecoded = $_GET['sId'];
echo "encryptedSubmissionIdAndIvUrlDecoded: " . $encryptedSubmissionIdAndIvUrlDecoded . "<br>";
// Store the cipher method
$ciphering = "AES-128-CTR";
// Use OpenSSl Encryption method
$iv_length = openssl_cipher_iv_length($ciphering);
$options = 0;
// Collecting the Initialization Vector
$iv = substr($encryptedSubmissionIdAndIvUrlDecoded, 0, 16);
echo "iv: " . $iv . "<br>";
// Collecting the encrypted message
$encryption = substr($encryptedSubmissionIdAndIvUrlDecoded, 16);
echo "encryption: " . $encryption . "<br>";
// Non-NULL Initialization Vector for decryption
$decryption_iv = $iv;
// Store the decryption key
$decryption_key = "test";
// Use openssl_decrypt() function to decrypt the data
$decryption = openssl_decrypt($encryption, $ciphering,
$decryption_key, $options, $decryption_iv);
// Display the decrypted string
echo "decryption: " . $decryption . "<br>";
- 我已经尝试改变使用的键的长度。(出于测试目的,现在只是测试)
- 试着把IV放在URL的单独参数中,这样我就不必把IV和ID结合起来,但这导致了同样的事情。在链接页上工作,但不在表单页上。
- 还尝试通过硬编码将IV和加密消息放入测试中,但这会导致相同的垃圾。
- 当我在表单页面
$decryption_key = "testtesttesttest";
上故意添加一个错误的键时,它不显示垃圾:�b�
,但它显示K
,这是其他垃圾,但它是不同的。这让我假设IV和KEY是正确的。 - 在评论中提出了一些很好的建议,上面的代码反映了这一点。
我不确定它是否重要,但它都在同一个域/服务器和PHP 8+版本。
任何人都知道为什么它会返回这样的东西:�b�
在表单页面上,但在链接页面上正常工作,它返回的东西像311
?
第二个片段中的解密失败,因为缺少IV的十六进制解码。
$decryption = openssl_decrypt($encryption, $ciphering, $decryption_key, $options, hex2bin($decryption_iv));
解密工作。
在您的答案中发布的解决方案不会导致可以由第二个代码片段解密的密文(至少不是没有额外的修改)。除此之外,正如已经在评论中提到的那样,它是不安全的。
即使修复了错误,代码中仍然存在一些缺陷和漏洞:
IV大小对应于块大小,这是AES的16字节。在代码中使用了一个8字节的IV,所以太短的IV。PHP用0x00值填充太短的IV,直到所需的长度。实际上,太短的IV会触发适当的警告:IV传递的长度只有8个字节,cipher期望的IV正好是16个字节,填充 在…
PHP允许使用openssl_cipher_iv_length()
显式地确定IV,这在代码中使用。然而,结果并没有被考虑在内。
另外,出于安全原因,必须为每个加密生成一个随机IV,这就是代码中发生的事情(除了错误的长度)。随机IV阻止了密钥/IV对的重用,这将是一个非常严重的漏洞,特别是对于CTR模式:$iv_length = openssl_cipher_iv_length($ciphering); $iv = random_bytes($iv_length); // apply a random IV of the right length
另外,在加密时不需要对IV进行十六进制编码以进行连接,而是应该将原始IV和原始密文连接起来,然后将结果进行Base64编码。要做到这一点,必须禁用
openssl_encrypt()
的隐式Base64编码,这可以使用标志OPENSSL_RAW_DATA
作为第四个参数来完成。之后返回原始密文。$options = OPENSSL_RAW_DATA; $encryption = openssl_encrypt($submissionId, $ciphering, $encryption_key, $options, $encryption_iv); // disable implicit Base64 encoding $encryptedSubmissionIdAndIv = base64_encode($encryption_iv.$encryption); // concatenate IV and ciphertext and Base64 encode
因此,在解密过程中,首先必须进行Base64解码,然后必须将IV和密文分开:
$encryptedSubmissionIdAndIvUrlDecoded = base64_decode($encryptedSubmissionIdAndIv); // Base64 decode (URL decoded Data) $iv_length = openssl_cipher_iv_length($ciphering); $decryption_iv = substr($encryptedSubmissionIdAndIvUrlDecoded, 0, $iv_length); // separate IV... $encryption = substr($encryptedSubmissionIdAndIvUrlDecoded, $iv_length); // ...and ciphertext $options = OPENSSL_RAW_DATA; // disable implicit Base64 decoding $decryption = openssl_decrypt($encryption, $ciphering, $decryption_key, $options, $decryption_iv);
出于安全原因,密钥不能是字符串,而是正确长度的随机字节序列(AES-128为16字节)。PHP用0x00值填充一个太短的键材料,直到所需的长度。