How do contact hashes work, and how do I keep them secure?
My understanding is that Lobo's answer above is slightly misleading (at least if you read it 100% literally--the gist what he is saying is exactly correct, as usual, but the exact terminology may lead some of us astray).

The contact hash is a value stored in the civicrm_contact table of the database (i.e., civicrm_contact.hash). 

The contact hash is used to generate the contact checksum, which is more precisely what Lobo is discussing above. It's easy to conflate to two, because they are closely related.  To add to the confusion, the contact checksum is itself a type of hash--but a different one from that hash found in the database under civicrm_contact.hash.

The contact checksum isn't stored in the database but is generated afresh by a call to a CiviCRM function each time it is used. The checksum encodes the time it was generated and also the time it should expire, along with the pertinent civicrm_contact.hash to make a new hash. So the checksum is designed to be shared (e.g. with the contact associated with it) to allow temporary access to various parts of CiviCRM, and it expires after a set period.  By default, checksums that civicrm generates will expire after 7 days.  But tou can set that expiration time period to whatever you want, including infinite, when you request a checksum to be generated.

The civicrm_contact.hash, by contrast is a secret and shouldn't be shared publicly--probably not even with the contact.  It is something like the user's password, and someone with the contact hash could use it to permanently gain access to do anything with that contact that they could do with a valid checksum: edit that contact's information, view payment and general CiviCRM history for that contact, register for events or making payments on behalf of that contact, etc. 

Also, as far as I know, the contact hash is never automatically regenerated.  (If it were ever changed or regenerated, it would immediately invalidate any checksums that happened to have been recently issued for that contact--generally not something that you would want to do.  If you want to give temporary access that expires, you would send them a checksum that expires, not change the hash.)

So leaking the hash is not quite as serious as, say, leaking a person's Drupal password, but it is still a privacy breach and something you'll want to avoid in almost all cases I can imagine.

Just for example, someone with the hash could generate a valid checksum to allow access to that CiviCRM contact at any time.

In short, when working to allow contacts temporary/non-password access to view or update their account info, you want to use the checksum--which is *based on the *contact hash** but not the hash itself.  The hash is a secret and you should keep it secret.

An example of how to get the contact's checksum &amp; turn it into a URL that could be used to edit the contact's profile:

// initialize civicrm stuff
// Source: https://forum.civicrm.org/index.php?topic=21438.0
civicrm_initialize(true);
require_once(drupal_get_path('module', 'civicrm').'/../CRM/Contact/BAO/Contact/Utils.php');
require_once(drupal_get_path('module', 'civicrm').'/../CRM/Utils/Mail.php');

// try looking up the contact
$result = civicrm_api('Contact', 'get', array('email' =&gt; $email, 'version' =&gt; 3));
foreach($result['values'] as $contact) {
    // generate a url for this contact
    $checksum = CRM_Contact_BAO_Contact_Utils::generateChecksum($contact['contact_id'], time(), 24); // 24 hour checksum
    $query = "reset=1&amp;gid=9&amp;id=".$contact['contact_id']."&amp;".$checksum;
    $url = CRM_Utils_System::url('civicrm/profile/edit', $query, true, null, false);


Code from CRM/Contact/BAO/Contact/Utils.php showing the header for the checksum generation function

  /**
   * Generate a checksum for a contactID
   *
   * @param int    $contactID
   * @param int    $ts         timestamp that checksum was generated
   * @param int    $live       life of this checksum in hours/ 'inf' for infinite
   * @param string $hash       contact hash, if sent, prevents a query in inner loop
   *
   * @return array ( $cs, $ts, $live )
   * @static
   * @access public
   */
  static
  function generateChecksum($contactID, $ts = NULL, $live = NULL, $hash = NULL) {


Header from Utils.php showing how to use the checksum validate function:

  /**
   * Make sure the checksum is valid for the passed in contactID
   *
   * @param int    $contactID
   * @param string $inputCheck the checksum to validate
   *
   * @return boolean           true if valid, else false
   * @static
   * @access public
   */
  static
  function validChecksum($contactID, $inputCheck)  ...


Sources: https://wiki.civicrm.org/confluence/display/CRMDOC/Tokens

Get or generate a contact checksum via API?

https://drupal.stackexchange.com/questions/140841/how-to-ensure-civicrm-checksum-cid-are-valid-in-custom-drupal-module

http://backofficethinking.com/blog/civicrm-tip-did-you-know-you-can-use-checksum-tokens-webforms