Solved

SHA512 Hashing giving incorrect results

Posted on 2009-05-08
13
382 Views
Last Modified: 2012-05-06
I'm using the code listed below for my security hashing. The problem is that when I store the hashed value to the database and try to verify it later, It isn't working. This code is not my own, it is from http://www.obviex.com/samples/Code.aspx?Source=HashCS&Title=Hashing%20Data&Lang=C%23

The actual 'string' contents are correct - but the database version always has trailing spaces. I've tried calling .Trim() on the database item and it still gives me errors.

I am storing the data as char(128), using SHA512 as the method.

Any ideas?
/// <summary>

	/// This class generates and compares hashes using MD5, SHA1, SHA256, SHA384, 

	/// and SHA512 hashing algorithms. Before computing a hash, it appends a

	/// randomly generated salt to the plain text, and stores this salt appended

	/// to the result. To verify another plain text value against the given hash,

	/// this class will retrieve the salt value from the hash string and use it

	/// when computing a new hash of the plain text. Appending a salt value to

	/// the hash may not be the most efficient approach, so when using hashes in

	/// a real-life application, you may choose to store them separately. You may

	/// also opt to keep results as byte arrays instead of converting them into

	/// base64-encoded strings.

	/// </summary>

	public class Hash

	{

		/// <summary>

		/// Generates a hash for the given plain text value and returns a

		/// base64-encoded result. Before the hash is computed, a random salt

		/// is generated and appended to the plain text. This salt is stored at

		/// the end of the hash value, so it can be used later for hash

		/// verification.

		/// </summary>

		/// <param name="plainText">

		/// Plaintext value to be hashed. The function does not check whether

		/// this parameter is null.

		/// </param>

		/// <param name="hashAlgorithm">

		/// Name of the hash algorithm. Allowed values are: "MD5", "SHA1",

		/// "SHA256", "SHA384", and "SHA512" (if any other value is specified

		/// MD5 hashing algorithm will be used). This value is case-insensitive.

		/// </param>

		/// <param name="saltBytes">

		/// Salt bytes. This parameter can be null, in which case a random salt

		/// value will be generated.

		/// </param>

		/// <returns>

		/// Hash value formatted as a base64-encoded string.

		/// </returns>

		public static string ComputeHash(string plainText,

										 string hashAlgorithm,

										 byte[] saltBytes)

		{

			// If salt is not specified, generate it on the fly.

			if (saltBytes == null)

			{

				// Define min and max salt sizes.

				int minSaltSize = 4;

				int maxSaltSize = 8;
 

				// Generate a random number for the size of the salt.

				Random random = new Random();

				int saltSize = random.Next(minSaltSize, maxSaltSize);
 

				// Allocate a byte array, which will hold the salt.

				saltBytes = new byte[saltSize];
 

				// Initialize a random number generator.

				RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
 

				// Fill the salt with cryptographically strong byte values.

				rng.GetNonZeroBytes(saltBytes);

			}
 

			// Convert plain text into a byte array.

			byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
 

			// Allocate array, which will hold plain text and salt.

			byte[] plainTextWithSaltBytes =

					new byte[plainTextBytes.Length + saltBytes.Length];
 

			// Copy plain text bytes into resulting array.

			for (int i = 0; i < plainTextBytes.Length; i++)

				plainTextWithSaltBytes[i] = plainTextBytes[i];
 

			// Append salt bytes to the resulting array.

			for (int i = 0; i < saltBytes.Length; i++)

				plainTextWithSaltBytes[plainTextBytes.Length + i] = saltBytes[i];
 

			// Because we support multiple hashing algorithms, we must define

			// hash object as a common (abstract) base class. We will specify the

			// actual hashing algorithm class later during object creation.

			HashAlgorithm hash;
 

			// Make sure hashing algorithm name is specified.

			if (hashAlgorithm == null)

				hashAlgorithm = "";
 

			// Initialize appropriate hashing algorithm class.

			switch (hashAlgorithm.ToUpper())

			{

				case "SHA1":

					hash = new SHA1Managed();

					break;
 

				case "SHA256":

					hash = new SHA256Managed();

					break;
 

				case "SHA384":

					hash = new SHA384Managed();

					break;
 

				case "SHA512":

					hash = new SHA512Managed();

					break;
 

				default:

					hash = new MD5CryptoServiceProvider();

					break;

			}
 

			// Compute hash value of our plain text with appended salt.

			byte[] hashBytes = hash.ComputeHash(plainTextWithSaltBytes);
 

			// Create array which will hold hash and original salt bytes.

			byte[] hashWithSaltBytes = new byte[hashBytes.Length +

												saltBytes.Length];
 

			// Copy hash bytes into resulting array.

			for (int i = 0; i < hashBytes.Length; i++)

				hashWithSaltBytes[i] = hashBytes[i];
 

			// Append salt bytes to the result.

			for (int i = 0; i < saltBytes.Length; i++)

				hashWithSaltBytes[hashBytes.Length + i] = saltBytes[i];
 

			// Convert result into a base64-encoded string.

			string hashValue = Convert.ToBase64String(hashWithSaltBytes).TrimEnd();
 

			// Return the result.

			return hashValue;

		}
 

		/// <summary>

		/// Compares a hash of the specified plain text value to a given hash

		/// value. Plain text is hashed with the same salt value as the original

		/// hash.

		/// </summary>

		/// <param name="plainText">

		/// Plain text to be verified against the specified hash. The function

		/// does not check whether this parameter is null.

		/// </param>

		/// <param name="hashAlgorithm">

		/// Name of the hash algorithm. Allowed values are: "MD5", "SHA1", 

		/// "SHA256", "SHA384", and "SHA512" (if any other value is specified,

		/// MD5 hashing algorithm will be used). This value is case-insensitive.

		/// </param>

		/// <param name="hashValue">

		/// Base64-encoded hash value produced by ComputeHash function. This value

		/// includes the original salt appended to it.

		/// </param>

		/// <returns>

		/// If computed hash mathes the specified hash the function the return

		/// value is true; otherwise, the function returns false.

		/// </returns>

		public static bool VerifyHash(string plainText,

									  string hashAlgorithm,

									  string hashValue)

		{

			// Convert base64-encoded hash value into a byte array.

			byte[] hashWithSaltBytes = Convert.FromBase64String(hashValue);
 

			// We must know size of hash (without salt).

			int hashSizeInBits, hashSizeInBytes;
 

			// Make sure that hashing algorithm name is specified.

			if (hashAlgorithm == null)

				hashAlgorithm = "";
 

			// Size of hash is based on the specified algorithm.

			switch (hashAlgorithm.ToUpper())

			{

				case "SHA1":

					hashSizeInBits = 160;

					break;
 

				case "SHA256":

					hashSizeInBits = 256;

					break;
 

				case "SHA384":

					hashSizeInBits = 384;

					break;
 

				case "SHA512":

					hashSizeInBits = 512;

					break;
 

				default: // Must be MD5

					hashSizeInBits = 128;

					break;

			}
 

			// Convert size of hash from bits to bytes.

			hashSizeInBytes = hashSizeInBits / 8;
 

			// Make sure that the specified hash value is long enough.

			if (hashWithSaltBytes.Length < hashSizeInBytes)

				return false;
 

			// Allocate array to hold original salt bytes retrieved from hash.

			byte[] saltBytes = new byte[hashWithSaltBytes.Length -

										hashSizeInBytes];
 

			// Copy salt from the end of the hash to the new array.

			for (int i = 0; i < saltBytes.Length; i++)

				saltBytes[i] = hashWithSaltBytes[hashSizeInBytes + i];
 

			// Compute a new hash string.

			string expectedHashString =

						ComputeHash(plainText, hashAlgorithm, saltBytes);
 

			// If the computed hash matches the specified hash,

			// the plain text value must be correct.

			return (hashValue.Trim() == expectedHashString);

		}

	}

Open in new window

0
Comment
Question by:veruthandai
  • 7
  • 6
13 Comments
 
LVL 39

Expert Comment

by:abel
ID: 24348768
> string hashValue = Convert.ToBase64String(hashWithSaltBytes).TrimEnd();
not sure here, but are you positive that the hash you are storing for the SHA512 isn't too long for the 128 char field?

Three other small questions, just to find out where you stand with this:

 - you do not show the code you use to call VerifyHash. You say you trim the hashValue, but do you really? In the code above the only trimming is done on the result of ToBase64String, which will never contain spaces, so the TrimEnd in the codeline above can be removed.

 - have you checked whether the functions work when you do not store it at all in the database, but just run the verify with the output? I.e., using the data the would be stored in the database.

 - if that works, can you verify, by hand or with code, whether the data in the database is indeed the same as what's held int he variable, after storing?

-- Abel --
0
 

Author Comment

by:veruthandai
ID: 24349402
I have included the exact code I am using below (it is commented in my project because it isn't working, of course.)
The "TrimEnd()" is actually not in the actual project, I added that to see if it worked, and it never did. I'm having a tough time figuring out where the problem is so I was just trying a lot of things.
Yes, I can verify that it works when not stored in the database. I've included the full source of the Hashing file below (from the website I mentioned). I have run this in a console application and it works fine, even if I use my own password values.

				// if we received a result, hash the password and verify it with the database

				// return Hash.VerifyHash( LoginProvider.Password, "SHA512", user.Password );
 

------------------------------------------
 

///////////////////////////////////////////////////////////////////////////////

// SAMPLE: Hashing data with salt using MD5 and several SHA algorithms.

//

// To run this sample, create a new Visual C# project using the Console

// Application template and replace the contents of the Class1.cs file with

// the code below.

//

// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,

// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED

// WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.

// 

// Copyright (C) 2002 Obviex(TM). All rights reserved.

// 

using System;

using System.Text;

using System.Security.Cryptography;
 

/// <summary>

/// This class generates and compares hashes using MD5, SHA1, SHA256, SHA384, 

/// and SHA512 hashing algorithms. Before computing a hash, it appends a

/// randomly generated salt to the plain text, and stores this salt appended

/// to the result. To verify another plain text value against the given hash,

/// this class will retrieve the salt value from the hash string and use it

/// when computing a new hash of the plain text. Appending a salt value to

/// the hash may not be the most efficient approach, so when using hashes in

/// a real-life application, you may choose to store them separately. You may

/// also opt to keep results as byte arrays instead of converting them into

/// base64-encoded strings.

/// </summary>

public class SimpleHash

{

	/// <summary>

	/// Generates a hash for the given plain text value and returns a

	/// base64-encoded result. Before the hash is computed, a random salt

	/// is generated and appended to the plain text. This salt is stored at

	/// the end of the hash value, so it can be used later for hash

	/// verification.

	/// </summary>

	/// <param name="plainText">

	/// Plaintext value to be hashed. The function does not check whether

	/// this parameter is null.

	/// </param>

	/// <param name="hashAlgorithm">

	/// Name of the hash algorithm. Allowed values are: "MD5", "SHA1",

	/// "SHA256", "SHA384", and "SHA512" (if any other value is specified

	/// MD5 hashing algorithm will be used). This value is case-insensitive.

	/// </param>

	/// <param name="saltBytes">

	/// Salt bytes. This parameter can be null, in which case a random salt

	/// value will be generated.

	/// </param>

	/// <returns>

	/// Hash value formatted as a base64-encoded string.

	/// </returns>

	public static string ComputeHash(string plainText,

									 string hashAlgorithm,

									 byte[] saltBytes)

	{

		// If salt is not specified, generate it on the fly.

		if (saltBytes == null)

		{

			// Define min and max salt sizes.

			int minSaltSize = 4;

			int maxSaltSize = 8;
 

			// Generate a random number for the size of the salt.

			Random random = new Random();

			int saltSize = random.Next(minSaltSize, maxSaltSize);
 

			// Allocate a byte array, which will hold the salt.

			saltBytes = new byte[saltSize];
 

			// Initialize a random number generator.

			RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
 

			// Fill the salt with cryptographically strong byte values.

			rng.GetNonZeroBytes(saltBytes);

		}
 

		// Convert plain text into a byte array.

		byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
 

		// Allocate array, which will hold plain text and salt.

		byte[] plainTextWithSaltBytes =

				new byte[plainTextBytes.Length + saltBytes.Length];
 

		// Copy plain text bytes into resulting array.

		for (int i = 0; i < plainTextBytes.Length; i++)

			plainTextWithSaltBytes[i] = plainTextBytes[i];
 

		// Append salt bytes to the resulting array.

		for (int i = 0; i < saltBytes.Length; i++)

			plainTextWithSaltBytes[plainTextBytes.Length + i] = saltBytes[i];
 

		// Because we support multiple hashing algorithms, we must define

		// hash object as a common (abstract) base class. We will specify the

		// actual hashing algorithm class later during object creation.

		HashAlgorithm hash;
 

		// Make sure hashing algorithm name is specified.

		if (hashAlgorithm == null)

			hashAlgorithm = "";
 

		// Initialize appropriate hashing algorithm class.

		switch (hashAlgorithm.ToUpper())

		{

			case "SHA1":

				hash = new SHA1Managed();

				break;
 

			case "SHA256":

				hash = new SHA256Managed();

				break;
 

			case "SHA384":

				hash = new SHA384Managed();

				break;
 

			case "SHA512":

				hash = new SHA512Managed();

				break;
 

			default:

				hash = new MD5CryptoServiceProvider();

				break;

		}
 

		// Compute hash value of our plain text with appended salt.

		byte[] hashBytes = hash.ComputeHash(plainTextWithSaltBytes);
 

		// Create array which will hold hash and original salt bytes.

		byte[] hashWithSaltBytes = new byte[hashBytes.Length +

											saltBytes.Length];
 

		// Copy hash bytes into resulting array.

		for (int i = 0; i < hashBytes.Length; i++)

			hashWithSaltBytes[i] = hashBytes[i];
 

		// Append salt bytes to the result.

		for (int i = 0; i < saltBytes.Length; i++)

			hashWithSaltBytes[hashBytes.Length + i] = saltBytes[i];
 

		// Convert result into a base64-encoded string.

		string hashValue = Convert.ToBase64String(hashWithSaltBytes);
 

		// Return the result.

		return hashValue;

	}
 

	/// <summary>

	/// Compares a hash of the specified plain text value to a given hash

	/// value. Plain text is hashed with the same salt value as the original

	/// hash.

	/// </summary>

	/// <param name="plainText">

	/// Plain text to be verified against the specified hash. The function

	/// does not check whether this parameter is null.

	/// </param>

	/// <param name="hashAlgorithm">

	/// Name of the hash algorithm. Allowed values are: "MD5", "SHA1", 

	/// "SHA256", "SHA384", and "SHA512" (if any other value is specified,

	/// MD5 hashing algorithm will be used). This value is case-insensitive.

	/// </param>

	/// <param name="hashValue">

	/// Base64-encoded hash value produced by ComputeHash function. This value

	/// includes the original salt appended to it.

	/// </param>

	/// <returns>

	/// If computed hash mathes the specified hash the function the return

	/// value is true; otherwise, the function returns false.

	/// </returns>

	public static bool VerifyHash(string plainText,

								  string hashAlgorithm,

								  string hashValue)

	{

		// Convert base64-encoded hash value into a byte array.

		byte[] hashWithSaltBytes = Convert.FromBase64String(hashValue);
 

		// We must know size of hash (without salt).

		int hashSizeInBits, hashSizeInBytes;
 

		// Make sure that hashing algorithm name is specified.

		if (hashAlgorithm == null)

			hashAlgorithm = "";
 

		// Size of hash is based on the specified algorithm.

		switch (hashAlgorithm.ToUpper())

		{

			case "SHA1":

				hashSizeInBits = 160;

				break;
 

			case "SHA256":

				hashSizeInBits = 256;

				break;
 

			case "SHA384":

				hashSizeInBits = 384;

				break;
 

			case "SHA512":

				hashSizeInBits = 512;

				break;
 

			default: // Must be MD5

				hashSizeInBits = 128;

				break;

		}
 

		// Convert size of hash from bits to bytes.

		hashSizeInBytes = hashSizeInBits / 8;
 

		// Make sure that the specified hash value is long enough.

		if (hashWithSaltBytes.Length < hashSizeInBytes)

			return false;
 

		// Allocate array to hold original salt bytes retrieved from hash.

		byte[] saltBytes = new byte[hashWithSaltBytes.Length -

									hashSizeInBytes];
 

		// Copy salt from the end of the hash to the new array.

		for (int i = 0; i < saltBytes.Length; i++)

			saltBytes[i] = hashWithSaltBytes[hashSizeInBytes + i];
 

		// Compute a new hash string.

		string expectedHashString =

					ComputeHash(plainText, hashAlgorithm, saltBytes);
 

		// If the computed hash matches the specified hash,

		// the plain text value must be correct.

		return (hashValue == expectedHashString);

	}

}
 

/// <summary>

/// Illustrates the use of the SimpleHash class.

/// </summary>

public class SimpleHashTest

{

	/// <summary>

	/// The main entry point for the application.

	/// </summary>

	[STAThread]

	static void Main(string[] args)

	{

		string password = "myP@5sw0rd";  // original password

		string wrongPassword = "password";    // wrong password
 

		string passwordHashMD5 =

			   SimpleHash.ComputeHash(password, "MD5", null);

		string passwordHashSha1 =

			   SimpleHash.ComputeHash(password, "SHA1", null);

		string passwordHashSha256 =

			   SimpleHash.ComputeHash(password, "SHA256", null);

		string passwordHashSha384 =

			   SimpleHash.ComputeHash(password, "SHA384", null);

		string passwordHashSha512 =

			   SimpleHash.ComputeHash(password, "SHA512", null);
 

		Console.WriteLine("COMPUTING HASH VALUES\r\n");

		Console.WriteLine("MD5   : {0}", passwordHashMD5);

		Console.WriteLine("SHA1  : {0}", passwordHashSha1);

		Console.WriteLine("SHA256: {0}", passwordHashSha256);

		Console.WriteLine("SHA384: {0}", passwordHashSha384);

		Console.WriteLine("SHA512: {0}", passwordHashSha512);

		Console.WriteLine("");
 

		Console.WriteLine("COMPARING PASSWORD HASHES\r\n");

		Console.WriteLine("MD5    (good): {0}",

							SimpleHash.VerifyHash(

							password, "MD5",

							passwordHashMD5).ToString());

		Console.WriteLine("MD5    (bad) : {0}",

							SimpleHash.VerifyHash(

							wrongPassword, "MD5",

							passwordHashMD5).ToString());

		Console.WriteLine("SHA1   (good): {0}",

							SimpleHash.VerifyHash(

							password, "SHA1",

							passwordHashSha1).ToString());

		Console.WriteLine("SHA1   (bad) : {0}",

							SimpleHash.VerifyHash(

							wrongPassword, "SHA1",

							passwordHashSha1).ToString());

		Console.WriteLine("SHA256 (good): {0}",

							SimpleHash.VerifyHash(

							password, "SHA256",

							passwordHashSha256).ToString());

		Console.WriteLine("SHA256 (bad) : {0}",

							SimpleHash.VerifyHash(

							wrongPassword, "SHA256",

							passwordHashSha256).ToString());

		Console.WriteLine("SHA384 (good): {0}",

							SimpleHash.VerifyHash(

							password, "SHA384",

							passwordHashSha384).ToString());

		Console.WriteLine("SHA384 (bad) : {0}",

							SimpleHash.VerifyHash(

							wrongPassword, "SHA384",

							passwordHashSha384).ToString());

		Console.WriteLine("SHA512 (good): {0}",

							SimpleHash.VerifyHash(

							password, "SHA512",

							passwordHashSha512).ToString());

		Console.WriteLine("SHA512 (bad) : {0}",

							SimpleHash.VerifyHash(

							wrongPassword, "SHA512",

							passwordHashSha512).ToString());

	}

}

Open in new window

0
 

Author Comment

by:veruthandai
ID: 24349427
Also, to try and answer the other question - I had considered that the char(128) was not long enough, so I did try increasing it to (256) and then even (512). I got the same results.
I have also tried varchar(512), and nvarchar(512) with the same results.
I saw someone suggest once to try binary(128), and I looked into that, but it caused a great deal of trouble with C# properties and what type of data was expected in LINQ in many places. If I had seen results in the debugger I would have explored this further, but the actual length of the data was still the same even using a binary(128) and binary(512) field, so I abandoned that idea.
0
 
LVL 39

Expert Comment

by:abel
ID: 24351543
Before I setup your situation and a database and try it myself, can you run it and check the length of the strings just before you send them to the database? And, can you put a breakpoint just after you send it to the database and copy / paste the strings (using the Immediate window) to compare them visually? You can even copy/paste them here if you like.
0
 

Author Comment

by:veruthandai
ID: 24364889
Oh man, now I'm really confused.
 
It's returning true if I try to step through the debugger.

001.jpg
002.jpg
0
 
LVL 39

Expert Comment

by:abel
ID: 24367633
Hmm, odd, but I'm not sure if I follow.

You mean that the bool verify at the bottom of the code (obscured by your popups, I don't know how it reads) gives true? Of you mean that the visual resemblance is positively equal, but the Cryptography method return false?
0
Enabling OSINT in Activity Based Intelligence

Activity based intelligence (ABI) requires access to all available sources of data. Recorded Future allows analysts to observe structured data on the open, deep, and dark web.

 

Author Comment

by:veruthandai
ID: 24369420
The bool gives true, yes. But in the original test it did not...
I've got to have made a coding mistake somewhere. That's the only way I can explain it. Unless hashes of different lengths of text are different length results. I'm fairly sure that isn't true though.
0
 
LVL 39

Accepted Solution

by:
abel earned 500 total points
ID: 24369854
Yes, the length is always the same when it comes to the hash string (8 quadwords, or 8 x 64 bit). But you use Base64, but if I'm not mistaken, that means equal length for equal amount of bytes...

Can you show the offending code? Maybe I can see something?
0
 

Author Comment

by:veruthandai
ID: 24375250
I will have to get back to you on that. Someone deleted that part of the code before I had the chance to clock it in...
0
 

Author Comment

by:veruthandai
ID: 24459909
I hope you're still watching this. I've included the offending code below.
 

			database.Members.InsertOnSubmit(new Azure.Models.Member("name", "name@gmail.com", new DateTime(1984, 2, 14), "passwordstring"));

				
 

// this will validate the password given through the 

				// sha512 hash

				if (!userMember.Password.Verify(password))

					return false;
 

using System;
 

namespace Azure.Models

{

	public partial class Password

	{

		public Password(string pwd)

			: this()

		{

			Hash = pwd;

		}
 

		public bool Verify(string pwd)

		{

			return System.Security.Cryptography.Hashing.VerifyHash(pwd, "SHA512", Hash);

		}

	}

}

Open in new window

0
 
LVL 39

Expert Comment

by:abel
ID: 24459964
Yes, I'm still watching this, but you have to help me a bit. So the code above returns true when you step through but it returns false when you run it without the debugger. Can you please answer the following questions, just to be sure we're on the same track?

  • Is this WinForms or ASP.NET?
  • If ASP.NET, how is the original hash saved, i.e., in a session or else, after you retrieve it from the DB, or do you retrieve it from the DB each time again?
  • In other words, you show a partial class, can you show the Hash property + local members belonging to it, and where you call the constructor (also in case of winforms)
  • The code above does not seem correct: Hashing does not exist (did you make an alias?) and VerifyHash (from D/RSA?? That'd only allow SHA1) takes three parameters, but they should be Byte[], String, Byte[], which is not what you show. Is that really the code you use?
  • If not, what is?
  • Seems like you implemented this: http://www.obviex.com/samples/hash.aspx (if not, check it, it seems to do the same you are trying to do, including the base64 encoding).
Correct me if I'm wrong, but I am just under the impression that I'm missing vital information about your code

-- Abel --
0
 

Author Closing Comment

by:veruthandai
ID: 31579539
This was the problem. The size of the string generated is dynamic and non-static. Changing the field to a varchar instead of a char fixed the issue.
0
 
LVL 39

Expert Comment

by:abel
ID: 24668662
(copying grading comment here because it contained the actual solution)> This was the problem. The size of the string generated is dynamic and
> non-static. Changing the field to a varchar instead of a char fixed the issue.


Quite amazed that that was it, thought we covered that somewhere in the beginning (but I can't remember what all we tried...). Anyway, very glad it is finally resolved and that you found the culprit. Thanks for giving me credits even though I didn't come to the answer.

-- Abel --
0

Featured Post

How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

Join & Write a Comment

Password hashing is better than message digests or encryption, and you should be using it instead of message digests or encryption.  Find out why and how in this article, which supplements the original article on PHP Client Registration, Login, Logo…
This article explains in simple steps how to renew expiring Exchange Server Internal Transport Certificate.
Sending a Secure fax is easy with eFax Corporate (http://www.enterprise.efax.com). First, Just open a new email message.  In the To field, type your recipient's fax number @efaxsend.com. You can even send a secure international fax — just include t…
When you create an app prototype with Adobe XD, you can insert system screens -- sharing or Control Center, for example -- with just a few clicks. This video shows you how. You can take the full course on Experts Exchange at http://bit.ly/XDcourse.

746 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

10 Experts available now in Live!

Get 1:1 Help Now