在PHP中完美加密和解密密码、文件的方法是什么?

7

我对这个主题进行了一系列的研究,但不幸的是我无法找到一种在PHP中完美地加密和解密文件的方法。我的目标是寻找一种加密和解密物品的方式,而不必担心黑客知道我的算法。如果某个算法需要保密和隐藏,它不能解决我的问题,因为一旦逻辑共享到任何地方,或者他们闯入我的服务器并获取源文件,那么就应该有一种使用相同解密算法的解密方式。之前我在StackOverFlow网站上发现了几篇很棒的文章,但仍然不能回答我的问题。

通过阅读,我得出结论,世界上最好的加密密码的方法是Blowfish加密。它是一种单向哈希算法,有1000次迭代,使得黑客需要使用相同规格的GPU花费7年才能解密。

显然,由于是单向哈希,这使得解密变得不可能。

  1. 如何在PHP中使用bcrypt对密码进行哈希?
  2. 为什么盐可以使字典攻击变得“不可能”?

关于在PHP中加密和解密密码的最佳方法,引用这个问题。根据我在网上发现的内容,sha1和md5都是被破解和破坏的算法,即使我们改变算法。

$encrypted = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, md5($key), $string, MCRYPT_MODE_CBC, md5(md5($key))));

To

$encrypted = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, sha1(md5($key)), $string, MCRYPT_MODE_CBC, sha1(md5(md5($key)))));

这仅仅是增加解密的难度,但从时间上来讲依然可以被破解吗?

  1. 使用PHP进行密码加密和解密的最佳方法是什么?

我想使用我们服务器处理器/硬盘GUID生成盐并加密密码。

这仍然是一种愚蠢的做法,因为黑客可以访问服务器并使用PHP回显GUID以进行解密。或者如果它可以工作,几年后我的网站将会陷入麻烦。原因是硬盘、处理器永远不持久。当处理器或硬盘出现问题时,我的网站就会出现问题,并且丢失所有凭据。

更新

发现这个问题用blowfish算法在PHP中进行了解密。这是否解决了找到安全的加密方式并且难以被其他人解密的问题?

  1. 如何使用PHP中的Blowfish算法进行解密?

请问有人能建议我如何解决这个问题吗?谢谢。


1
如果你的代码可以解密你的代码,那么你的代码就可以被制作成解密你的代码的工具。这是无法避免的。 - user149341
1
没有办法做到那样。您的应用程序并不独一无二——它所做的任何事情都可以在另一个应用程序中复制。 - user149341
@TheShiftExchange,我需要帮助编写算法,使文件只能通过应用程序本身进行加密和解密。或者如何使用/dev/srandom或openssl获取随机性并改进上面的代码行? - 1myb
但是你加密的是什么?应用程序文件本身?还是随机文件? - Laurence
@TheShiftExchange,用户私人文件。例如身份证明PDF文件。 - 1myb
显示剩余9条评论
6个回答

5

请看这篇文档 PHP可逆密码加密例程,它适用于那些想要一个可逆的密码加密例程的PHP开发人员。

虽然该类是用于密码加密,但也可以用于任何文本的加密/解密。

function encryption_class() {
    $this->errors = array();

    // Each of these two strings must contain the same characters, but in a different order.
    // Use only printable characters from the ASCII table.
    // Do not use single quote, double quote or backslash as these have special meanings in PHP.
    // Each character can only appear once in each string.
    $this->scramble1 = '! #$%&()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~';
    $this->scramble2 = 'f^jAE]okIOzU[2&q1{3`h5w_794p@6s8?BgP>dFV=m D<TcS%Ze|r:lGK/uCy.Jx)HiQ!#$~(;Lt-R}Ma,NvW+Ynb*0X';

    if (strlen($this->scramble1) <> strlen($this->scramble2)) {
        trigger_error('** SCRAMBLE1 is not same length as SCRAMBLE2 **', E_USER_ERROR);
    } // if

    $this->adj = 1.75;  // this value is added to the rolling fudgefactors
    $this->mod = 3;     // if divisible by this the adjustment is made negative
}

注意:

如果您使用的是PHP版本>= 5.3.3,则必须将类名从encryption_class更改为__construct

原因:

自PHP 5.3.3以来,具有与命名空间类名称的最后一个元素相同名称的方法将不再被视为构造函数。

用法:

$crypt = new encryption_class();

$crypt->setAdjustment(1.75); // 1st adjustment value (optional)
$crypt->setModulus(3); // 2nd adjustment value (optional)

/**
 * 
 * @param string $key - Your encryption key
 * @param string $sourceText - The source text to be encrypted
 * @param integer $encLen - positive integer indicating the minimum length of encrypted text
 * @return string - encrypted text
 */
$encrypt_result = $crypt->encrypt($key, $sourceText, $encLen);

/**
 * 
 * @param string $key - Your encryption key (same used for encryption)
 * @param string $encrypt_result - The text to be decrypted
 * @return string - decrypted text
 */
$decrypt_result = $crypt->decrypt($key, $encrypt_result);

更新:

上面的类不是用来加密文件的,但是你可以使用以下步骤进行加密:

  1. base64_encode 源文本(文件内容)
  2. 对于实际加密,将上述加密/解密类应用于 base64 编码的文本
  3. 对于解密,将上述加密/解密类应用于实际加密的文本
  4. base64_decode 将给出实际文件内容(您可以使用此内容保存文件的副本)

我已经加密了一张图片,解密并保存到了一个新文件中!!! 查看代码。

//class for encrypt/decrypt routines 
require 'class.encryption.php';

//configuring your security levels
$key = 'This is my secret key; with symbols (@$^*&<?>/!#_+), cool eh?!!! :)';
$adjustment = 1.75;
$modulus = 2;

//customizing
$sourceFileName = 'source-image.png';
$destFileName = 'dest-image.png';
$minSpecifiedLength = 512;

//base64 encoding file contents, to get all characters in our range
//binary too!!!
$sourceText = base64_encode(file_get_contents($sourceFileName));

$crypt = new encryption_class();
$crypt->setAdjustment($adjustment); //optional
$crypt->setModulus($modulus); //optional

//encrypted text
$encrypt_result = $crypt->encrypt($key, $sourceText, $minSpecifiedLength);

//receive initial file contents after decryption
$decrypt_result = base64_decode($crypt->decrypt($key, $encrypt_result));

//save as new file!!!
file_put_contents($destFileName, $decrypt_result);

如果我想让我的“随机”算法只需更改调整值和模数值,那么它就是用于加密类操作的值吗?这对文件加密有效吗? - 1myb
1
@SLim - 实际上,上面的类并不是用于加密文件的,但您也可以将其用于文件加密...请查看我的更新... - rajukoyilandy
你所说的配置安全级别是什么意思?按照这个逻辑,这是否意味着黑客无法轻易地“破解”它或者只能采用最困难的方式进行破解?我仍然对调整和模数感到困惑。谢谢回复。 - 1myb
1
@SLim - “调整”和“模数”的作用在此部分中有描述。我所说的安全级别是指: 1)长而强大的“密钥”将帮助您获得强大的加密文本 2)最好通过提供新值来更改“模数”和“调整”的默认值,不要使用开源代码的默认密钥和/或秘密,这总是一个好主意。 - rajukoyilandy

5
请记住,为了破解密码,黑客首先必须能够访问加密密码。为了做到这一点,他们必须破坏服务器的安全性,如果网站编码正确(适当的转义或准备好的语句),这应该是不可能的。
最强大但也最简单的加密形式之一是XOR,然而它完全依赖于密钥。如果密钥与编码文本的长度相同,则完全无法在没有该密钥的情况下破解。即使密钥长度是文本长度的一半,也极不可能被破解。
最终,无论你选择的方法如何,都由允许你访问服务器文件的FTP / SSH /任何密码来保护。如果你自己的密码被破解,黑客可以看到所有东西。

5
您的问题导致了两种不同的答案。如果您以后需要解密数据(例如文件),或者如果您可以使用单向哈希(用于密码),这是一个重要的区别。
单向哈希
如果您不需要解密数据(密码),则应使用哈希函数。这样更安全,因为即使攻击者控制您的服务器和数据库,他也不应该能够检索原始密码。由于用户经常在多个网站上使用其密码,因此至少他不会获得访问其他网站的权限。
正如您已经提到的,今天最受推荐的哈希函数之一是bcrypt。尽管它起源于blowfish算法,但实际上它是一种哈希函数(而不是加密)。Bcrypt专门设计用于哈希密码,因此速度较慢(需要计算时间)。建议使用像phpass这样的知名库,如果您想了解如何实现它,可以阅读这篇文章,我试图解释最重要的点。
加密
如果您需要解密数据(文件),则无法防止服务器控制权的攻击者也能够解密文件(毕竟服务器必须能够解密文件)。这一切都归结为存储秘密密钥的位置。您唯一能做的就是使获取密钥更加困难。
这意味着,如果您将密钥存储在文件中,则应该将其放在http根目录之外,以便无论如何都无法从Internet访问它。您可以将其存储在不同的服务器上,因此攻击者需要控制两个服务器,尽管然后您面临服务器之间安全通信的问题。在每种情况下,您都可以使盗窃更加困难,但无法完全防止它。
根据您的情况,您可以在计算机上本地加密文件,然后仅将加密文件存储在服务器上。那么服务器将无法自行解密文件,因此它们是安全的。

3
您已经了解了盐值和哈希,但您也可以“拉伸”密码,即不仅仅对每个密码进行一次哈希,而是对其进行数千次哈希。这将减慢暴力攻击的速度并延长哈希算法的生命周期。有趣的是它通过故意减慢服务器的速度来实现...
我建议编写自定义哈希函数。首先,向密码添加盐,然后选择哈希算法(比如 sha512,或者设计为不高效的新算法用于这个目的),对其进行哈希,比如说 10,000 次,然后将其存储在数据库中。正如您已经知道的那样,当用户登入时,您只需通过相同的算法运行其输入,看看是否匹配,而不是反转哈希。
编写自己的哈希函数的美妙之处在于,当到了更新哈希算法的时候,因为旧的哈希算法已经容易受到暴力攻击,您只需要在自己的哈希函数中添加结果,重新盐化并使用新的算法再次哈希。您可以使用当时被认为是安全的任何哈希算法。然后,您可以简单地用新的哈希函数重新哈希已存储在数据库中的每个密码,从而确保向后兼容性。根据用户数量和服务器速度的快慢,可能只需要几秒钟就可以执行此更新。
然而,仍存在漏洞。如果黑客拥有您的旧数据库的副本并且破解了它,他仍然知道任何尚未更改密码的用户的密码。唯一的解决办法是要求用户定期更改密码,这可能适用于您的网站,也可能不适用,具体取决于其包含的信息的性质。一些安全专业人士建议,只有在被攻击时用户才更改他们的密码,因为如果系统让管理密码太困难,他们将开始做不安全的事情,比如把密码放在键盘下面,对某些组织来说是一个更大的威胁,而不是让用户永远不改变他们的密码。如果您的网站是一个论坛或评论网站或类似的东西,您应该考虑用户通过账户被盗失去多少,恢复数据回到被盗之前的状态有多容易,以及如果您的密码策略太麻烦,他们是否会认为您的网站值得为之更新密码。
以下是一种可能的哈希函数:
function the_awesomest_hash($password)
{
    $salt1 = "awesomesalt!";
    $password = $salt1 . $password;
    for($i = 0; $i < 10000; $i++)
    {
        $password = hash('sha512', $password);
    }
    // Some time has passed, and you have added to your hash function
    $salt2 = "niftysalt!";
    $password = $salt2 . $password;
    for($i = 0; $i < 10000; $i++)
    {
        $password = hash('futuresuperhash1024', $password);
    }
    return $password;
}

现在,为了更新已经存在于您的数据库中的所有密码,您需要将它们运行通过这个功能:
function update_hash($password)
{
    // This is the last part of your the_awesomest_hash() function
    $salt2 = "niftysalt!";
    $password = $salt2 . $password;
    for($i = 0; $i < 10000; $i++)
    {
        $password = hash('futuresuperhash1024', $password);
    }
    return $password;
}

我喜欢编写自己的哈希函数,因为在更新时更容易跟踪确切发生了什么。


1
经过对PHP的一些研究,特别是随机数生成方面的研究,使用OpenSSL包装器是PHP中唯一安全加密的方法。特别是mcrypt的创建者们都是一群白痴,只需看看他们在示例中如何不进行密码学操作的示例即可明白。
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$key = "This is a very secret key";
$text = "Meet me at 11 o'clock behind the monument.";
echo strlen($text) . "\n";

$crypttext = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $text, MCRYPT_MODE_ECB, $iv);
echo strlen($crypttext) . "\n";

请注意,默认情况下,MCRYPT_RAND的种子不是很好。此外,仅在上面的代码中就至少有5个错误,并且它们不会修复它。
[编辑] 请参见下面的修改示例。请注意,此示例也不是非常安全(如上所述)。此外,通常不应加密密码...
# the key should be random binary, use scrypt, bcrypt or PBKDF2 to convert a string into a key
# key is specified using hexadecimals
$key = pack('H*', "bcb04b7e103a0cd8b54763051cef08bc55abe029fdebae5e1d417e2ffb2a00a3");
echo "Key size (in bits): " . $key_size * 8 . "\n";
$plaintext = "This string was AES-256 / CBC / ZeroBytePadding encrypted.";
echo "Plain text: " . $plain_text . "\n";
$ciphertext_base64 = encryptText($key, $plaintext);
echo  $ciphertext_base64 . "\n";


function encryptText(string $key_hex, string $plaintext) {

    # --- ENCRYPTION ---


    # show key size use either 16, 24 or 32 byte keys for AES-128, 192 and 256 respectively
    $key_size =  strlen($key);


    # create a random IV to use with CBC encoding
    $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
    $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);

    # use an explicit encoding for the plain text
    $plaintext_utf8 = utf8_encode($plaintext);

    # creates a cipher text compatible with AES (Rijndael block size = 128) to keep the text confidential 
    # only suitable for encoded input that never ends with value 00h (because of default zero padding)
    $ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $plaintext_utf8, MCRYPT_MODE_CBC, $iv);

    # prepend the IV for it to be available for decryption
    $ciphertext = $iv . $ciphertext;

    # encode the resulting cipher text so it can be represented by a string
    $ciphertext_base64 = base64_encode($ciphertext);

    return $ciphertext_base64;
}


# === WARNING ===

# Resulting cipher text has no integrity or authenticity added
# and is not protected against padding oracle attacks.

# --- DECRYPTION ---

$ciphertext_dec = base64_decode($ciphertext_base64);

# retrieves the IV, iv_size should be created using mcrypt_get_iv_size()
$iv_dec = substr($ciphertext_dec, 0, $iv_size);

# retrieves the cipher text (everything except the $iv_size in the front)
$ciphertext_dec = substr($ciphertext_dec, $iv_size);

# may remove 00h valued characters from end of plain text
$plaintext_utf8_dec = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $ciphertext_dec, MCRYPT_MODE_CBC, $iv_dec);

echo  $plaintext_utf8_dec . "\n";

有没有可能让它完美,或者像你说的那样用另一种方式做,因为他们不会修复它。谢谢 =D - 1myb
1
并不是这样,因为几乎所有的加密操作都需要随机数生成。所以你需要访问 /dev/srandom 或者使用 openssl 来获取随机性。使用 openssl 封装器会更容易些。 - Maarten Bodewes
请问您能否指导我如何加密和解密文件,或者提供一些足够安全的示例? - 1myb

-1

到目前为止,我知道保存密码的最佳方式是使用像Joomla中使用的盐哈希。您还可以在传统的base64之外,将额外的密钥添加到md5哈希中。我以前写过一个类似的脚本,但现在找不到了。

Joomla使用盐化的md5密码。以您提供的哈希密码为例:30590cccd0c7fd813ffc724591aea603:WDmIt53GwY2X7TvMqDXaMWJ1mrdZ1sKb

如果您的密码是'password',那么: md5('passwordWDmIt53GwY2X7TvMqDXaMWJ1mrdZ1sKb') = 30590cccd0c7fd813ffc724591aea603

因此,取出您的密码。生成一个随机的32个字符的字符串。计算密码与随机字符串连接后的md5值。将md5结果加上冒号和随机的32个字符的字符串存储在数据库中。


我很了解盐和密码。正如我在问题中提到的,SHA1和MD5都是被破解和破坏的算法... - 1myb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接