dongzhisang5342 2013-04-21 17:09
浏览 71
已采纳

PHP最强单向加密/散列方法[重复]

This question already has an answer here:

I have a site on which people can sign up and I need to encrypt their password. I've researched it but I can't find any methods that won't be beat by the power of a modern GPU.

So I have come to the good people of StackOverflow to ask, what is the strongest method of encryption possible, I have done my best to stop people getting their hands on the database but I want to be as sure as possible that their data would be fine if the database was somehow stolen.

Other things I wonder are, would be it be safer to just randomize the characters in the password somehow but in a way where you would be able to randomize them again for login?

Edit: I have used the implementation of bcrypt by Andrew Moore (How do you use bcrypt for hashing passwords in PHP?) and come up with this:

public static function Encrypt($Str,$Salt)
{
    $bcrypt = new \bcrypt();
    return $bcrypt->hash(SERVER_SALT . md5($Str) . $Salt);
}

if anyone sees anything wrong with it or any weaknesses please tell me.

</div>
  • 写回答

2条回答 默认 最新

  • dpjjmo3079 2013-04-21 17:14
    关注

    For passwords, you can't beat bcrypt. Here is a link on SO: How do you use bcrypt for hashing passwords in PHP? . Its main advantage: it is inherently slow (as well as secure). While a normal user will use it once, and will not appreciate the difference between one tenth and one millionth of a second, a cracker will discover that instead of finding the password in four days, he now needs twelve centuries (except that forcing Blowfish even at one million attempts per second does not require four days: barring implementation errors and as yet unforeseen crypto breakthroughs, the heat death of the Universe will still come first).

    For data, I'd rely on the database engine itself; MySQL supports AES, and that's quite good. Yes, with enough GPUs one could beat it, and since to crack the code within the century one would need some four billions of them, he'd probably get a good quantity discount too. But I wouldn't worry.

    As for the "randomization" of the password, it would serve no purpose. If someone were to resort to bruteforcing your password, he would have the same probability of finding the randomized sequence or the non-randomized one. It might be of some help if one were to use a dictionary attack (i.e., not trying everything from AAAAA to ZZZZZ, but trying ABRAHAM, ACCORDION, ... up to, say, ZYGOTE). But even then, an attack against the server would change nothing (the server receives the non-randomized password!), and an attack against the stored hash would be better nullified by salting, which bcrypt automatically does: i.e., storing the hash of $76X.J:MICKEYMOUSE instead of the hash of MICKEYMOUSE, together with the needed overhead to handle the extra '$76X.J:' on input. And while the randomization would have to be fixed, the '$76X.J:' sequence is completely different on every write -- good luck figuring that out!

    Salting

    If you implement salting yourself (as shown above, with bcrypt you don't need to), you can do it by generating a unique random sequence and storing it either in the password field or in a second field. For example

    user    password
    alice   d9c06f88:6c14d6d313d7cbcb132b5c63650682c4
    

    Then, upon receipt of the password by Alice ("mickeymouse"), you would look in the database to see whether a user called alice exists. If it does, recover the salt (here d9c06f88) and the hash. If it does not, set a "BAD" flag and fetch a fixed salt and hash (e.g. 12345678:0000000...).

    In MySQL this can be done using UNION:

    SELECT password_fields FROM users WHERE user=? AND hash=?
    UNION SELECT
        '12345789' as salt,
        'ffffffffffffffffffffffff' as hash,
        'fake' as user
    LIMIT 1;
    

    will retrieve either the correct data or an incorrect set in roughly the same time (this prevents someone to guess which names exist and which do not by timing the time needed to answer "wrong user or password").

    Then concatenate the salt and the password, and generate the hash of d9c06f88:mickeymouse. If it does not match, or the "BAD" flag is set, reject the password (instead of using a 'bad' flag, you can repeat the test for user match that MySQL already did: you can initialize the user name by replacing the last character with an invalid one, to ensure it will never match a real user).

    Security through noninformation

    The added twist of selecting a fixed string is useful because you want the three cases, "user does not exist", "user exists but password is incorrect" and "user exists and password is correct" to be as similar (same complexity, same expense in calculations) as possible.

    This way, an attacker will be less likely to tell what happened: was the user incorrect? Was the password wrong? And so on. If the times were different enough (say two queries for valid users, one for invalid users), an attacker with care, time and a good stopwatch could determine statistically whether a given username is present or not.

    In the case of the user, even the name comparison will be

     johnsmith     against   johnsmith            if johnsmith exists
     johnsmith     against   johnsmit?            if johnsmith does not exist
    

    so telling what happened from the other end of a HTTP(s) connection won't be easy.

    For the same reason, you will not return two different errors for "Bad user" and "Bad password", but always a "Bad user or password"; maybe with the option, for the user, to receive an email to his/her registered email to remind him/her of the username. And of course, you want the system to not send such an email before 24 hours have passed from sending a similar email to the same user, to prevent the system from being exploited to harass someone with spurious "recovery emails".

    If you do send the email, you will wait until a preset time has expired (say, 3 seconds), and then inform users that if the username was present, then they should check their inbox (and the spam folder for good measure).

    There would have been a time for such a password

    A convenient way to improve server security against bruteforcing is to implement a delay on password authentication, and maybe (if you're really paranoid) a CAPTCHA-ed lockout after some X wrong attempts.

    You don't want to CAPTCHA the first attempts because users take a dim view of CAPTCHAs.

    Delayed lockout is often implemented with sleep (or equivalent), or using a "lock out until manually reset by the admin" strategy. Both methods are bad. The lockout feature can be used to create a denial of service attack, either by locking out an user, or creating lots of server threads stopped in "password authentication delay" state (they won't use CPU, but they will still use memory, sockets and other resources).

    In some cases this can happen unwittingly. There seems to be some idiot who uses my bank, and every couple of months I get a SMS saying "WRONG PIN ENTERED IN YOUR HOME BANK SYSTEM". Then I have to log in from my phone, to reset the unsuccessful attempt counter; because if the idiot doesn't realize that he can't get in his account because he entered my account number, for three times, it is MY account that gets locked and I have to go physically to the bank and beg them to reset my access. This, let me tell you, is a major pain in the nether regions, and even knowing it's not their fault I still resent my bank. You don't want to engender such feelings in your users.

    And it is best to move the burden on the client:

    (very pseudo code)
    login = FAIL
    if in SECURITY LOCKOUT MODE for this account
        if a session is open and contains a last-attempt time
            if at least DELAY seconds have elapsed since last-attempt
                check the password
                if it is correct
                    login = OK
                    zero password counter, exit lockout mode.
                    # An "innocent" user enters with no lockout! Yay!
                end
            else
                # "Early knocker". Either a bruteforcing robot
                # or a too clever user in a hurry. But we can't
                # tell them apart.
            end
        else
            # No session. Either a sessionless bruteforcing robot
            # or a unaware, innocent user. Again we can't tell them
            # apart. So we bounce both.
    
            # But a genuine user will assume he has mistyped the password,
            # or anyway will read the warning page, and will login after ONE
            # round of delay.
    
            # Users with password saved in browser will just click
            # "login" again and be logged in.
    
            # A robot will find itself delayed and ALL ITS PASSWORDS IGNORED
            # every single time. Even if it finds the right password... it will
            # not work.
        end
    else
        check the password
        if it is correct
            # Good user, first attempt, fast login.
            login = OK
        else
            # Beginning to doubt this is a good user...
            increase password counter
            if it is > MAX_ATTEMPTS
                enter SECURITY LOCKOUT MODE for this account
            end
        end
    end
    if login is not OK
        generate a page with HTTP_REFRESH time of DELAY+1 seconds
        and a session ID, saying "User or password unknown,
        or you tried to login before HH:MM:SS (DELAY seconds).
        The page might also contain a Javascript timer, just in
        case. The delay is 1s more than necessary as a safety
        margin.
    end
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥15 如何在scanpy上做差异基因和通路富集?
  • ¥20 关于#硬件工程#的问题,请各位专家解答!
  • ¥15 关于#matlab#的问题:期望的系统闭环传递函数为G(s)=wn^2/s^2+2¢wn+wn^2阻尼系数¢=0.707,使系统具有较小的超调量
  • ¥15 FLUENT如何实现在堆积颗粒的上表面加载高斯热源
  • ¥30 截图中的mathematics程序转换成matlab
  • ¥15 动力学代码报错,维度不匹配
  • ¥15 Power query添加列问题
  • ¥50 Kubernetes&Fission&Eleasticsearch
  • ¥15 報錯:Person is not mapped,如何解決?
  • ¥15 c++头文件不能识别CDialog