8
\$\begingroup\$

I am attempting to create functions for getting an algorithm, hashing and un-hashing a string.

As it stands, it works, and very well. But, I feel this is not secure enough and someone could easily break it!

Can someone help me to improve this?

Code:

<?php
function HashIt($string)
{
    $return = '';
    $algo = GetAlgo();
    $hashing = str_split($string, 1);
    foreach ($hashing as $part)
    {
        $return .= $algo[$part] . "|";
    }
    return rtrim($return, "|");
}

function UnhashIt($hash)
{
    $return = '';
    $algo = GetAlgo();
    $unhash = explode("|", $hash);
    foreach ($unhash as $value)
    {
        $find = array_search($value, $algo);
        if ($find)
            $return .= $find;
    }
    return $return;
}

function GetAlgo()
{
    return [
        "a"=>"[H]",
        "b"=>"[He]",
        "c"=>"[Li]",
        "d"=>"[Be]",
        "e"=>"[B]",
        "f"=>"[C]",
        "g"=>"[N]",
        "h"=>"[O]",
        "i"=>"[F]",
        "j"=>"[Ne]",
        "k"=>"[Na]",
        "l"=>"[Mg]",
        "m"=>"[Al]",
        "n"=>"[Si]",
        "o"=>"[P]",
        "p"=>"[S]",
        "q"=>"[Cl]",
        "r"=>"[Ar]",
        "s"=>"[K]",
        "t"=>"[Ca]",
        "u"=>"[Sc]",
        "v"=>"[Ti]",
        "w"=>"[V]",
        "x"=>"[Cr]",
        "y"=>"[Mn]",
        "z"=>"[Fe]",
        "A"=>"[Co]",
        "B"=>"[Ni]",
        "C"=>"[Cu]",
        "D"=>"[Zn]",
        "E"=>"[Ga]",
        "F"=>"[Ge]",
        "G"=>"[As]",
        "H"=>"[Se]",
        "I"=>"[Br]",
        "J"=>"[Kr]",
        "K"=>"[Rb]",
        "L"=>"[Sr]",
        "M"=>"[Y]",
        "N"=>"[Zr]",
        "O"=>"[Nb]",
        "P"=>"[Mo]",
        "Q"=>"[Tc]",
        "R"=>"[Ru]",
        "S"=>"[Rh]",
        "T"=>"[Pd]",
        "U"=>"[Ag]",
        "V"=>"[Cd]",
        "W"=>"[In]",
        "X"=>"[Sn]",
        "Y"=>"[Sb]",
        "Z"=>"[Te]",
        " "=>"[I]",
        "1"=>"[Xe]",
        "2"=>"[Cs]",
        "3"=>"[Ba]",
        "4"=>"[Hf]",
        "5"=>"[Ta]",
        "6"=>"[W]",
        "7"=>"[Re]",
        "8"=>"[Os]",
        "9"=>"[Ir]",
        "0"=>"[Pt]",
        "!"=>"[Au]",
        "£"=>"[Hg]",
        "$"=>"[Tl]",
        "%"=>"[Pb]",
        "^"=>"[Bi]",
        "&"=>"[Po]",
        "*"=>"[At]",
        "("=>"[Rn]",
        ")"=>"[Fr]",
        "_"=>"[Ra]",
        "-"=>"[Rf]",
        "="=>"[Db]",
        "+"=>"[Sg]",
        "`"=>"[Bh]",
        "¬"=>"[Hs]",
        ""=>"[Mt]",
        ","=>"[Ds]",
        "<"=>"[Rg]",
        "."=>"[Cn]",
        ">"=>"[Uut]",
        "/"=>"[Fl]",
        "?"=>"[Uup]",
        ";"=>"[Lv]",
        ":"=>"[Uus]",
        "'"=>"[Uuo]",
        "@"=>"[La]",
        "#"=>"[Ce]",
        "~"=>"[Pr]",
        "["=>"[Nd]",
        "]"=>"[Pm]",
        "{"=>"[Sm]",
        "}"=>"[Eu]",
        "."=>"Gd",
    ];
}
?>
\$\endgroup\$
  • 34
    \$\begingroup\$ "un-hash" (you probably mean decode) and "password" in the same context is very very bad. I don't feel there is anything here to review here, other than "don't write your own cryptographic algorithms unless you are an expert and let other experts take a look at it". You do not seem an expert to me. \$\endgroup\$ – Sumurai8 Dec 23 '15 at 15:18
  • 5
    \$\begingroup\$ @SamSwift웃 : What Sumurai8 said. The general rule is "don't roll your own crypto", so do NOT use the above in a production system. On the other hand, if it's just to tinker and learn - good for you! The answer by Tim below is great. Look up the difference between Hashing and Encryption. If you want it reversible, then you're not hashing - you're encrypting. Have fun! \$\endgroup\$ – loneboat Dec 23 '15 at 17:39
  • 16
    \$\begingroup\$ I upvoted you because you wrote a concise problem and want some suggestions, but I'm pretending that everywhere it says password you really mean love letters because that's the only place this should be used for cryptography. \$\endgroup\$ – Zach Mierzejewski Dec 23 '15 at 17:39
  • 6
    \$\begingroup\$ There's no such thing as un-hashing. If you can undo a thing, then that thing is not hashing. \$\endgroup\$ – immibis Dec 24 '15 at 1:34
  • 1
    \$\begingroup\$ I feel this is not secure enough. rightly so. \$\endgroup\$ – njzk2 Dec 24 '15 at 19:26
25
\$\begingroup\$

First of all, what you have is an encryption function, not a hashing function (encryption can be reversed by knowing the algorithm and optionally a password, hashing is one way).

For passwords, you always* want to hash, not encrypt (because if an attacker gets your source code, they can easily decrypt the passwords).

You are using a substitution cipher, which is also very easy to break. You might want to look into block or stream ciphers, if you want encryption that is more secure.

As to the code you do have:

  • function names should start with a lower-case character (classes start with an uppercase one).
  • with the information given above, it's obvious that the function names don't really fit. Also, you don't need filler words such as It in function names. Something like encrypt(plaintext) and decrypt(ciphertext) would be better.
  • You shouldn't silently ignore errors. I would add an else clause to your if ($find) and throw an exception.
  • In UnhashIt you check if the string exists, but you don't do it for the more important HashIt function. This means that UnhashIt(HashIt(text)) != text in some cases, which is not good.
  • For any real-world code, use bcrypt.

* there are exceptions such as kerberos and password managers, but in use cases where you want to authenticate a user by checking a user supplied password against a stored password (ie whenever you do have a user supplied plaintext password to check against), hashing is the correct way, not encryption (see the comments/chat for an extended discussion)

\$\endgroup\$
13
\$\begingroup\$

Since this topic has come up more than once on Security SE, this answer is heavily based upon the answers there.

The wrong way

The number one rule of secure cryptograpy has to be: don't roll your own crypto[1]

As cryptography expert Bruce Schneider put it:

Anyone, from the most clueless amateur to the best cryptographer, can create an algorithm that he himself can't break. It's not even hard. What is hard is creating an algorithm that no one else can break, even after years of analysis. And the only way to prove that is to subject the algorithm to years of analysis by the best cryptographers around.[2]

A lot of reasons as to why you shouldn't try building your own cryptosystem can be found in this thread: Is my developer's home-brew password security right or wrong, and why?

The right way

Use a hashing function – which, in contrast with the encryption/decryption code you posted, is one-way – that has been developed by experts and has been in widespread public use for years without having any security security issues found with it.

Read How to securely hash passwords? to understand what qualifies as a good password hashing scheme.

The code

Fortunelately, PHP makes it easy for us to hash passwords the right way. Using the functions password_hash() and password_verify(), which by default use the bcrypt algorithm with a proper salt and sufficient cost*, properly hashing passwords becomes a breeze.

These functions have been in PHP since version 5.5; however, a compatibility library is available for PHP version 5.3.7 and greater.

Usage

Creating Password Hashes

To create a password hash from a password, simply use the password_hash function.

$hash = password_hash($password, PASSWORD_BCRYPT);

Note that the algorithm that we chose is PASSWORD_BCRYPT. That's the current strongest algorithm supported. This is the BCRYPT crypt algorithm. It produces a 60 character hash as the result.

BCRYPT also allows for you to define a cost parameter in the options array. This allows for you to change the CPU cost of the algorithm:

$hash = password_hash($password, PASSWORD_BCRYPT, array("cost" => 10));

That's the same as the default. The cost can range from 4 to 31. I would suggest that you use the highest cost that you can, while keeping response time reasonable (I target between 0.1 and 0.5 seconds for a hash, depending on use-case).

Another algorithm name is supported:

PASSWORD_DEFAULT

This will use the strongest algorithm available to PHP at the current time. Presently, this is the same as specifying PASSWORD_BCRYPT. But in future versions of PHP, it may be updated to use a stronger algorithm if one is introduced. It can also be changed if a problem is identified with the BCRYPT algorithm. Note that if you use this option, you are strongly encouraged to store it in a VARCHAR(255) column to avoid truncation issues if a future algorithm increases the length of the generated hash.

It is very important that you should check the return value of password_hash prior to storing it, because a false may be returned if it encountered an error.

Verifying Password Hashes

To verify a hash created by password_hash, simply call:

if (password_verify($password, $hash)) {
  /* Valid */
} else {
  /* Invalid */
}

That's all there is to it.

Rehashing Passwords

From time to time you may update your hashing parameters (algorithm, cost, etc). So a function to determine if rehashing is necessary is available:

if (password_verify($password, $hash)) {
    if (password_needs_rehash($hash, $algorithm, $options)) {
        $hash = password_hash($password, $algorithm, $options);
        /* Store new hash in db */
    }
}

These instructions have been taken from the aforementioned library's README.

The function password_needs_rehash() is available natively in PHP 5.5 and above as well.

* cost means how hard it is to calculate a password hash for you and for any attacker that obtains the password hashes. If the cost of hashing is too low, it becomes easy for an attacker to obtain the password matching a hash by trying possible solutions (brute-forcing). If it is too high, hashing will be too slow for your users and your server may not be able to cope with the load.

\$\endgroup\$
8
\$\begingroup\$

Hashing functions usually are not one-to-one so they are not invertable. What you have is a simple substitution cipher. You are correct that it is not secure, you can see this for yourself by comparing the frequency of each character in your plaintext with the frequency of each element abbreviation in your ciphertext.

Substitution ciphers have been in use for hundreds of years and there have been many strategies to improve them such as row or column transpositions. However, even with these strategies, substitution ciphers are not often used for serious encryption.

\$\endgroup\$
5
\$\begingroup\$

HASH

Hash functions stem from a set of functions in mathematics called trapdoor functions. The idea of a hash function is that it can be encoded (hashed) in constant time while decoding it could take years. So, it is possible to decode the encoded data the idea is that by the time the adversary decodes it, the data will no longer have any meaning or the adversary would be dead.


Naming

In cryptography, you usually do not HashIt and UnhashIt. You Encode and Decode. Unless you are hashing then you only Hash. The only way to know if what you hashed matches, say a password that has been hashed and stored, then you hash the plain text of the password with the same hash function and compare the two hash values. If they match, then they are the same or you have found a weak point in your hash function where it produces duplicate keys.


What You Made

What you have made is what is called a substitution cipher. Essentially a one-to-one mapping where you merely replace/substitute each character for another and are very easy to crack given enough messages or long enough message that has been encoded.


Walk Before You Run

I would recommend trying to make a basic symmetric key cryptosystem then move to a public-key cryptosystem then to a hybrid key cryptosystem. Because, hash algorithms require a lot of experience to create a good one. And the other cryptosystems I mentioned will be a good way to ease yourself into cryptography.


Note: I am no expert myself by any means! I just have a strong background in mathematics so I've run into basic cryptography.


Hope this helps!

\$\endgroup\$
3
\$\begingroup\$

In addition to the other excellent answers on this post, there's a good point you're missing: You're not actually hashing, you're encoding.

The idea of a hash is that you cannot retrieve it, simply re-hash a new data input and compare the two.

In addition:

Your encoding is literally the periodic table of elements.

I bet it would take a genius, chemistry doctorate, chemistry major, high school student to figure that out!

I don't know how you value the IQ or knowledge of a common internet thief, but you're doing it wrong.

If I'm a thief, and I put the password abcd into the system as a test, and I get
[H][He][Li][Be] as a result, I'm sure as hell going to make the connection.

And then it's just a matter of matching one for one and your encryption is as good as gone.

For future reference though, the brackets totally give it away for thieves, however without them, it may be a little safer.

Like the answers say above, you shouldn't be writing your own encryption or hashing algorithm.

But there's no harm in learning, as long it never reaches production code.

This is the way I've done hashing before in non-production projects:

Sign up:

  1. Generate a random 16 or larger digit salt.
  2. Prefix field to hash with salt.
  3. Use a hashing algorithm like bcrypt over the top of that combo.
  4. Pass hashed field and salt to database.

Login:

  1. Retrieve salt and hashed field from database.
  2. Prefix field with salt.
  3. Use hashing algorithm on it.
  4. Compare the result of that with hashed field from database.

Imagine there are two possible security flaws here:

  • Your database is broken into or dumped through injections
  • Your code is dumped or reverse engineered to reveal methods of field protection

You don't want to have it so either of those flaws totally screws your site, you want it so in the event of disaster, you can fix the vulnerability and keep your data intact.

If your database is compromised, the attackers can use a rainbow table to test for possible passwords, however, it's a potentially long process.

If either was compromised, you ought to change the hashing algorithm.

\$\endgroup\$
  • \$\begingroup\$ The periodic table of elements was purely used as a "I cant think what else to do and I'm just seeing if I can do it" kinda thing... \$\endgroup\$ – Sam Swift 웃 Jan 8 '16 at 9:20
0
\$\begingroup\$

[Rh][Pd][Nb][Mo]

(Please don't do this.)

this-encryption-sucks

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.