如何使用PHP加密和解密密码是最佳方法?

219
可能重复:PHP 2-way encryption: I need to store passwords that can be retrieved 我打算在我的网站上为用户存储外部帐户信息,例如rapidshare用户名和密码等。我希望保护这些信息的安全性,但如果我对其进行哈希处理,则无法以后检索它。
Base64是可解密的,因此没有使用它的意义。我的想法是在将用户和密码base64编码之前和之后对其进行混淆,这样即使在解密后,如果您尝试解密,您也会得到一些看起来很有趣的文本。是否有一个PHP函数可以接受值,并使字符串生成唯一的混淆文本,并在重新输入该值时解除混淆?
有什么建议吗?

7
Base64?哈哈哈,不行。像PassPack一样使用AES加密并使用主密码来加密。http://www.phpaes.com/ - mcandre
1
Web服务器如何获取加密的用户凭据信息?只需要知道解密密码即可。如果我是具备访问您的服务器的攻击者,你的方案如何阻止我获取这些信息?这里你仅仅得到了一种“混淆”的方式,因此无法在您的服务器上完全以明文状态存储这些凭据。但这最多只能算是安全性通过混淆实现。 - stolsvik
2
@stolsvik 这是一种额外的层,用于防止 SQL 注入攻击。在您的数据库损坏而不是服务器的情况下,您仍需要密钥才能获取敏感信息。 - haknick
8个回答

303

你不应该加密密码,而应该使用像bcrypt这样的算法对密码进行哈希。 这个答案解释了如何在PHP中正确实现密码哈希 不过,以下是如何加密/解密:

$key = 'password to (en/de)crypt';
$string = ' string to be encrypted '; // note the spaces

加密:

$iv = mcrypt_create_iv(
    mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC),
    MCRYPT_DEV_URANDOM
);

$encrypted = base64_encode(
    $iv .
    mcrypt_encrypt(
        MCRYPT_RIJNDAEL_128,
        hash('sha256', $key, true),
        $string,
        MCRYPT_MODE_CBC,
        $iv
    )
);

要解密:

$data = base64_decode($encrypted);
$iv = substr($data, 0, mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC));

$decrypted = rtrim(
    mcrypt_decrypt(
        MCRYPT_RIJNDAEL_128,
        hash('sha256', $key, true),
        substr($data, mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC)),
        MCRYPT_MODE_CBC,
        $iv
    ),
    "\0"
);
警告:上述示例对信息进行了加密,但未对密文进行身份验证以防篡改。您不应该依赖未经身份验证的加密来保证安全性,尤其是由于所提供的代码易受填充神谕攻击的影响。
另请参见:

此外,不要仅使用“密码”作为加密密钥。加密密钥应该是随机字符串。


在 3v4l.org 上查看演示:

echo 'Encrypted:' . "\n";
var_dump($encrypted); // "m1DSXVlAKJnLm7k3WrVd51omGL/05JJrPluBonO9W+9ohkNuw8rWdJW6NeLNc688="

echo "\n";

echo 'Decrypted:' . "\n";
var_dump($decrypted); // " string to be encrypted "

20
实际上,不是“一些随机的”,而是“随机的并在之后被存储”。 - BasTaller
2
我只是想知道 $string 周围空格的意义。它们是在加密之前人为添加以增加破解难度吗?如果我理解有误请不要介意 ;) - wired00
5
不,完全不是这样!如果你解密一个加密的字符串,你会注意到字符串末尾添加了一些空字节。通常人们只需使用 [r]trim() 来去掉这些空字节,但在此过程中也会删除空格。我添加了这些空格,以便人们可以测试和验证正确的修剪解密后的字符串的方法是:$original = rtrim($decrypted, "\0"); - 注意 \0。=) - Alix Axel
13
天啊,即使只是作为存储密码的建议,这也太糟糕了。它甚至不能被反向解密(那么你为什么一开始要使用加密)...并且它没有使用bcrypt(虽然在第一行中提到了)。关于如何加密数据,请看这个[SO Answer](https://dev59.com/m2445IYBdhLWcg3wAFi0#5093422),其中包括正确设置工具的方法...例如:你缺少填充(CBC需要填充)... - ircmaxell
11
好的,在深入查看代码后,您根本没有存储密码。您正在使用密码作为密码密钥来存储其他数据。因此,这甚至不能回答问题。如果您这样做,您需要首先对密码使用密钥派生函数。例如 PBKDF2... - ircmaxell
显示剩余27条评论

37

安全警告:此类不安全。 它使用的是Rijndael256-ECB,这不是语义上安全的。仅仅因为"它可以工作"并不意味着"它是安全的"。此外,由于未使用适当的填充方式,它会剥离尾随空格。

最近发现了这个类,它能完美运行!

class Encryption {
    var $skey = "yourSecretKey"; // you can change it

    public  function safe_b64encode($string) {
        $data = base64_encode($string);
        $data = str_replace(array('+','/','='),array('-','_',''),$data);
        return $data;
    }

    public function safe_b64decode($string) {
        $data = str_replace(array('-','_'),array('+','/'),$string);
        $mod4 = strlen($data) % 4;
        if ($mod4) {
            $data .= substr('====', $mod4);
        }
        return base64_decode($data);
    }

    public  function encode($value){ 
        if(!$value){return false;}
        $text = $value;
        $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
        $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
        $crypttext = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $this->skey, $text, MCRYPT_MODE_ECB, $iv);
        return trim($this->safe_b64encode($crypttext)); 
    }

    public function decode($value){
        if(!$value){return false;}
        $crypttext = $this->safe_b64decode($value); 
        $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
        $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
        $decrypttext = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $this->skey, $crypttext, MCRYPT_MODE_ECB, $iv);
        return trim($decrypttext);
    }
}

并且要调用它:

$str = "My secret String";

$converter = new Encryption;
$encoded = $converter->encode($str );
$decoded = $converter->decode($encoded);    

echo "$encoded<p>$decoded";

1
谢谢!我使用这个类来存储会话中的敏感数据,并进行了一些修改,我添加了一个构造函数:function __construct() { $this->skey = md5(session_name()); }。我认为这样更安全一些 :) - BCsongor
6
此答案不会生成安全的密码文本,请勿使用。它只能在加密/解密方面发挥作用,我会将其翻译成中文。 - Maarten Bodewes
7
“// you can change it” 应该翻译为 “// 你可以修改它”,而不是 “// 你必须修改它”。 - Francisco Presencia
15
不,不,不。这不是好的做法。首先,它使用了ECB模式,这是不安全的。然后,它使用了一种不安全的生成IVs的方法。它根本不会以安全的方式运行。相反,可以参考此回答提供的内容,或者直接使用库。这不是安全的 - ircmaxell
1
错误 Warning: mcrypt_encrypt(): Key of size 4 not supported by this algorithm. Only keys of sizes 16, 24 or 32 supported in 是针对 PHP 5.6.x 的。 - Editor
显示剩余3条评论

19

安全警告:此代码不安全。

工作示例

define('SALT', 'whateveryouwant'); 

function encrypt($text) 
{ 
    return trim(base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, SALT, $text, MCRYPT_MODE_ECB, mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND)))); 
} 

function decrypt($text) 
{ 
    return trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, SALT, base64_decode($text), MCRYPT_MODE_ECB, mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND))); 
} 

$encryptedmessage = encrypt("your message"); 
echo decrypt($encryptedmessage); 

9
样本不佳;使用两次随机数生成初始化向量(IV),却因为使用ECB模式而没有使用IV,一开始就使用了ECB编码,没有使用符合AES标准的代码,也没有正确填充消息。是的,它的运行方式就像xkcd上的随机数生成器一样。 - Maarten Bodewes
12
此答案无法生成安全的密码文本,请勿使用。它只是在加密/解密方面发挥作用。 - Maarten Bodewes

13

在处理加密时,有一件事情你必须非常清楚:

试图聪明地发明自己的东西通常会让你得到一个不安全的东西。

你最好使用 PHP 自带的密码学扩展之一。


这些并不总是易于使用,甚至不知道该使用哪一个。也许一些代码示例在这里会证明有价值的。 - Simon East
1
此外,Mcrypt 已不再推荐使用。请参见 https://paragonie.com/blog/2015/05/if-you-re-typing-word-mcrypt-into-your-code-you-re-doing-it-wrong。 - Simon East

4

安全警告:此代码存在不安全性。除了容易受到选择明文攻击的影响外,它对unserialize()的依赖也使其容易受到PHP对象注入的攻击。

为了处理字符串/数组,我使用以下两个函数:

function encryptStringArray ($stringArray, $key = "Your secret salt thingie") {
 $s = strtr(base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, md5($key), serialize($stringArray), MCRYPT_MODE_CBC, md5(md5($key)))), '+/=', '-_,');
 return $s;
}

function decryptStringArray ($stringArray, $key = "Your secret salt thingie") {
 $s = unserialize(rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, md5($key), base64_decode(strtr($stringArray, '-_,', '+/=')), MCRYPT_MODE_CBC, md5(md5($key))), "\0"));
 return $s;
}

它是灵活的,因为在加密之前,字符串/数组会被序列化,所以你可以通过URL存储/发送字符串或数组。


2
为什么使用 md5($key) 作为密钥,而将 md5(md5($key)) 作为初始向量(IV)? - Scott Arciszewski

3

这只会给你一点保护。如果攻击者可以在你的应用程序中运行任意代码,他们可以以与你的应用程序完全相同的方式获取密码。如果你在文件中存储一个秘密密钥,并在传输到数据库时使用该密钥进行加密并在传输出时进行解密,则仍然可以从某些SQL注入攻击和错误的数据库备份中获得一些保护。但是你应该使用绑定参数来完全避免SQL注入问题。

如果决定加密,你应该使用一些高级加密库,否则你可能会做错。你必须正确设置密钥、消息填充和完整性检查,否则所有的加密努力都没有多大用处。例如,GPGME 是一个不错的选择。Mcrypt 太低级了,你很可能会做错。


2

1
Mcrypt不是一个好的选择。 - Scott Arciszewski

1

即使您可以访问代码,加密/解密数据库中的数据的最佳方法是使用两个不同的密码:每个用户使用一个私有密码(user-pass),所有用户使用一个私有代码(system-pass)。

场景

  1. user-pass以md5格式存储在数据库中,并用于验证每个用户登录系统。每个用户的user-pass都是不同的
  2. 数据库中每个用户条目都有一个以md5格式存储的system-pass,用于加密/解密数据。该system-pass对于每个用户都是相同的
  3. 每当从系统中删除用户时,必须重新加密旧system-pass下加密的所有数据,以避免安全问题。

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